1. シグモイド関数
ロジスティック回帰分析や機械学習を勉強していると出てくるのがシグモイド関数。
この記事ではシグモイド関数をpythonで実装する方法を検討します。
使い勝手や処理速度などを検証して、結論として
scipy.special.expit
を使用するのが一番良さそうという結論に至っています。
2. シグモイド関数の定義
数式に苦手意識を持っていると定義から逃げたくなりますが確認しておきます。
\$ \forall a>0 \$
\$ \f_a(x)=\frac{1}{1+e^(-ax)} \$
特にa=1の場合、狭義のシグモイド関数で標準シグモイド関数と呼ばれています。
\$ \f(x)=\frac{1}{1+e^(-x)} \$
今後、この記事内では標準シグモイド関数をpythonで実装する方法を検討します。
3. pythonで実装
2つの実装方法を試します。
-
scipy.special.expit
-
numpy.expを使用して定義を関数化
3.1. scipy.special.expit
scipyの関数を使用するだけなので簡単です。
from scipy.special import expit
print(expit(0))
In [3]: print(expit(0))
0.5
引数にリストを指定することが可能です。
from scipy.special import expit
print(expit([-1, 0, 1]))
In [4]: print(expit([-1, 0, 1]))
[0.26894142 0.5 0.73105858]
独自に関数を定義する必要がないので簡単です。
3.2. numpy.expを使用して定義を関数化
シグモイド関数の定義をそのまま実装します。
expitとは異なり引数にリストを指定することができません。
リストを指定したい場合にはnumpy配列に変換して指定します。
import numpy as np
def sigmoid(x):
return 1 / (1 + np.exp(-x))
print(
sigmoid(
np.array([-1, 0, 1])
)
)
[0.26894142 0.5 0.73105858]
4. オーバーフローの考慮
定義を関数化したsigmoidではオーバーフローが起こることがあります。
指数関数を使用しているのでxの値によっては大きくなりすぎるからです。
sigmoid(-800)
In [35]: sigmoid(-800)
<ipython-input-28-01c06bcdd919>:3: RuntimeWarning: overflow encountered in exp
return 1 / (1 + np.exp(-x))
Out[35]: 0.0
オーバーフローが発生しています。
一方でexpitはオーバーフローが起こりません。
expit(-10000)
In [38]: expit(-10000)
Out[38]: 0.0
4.1. オーバーフローを考慮したシグモイド関数の定義
xが正負の場合分けをすることでオーバーフローを考慮した関数を実装できます。
\$ \x>=0 \$
\$ \f(x)=\frac{1}{1+e^(-x)} \$
\$ \x<0 \$
\$ \f(x)=\frac{e^(x)}{e^(x) + 1} \$
def sigmoid2(x):
if x >= 0:
s = 1 / (1 + np.exp(-x))
else:
e=np.exp(x)
s = e / (e + 1)
return s
sigmoid2(-10000)
In [43]: sigmoid2(-10000)
Out[43]: 0.0
オーバーフローが発生していません。
5. 処理時間
シグモイド関数の実装について調べているとexpitの処理時間が遅いという情報がいくつか見つかりました。
検証してみます。
5.1. expitの処理時間
import numpy as np
from scipy.special import expit
x = np.random.randn(10000)
%%timeit
r = expit(x)
%%timeit
r = expit(x)
--
164 µs ± 3.54 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
164usとなりました
5.2. 定義に従った独自関数の処理時間
%%timeit
r = sigmoid(x)
%%timeit
207 µs ± 8.24 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
207usとなりました
5.3. 処理時間の比較
検証結果です
関数 | 処理時間 |
---|---|
expit |
164us |
sigmoid |
207us |
expitのほうが処理時間が早いです。
ネットで検索した結果とは反対の結果となりました。
expitの実装が変わって処理速度の問題が解消されたのかもしれません。
6. まとめ
シグモイド関数の実装方法を検討しました。
簡単さ、リストへの対応、オーバーフローの考慮、処理時間を考慮してみるとexpitを使用するのが良さそうです。
でも、シグモイド関数の理解を深めるために定義にしたがって独自に関数を定義してみるのも良い勉強になります。
今回は以上です。