からっぽのしょこ

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

5.6.3:Softmax-with-Lossレイヤ【ゼロつく1のノート(実装)】

はじめに

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

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

 この記事は、5.6.3項「Softmax-with-Lossレイヤ」の内容になります。ニューラルネットワークの最終層の活性化関数に用いるためのソフトマックス関数と交差エントロピー誤差のレイヤをPythonで実装します。

【前節の内容】

https://www.anarchive-beta.com/entry/2020/08/02/180000www.anarchive-beta.com

【他の節の内容】

www.anarchive-beta.com

【この節の内容】

5.6.3 Softmax-with-Lossレイヤ

 この項では、ソフトマックス関数と交差エントロピー誤差(損失関数)の順伝播と逆伝播を、Softmax-with-Lossレイヤとして実装します。ソフトマックス関数の順伝播については3.5節、交差エントロピー誤差の順伝播については4.2節を確認してしてください。逆伝播については「ソフトマックス関数と交差エントロピー誤差の逆伝播」の方で導出します。

 ソフトマックス関数の順伝播について、$i$番目のデータに関する入力を$\mathbf{a}_i = (a_{i1}, a_{i2}, \cdots, a_{i,10})$、出力を$\mathbf{y}_i = (y_{i1}, a_{i2}, \cdots, y_{i,10})$とすると、$k$番目の項の計算式は次のようになります。

$$ y_{ik} = \frac{ \exp(a_{ik}) }{ \sum_{k'=1}^{10} \exp(a_{ik'}) } \tag{3.10} $$

 ただし実装上の計算式は、この式にオーバーフロー対策として式(3.11)を導入します。またその処理は、4.4.2項でバッチデータ対応版のソフトマックス関数softmax()として実装しました。

 バッチ版交差エントロピー誤差の順伝播について、入力を$\mathbf{Y} = (y_{11}, \cdots, y_{N,10})$、教師データを$\mathbf{T} = (t_{11}, \cdots, t_{N,10})$、出力を$L$とすると、計算式は次のようになります。

$$ L = - \frac{1}{N} \sum_{i=1}^N \sum_{k=1}^{10} t_{ik} \log y_{ik} \tag{4.2} $$

 計算結果はスカラ(1つの値)になります。またこの処理は、4.2.4項でバッチデータ対応版の交差エントロピー誤差関数cross_entropy_error()として実装しました。

 ソフトマックス関数と交差エントロピー誤差の逆伝播について、ソフトマックス関数の出力$\mathbf{Y}$と教師データ$\mathbf{T}$を用いて、$i$行$k$列目の項の出力(微分)の計算式は次のようになります。

$$ \frac{\partial L}{\partial a_{ik}} = \frac{1}{N} \left( y_{ik} - t_{ik} \right) $$


 この項ではこれまでに実装した関数を使うため、それぞれの関数定義を再度実行する必要があります。あるいは次の方法で、マスターデータの「common」フォルダの「functions.py」ファイルから定義されている関数を読み込むこともできます。マスターデータからのファイルの読み込みについては、「MNISTデータセットの読み込み【ゼロつく1のノート(Python)】 - からっぽのしょこ」の記事にて解説しています。

# 関数の読み込みに利用するライブラリを読み込む
import sys
import os

# ファイルパスを指定
sys.path.append("C:\\Users\\「ユーザー名」\\Documents\\・・・\\deep-learning-from-scratch-master")

# ファイルから関数を読み込む
from common.functions import softmax # ソフトマックス関数:バッチ対応版
from common.functions import cross_entropy_error # 交差エントロピー誤差:4.2.4項


 ではこれまでと同様に、仮の値を作って計算してみましょう。

# NumPyを読み込む
import numpy as np

# 仮の入力を指定
A = np.array([
    [1.0, 3.0, 5.0, 7.0, 9.0, 1.5, 3.5, 5.5, 7.5, 9.5], 
    [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0], 
    [-10.0, -8.0, -6.0, -4.0,-2.0, 0.0, 2.0, 4.0, 6.0, 8.0]
])
print(A.shape)

# 仮の教師データを指定
T = np.array([
    [0, 0, 0, 0, 0, 0, 0, 1, 0, 0], 
    [0, 0, 0, 0, 1, 0, 0, 0, 0, 0], 
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 1]
])
print(T.shape)
(3, 10)
(3, 10)

 バッチサイズは3とします。

 順伝播(交差エントロピー誤差)の計算をします。

## 順伝播

# ソフトマックス関数による活性化
Y = softmax(A)
print(Y)
print(np.sum(Y, axis=1))

