活性化関数には、中間層でよく使われる「シグモイド関数」や「ReLU(ランプ関数)」、出力層で使われる「恒等関数」や「ソフトマックス関数」があります。どれも聞きなれない名前が付いているので、いっけん難しそうに思えますが、実際にプログラムでこれらの関数を作ってみると、わずか数行のコードでできてしまうほど簡単です。そこで今回は、Python(バージョン3)での活性化関数の実装方法をまとめてみました。
線形と非線形
ディープラーニングの解説では、「線形」「非線形」という言葉がよく出てきます。これもまた聞きなれない言葉なので構えてしまいますが、なにかをグラフにしたときに、直線であればそれを「線形」と呼び、直線でなければ「非線形」と呼んでいるだけのことです。
そしてディープラーニングや、その基礎となるニューラルネットワークの中間層で使う活性化関数は「非線形」であることがとても重要です。(出力層では、線形の活性化関数を使うこともあります)
ステップ関数
まずはじめに、ステップ関数です。
入力(x)が 0 を超えたら 1 を出力(y)し、それ以外は 0 を出力するとてもシンプルな活性化関数です。これを Python 実装すると次のようになります。(グラフを表示するためコードが長いですが、ステップ関数は青字の部分2行のみです)
import numpy as np
import matplotlib.pylab as plt
def step_function(x):
return np.where(x > 0, 1, 0)
x = np.array([-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5])
y = step_function(x)
plt.plot(x, y)
plt.show()
ステップ関数の実装方法は色々ありますが、今回は numpy.where を使っています。条件「x > 0」が成り立てば 1 を返し、それ以外は 0 を返します。
ステップ関数は下のようなグラフになります。いっけん 線形(グラフが直線)に見えますが、x = 0 のあたりでまがっているので、非線形(グラフが直線でない)の活性化関数です。
ステップ関数は、ディープラーニングの大元となった「パーセプトロン」の活性化関数として使われています。パーセプトロンの活性化関数はこのステップ関数に限定されますが、ニューラルネットワークの活性化関数は、非線形の活性化関数であればなんでも構わないところに違いがあります。
関連記事:5分でわかる!パーセプトロンの仕組みと実装方法(Python)
中間層の活性化関数
続いて中間層でよく使われる活性化関数の実装方法です。
シグモイド関数
代表的な活性化関数、「シグモイド関数」です。
シグモイド関数を Python で実装すると次のようになります。
import matplotlib.pylab as plt
import numpy as np
def sigmoid_function(x):
return 1 / (1 + np.exp(-x))
x = np.array([-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5])
y = sigmoid_function(x)
plt.plot(x, y)
plt.show()
シグモイド関数は、ネイピア数 e(2.718281・・・)の累乗を活用しているのが特徴です。Python ではよくこれを numpy.exp で実装します。(上のコードの「np.exp(-x)」部分で使っています)
シグモイド関数は、これぞ 非線形(直線でない)と言えるグラフになります。ステップ関数と比べるとグラフの線がかなりなめらかになりましたね。
シグモイド関数は、ニューラルネットワークの活性化関数として古くから使われてきましたが、ニューラルネットワークの中間層(隠れ層とも言います)を3層以上に増やすと、ニューラルネットワークでの学習がうまくいかなくなる問題(「勾配消失問題」と呼ばれています)がありました。そのため、最近では次の ReLU(ランプ関数)が、中間層の活性化関数としてよく使われています。
ReLU(ランプ関数)
ReLU(「ランプ関数」とも呼ばれています)は、入力(x)が 0 を超えたら入力(x)をそのまま出力し、0 以下ならば 0 を出力するという実にシンプルな関数です。
ReLU(ランプ関数)を Python で実装すると次のようになります。
import matplotlib.pylab as plt
import numpy as np
def relu_function(x):
return np.where(x > 0, x, 0)
x = np.array([-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5])
y = relu_function(x)
plt.plot(x, y)
plt.show()
ステップ関数でも使った numpy.where を使って実装しています。条件「x > 0」が成り立てば入力(x)をそのまま返し、それ以外は 0 を返します。
ReLU(ランプ関数)は下のようなグラフになります。どうみても 線形(グラフが直線)に見えますが、x = 0 のところでまがってるので、これでもりっぱな 非線形(グラフが直線でない)の活性化関数です。
上にも書きましたが、ReLU(ランプ関数)は、計算が簡単で中間層が多いディープラーニングでも安定して学習ができるので、現在よく使われている活性化関数です。
出力層の活性化関数
出力層の活性化関数は、解決したい問題の種類で使い分けをします。回帰問題では恒等関数が、分類問題ではソフトマックス関数が、よく使われています。
問題 | 例 | 活性化関数 |
---|---|---|
回帰問題 | 身長から体重を予想する | 恒等関数 |
分類問題 | 動物の写真から犬を分類する | ソフトマックス関数 |
恒等関数
回帰問題で使われる 恒等関数は、入力(x)をそのまま出力するだけの関数です。(なので、出力層の活性化関数を省略したのと同じです)
コードを示すまでもありませんが、恒等関数を Python で実装すると次のようになります。
import matplotlib.pylab as plt
import numpy as np
def identity_function(x):
return x
x = np.array([-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5])
y = identity_function(x)
plt.plot(x, y)
plt.show()
恒等関数は下のようなグラフになります。みごとに 線形(グラフが直線)な活性化関数です。
ソフトマックス関数
分類問題で使われる ソフトマックス関数は、複数ある出力の合計を 1 にしてくれる関数です。これまでの活性化関数と少し働きが異なります。
例えば、動物の写真を、イヌ、ネコ、ウザギ、に分類する(分類問題)ニューラルネットワークが、それぞれの確率を次のように計算した場合、出力層の活性化関数にソフトマックス関数を使うと、確率の合計が 1 になるように変換してくれます。
ソフトマックス関数を Python で実装すると次のようになります。
import numpy as np
def softmax_function(x):
# オーバーフロー対策
max_x = np.max(x)
exp_x = np.exp(x - max_x)
return exp_x / np.sum(exp_x)
x = np.array([3, 2, 1])
y = softmax_function(x)
print(y)
上のソフトマックス関数の出力(y)は、次のようになります。
シグモイド関数で使った、ネイピア数 e(2.718281・・・)の累乗 numpy.exp をソフトマックス関数でも使って実装しています。ただ入力(x)が大きい値の場合、オーバーフロー(計算結果が大きい値になりすぎて、値を表示できなくなった状態のこと)が発生してしまうため、「np.exp(x - max_x)」の部分で、入力の最大の値を引いています。
オーバーフロー対策の部分では、例えば入力(x)が、[300 , 270, 250] の場合に、それぞれの値から最大値の300を引いて [0 , -30, -50] となり値を小さくしています。
おわりに(数学の関数とプログラムの関数)
余談ですが、活性化関数の「関数」は、数学で言うところの関数のことです。実にややこしい話なのですが、今回はこの数学の「関数」を、プログラムの「関数」で実装しました。
ある値を関数に入れると、なにかしらの計算処理が行われた値が出てくるという意味では、数学の「関数」とプログラムの「関数」は、よく似ていますが、数学の関数をプログラムで実装した関数は、あくまで近似(ある数値に非常に近いこと)です。
プログラムの関数には、値を返さずにデーターベースに値を書き込むといった働きをするものもありますので、数学の「関数」とプログラムの「関数」は、あくまで別物だと考えておいた方がよさそうですね。
コメント