6.5. Affine / Softmax レイヤの実装¶
6.5.2. バッチ版Affineレイヤ¶
バッチ版の場合、入力\(X\)の要素数が増えたり減ったりする。がバイアス\(B\)の要素数は固定なので、このバイアス\(B\)の逆要素をどうやって計算するのかがカギとなる。
まず順伝播の計算から。
[1]:
import sys, os
sys.path.append(os.path.abspath(os.path.join('..', 'sample')))
import numpy as np
X_dot_W = np.array([[0, 0, 0,], [10, 10, 10]])
B = np.array([1, 2, 3])
print(X_dot_W)
[[ 0 0 0]
[10 10 10]]
[2]:
X_dot_W + B
[2]:
array([[ 1, 2, 3],
[11, 12, 13]])
ここから、逆伝播を考える。
バイアス\(B\)は、入力\(X\)のそれぞれの要素に対して加算される。そのため、逆伝播の値がバイアスの要素に集約される必要がある。
まず、逆伝播による、前のノードの値を\(dY\)、計算したデータが2個だとすると
[3]:
dY = np.array([[1, 2, 3], [4, 5, 6]])
print(dY)
[[1 2 3]
[4 5 6]]
[4]:
dB = np.sum(dY, axis=0)
print(dB)
[5 7 9]
[5]:
class Affine:
def __init__(self, W, b):
"""
重みとバイアスを初期化します
Attributes
----------
W : numpy.array
重み
b : numpy.array
バイアス
x : numpy.array
順伝播で入力されるx
original_x_shape : numpy.array
xのデータ数
dW : numpy.array
Wからの逆伝搬
db : numpy.array
bからの逆伝播
"""
self.W =W
self.b = b
self.x = None
self.original_x_shape = None
# 重み・バイアスパラメータの微分
self.dW = None
self.db = None
def forward(self, x):
"""
順伝播の計算をします
Parameters
----------
x : numpy.array
入力1
Returns
-------
out : numpy.array
計算結果
"""
# テンソル対応
self.original_x_shape = x.shape
x = x.reshape(x.shape[0], -1)
self.x = x
out = np.dot(self.x, self.W) + self.b
return out
def backward(self, dout):
"""
逆伝播の計算をします(乗算)
Parameters
----------
dout : int
逆伝播で上流から伝わってきた微分
Returns
-------
dx : int
入力2の微分
"""
dx = np.dot(dout, self.W.T)
self.dW = np.dot(self.x.T, dout)
self.db = np.sum(dout, axis=0)
dx = dx.reshape(*self.original_x_shape) # 入力データの形状に戻す(テンソル対応)
return dx
6.5.2.1. x.reshape(x.shape[0], -1)
の -1?¶
reshape
は、次元数を書くとその次元数に合わせて変換した行列を作ってくれる何かの次元数と
-1
を組み合わせると、指定してくれた次元数以外の次元を自動的に生成してくれる
[6]:
a = np.arange(24) # 0~23の一次元配列
print(a)
[ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23]
[7]:
a.reshape([2, -1]) # (2, 12)のshape
[7]:
array([[ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
[12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23]])
[8]:
a.reshape([2, 0]) # (2, 0)のshape…はつくれない
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-8-b6375f3eef96> in <module>
----> 1 a.reshape([2, 0]) # (2, 0)のshape…はつくれない
ValueError: cannot reshape array of size 24 into shape (2,0)
[9]:
a.reshape([3, -1]) # (3, 8)のshape
[9]:
array([[ 0, 1, 2, 3, 4, 5, 6, 7],
[ 8, 9, 10, 11, 12, 13, 14, 15],
[16, 17, 18, 19, 20, 21, 22, 23]])
6.5.3. Softmax-with-Lossレイヤ¶
Softmax-with-Lossレイヤーは、Softmax関数と交差エントロピー誤差(cross entropy error)の二つが合体したレイヤー。
Softmax関数は、出力層で使われる関数で、n個の出力結果の合計が「1.0」になるように正規化してくれます。(3章の話)
ニューラルネットワークの「学習」フェーズで使う
推論では、結果が高い、低いだけわかればよかった
学習では、高い、低いの正しいのかを機械的に比較する必要があるので使う
一方、交差エントロピー誤差(cross entropy error)は、「教師データが示す答え」と「学習によって出力した答え」を比較して、「学習によって出力した答え」がどれだけ間違っているのかを示すための方法です。(4章)
交差エントロピー誤差を出す前に正規化しないと、わけわかんない結果になるよ!たぶん!
(計算グラフについては時間がないので省略しまする…大事なところだけど)
Softmax関数の逆伝播は、前の交差エントロピー誤差によって出た結果をキレイに伝えてくれる。
[10]:
class SoftmaxWithLoss:
def __init__(self):
"""
損失、softmaxの出力、教師データを入れる場所を作ります
Attributes
----------
loss : numpy.array
損失
y : numpy.array
softmaxの出力
t : numpy.array
教師データの答え
"""
self.loss = None
self.y = None # softmaxの出力
self.t = None # 教師データ
def forward(self, x, t):
"""
順伝搬の計算をします
Parameters
----------
x : numpy.array
前のレイヤーから出力した結果
t : numpy.array
教師データの答え
Returns
-------
dx : float
損失
"""
self.t = t
self.y = softmax(x)
self.loss = cross_entropy_error(self.y, self.t)
return self.loss
def backward(self, dout=1):
"""
逆伝播の計算をします
Parameters
----------
dout : int
正規化の値(1)
Returns
-------
dx : int
損失から計算した差
"""
batch_size = self.t.shape[0]
if self.t.size == self.y.size: # 教師データがone-hot-vectorの場合
dx = (self.y - self.t) / batch_size
else:
dx = self.y.copy()
dx[np.arange(batch_size), self.t] -= 1
dx = dx / batch_size
return dx