#まえがき
計算過程に巨大すぎる数字を使わないように気をつけようという話です。
#算数の話をしましょう
logの定義を確認しましょう。底をeとして
\mathrm{log}(e) = 1
ですね。同様に
\mathrm{log}(e^2) = 2
\mathrm{log}(e^x) = x
です。では以下の式はどうでしょうか。
\mathrm{log}(1 + e^x)
これはソフトプラス関数といいまして、機械学習のなんやかんやで使用されるものです。これは
$e^x >> 1$の時
\mathrm{log}(1 + e^x) \simeq x
というように、xが十分に大きいときに1はほぼ無視できます。
#実装してみよう
import numpy
def softplus(x):
return numpy.log(1 + numpy.exp(x))
適当に数字を試してみます。
print(softplus(0)) # 0.6931471805599453
print(softplus(1)) # 1.3132616875182228
print(softplus(2)) # 2.1269280110429727
print(softplus(100)) # 100.0
$x = 100$の時点で$e^x$は十分に大きいみたいですね。
何が問題か?
さらにxを大きくしてみましょう。
print(softplus(1000)) # inf
想定される返り値は1000のはずですが、inf(無限大)という値が返ってきました。
#数値範囲
x = 100の時点で、$(1 + e^x)$はすごいことになっています。
$(1+e^{100}) = 2.6881171\times 10^{43}$
さらにこれを10乗した値が$(1+e^{1000})$。
最終的な計算結果は1000でも、途中でこんな異常な数字が出てきたらpythonの扱える範囲を超えてinfとして扱われてしまいます。
infのlogをとってもinf。これではいけません。
#改善策
要するに巨大な数字が計算途中に入らないようにすればよい。
元の式を変形してみましょう。
\mathrm{softplus}(x) = \mathrm{log}(1 + e^x)\\
=\mathrm{log}(e^x(e^{-x} + 1))
\\
= \mathrm{log}~e^x + \mathrm{log}(e^{-x} + 1)\\
= x + \mathrm{log}(e^{-x} + 1)
これなら$x \geq 0$の範囲でinfをとることはありません。
逆に$x < 0$の範囲ではinfをとってしまうので
\mathrm{softplus}(x) = \begin{cases}
x + \mathrm{log}(e^{-x} + 1) & (x \geq 0) \\
\mathrm{log}(1 + e^x) & (x < 0)
\end{cases}
というように場合分けしてやればいいわけです。
#実装してみよう
import numpy
def softplus(x): # max(0, x) 0かxか、大きい方を採用する関数
return max(0, x) + numpy.log(1 + numpy.exp(-abs(x)))
print(softplus(-1000)) # 0.0
print(softplus(0)) # 0.6931471805599453
print(softplus(1)) # 1.3132616875182228
print(softplus(1000)) # 1000.0
問題なく計算できました。
#まとめ
計算過程に巨大な数字が出るとinfを吐いてしまう。工夫して回避しよう。
『機械学習のエッセンス』という本を参考にしました。引き続きこの本についてまとめていきます。
前回:桁落ちに気をつけよう
https://qiita.com/NNNiNiNNN/items/af550e9f1c72aadd2577
次回:数理最適化問題
https://qiita.com/NNNiNiNNN/items/57e409e5dbcfac9897ec