AIって結局何なのかよく分からないので、とりあえず100日間勉強してみた Day52
経緯についてはこちらをご参照ください。
■本日の進捗
- ソフトマックス関数を理解
■はじめに
今回も「ゼロから作るDeep Learning Pythonで学ぶディープラーニングの理論と実装(オライリー・ジャパン)」で、深層学習を学んでいきます。
前回、多層パーセプトロンの活性化関数をシグモイド関数やReLUに変えることで、基礎的なニューラルネットワークを構築しました。このモデルを回帰タスクやクラス分類タスクに適用するにあたっては、出力層にも少し工夫が必要になります。今回は主に出力層で用いられる関数を見ていきます。
■出力層の活性化関数
隠れ層(入力層や出力層を除いた中間層)では活性化関数にシグモイド関数やReLUを用いることで、ニューラルネットワークを構築しましたが、出力層には別の活性化関数を用いることでニューラルネットワークの出力値を調整することができます。
これを行うことで、(隠れ層などの中身のアルゴリズムを変えずに)ニューラルネットワークを回帰タスクに用いる場合には予測値を、クラス分類タスクに用いる場合には(例えば0から1で表現される)確率などの値にスケーリングすることで、どちらの問題にも適用できるようになります。
具体的には前回のコードで identity_function として定義した部分を変えることに当たります。
import numpy as np import matplotlib.pyplot as plt def sigmoid(x): return 1 / (1 + np.exp(-x)) def identity_function(x): return x def a(x, W, b): a = np.dot(x, W) + b return a def init_network(): network = {} network['W1'] = np.array([[0.1, 0.3, 0.5], [0.2, 0.4, 0.6]]) network['b1'] = np.array([0.1, 0.2, 0.3]) network['W2'] = np.array([[0.1, 0.4], [0.2, 0.5], [0.3, 0.6]]) network['b2'] = np.array([0.1, 0.2]) network['W3'] = np.array([[0.1, 0.3], [0.2, 0.4]]) network['b3'] = np.array([0.1, 0.2]) return network def forward(network, x): W1, W2, W3 = network['W1'], network['W2'], network['W3'] b1, b2, b3 = network['b1'], network['b2'], network['b3'] a1 = a(x, W1, b1) z1 = sigmoid(a1) a2 = a(z1, W2, b2) z2 =sigmoid(a2) a3 = a(z2, W3, b3) y = identity_function(a3) return y network = init_network() x = np.array([1.0, 0.5]) y = forward(network,x) print("y = {}".format(y))
■恒等関数
恒等関数(identity function)とは、入力値をそのまま返す関数で、その性質から恒等写像(identity mapping)とも呼ばれます。先ほどの例で示したニューラルネットワークのコードにも使われているもので、入力値をそのまま返している様子が分かります。
$$ f(x) = x $$
つまり、隠れ層から出力層への出力そのものを返すということになり何の変換もしません。この恒等関数は主に回帰タスクの場合に用いられます。
■ソフトマックス関数
ソフトマックス関数(softmax function)はシグモイド関数を多次元に拡張した関数で、お察しの通りニューラルネットワークをクラス分類タスクに適用する際の出力層の活性化関数として用いられます。
$$ \sigma (z_i) = \frac{ e^{z_i}}{ \displaystyle \sum_{j=1}^n e^{z_j} } $$
分子は入力値を指数関数的に増大させてクラス間の差をより強く際立たせます(重み付け)。分母に指数値の合計を置くことで、出力値が正規化されます。これにより、結果を確率として扱うことができます。
ソフトマックス関数は指数関数を計算するので、計算コストはもちろん、入力値 zi が少しでも大きいとメモリのオーバーフローを引き起こす可能性があります。この対策には入力値の最大値を引くことで、数値の増大を防ぐことができます。
$$ C = z_i – \mathrm{maximize} (z_j) $$
$$ \sigma (z_i) = \frac{ e^{z_i – C} }{ \displaystyle \sum_{j=1}^n e^{z_j – C} } $$
実際にニューラルネットワークに適用してみます。
import numpy as np import matplotlib.pyplot as plt def relu(x): return np.maximum(0, x) def softmax(z): c = np.max(z) exp_z = np.exp(z - c) sum_exp_z = np.sum(exp_z) y = exp_z / sum_exp_z return y def a(x, W, b): a = np.dot(x, W) + b return a def init_network(): network = {} network['W1'] = np.array([[0.1, 0.3, 0.5], [0.2, 0.4, 0.6]]) network['b1'] = np.array([0.1, 0.2, 0.3]) network['W2'] = np.array([[0.1, 0.4], [0.2, 0.5], [0.3, 0.6]]) network['b2'] = np.array([0.1, 0.2]) network['W3'] = np.array([[0.1, 0.3], [0.2, 0.4]]) network['b3'] = np.array([0.1, 0.2]) return network def forward(network, x): W1, W2, W3 = network['W1'], network['W2'], network['W3'] b1, b2, b3 = network['b1'], network['b2'], network['b3'] # a1 = np.dot(x, W1) + b1 a1 = a(x, W1, b1) z1 = relu(a1) # a2 = np.dot(z1, W2) + b2 a2 = a(z1, W2, b2) z2 =relu(a2) # a3 = np.dot(z2, W3) + b3 a3 = a(z2, W3, b3) y = softmax(a3) return y network = init_network() x = np.array([1.0, 0.5]) y = forward(network,x) print("y = {}".format(y)) print("sum(y) = {}".format(np.sum(y)))
y = [0.38083632 0.61916368]
sum(y) = 1.0
結果は確率として受け取ることを想定しているので、合計は1になるように正規化されています。また、出力層のニューロン数が2つ(Class 0とClass 1)なので、これは2クラス分類モデルであると考えることができます。
■おわりに
単純パーセプトロン(正確には形式ニューロン)から始まったニューラルネットワークの構築ですが、いよいよ機械学習問題に対応できそうな兆しが見えてきました。
でも実はこのニューラルネットワークは機械学習を行うにあたって根本的な問題を抱えています。コードを良く読んでいただくとすぐに分かると思います。
■参考文献
- Andreas C. Muller, Sarah Guido. Pythonではじめる機械学習. 中田 秀基 訳. オライリー・ジャパン. 2017. 392p.
- 斎藤 康毅. ゼロから作るDeep Learning Pythonで学ぶディープラーニングの理論と実装. オライリー・ジャパン. 2016. 320p.
- ChatGPT. 4o mini. OpenAI. 2024. https://chatgpt.com/
- API Reference. scikit-learn.org. https://scikit-learn.org/stable/api/index.html