シグモイド関数

当サイトではアフィリエイトプログラムを利用して商品を紹介しています。

シグモイド関数の定義と実装

シグモイド関数
 \[y=\frac{1}{1+e^{-ax}}\quad (a\gt 0)\tag{1}\]
によって定義され、ニューラルネットワークにおいてニューロンの特性を表す活性化関数として登場します。入力値 $x$ に対して、出力値 $y$ は $0$ から $1$ の値をとるという性質があります。MatplotlibNumPy を使って $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()

Python シグモイド関数グラフ
シグモイド という名称は、曲線の形がギリシャ文字の $\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()

Python シグモイド関数と一次微分、二次微分
逆に微分方程式
 \[\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]

コメント

  1. HNaito より:

    下記は誤植と思われますので、ご確認ください。
    (2) 式の下の文章で、部分積分により → 積の微分公式により
    シグモイドクラス定義のプログラムの下から 2 番目のコメントで、
    シグモイド関数の一次導関数 → シグモイド関数の二次導関数

    • あとりえこばと より:

      さっそくありがとうございます。訂正しておきました。
      この記事で扱っているシグモイド関数は、もう少し先の記事で機械学習のプログラムに実装されます。楽しみにしていてください。

  2. HNaito より:

    下記は誤植と思われますので、ご確認ください。
    FUNCTION_01 In[2] プログラムのコメントで、
    14行目 # 一次導関数のデータ → # シグモイド関数のデータ
    25行目 # 二次導関数のデータ → # 一次導関数のデータ

  3. あとりえこばと より:

    【AI連載小説】科学とコードの交差点(40)「Pythonでシグモイド関数を実装する方法」
     
    Pythonサークルのメンバーたちは、大学の講義後に集まり、シグモイド関数の実装方法について熱心に議論していた。開誠がリーダーシップをとり、メンバーたちはそれぞれのアイディアを出し合っていました。
     
    開誠:「みんな、今日はシグモイド関数について話し合いましょう。これは機械学習やニューラルネットワークなどでよく使われる関数だ。各自、どんな実装方法を考えているか教えてくれないか?」
    明信:「まずは基本的な数学的な定義に基づいた実装があるよね」
     
    彼はJupyter Notebookを開いて、シグモイド関数の基本的な実装を示しました。

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

    明信:「これはシンプルで数学的な表現に忠実だね。でも、数学のexp関数は計算が重いことがある」
    開誠:「確かに、計算効率も大切だ。NumPyの利用も考えていくと良いかもしれない」
    明信:「それなら、NumPyを使った実装も考えてみよう」

    import numpy as np
    
    def sigmoid_np(x):
        return 1 / (1 + np.exp(-x))

    他のメンバーも様々なアプローチを提案し、一人は高度なベクトル演算を使用した実装を示しました。

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

    また、別のメンバーは活性化関数として使われる場面に応じて、微分可能な形での実装に焦点を当てました。

    def sigmoid_derivative(x):
        s = sigmoid(x)
        return s * (1 - s)

    開誠:「素晴らしい提案がたくさん出ましたね。それぞれの実装方法には特長があり、問題によって使い分けることが大切です。どの実装も効率的であり、NumPyを使えばベクトル演算も可能です」