からっぽのしょこ

読んだら書く!書いたら読む!読書読読書読書♪同じ事は二度調べ(たく)ない

3.2:活性化関数【ゼロつく1のノート(実装)】

はじめに

 「プログラミング」学習初手『ゼロから作るDeep Learning』民のための実装攻略ノートです。『ゼロつく1』学習の補助となるように適宜解説を加えています。本と一緒に読んでください。

 関数やクラスとして実装される処理の塊を細かく分解して、1つずつ処理を確認しながらゆっくりと組んでいきます。

 この記事は、3.2節「活性化関数」の内容になります。ステップ関数、シグモイド関数、ReLU関数をPythonで実装します。

【前節の内容】

www.anarchive-beta.com

【他の節の内容】

www.anarchive-beta.com

【この節の内容】

3.1 パーセプトロンからニューラルネットワークへ

 パーセプトロンの式(3.1)を次のように書き換えます。

$$ \begin{align} y &= h(b + w_1 x_1 + w_2 x_2) \tag{3.2}\\ h(x) &= \begin{cases} 0 \quad (x \leq 0) \\ 1 \quad (x > 0) \end{cases} \tag{3.3} \end{align} $$

 関数$h(\cdot)$は条件式(3.3)に従って値をとる関数です。つまり、関数$h(\cdot)$の中身(入力信号の総和)が0以下なら出力$y$は0となり、0より大きければ$y$は1となることを表しています。(前節の式を同じ働きですが、それを示す式がすっきりしました。)

 このように、入力信号の総和を出力信号に変換する関数を活性化関数と呼びます。

3.2 活性化関数

 この節では、活性化関数として用いられる3つの関数「ステップ関数」「シグモイド関数」「ReLU関数」を順番に実装します。

# 3.2節で利用するライブラリ
import numpy as np
import matplotlib.pyplot as plt


3.2.1 シグモイド関数

 ニューラルネットワークの活性化関数としてよく用いられるシグモイド関数は次のような式です。

$$ h(x) = \frac{1}{1 + \exp(-x)} \tag{3.6} $$

 分母の$\exp(\cdot)$については【指数関数(1):指数関数とは【ゼロつく1のノート(数学)】 - からっぽのしょこ】で確認します(括弧の中が点なのは、$\exp(x)$と書くとマイナスどこ行った??となりそうなので、括弧の中に何か入りますよという記号として$\cdot$を使います。ちなみにプログラミングでいう関数は空の括弧を付けてfunction()と書きますね、書き分けというよりも慣習ですかね)。

3.2.2 ステップ関数の実装

 それでは始めに、このパーセプトロンの活性化関数として用いられるステップ関数を実装します。

 ステップ関数は式(3.3)のことです。式(3.2)と合わせてパーセプトロンとなります。式(3.3)の条件式を条件分岐ifを使って組みます。

# (仮の)入力データを設定
x = -1

if x > 0:
    # 0より大きい値の場合
    print(1)
else:
    # それ以外(0以下)の場合
    print(0)
0


 xの値を変えても条件通りに機能するようなら、この処理を関数として実装します。

# ステップ関数の実装
def step_function(x):
    
    # 式(3.3)
    if x > 0:
        # 引数xが0より大きい値の場合
        return 1
    else:
        # 引数xが0以下の値の場合
        return 0

 確認してみましょう。

step_function(-5)
0

 これでステップ関数を実装できました。

 しかし実際に利用することを考えると、一度に複数のデータを処理する必要があります。ここに複数のデータを渡してみると・・・

x = np.array([1, 2, 3])
step_function(x)
---------------------------------------------------------------------------

ValueError                                Traceback (most recent call last)

<ipython-input-36-a56890716952> in <module>
      1 x = np.array([1, 2, 3])
----> 2 step_function(x)


<ipython-input-34-d9e6eaa3fc2d> in step_function(x)
      3 
      4     # 式(3.3)
----> 5     if x > 0:
      6         # 引数xが0より大きい値の場合
      7         return 1


ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

エラーとなります。。。NumPy配列を渡しても処理できるようにしてみましょう。(この解説は本が詳しいので、本で確認してください。)

