誤差逆伝播法

誤差逆伝播

前回までは、勾配の計算は数値微分で行いました。数値微分は、実行に時間がかかるため、より早い誤差逆伝播法を使います。

加算ノード

定義

入力x,yで出力zのとき、順伝播は
 { z=x+y }
逆伝播は
 { \frac{∂z}{∂x}=1 }
 { \frac{∂z}{∂y}=1 }
なので、上位層からの出力をEとすると、
 { E * \frac{∂z}{∂x} = E * 1 }
 { E * \frac{∂z}{∂y} = E * 1 }

コード

class AddLayer:
    def __init__(self):
        pass
    
    def forward(self, x, y):
        out = x + y
        return out
    
    def backward(self, dout):
        dx = dout*1
        dy = dout*1
        return dx,dy

乗算ノード

定義

順伝播
 { z=xy }
逆伝播
 { \frac{∂z}{∂x}=y }
 { \frac{∂z}{∂y}=x }
ひっくり返る。
出力をEとすると
 { E * \frac{∂z}{∂x}=E * y }
 { E * \frac{∂z}{∂y}=E * x }

コード

class MulLayer:
    def __init__(self):
        self.x = None
        self.y = None
    
    def forward(self, x, y):
        self.x = x
        self.y = y
        out = x * y
        return out
    
    def backward(self, dout):
        dx = dout * self.y
        dy = dout * self.x
        return dx,dy

Affineノード

定義

順伝播
 { y=XW+b }
逆伝播
 { \frac{∂L}{∂X} = \frac{∂L}{∂Y} * {W^{T}} }
 { \frac{∂L}{∂W} = {X^{T}} * \frac{∂L}{∂Y} }
 { \frac{∂L}{∂b} = \frac{∂L}{∂Y} }
出力をEとすると
 { E * \frac{∂L}{∂X} = E * \frac{∂L}{∂Y} * {W^{T}} }
 { E * \frac{∂L}{∂W} = E * {X^{T}} * \frac{∂L}{∂Y} }
 { E * \frac{∂L}{∂b} = E * \frac{∂L}{∂Y} }

コード

class Affine:
    def __init__(self, W, b):
        self.W = W
        self.b = b
        self.x = None
        self.dW = None
        self.db = None
    
    def forward(self, x):
        self.x = x
        out = np.dot(x, self.W) + self.b
        return out

    def backward(self, dout):
        dx = np.dot(dout, self.W.T)
        self.dW = np.dot(self.x.T, dout)
        self.db = np.sum(dout, axis=0)
        return dx #必要なのは入力xの逆伝播のみ

ReLU

定義

順伝播
y = x (x > 0)
y = 0 ( x <= 0)
逆伝播
∂y/∂x = 1 (x > 0)
∂y/∂x = 0 (x <= 0)

コード

class Relu:
    def __init__(self):
        self.mask
    
    def forward(self, x):
        self.mask = (x <= 0)
        out = x.copy()
        out[self.mask] = 0
        return out
    
    def backward(self, dout):
        dout[self.mask] = 0
        dx = dout
        return dx

SoftmaxWithLoss

コード

class SofmaxWithLoss:
    def __init__(self):
        self.y = None
        self.t = None

    def forward(self, x, t):
        self.y = sofmax(x)
        self.t = t
        loss = cross_entropy_error(self.y, self.t)
        return loss
    
    def backward(self, dout=1):
        batch_size = self.t.shape[0]
        dx = (self.y - self.t) / batch_size #データ1個当たりの誤差
        return dx

レイヤの生成

from collections import OrderedDict
self.layers = OrderedDict()
self.layers['Affine1'] = Affine(self.params['W1'], self.params['b1'])
self.layers['Relu1'] = Relu()
self.layers['Affine2'] = Affine(self.params['W2'], self.params['b2'])
self.lastLayer = SoftmaxWithLoss()

順伝播

def predict(self, x):
    for layer in self.layers.values():
        x = layer.forward(x)  #それぞれのレイヤの順伝播を実行
    return x

def loss(self, x):
    y = self.predict(x)
    return self.lastLayer.forward(y, t)

def gradient(self, x, t):
    #forward
    self.loss(x, t)

逆伝播を追加

def gradient(self, x, t):
    #forward
    self.loss(x, t)
    
    #backward
    dout = 1
    dout = self.lastLayer.backward(dout)

    layers = list(self.layers.values())
    layers.reverse()
    for layer in layers:
        dout = layers.backward(dout)
    
    grads = {}
    grads['W1'] = self.layers['Affine1'].dW
    grads['b1'] = self.layers['Affine1'].db
    grads['W2'] = self.layers['Affine2'].dW
    grads['b2'] = self.layers['Affine2'].db
    return grads