概要
タイトルの通りなのだが、LightGBMをGPUで回すと結果の再現性が得られないことに気づいた。
CPUの場合は乱数seedを42などに固定すれば、同じ条件で何回回しても毎回同じ結果が得られる。
しかし、GPUの場合は乱数seedを固定しても回すたびに結果が変わってしまうのだ。
なぜ再現性がないのか?
この問題はLightGBMの公式のissueでも議論されている。
まず、GPUを使う場合は並列で計算できる部分は並列処理をすることで効率化している。
さらに、並列化した結果を足し算するときに、順番によって微妙に値が変わってしまうということだ。
もちろん数学的には足し算の順番が変わっても結果が変わることなんてないんだけど、コンピュータでfloatなどの値を計算する以上、丸め誤差だったり複数の要因で結果が「ほんのわずかに」違うということが起きうる。
さらに、LightGBMをGPUで回した場合、効率を重視するため浮動小数点の精度を32bitに落として計算するため、この影響が大きくなりやすい。
X(Twitter)でのこちらの議論も参照:
暫定的な解決策
1. GPUではなくCPUを使う
まず、本当の意味での再現性を確保したかったらGPUではなくCPUを使うというのが一つ。
大きいデータセットでCPUで時間がかかる場合であればGPUを使う必要性が出てきそうだが、比較的小さなデータであればCPUで十分だ。
2. DP(倍精度=64bit)の設定にする
次に、GPUを使う場合はSP (単精度=32bit)ではなくDP(倍精度=64bit)の設定にすることでこの変動を抑えるという手もある。
上のissueでも議論されているが、
gpu_use_dp=True
とパラメータを設定することで、LightGBMの浮動小数点の計算を64bitで行うことができる。
gpu_use_dp=Trueの副作用
ただ、当然これにも副作用はある。
まず、デフォルトのSPだと効率化のために32bitで計算を行っているため、より多くのリソースを必要とする可能性がある。
自分が試した環境だと
[LightGBM] [Fatal] bin size 258 cannot run on GPU
のエラーが出た。
LightGBMでは効率的に計算するためにfeatureの情報をhistogramに詰めて、そこから適切な分割を探すのだが、そのbinの数が上限値を超えてしまったということだ。
これは細かい精度で計算しようとすればするほど起きうる問題であるため、32bitだと発生しないが64bitだと発生する、などの状況が(環境によっては)起きうる。
このようは場合はLightGBMのパラメータにmax_binを指定すれば良い。
params = {
# 略
'gpu_use_dp': 'true',
'max_bin': 255 # added GPU上での計算に適合するようにビンの数を調整
}
ただ、結局これもベストな方法かと言われると微妙で、細かい精度を上げるためにgpu_use_dp
を使っているのに、結局histogramのbin数に制限をかけてそれを抑制することに繋がってしまう。
結局のところ「細かい浮動小数点まで計算したい」と「少ないリソースで効率的に計算したい」のトレードオフの関係があるため、パラメータをいじったところでこれをどのように制御するのかという話でしかない。
結論
- GPUで計算を回すということは単に「CPUとまったく同じ計算をめちゃくちゃ早くやってくれる!」という夢のような話ではない
- 並列化することによるわずかな影響がどこかに出るということを理解する
- どれくらいの精度で計算すべきかは環境やリソース等さまざまな要件に応じて決めるべし