からっぽのしょこ

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

3.5:出力層の設計【ゼロつく1のノート(実装)】

はじめに

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

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

 この記事は、3.5節「出力層の設計」の内容になります。第3層の活性化関数にソフトマックス関数を用いた3層のニューラルネットワークをPythonで実装します。

【前節の内容】

www.anarchive-beta.com

【他の節の内容】

www.anarchive-beta.com

【この節の内容】

3.5 出力層の設計

 この節では、分類問題で用いられるソフトマックス関数を扱います。

3.5.1 恒等関数とソフトマックス関数

 ソフトマックス関数とは次の式です。詳細は【ソフトマックス関数のオーバーフロー対策【ゼロつく1のノート(数学)】 - からっぽのしょこ】の方をご覧ください。

$$ y_k = \frac{\exp(a_k)}{\sum_{i=1}^n \exp(a_i)} \tag{3.10} $$


 早速この式の通りに実装してみましょう。(まぁこのままだと問題が生じる訳です。)

 分子の計算はnp.exp()による指数関数の計算です。

# NumPyを読み込み
import numpy as np

# 重み付き和を設定
a = np.array([0.3, 2.9, 4.0])

# 指数をとる
exp_a = np.exp(a)
print(exp_a)
[ 1.34985881 18.17414537 54.59815003]


 分子の値の総和が分母の値となります。

# 和をとる
sum_exp_a = np.sum(exp_a)
print(sum_exp_a)
74.1221542101633


 分母分子の値が得られたので式全体の計算を行います。

# 式(3.10)の計算
y = exp_a / sum_exp_a
print(y)
[0.01821127 0.24519181 0.73659691]


 ではこれをsoftmax()関数として実装します。

# ソフトマックス関数を実装
def softmax(a):
    
    # 式(3.10)の計算
    exp_a = np.exp(a) # 分子
    sum_exp_a = np.sum(exp_a) # 分母
    y = exp_a / sum_exp_a # 式(3.10)
    
    return y


# 重み付き和を設定
a = np.array([0.3, 2.9, 4.0])

# ソフトマックス関数による活性化
y = softmax(a)
print(y)
[0.01821127 0.24519181 0.73659691]


 これで式(3.10)の通りに実装できました。しかしこのままだと、引数にとる値が大きいときオーバーフローが起きてしまい正しく計算できません。

# 重み付き和を設定
a = np.array([1010, 1000, 900])

# ソフトマックス関数による活性化
y = softmax(a)
print(y)
[nan nan nan]


 そこで、引数にとる値(ここではa)の最大値を全ての要素から引いておくことでオーバーフロー対策となります。式(3.11)については、【ソフトマックス関数のオーバーフロー対策】で取り上げます。

# 重み付き和を設定
a = np.array([1010, 1000, 990])

# 最大値を抽出
c = np.max(a)
print(c)
1010


# 最大値との差(を確認)
print(a - c)

# 最大値との差の指数をとる
exp_a = np.exp(a - c)
print(exp_a)
[  0 -10 -20]
[1.00000000e+00 4.53999298e-05 2.06115362e-09]

 オーバーフローが起きずに計算できていることを確認できました。

 残りの計算は前のままで行えます。

# 和をとる
sum_exp_a = np.sum(exp_a)
print(sum_exp_a)

# 式(3.10)の計算
y = exp_a / sum_exp_a
print(y)
1.0000454019909162
[9.99954600e-01 4.53978686e-05 2.06106005e-09]


 これでオーバーフロー対策版の処理過程を確認できたので、実装します。

# ソフトマックス関数を実装
def softmax(a):
    
    # 式(3.10)の計算
    c = np.max(a) # 最大値
    exp_a = np.exp(a - c) # オーバーフロー対策:式(3.11)
    sum_exp_a = np.sum(exp_a)
    y = exp_a / sum_exp_a
    
    return y


# 重み付き和を設定
a = np.array([1010, 1000, 990])

# ソフトマックス関数による活性化
y = softmax(a)
print(y)
[9.99954600e-01 4.53978686e-05 2.06106005e-09]

 以上でソフトマックス関数softmax()が実装できました!これ以降の内容でもsoftmax()を何度も使います。その度にこの関数定義を実行する必要があります(エラー:softmax()が定義されていない!)。

 次は、このソフトマックス関数による活性化前後の変化を確認します。

3.5.3 ソフトマックス関数の特徴

 適当な値を用意してソフトマックス関数で計算してみましょう。

# (分かりやすいように)段々大きくなる値を生成
a = np.arange(1, 10)
print(a)

# ソフトマックス関数による活性化
y = softmax(a)
print(np.round(y, 3))
[1 2 3 4 5 6 7 8 9]
[0.    0.001 0.002 0.004 0.012 0.031 0.086 0.233 0.632]

 ソフトマックス関数による変換を行っても大小関係は変わりません。そのため最終的に一番(確率)値の高いものを推論結果とするのであれば、最後の層でのソフトマックス関数の計算は必要ないということです。(0から1の値に正規化する必要があるときのみ行えばよいということです。)

 データ数が数百程度なら気になりませんが、数万のデータを計算するのはとても時間コスト等かかるので、できるだけ避けたいというのはどの手法でも共通する考え方です。

# 出力の和
print(np.sum(y))
1.0

 ソフトマックス関数の出力aは、全ての要素を足し合わせると1となります。また各要素は0から1の値となります。

 これは確率値へ変換されたことを意味します。

 とにかくこれでニューラルネットワークが組めました!!!では使ってみよう!画像認識してみよう!

参考文献

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

おわりに

 Jupyter Labでの資料作りも大変ではあるのですが、はてなに転載する作業も中々しんどいものがあります、、、何よりこの作業に手を取られて、そもそもの資料の続きを書けてません。。3章関連の記事はあと3つ!

【次節の内容】

www.anarchive-beta.com