# ステップ関数の実装
def step_function(x):
    return np.array(x > 0, dtype = np.int)

 このように、数式をそのまま実現しようとするのではなく、Pythonの機能をうまく利用することも大切です。

 実装したstep_function()を使って、ステップ関数の入力データに対する出力の変化をグラフで確認してみましょう。

 まずはプロットする範囲の入力データを用意します。(グラフを描くときには、ある程度値の間隔(第3引数の値)を刻まないと滑らかなグラフになりません(このグラフでは関係ないですが)。)浮動小数点型の値の性質上小数点以下の桁数が多くなるので、np.round(x, 1)で1桁まで表示することにします。

# x軸の値を生成
x = np.arange(-5, 5, 0.1)
print(np.round(x, 1))
[-5.  -4.9 -4.8 -4.7 -4.6 -4.5 -4.4 -4.3 -4.2 -4.1 -4.  -3.9 -3.8 -3.7
 -3.6 -3.5 -3.4 -3.3 -3.2 -3.1 -3.  -2.9 -2.8 -2.7 -2.6 -2.5 -2.4 -2.3
 -2.2 -2.1 -2.  -1.9 -1.8 -1.7 -1.6 -1.5 -1.4 -1.3 -1.2 -1.1 -1.  -0.9
 -0.8 -0.7 -0.6 -0.5 -0.4 -0.3 -0.2 -0.1 -0.   0.1  0.2  0.3  0.4  0.5
  0.6  0.7  0.8  0.9  1.   1.1  1.2  1.3  1.4  1.5  1.6  1.7  1.8  1.9
  2.   2.1  2.2  2.3  2.4  2.5  2.6  2.7  2.8  2.9  3.   3.1  3.2  3.3
  3.4  3.5  3.6  3.7  3.8  3.9  4.   4.1  4.2  4.3  4.4  4.5  4.6  4.7
  4.8  4.9]


 このxをそのままstep_function()に渡すことで、各要素が変換されて出力されます。

# ステップ関数による活性化
y = step_function(x)
print(y)
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1]

 全て0か1の値に変換されていますね。

 matplotlib.pyplotを使って、xyのグラフを描きます。

# 作図
plt.plot(x, y) # 点の位置
plt.title("Step Function", fontsize = 20) # タイトル
plt.show() # グラフを表示

f:id:anemptyarchive:20200602183004p:plain
ステップ関数

 $x = 0$を境に、出力(y軸)が0から1に一気に変化しているのが分かりますね。

3.2.4 シグモイド関数の実装

 続いて、シグモイド関数を実装します。

 式(3.6)の通りに組みます。

# シグモイド関数の実装
def sigmoid(x):
    return 1 / (1 + np.exp(-x)) # 式(3.6)

 簡単に組めましたね。シンプルなのに高機能なのもシグモイド関数がよく使われる理由です。

 では、使ってみます。

# x軸の値を生成
x = np.arange(-10, 10, 0.1)
print(np.round(x, 1))
[-10.   -9.9  -9.8  -9.7  -9.6  -9.5  -9.4  -9.3  -9.2  -9.1  -9.   -8.9
  -8.8  -8.7  -8.6  -8.5  -8.4  -8.3  -8.2  -8.1  -8.   -7.9  -7.8  -7.7
  -7.6  -7.5  -7.4  -7.3  -7.2  -7.1  -7.   -6.9  -6.8  -6.7  -6.6  -6.5
  -6.4  -6.3  -6.2  -6.1  -6.   -5.9  -5.8  -5.7  -5.6  -5.5  -5.4  -5.3
  -5.2  -5.1  -5.   -4.9  -4.8  -4.7  -4.6  -4.5  -4.4  -4.3  -4.2  -4.1
  -4.   -3.9  -3.8  -3.7  -3.6  -3.5  -3.4  -3.3  -3.2  -3.1  -3.   -2.9
  -2.8  -2.7  -2.6  -2.5  -2.4  -2.3  -2.2  -2.1  -2.   -1.9  -1.8  -1.7
  -1.6  -1.5  -1.4  -1.3  -1.2  -1.1  -1.   -0.9  -0.8  -0.7  -0.6  -0.5
  -0.4  -0.3  -0.2  -0.1  -0.    0.1   0.2   0.3   0.4   0.5   0.6   0.7
   0.8   0.9   1.    1.1   1.2   1.3   1.4   1.5   1.6   1.7   1.8   1.9
   2.    2.1   2.2   2.3   2.4   2.5   2.6   2.7   2.8   2.9   3.    3.1
   3.2   3.3   3.4   3.5   3.6   3.7   3.8   3.9   4.    4.1   4.2   4.3
   4.4   4.5   4.6   4.7   4.8   4.9   5.    5.1   5.2   5.3   5.4   5.5
   5.6   5.7   5.8   5.9   6.    6.1   6.2   6.3   6.4   6.5   6.6   6.7
   6.8   6.9   7.    7.1   7.2   7.3   7.4   7.5   7.6   7.7   7.8   7.9
   8.    8.1   8.2   8.3   8.4   8.5   8.6   8.7   8.8   8.9   9.    9.1
   9.2   9.3   9.4   9.5   9.6   9.7   9.8   9.9]


 この値(要素)全てをsigmoid()関数で計算します。

