##結論から
ChainerやTensorFlowなどの機械学習(特にDeepLearning)フレームワークでGPUを使うと、誤差程度ではあるものの演算結果が毎回変わってしまいます。(非決定的な演算)
「乱数使ってるから当たり前でしょ」って話ではなく、乱数種を指定してもGPU内部での演算順序が非決定的であるためGPU演算の結果は安定しません。
浮動小数点演算なので誤差が出るのは当然だが、その誤差が安定しない(非決定的)なのが気になるところです。
Chainerでは環境変数(CHAINER_CUDNN)の指定またはConvolution2Dなどへのパラメータ追加で本事象を回避可能。
TensorFlowについてはGoogle社曰く「EigenライブラリまたはcuDNNの仕様によるとのこと」であり現状では対応策無し。(詳細は次の記事に記載のIssuesを参照のこと)
尚、Caffeでも同様の事象が発生していたが、現状ではfixされているらしい。(詳細未確認)
##経緯
実務的には試験の時に結構困るのがこのGPU演算の誤差問題です。
###試験時に再現性が得られないのがかなり困りどころ
科学の実験と同じですが、システムの試験とは「同一条件下であれば、いつ誰が実行しても同じ結果が得られる」必要があります。
しかし、誤差程度とは言え、実行する度に結果が変わってしまうのはちょっと痛いところです。
###環境移行後の試験が厄介
開発(試験)環境から本番環境への移行時に行うテストで、両環境での試験結果が完全に一致しないと気持ち悪いですよね。
「Conv層が1層少ない古いバージョンをデプロイしちゃったか?」とか「パラメータ定義ファイルが古くてLearningRateが大きいのか?」なんて心配になるわけです。
「GPUだからきっと演算誤差だろう」なんて思っていたら痛い目に遭うケースもあるでしょう。
###機能修正後の試験が厄介
本稼働後に機能修正を行う際、JenkinsなどのCIツールやJUnit的な自動テストツールを使用してデグレードが発生していないことを確認することが多いと思います。
しかし、テスト結果が不定ですので、過去の試験結果との照合が困難になります。
過去の試験結果と比較する必要があるときだけCPU演算に切り替える手もありますが...
(実際にこのような運用をするケースもあります)
##速度優先で並列演算を実行することが原因のようです
GPUでは行列を多数のコアに分散して演算を行いますが、オペランド(値など)やGPUの内部状態によって各コアでの演算時間が異なります。
各コアでの演算終了後に結果を集約することになりますが、コアごとに演算時間が異なるため集約される順序は不定となります。
これはCPU(FPU)の知識ですがGPUでも同じでしょう。
そのため、浮動小数点演算では単純な足し算であっても、演算順序が異なれば演算誤差の程度が異なるケースが発生します。
しかし、精度を重視して集約する順序を決定してしまうと、集約処理を並列化しにくいため、実行効率が低下してしまうようです。
(Chainerで実験したところ、精度優先の設定でも処理速度の低下は数%にとどまるようですが)
##CPUでも演算順序が異なると演算結果が変わることがあります
ここでCPUを使用してちょっとした実験を行ってみます。
0.0から0.999999まで0.000001刻みの階差数列を用意します。
この1000000の値をランダムな順序で加算することにより総和を求めます。
import numpy as np
v = np.arange(0.0, 1.0, 1e-6)
for _ in range(10):
total = 0.0
for p in np.random.permutation(v.shape[0]):
total += v[p]
print("{:6.9f}".format(total))
10回繰り返した結果が下記の通り。
$ python calculation_error_test.py
499999.500000007
499999.500000000
499999.500000002
499999.500000009
499999.499999990
499999.499999996
499999.500000016
499999.499999984
499999.499999991
499999.500000009
倍精度浮動小数点数ですので16桁目で誤差が発生し、多数の加算を繰り返しているため誤差が蓄積し15桁目や14桁目にも誤差が発生しています。
ここまでは当然の話しですね。
でも、計算の順序が変わるとその誤差の程度が変化してしまいます。
この実験はわかりやすくCPUで行っていますが、ChainerやTensorFlowでGPU演算が行われる際、同様の事象が発生しているようです。
詳細につきましてはChainerとTensorFlowで分けて記述しましたので下記の記事を参照ください。
ChainerでGPUを使うと毎回結果が変わる理由と対策
TensorFlowでGPUを使うと毎回結果が変わる理由と対策