シグモイド関数の定義と実装
シグモイド関数は
\[y=\frac{1}{1+e^{-ax}}\quad (a\gt 0)\tag{1}\]
によって定義され、ニューラルネットワークにおいてニューロンの特性を表す活性化関数として登場します。入力値 $x$ に対して、出力値 $y$ は $0$ から $1$ の値をとるという性質があります。Matplotlib と NumPy を使って $a$ を変化させながらシグモイド関数のグラフを描いてみます。
# PYTHON_SIGMOID_FUNCTION # In[1] import numpy as np import matplotlib.pyplot as plt # シグモイド関数を定義 def sigmoid(x, a): return 1 / (1 + np.exp(-a * x)) # フィギュアを設定 fig = plt.figure() # グラフ描画領域を追加 ax = fig.add_subplot(111) # グリッド線を表示 ax.grid(linestyle="--") # グラフタイトルを設定 ax.set_title("Sigmoid Function", fontsize=16) # x軸, y軸のラベルを設定 ax.set_xlabel("x", fontsize=16) ax.set_ylabel("y", fontsize=16) # -10~10,256分割の点 x = np.linspace(-10, 10, 256) # シグモイド関数をプロット a = [0.5, 1, 4] for k in range(3): y = sigmoid(x, a[k]) ax.plot(x, y, label="a = {}".format(a[k])) # 凡例を表示 ax.legend() plt.show()
シグモイド という名称は、曲線の形がギリシャ文字の $\varsigma$ (シグマ) に似ていることに由来します。$a$ はゲイン (gain) とよばれるパラメータです。図にあるように、$a$ が大きくなるほど原点付近の傾斜は大きくなって階段関数に近づいていきます。特にゲイン $a$ が $1$ のときは 標準シグモイド関数 とよばれます。
シグモイド関数の微分
シグモイド関数を微分して導関数を求めてみます。
定義式 (1) の両辺に $1+e^{-ax}$ を掛けて右辺の分母を払うと
\[(1+e^{-ax})y=1\tag{2}\]
となります。両辺を $x$ で微分すると、積の微分公式により
\[\begin{align*}(1+e^{-ax})’y+(1+e^{-ax})y’=&0\tag{3}\\[6pt]
-ae^{-ax}y+(1+e^{-ax})y’=&0\tag{4}\\[6pt]\end{align*}\]
となるので、一次導関数
\[y’=ay\,\frac{e^{-ax}}{1+e^{-ax}}=ay(1-y)\tag{5}\]
が得られます。この式をもう一度微分すると 二次導関数を得ます。(5) の表式を用いれば簡単です。
\[\begin{align*}y”=&a\{y'(1-y)-yy’\}\\[6pt]=&a\{ay(1-y)(1-y)-yay(1-y)\}\\[6pt]
=&a^2y(1-y)(1-2y)\\[6pt]\end{align*}\tag{6}\]
ある関数 $f(x)$ と、その導関数 $f'(x),\ f”(x)$ はクラスの中にメソッドとしてまとめてしまうと便利です。以下にクラス定義の一例を掲載しておくので、参考にしてください。
# In[2] # シグモイドクラスを定義 class Sigmoid: # ゲインの値をインスタンス変数aに格納 def __init__(self, gain): self.a = gain # シグモイド関数 def sigmoid(self, x): return 1 / (1 + np.exp(-self.a * x)) # シグモイド関数のデータ def sigmoid_data(self, x_range, n): x_data = np.linspace(x_range[0], x_range[1], n) y_data = self.sigmoid(x_data) return x_data, y_data # シグモイド関数の一次導関数 def first(self, x): y = self.sigmoid(x) return self.a * y * (1 - y) # 一次導関数のデータ def first_data(self, x_range, n): x_data = np.linspace(x_range[0], x_range[1], n) y_data = self.first(x_data) return x_data, y_data # シグモイド関数の二次導関数 def second(self, x): y = self.sigmoid(x) return self.a**2 * y * (1 - y)*(1 - 2*y) # 二次導関数のデータ def second_data(self, x_range, n): x_data = np.linspace(x_range[0], x_range[1], n) y_data = self.second(x_data) return x_data, y_data
Sigmoid クラスのインスタンスは、コンストラクタにゲイン a を指定して生成します。たとえば Sigmoid(1) はゲイン 1 のシグモイド関数、およびその一次導関数、二次導関数をまとめたインスタンスです。
Sigmoid.sigmoid() は任意の引数 x におけるシグモイド関数の値を返します。引数 x には数値または配列を指定できます。
Sigmoid.sigmoid_data() はシグモイド関数のプロット用の連続データを生成するメソッドです。引数 x_range には変数 x の範囲 (x の最小値と最大値) をリストやタプルで指定します。n は分割数です。
Sigmoid.first() は引数 x (数値または配列) におけるシグモイド関数の一次導関数の値を返します。
Sigmoid.first_data() はシグモイド一次導関数のプロット用のデータを生成します。引数 x_range には変数 x の範囲を指定します。第 2 引数 n で分割数を指定できます。
Sigmoid.second() は引数 x (数値または配列) におけるシグモイド関数の二次導関数の値を返します。
Sigmoid.second_data() はシグモイド二次導関数のプロット用のデータを生成します。引数 x_range には変数 x の範囲を指定します。第 2 引数 n で分割数を指定します。
ゲイン 1 のシグモイドクラスのインスタンスを生成して、シグモイド関数および一次導関数、二次導関数のプロット用データを生成してみます。
# In[3] # ゲイン1のシグモイドクラスのインスタンスを生成 my_func = Sigmoid(1) # 変数xの範囲を設定 x_range = [-8, 8] # 連続データの分割数を設定 n = 257 # シグモイド関数のデータを取得 data_0 = my_func.sigmoid_data(x_range, n) # 一次導関数のデータを取得 data_1 = my_func.first_data(x_range, n) # 二次導関数のデータを取得 data_2 = my_func.second_data(x_range, n)
作成したデータを使って、Matplotlib でグラフを表示してみます。
# In[4] # フィギュアとサブプロット fig = plt.figure() ax = fig.add_subplot(111) ax.grid(linestyle="--") ax.set_title("Sigmoid Function (a=1)", fontsize=16) ax.set_xlabel("x", fontsize=16) ax.set_ylabel("y", fontsize=16) # シグモイド関数をプロット ax.plot(data_0[0], data_0[1], label="Sigmoid function") # 一次導関数をプロット ax.plot(data_1[0], data_1[1], label="first derivative") # 二次導関数をプロット ax.plot(data_2[0], data_2[1], label="second derivative") # 凡例を表示 ax.legend() plt.show()
逆に微分方程式
\[\frac{dy}{dx}=ay(1-y)\tag{7}\]
解いてシグモイド関数を導いてみましょう。上式を変数分離すると
\[\frac{dy}{y(1-y)}=adx\tag{8}\]
となります。左辺を分解すると
\[\left(\frac{1}{y}+\frac{1}{1-y}\right)dy=adx\tag{9}\]
両辺を積分すると
\[\begin{align*}\log|y|-\log|1-y|&=ax+C\\[6pt]\log\left|\frac{y}{1-y}\right|&=ax+C\\[6pt]\frac{y}{1-y}&=\pm e^{ax+C}\\[6pt]\end{align*}\tag{10}\]
$A=\pm e^C$ とおいて式を整理すると、
\[y=\frac{A}{A+e^{-ax}}\tag{11}\]
が得られます。初期条件 $y(0)=1/2$ を代入すると $A=1$ が決まるので、
\[y=\frac{1}{1+e^{-ax}}\tag{12}\]
となってシグモイド関数が現れます。
scipy.special.expit()
SciPyパッケージ の scipy.special.expit(x) は標準シグモイド関数を計算します。
# SCIPY_SPECIAL_EXPIT # In[1] import numpy as np from scipy.special import expit # 配列xを定義 x = np.array([-0.5, 0, 0.5]) # シグモイド関数を計算 s = expit(x) print(s) # [0.37754067 0.5 0.62245933]
$a$ の値を指定してシグモイド関数の値を計算したい場合は、以下のような関数を定義します。
# In[2] # シグモイド関数 def sp_sigmoid(a, x): return expit(a*x) # a=2を指定してシグモイド関数を計算 s = sp_sigmoid(2, x) print(s) # [0.26894142 0.5 0.73105858]
コメント
下記は誤植と思われますので、ご確認ください。
(2) 式の下の文章で、部分積分により → 積の微分公式により
シグモイドクラス定義のプログラムの下から 2 番目のコメントで、
シグモイド関数の一次導関数 → シグモイド関数の二次導関数
さっそくありがとうございます。訂正しておきました。
この記事で扱っているシグモイド関数は、もう少し先の記事で機械学習のプログラムに実装されます。楽しみにしていてください。
下記は誤植と思われますので、ご確認ください。
FUNCTION_01 In[2] プログラムのコメントで、
14行目 # 一次導関数のデータ → # シグモイド関数のデータ
25行目 # 二次導関数のデータ → # 一次導関数のデータ
修正しました。
いつもありがとうございます。m(_ _)m