# シグモイド関数による活性化
y = sigmoid(x)
print(np.round(y, 3))
[0.    0.    0.    0.    0.    0.    0.    0.    0.    0.    0.    0.
 0.    0.    0.    0.    0.    0.    0.    0.    0.    0.    0.    0.
 0.001 0.001 0.001 0.001 0.001 0.001 0.001 0.001 0.001 0.001 0.001 0.002
 0.002 0.002 0.002 0.002 0.002 0.003 0.003 0.003 0.004 0.004 0.004 0.005
 0.005 0.006 0.007 0.007 0.008 0.009 0.01  0.011 0.012 0.013 0.015 0.016
 0.018 0.02  0.022 0.024 0.027 0.029 0.032 0.036 0.039 0.043 0.047 0.052
 0.057 0.063 0.069 0.076 0.083 0.091 0.1   0.109 0.119 0.13  0.142 0.154
 0.168 0.182 0.198 0.214 0.231 0.25  0.269 0.289 0.31  0.332 0.354 0.378
 0.401 0.426 0.45  0.475 0.5   0.525 0.55  0.574 0.599 0.622 0.646 0.668
 0.69  0.711 0.731 0.75  0.769 0.786 0.802 0.818 0.832 0.846 0.858 0.87
 0.881 0.891 0.9   0.909 0.917 0.924 0.931 0.937 0.943 0.948 0.953 0.957
 0.961 0.964 0.968 0.971 0.973 0.976 0.978 0.98  0.982 0.984 0.985 0.987
 0.988 0.989 0.99  0.991 0.992 0.993 0.993 0.994 0.995 0.995 0.996 0.996
 0.996 0.997 0.997 0.997 0.998 0.998 0.998 0.998 0.998 0.998 0.999 0.999
 0.999 0.999 0.999 0.999 0.999 0.999 0.999 0.999 0.999 1.    1.    1.
 1.    1.    1.    1.    1.    1.    1.    1.    1.    1.    1.    1.
 1.    1.    1.    1.    1.    1.    1.    1.   ]

 $x$の値が小さくなると0に、大きくなると1に近づいていますね。(でも0にも1にもなってはいません。np.round()の第2引数の値を大きくして確認してみてください。)

 これをグラフでも確認しましょう。

# 作図
plt.plot(x, y) # 点の位置
plt.title("Sigmoid Function", fontsize = 20) # タイトル
plt.show() # グラフを表示

f:id:anemptyarchive:20200602183023p:plain
シグモイド関数

 以上でステップ関数とシグモイド関数が実装できました。

3.2.5 シグモイド関数とステップ関数の比較

 ステップ関数とシグモイド関数のグラフを重ねて、2つ関数の形を比較してみましょう。

 ステップ関数の出力$y$をy_step、シグモイド関数の出力$y$をy_sigmoidとします。(これまで変数xyの値を何度も上書きしてきました。今どの変数に何の値が入っているのか適切に管理することは重要です。)

# 共通のxの値
x = np.arange(-10, 10, 0.1)

# ステップ関数の計算
y_step = step_function(x)

# シグモイド関数の計算
y_sigmoid = sigmoid(x)

