python で sigmoid を算出する時は定義域に注意

# はじめに

本質は指数関数 <img alt="e^x.png" src="https://qiita-image-store.s3.amazonaws.com/0/1798/6c3608fb-c20e-35c8-8266-d9a6aea60589.png">  の計算で python の exp 関数を使う際の注意事項ですが、exp は sigmoid を算出するのによく使うので、そちらの例で紹介します。

# sigmoid関数

よく活性化関数として使われます。
値を 0 か 1 に寄せたいけど、やりすぎて 0 と 1 の2値だけに潰されるのは嫌。そんな時に使い勝手の良い関数です。

|<img src="https://wikimedia.org/api/rest_v1/media/math/render/svg/9537e778e229470d85a68ee0b099c08298a1a3f6" />|
|---|
|![55271264-2ef44d00-52ed-11e9-8675-a49be1fb4cee.png](https://qiita-image-store.s3.amazonaws.com/0/1798/8505d95e-5f50-6677-e7fd-ef04f20fae07.png) |

sigmoid の計算に良く使われる <img alt="e^x.png" src="https://qiita-image-store.s3.amazonaws.com/0/1798/6c3608fb-c20e-35c8-8266-d9a6aea60589.png"> は爆発的に大きくなる関数です。

| ![e^x.png](https://qiita-image-store.s3.amazonaws.com/0/1798/6c3608fb-c20e-35c8-8266-d9a6aea60589.png) | 
|---|
|![スクリーンショット 2019-04-02 1.31.20.png](https://qiita-image-store.s3.amazonaws.com/0/1798/f85a45d7-7691-5fae-db71-67c49c93becb.png) |

定義域の x:709〜710 あたりで double でも扱いきれず、特別な値 Infinity (無限大)になります。更にその Infinity の数値を元に sigmoid を計算すると NaN(Not a Number) になる事があります。

# 実験

<img alt="e^x.png" src="https://qiita-image-store.s3.amazonaws.com/0/1798/6c3608fb-c20e-35c8-8266-d9a6aea60589.png"> は exp 関数で計算できます。

```python:
import numpy, sys, warnings
warnings.filterwarnings('ignore')

def sigmoid1(x):
    return 1.0 / (1.0 + numpy.exp(-x))
def sigmoid2(x):
    return numpy.exp(x) / (numpy.exp(x) + 1.0)

for x in [sys.float_info.max, 710, 709, sys.float_info.min, 0, -sys.float_info.min, -709, -710, -sys.float_info.max]:
	  print("|", x, "|", numpy.exp(-x), "|", numpy.exp(x), "|", sigmoid1(x), "|", sigmoid2(x), "|")
```

|![x.png](https://qiita-image-store.s3.amazonaws.com/0/1798/60f93e21-c9b5-d539-6f6e-6cc8c1d0f9fd.png) | ![e^-x.png](https://qiita-image-store.s3.amazonaws.com/0/1798/b1d78225-0253-9197-9d8f-6dee2012a312.png) | ![e^x.png](https://qiita-image-store.s3.amazonaws.com/0/1798/6c3608fb-c20e-35c8-8266-d9a6aea60589.png) | ![1/(1+e^-x).png](https://qiita-image-store.s3.amazonaws.com/0/1798/1b1dce22-1928-31fc-8279-6deb12436153.png) | ![e^x/(e^x+1).png](https://qiita-image-store.s3.amazonaws.com/0/1798/785a0bf4-856f-4024-2f0f-ba367682b749.png) |
|---|---|---|---|---|
| 1.7976931348623157e+308 | 0.0 | inf | 1.0 | nan |
| 710 | 4.47628622567513e-309 | inf | 1.0 | nan |
| 709 | 1.216780750623423e-308 | 8.218407461554972e+307 | 1.0 | 1.0 |
| 2.2250738585072014e-308 | 1.0 | 1.0 | 0.5 | 0.5 |
| 0 | 1.0 | 1.0 | 0.5 | 0.5 |
| -2.2250738585072014e-308 | 1.0 | 1.0 | 0.5 | 0.5 |
| -709 | 8.218407461554972e+307 | 1.216780750623423e-308 | 1.216780750623423e-308 | 1.216780750623423e-308 |
| -710 | inf | 4.47628622567513e-309 | 0.0 | 4.47628622567513e-309 |
| -1.7976931348623157e+308 | inf | 0.0 | 0.0 | 0.0 |

この表をみると <img alt="1/(1+e^-x)" src="https://qiita-image-store.s3.amazonaws.com/0/1798/1b1dce22-1928-31fc-8279-6deb12436153.png"> を使えば良い、といった結論になりそうですが、そうは問屋が卸しません。

# numpy の exp

上記のスクリプトでは warnings.filterwarnings('ignore') で誤魔化していますが、exp が inf を出す時と、その inf で sigmoid を計算する時に警告が出ます。

```python:
% cat sigmoid_test.py
import numpy
def sigmoid(x):
    return 1.0 / (numpy.exp(-x) + 1.0)

print (numpy.exp(710))
print(sigmoid(-710))
%
% python sigmoid_test.py
sigmoid_test.py:7: RuntimeWarning: overflow encountered in exp
  print (numpy.exp(710))
inf
sigmoid_test.py:3: RuntimeWarning: overflow encountered in exp
  return 1.0 / (numpy.exp(-x) + 1.0)
0.0
```

ただ、警告は出ますが python の処理は続きます。

# math の exp

python 標準パッケージ math の exp だと更に深刻で、Warning でなく Error でプログラムが止まります。

```python:
% cat sigmoid_math.py
import math

print(math.exp(710))
%
% python sigmoid_math.py
Traceback (most recent call last):
  File "sigmoid_test.py", line 3, in <module>
    print(math.exp(710))
OverflowError: math range error
``` 

# 対処案

<img alt="e^-x.png" src="https://qiita-image-store.s3.amazonaws.com/0/1798/b1d78225-0253-9197-9d8f-6dee2012a312.png"> は x:-710 あたりから inf に、<img alt="e^x.png" src="https://qiita-image-store.s3.amazonaws.com/0/1798/6c3608fb-c20e-35c8-8266-d9a6aea60589.png"> は x:710 あたりから inf になるので、x の正負で <img alt="1/(1+e^-x).png" src="https://qiita-image-store.s3.amazonaws.com/0/1798/1b1dce22-1928-31fc-8279-6deb12436153.png"> と <img alt="e^x/(e^x+1).png" src="https://qiita-image-store.s3.amazonaws.com/0/1798/785a0bf4-856f-4024-2f0f-ba367682b749.png"> を使い分けると良いでしょう。

```python:
% cat sigmoid_test2.py
import numpy
def sigmoid(x):
    if x >= 0:
        return 1.0 / (1.0 + numpy.exp(-x))
    e = numpy.exp(x)
    return e / (e + 1.0)

print(sigmoid(710))
print(sigmoid(-710))
%
% python sigmoid_test2.py
1.0
4.47628622567513e-309
```



# 最後に

計算精度的にどうなのとか気になるので、時間があったら突き詰めたいです。
あと、自分は tanh で計算する派でした。_(┐「ε:)_