数値の落とし穴 — 桁落ち・丸め・logsumexp
「学習を回して3時間後に損失がnanになる」の正体を、浮動小数点の丸め・条件数・桁落ちの順に前提知識ゼロから解きます。最後は、softmaxと交差エントロピーの実装に必ず入っている logsumexp という一つの定石に合流します。
0.1 + 0.2 が 0.3 にならない
どの言語でもいいので 0.1 + 0.2 を評価してみてください。返ってくるのは 0.30000000000000004 です。バグではなく仕様です。10進では割り切れる 0.1 が2進では循環小数になり、有限のビット数に収めるために丸められている。ただそれだけのことです。
普段はこの を気にせず生きていけます。問題は、この程度の誤差が特定の操作を通ると桁違いに膨らむことです。しかも膨らんだ結果は、たいてい「学習を回して3時間後に損失が nan になる」という形でしか表に出ません。原因が実行から遠く離れているので、勘で直そうとすると何日も溶けます。
この記事では、誤差がどこで生まれ、どこで増幅され、どう書けば抑えられるかを順に見ます。
比喩: 右へ行くほど目盛りが粗くなる巻き尺
浮動小数点数は「有効数字の桁数だけが一定の巻き尺」だと思ってください。1 のあたりでは 0.0000001 刻みで測れるのに、1000万のあたりでは 1 刻みでしか測れない。目盛りの総数は決まっているので、遠くまで測れるようにすれば、そのぶん細かさを捨てることになります。
指数部が「いまどのあたりを測っているか」を、仮数部が「そこでの目盛りの細かさ」を決めます。型ごとの内訳は数の表し方 — FP32からFP8・INT4までで開けているので、ここでは帰結だけ持ち帰ってください。絶対誤差は場所によって全然違うが、相対誤差はどこでもほぼ一定。この一行から、以下の落とし穴がすべて導けます。
直感: 誤差は足し算ではなく掛け算で入る
1 のすぐ次に表せる数までの距離をマシンイプシロンと呼びます。その型の「目盛りの細かさ」の代表値です。
| 型 | 仮数のビット数 | マシンイプシロン | だいたいの有効数字 |
|---|---|---|---|
| float64 | 52 | 2^-52 ≈ 2.2e-16 |
15〜16桁 |
| float32 | 23 | 2^-23 ≈ 1.2e-7 |
約7桁 |
| float16 | 10 | 2^-10 ≈ 9.8e-4 |
約3桁 |
| bfloat16 | 7 | 2^-7 ≈ 7.8e-3 |
約2桁 |
四則演算は「真の答えを最寄りの目盛りに丸めた値」を返すので、1回あたりの誤差はマシンイプシロンの半分以下に収まります。1回なら誰も困りません。困るのは、これが回数で積み上がるときと、次に見る増幅器を通るときです。
整数を扱うときにも同じ話が刺さります。連続する整数を1つ残らず表せるのは float32 で 16,777,216(2の24乗)まで、float16 では 2,048 までです。それを超えると目盛りの間隔が 1 より粗くなるので、1ずつ足しても値が増えなくなります。fp16 のテンソルで件数や累計トークン数を数えると、カウンタは 2048 で凍りつきます。例外は出ません。
誤差を膨らませる増幅器 — 条件数
同じ大きさの入力誤差でも、答えが大きく動く問題と、びくともしない問題があります。その倍率が条件数です。連立方程式 を解くなら 、対称行列なら固有値の絶対値の比 になります。
使い方は目安1本だけです。入力の相対誤差 × 条件数 ≒ 答えの相対誤差。条件数が なら有効数字が 桁削られると思ってください。float32 の持ち札は約7桁ですから、条件数 の問題を float32 で解けば、答えに残るのは1桁です。式が正しくても、桁が足りない。
身近な例は「ほとんど平行な2直線の交点を求める問題」です。片方をわずかに傾けるだけで、交点は遠くへ吹き飛びます。同じことが、相関の強い特徴量を並べたときの正規方程式でも起こる。リッジ回帰が対角に小さな値を足すのは、正則化であると同時に条件数を下げる操作でもあります。
幾何的にはこうです。行列は単位円を楕円に変形します。条件数はその長軸と短軸の比。楕円が薄いほど、逆向きに戻すとき(=逆行列を掛けるとき)に短軸方向のわずかなズレが大きく引き伸ばされます。
コメント
コメントにはログインが必要です