# 交差エントロピー誤差を計算
L = cross_entropy_error(Y, T)
print(L)
[[1.09515437e-04 8.09215708e-04 5.97934026e-03 4.41816806e-02
  3.26460917e-01 1.80560431e-04 1.33417115e-03 9.85826548e-03
  7.28432766e-02 5.38243058e-01]
 [1.00000000e-01 1.00000000e-01 1.00000000e-01 1.00000000e-01
  1.00000000e-01 1.00000000e-01 1.00000000e-01 1.00000000e-01
  1.00000000e-01 1.00000000e-01]
 [1.31688261e-08 9.73051952e-08 7.18993546e-07 5.31268365e-06
  3.92557175e-05 2.90062699e-04 2.14328955e-03 1.58368867e-02
  1.17019645e-01 8.64664719e-01]]
[1. 1. 1.]
2.355810776839421

 ソフトマックス関数の出力は、データごとの和が1となっていますね。

 次は逆伝播の計算をします。

# バッチサイズを取得
batch_size = T.shape[0]
print(batch_size)

# 逆伝播
dA = (Y - T) / batch_size
print(dA)
3
[[ 3.65051457e-05  2.69738569e-04  1.99311342e-03  1.47272269e-02
   1.08820306e-01  6.01868102e-05  4.44723717e-04 -3.30047245e-01
   2.42810922e-02  1.79414353e-01]
 [ 3.33333333e-02  3.33333333e-02  3.33333333e-02  3.33333333e-02
  -3.00000000e-01  3.33333333e-02  3.33333333e-02  3.33333333e-02
   3.33333333e-02  3.33333333e-02]
 [ 4.38960872e-09  3.24350651e-08  2.39664515e-07  1.77089455e-06
   1.30852392e-05  9.66875662e-05  7.14429851e-04  5.27896225e-03
   3.90065482e-02 -4.51117605e-02]]

 バッチデータを扱うため、順伝播の出力Yと教師データTの差をバッチサイズで割ります。

 処理の確認ができたので、順伝播と逆伝播のメソッドを持つクラスとして実装します。

# ソフトマックス関数と交差エントロピー誤差の実装
class SoftmaxWithLoss:
    
    # インスタンス変数を定義
    def __init__(self):
        # 値を初期化
        self.loss = None # 交差エントロピー誤差
        self.y = None # ニューラルネットワークの出力
        self.t = None # 教師ラベル
    
    # 順伝播
    def forward(self, x, t):
        self.t = t
        self.y = softmax(x)
        self.loss = cross_entropy_error(self.y, self.t)
        
        return self.loss
    
    # 逆伝播
    def backward(self, dout=1):
        batch_size = self.t.shape[0]
        dx = (self.y - self.t) / batch_size
        
        return dx


 先ほどの値を使って動作確認をします。まずはインスタンスを作成して順伝播を求めます。

# インスタンスを作成
network = SoftmaxWithLoss()

# 順伝播
L = network.forward(A, T)
print(network.y)
print(np.sum(network.y, axis=1))
print(network.loss)
print(L)
[[1.09515437e-04 8.09215708e-04 5.97934026e-03 4.41816806e-02
  3.26460917e-01 1.80560431e-04 1.33417115e-03 9.85826548e-03
  7.28432766e-02 5.38243058e-01]
 [1.00000000e-01 1.00000000e-01 1.00000000e-01 1.00000000e-01
  1.00000000e-01 1.00000000e-01 1.00000000e-01 1.00000000e-01
  1.00000000e-01 1.00000000e-01]
 [1.31688261e-08 9.73051952e-08 7.18993546e-07 5.31268365e-06
  3.92557175e-05 2.90062699e-04 2.14328955e-03 1.58368867e-02
  1.17019645e-01 8.64664719e-01]]
[1. 1. 1.]
2.355810776839421
2.355810776839421


 続いて逆伝播メソッドを確認します。

# 逆伝播
dA = network.backward()
print(dA)
[[ 3.65051457e-05  2.69738569e-04  1.99311342e-03  1.47272269e-02
   1.08820306e-01  6.01868102e-05  4.44723717e-04 -3.30047245e-01
   2.42810922e-02  1.79414353e-01]
 [ 3.33333333e-02  3.33333333e-02  3.33333333e-02  3.33333333e-02
  -3.00000000e-01  3.33333333e-02  3.33333333e-02  3.33333333e-02
   3.33333333e-02  3.33333333e-02]
 [ 4.38960872e-09  3.24350651e-08  2.39664515e-07  1.77089455e-06
   1.30852392e-05  9.66875662e-05  7.14429851e-04  5.27896225e-03
   3.90065482e-02 -4.51117605e-02]]


 どちらの出力にも狂いがなければ実装完了です!これでニューラルネットワークの実装に必要なレイヤを全て用意できました。次はこれらを組み合わせてニューラルネットワークを実装します。

参考文献

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

おわりに

 これでパーツが揃った!ニューラルネットワークを組み立てるぞー。

【次節の内容】

https://www.anarchive-beta.com/entry/2020/08/07/180000www.anarchive-beta.com