-Python- 数値範囲の考慮

ソフトプラス関数
        softplus(x) = log(1 + e^x)
を考えます.この関数はxが十分に大きい場合は softplus(x) ≒ x で近似されます(e^xはxが大きくなると急激に大きくなるので,1 + e^x の1はほぼ無視できるようになるためです).従って,softmax(x) ≒ log e^x = x となります.

以下に上記の式のまま実装した例を示します.

import numpy as np
 
def softplus(x):

 

    return np.log(1 + np.exp(x))
 
実行結果は以下のようになります.
>>> import softplus
>>> softplus.softplus(-1)
0.31326168751822286
>>> softplus.softplus(0)
0.6931471805599453
>>> softplus.softplus(1000)
inf
>>> 

上記の実行結果では x = -1, 0 のときは計算できていますが,x = 1000 のときは inf という値が返ってきています.infは無限大を意味します.pythonにおいてinfが返ってくるのは,浮動小数点型で扱える限度を超えたときです.

しかし,softmax(x) ≒ log e^x = xであるということは,1000に近い値になるはずなので,浮動小数点型で十分に計算できる値です.infが返ってくる理由は,np.exp(1000)の計算がinfになるからです.このことを実際に確認してみます.

>>> import numpy as np
>>> np.exp(1000)
inf
>>> 

np.exp(1000)がすでに inf なので,inf に1を足してもinfになってしまい,そのlogをとってもinfになるのでした.

ここでの問題は,数学的には計算結果はさほど大きくない(Python浮動小数点型として十分に取り扱える範囲)にも関わらず,計算過程で inf が発生して,その後の計算も inf となることです.この問題を克服するには,もとの式を同値な式に変形し途中で inf が発生しないような工夫が必要です.
以下のように式変形を行います.
        log (1 + e^x) = log { e^x (e^(-x) + 1)} = log e^x + log (e^(-x) + 1) = x + log (e^(-x) + 1)
この計算を行うことによって,xが大きくなったときに inf が発生する問題を回避できます.x が十分に大きくなると,e^x が0に近づくからです.しかし,今後はxが十分に小さい場合にはe^(-x)が無限に大きくなってしまうので,もとの式の方が良いことになります.
よって,以下のように2つの指揮を使い分けることにします.
        softplus =
                x < 0:  log (1 + e^(-x))
                x ≧ 0: x + log (1 + e^(-x))
                = max(0, x) + log (1 + e^(-|x|))
上式において,max(a, b)はa, bのうち小さくない方とするといいう意味です.この式を実装すると以下のようになります.

import numpy as np
 
def softplus(x):
 
    return max(0, x) + np.log(1 + np.exp(-abs(x)))
 

実行結果は以下のようになります.

>>> import softplus2
>>> softplus2.softplus(-1)
0.31326168751822286
>>> softplus2.softplus(0)
0.6931471805599453
>>> softplus2.softplus(1000)
1000.0
>>> softplus2.softplus(-1000)
0.0
>>> 

x = 1000,x = -1000の場合も計算できています.

  • 浮動小数点型の比較には ==,!= などの完全一致するかどうかの比較は行わない.
  • 数値が近くなる場合の引き算は,桁落ち現象により有効桁数が失われるので,注意が必要.
  • 計算結果が計算機内で扱えるはずであるにもかかわらず,途中で inf,-inf が生じる計算には注意が必要.
  • 数学的に同値な式であっても,計算機内の計算手順としては同じ結果とならないことがある.