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つの実装方法を試します。

pythonで実装する方法
  1. scipy.special.expit

  2. numpy.expを使用して定義を関数化

3.1. scipy.special.expit

scipyの関数を使用するだけなので簡単です。

example.py
from scipy.special import expit
print(expit(0))
output
In [3]: print(expit(0))
0.5

引数にリストを指定することが可能です。

example.py
from scipy.special import expit
print(expit([-1, 0, 1]))
output
In [4]: print(expit([-1, 0, 1]))
[0.26894142 0.5        0.73105858]

独自に関数を定義する必要がないので簡単です。

3.2. numpy.expを使用して定義を関数化

シグモイド関数の定義をそのまま実装します。

expitとは異なり引数にリストを指定することができません。

リストを指定したい場合にはnumpy配列に変換して指定します。

example.py
import numpy as np

def sigmoid(x):
    return 1 / (1 + np.exp(-x))

print(
    sigmoid(
        np.array([-1, 0, 1])
    )
)
output
[0.26894142 0.5        0.73105858]

4. オーバーフローの考慮

定義を関数化したsigmoidではオーバーフローが起こることがあります。

指数関数を使用しているのでxの値によっては大きくなりすぎるからです。

example.py
sigmoid(-800)
output
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はオーバーフローが起こりません。

example.py
expit(-10000)
output
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} \$

example.py
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)
output
In [43]: sigmoid2(-10000)
Out[43]: 0.0

オーバーフローが発生していません。

5. 処理時間

シグモイド関数の実装について調べているとexpitの処理時間が遅いという情報がいくつか見つかりました。

検証してみます。

5.1. expitの処理時間

example.py
import numpy as np
from scipy.special import expit

x = np.random.randn(10000)

%%timeit
r = expit(x)
output
%%timeit
r = expit(x)
--
164 µs ± 3.54 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

164usとなりました

5.2. 定義に従った独自関数の処理時間

example.py
%%timeit
r = sigmoid(x)
output
%%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を使用するのが良さそうです。

でも、シグモイド関数の理解を深めるために定義にしたがって独自に関数を定義してみるのも良い勉強になります。

今回は以上です。