# 作図
plt.plot(x, y_step, linestyle = "--", label = "Step") # ステップ関数のグラフ
plt.plot(x, y_sigmoid, label = "Sigmoid") # シグモイド関数のグラフ
plt.legend() # 凡例
plt.show() # グラフを表示

f:id:anemptyarchive:20200602183036p:plain
ステップ関数とシグモイド関数の比較

 0から1の範囲の値をとるという共通部分を確認できますね。しかし、0から1への変化の仕方が大きく違いますね。(もっとも、シグモイド関数の方は厳密には0にも1にも達していません。そこも含めて)これを滑らかであると表現します。

3.2.7 RaLU関数

 最後に、ReLU関数は次の条件に従う関数です。

$$ h(x) = \begin{cases} x \quad (x > 0) \\ 0 \quad (x \leq 0) \end{cases} \tag{3.7} $$

 $x$が0以上であればそのまま$x$の値となり、$x$が0以下であれば($x$が何であれ)0となります。

 関数は変換器のようなもので、イメージする方程式とは異なるかもしれませんがこういうのも関数です。

 この条件に従う処理はnp.maximum()を使うと簡単に実装できます。np.maximum()は、2つの引数に渡された値の大きい方の値を返す関数です。

np.maximum(5, 10)
10
np.maximum(100, -100)
100


 では、これを使ってRaLU関数を実装します。

# RaLU関数の実装
def relu(x):
    return np.maximum(0, x) # 式(3.7)


 組んだものはとりあえず確認!

# お試し
print(relu(7))
print(relu(-3))
7
0


 大丈夫そうならグラフにしましょう。(グラフにしてダメのとこが分かることもあります。)

# x軸の値
x = np.arange(-5, 5, 0.1)

# ReLU関数の計算
y = relu(x)
print(y)
[0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.
 0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.
 0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.1 0.2 0.3
 0.4 0.5 0.6 0.7 0.8 0.9 1.  1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 2.  2.1
 2.2 2.3 2.4 2.5 2.6 2.7 2.8 2.9 3.  3.1 3.2 3.3 3.4 3.5 3.6 3.7 3.8 3.9
 4.  4.1 4.2 4.3 4.4 4.5 4.6 4.7 4.8 4.9]


# 作図
plt.plot(x, y) # 点の位置
plt.title("ReLU Function", fontsize = 20) # タイトル
plt.show() # グラフを表示

f:id:anemptyarchive:20200602183103p:plain
ReLU関数


 以上で3つの活性化関数を実装できました。折角なので、3つともプロットしておきましょうか。

# 共通のxの値
x = np.arange(-3, 3, 0.1)

# 活性化
y_step = step_function(x)
y_sigmoid = sigmoid(x)
y_relu = relu(x)

# 作図
plt.plot(x, y_step, linestyle = "--", label = "Step") # ステップ関数のグラフ
plt.plot(x, y_sigmoid, label = "Sigmoid") # シグモイド関数のグラフ
plt.plot(x, y_relu, linestyle = ":", label = "ReLU") # ReLU関数のグラフ
plt.legend() # 凡例
plt.show() # グラフを表示

f:id:anemptyarchive:20200602183116p:plain
ステップ関数・シグモイド関数・ReLU関数の比較

 ステップ関数やシグモイド関数と最小値が0というところは同じですが、最大値については異なりますね。なので利用目的も異なります(詳しくはn章にて)。

 ここまでは活性化関数として用いられる3つの関数による出力の話でした。ここからは入力データと重みの計算の話になります。

参考文献

  • 斎藤康毅『ゼロから作るDeep Learning』オライリー・ジャパン,2016年.

おわりに

 この記事は『ゼロつく1』シリーズ3記事(3日)目になります。明日の更新は、記事中にURLを貼ってある指数関数(とネイピア数)についての簡単な確認になります。投稿日に読んでいただいた方、まだないリンクを張っててすみません。明日と明後日の記事は本筋と直接の繋がりがないので、下の【次節の内容】には3.4節に関する記事を設定しています。つまり明々後日に下のリンクが有効になります!

 意図的に全ての記事が一直線に繋がらないようにしているので、最初の方にある記事一覧ページも是非確認してください!

 ぐだぐだはしてません。まだ順調です。次もよろしくお願いします。

【次節の内容】

www.anarchive-beta.com