误差反向传播法是一种高效地求解权重参数梯度的方法

5.1 计算图

这是为了便于理解“误差反向传播法”而引入的一种图。类似“表达式树”
计算图

箭头上的数字即为上一步的运算结果。
f = [(a x) +(b y)] * c
计算图的特点是:每个结点仅需进行“局部运算”,只需要把箭头上传来的数据进行一步处理就ok

以上就是一次正向传播过程
计算图的意义在于,可以反向传播高效求导数

计算图反向传播

5.2 链式法则

5.2.1 & 5.2.2 链式法则基本内容

涉及到微积分(二)内容🐶
已知f(u, v), u(x, y),v(x, y)

这个规则可用于反向传播求梯度

5.2.3 链式法则和计算图

计算图与链式法则

注:该图中**2是平方的意思
可以看出,反向传播是基于链式法则的

5.3 反向传播

对于加法节点,由于加法不会影响导数值,输入信号会原封不动地传入下一个节点

对于乘法节点呢?
对于函数

可见,在反向传播时,导数乘以正向传播时的翻转值
计算图与链式法则

实现反向传播时需要用到正向传播的输入信号,所以需要保存正向传播时的数据

反向传播举例

试求下面神经网络的正向传播和反向传播图:
例

(答案在该文章最后)

5.4 简单层的实现

乘法节点称为乘法层,加法节点称为加法层

1)乘法层

以下为随书源码:

# coding: utf-8
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

乘法层的使用可以是这样:

# coding: utf-8
from layer_naive import *#引入了上面创建的对象

apple = 100
apple_num = 2
tax = 1.1

mul_apple_layer = MulLayer()
mul_tax_layer = MulLayer()

# forward正向传播
apple_price = mul_apple_layer.forward(apple, apple_num)
price = mul_tax_layer.forward(apple_price, tax)

# backward 反向传播
dprice = 1
dapple_price, dtax = mul_tax_layer.backward(dprice)
dapple, dapple_num = mul_apple_layer.backward(dapple_price)

print("price:", int(price))
print("dApple:", dapple)
print("dApple_num:", int(dapple_num))
print("dTax:", dtax)

2)加法层

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

使用一个含有加法层和乘法层的计算图:

# coding: utf-8
from layer_naive import *

apple = 100
apple_num = 2
orange = 150
orange_num = 3
tax = 1.1

# layer
mul_apple_layer = MulLayer()
mul_orange_layer = MulLayer()
add_apple_orange_layer = AddLayer()
mul_tax_layer = MulLayer()

# forward
apple_price = mul_apple_layer.forward(apple, apple_num)  # (1)
orange_price = mul_orange_layer.forward(orange, orange_num)  # (2)
all_price = add_apple_orange_layer.forward(apple_price, orange_price)  # (3)
price = mul_tax_layer.forward(all_price, tax)  # (4)

# backward
dprice = 1
dall_price, dtax = mul_tax_layer.backward(dprice)  # (4)
dapple_price, dorange_price = add_apple_orange_layer.backward(dall_price)  # (3)
dorange, dorange_num = mul_orange_layer.backward(dorange_price)  # (2)
dapple, dapple_num = mul_apple_layer.backward(dapple_price)  # (1)

print("price:", int(price))
print("dApple:", dapple)
print("dApple_num:", int(dapple_num))
print("dOrange:", dorange)
print("dOrange_num:", int(dorange_num))
print("dTax:", dtax)

这是前面的答案!⬇⬇⬇⬇
答案

5.5 激活函数的实现

5.51 ReLU(rectified liner unit)层

ReLU函数表达式还能想起来吗?

ReLU函数的导数为:

ReLU函数计算图

随书源码:

class Relu:
    def __init__(self):
        self.mask = None

    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

mask数组的作用?

ReLU函数可以看作一个“开关”,当输入为正时开关打开,输入为负时开关关闭。
开关打开时,对信号没有影响;开关关闭后,信号一律置0

当mask实际上记录了“开关”的状态,当mask=True,开关就断开了。

5.52 Sigmoid层

sigmoid函数的表达式还记得嘛?

ReLU函数计算图

该式的推导运用了极其复杂的高数知识!🐷(好吧是我太菜了)
类的实现:(随书源码)

class Sigmoid:
    def __init__(self):
        self.out = None

    def forward(self, x):
        out = sigmoid(x)
        self.out = out
        return out

    def backward(self, dout):
        dx = dout * (1.0 - self.out) * self.out

        return dx

sigmoid函数可以通过正向传播时输出的y来确定反向传播的梯度

5.6 Affine/Softmax层的实现

5.6.1&5.6.2 Affine层

实现仿射变换的层叫做Affine层(仿射层)

仿射变换:对矩阵的一次线性变换和一次平移。如 X.*W + B

类似于一个加法层和一个乘法层的叠加,只不过这里的变量是矩阵

对于 ,
Affine层

代码实现:

class Affine:
    def __init__(self, W, 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):
        # テンソル対応
        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):
        dx = np.dot(dout, self.W.T)
        self.dW = np.dot(self.x.T, dout)
        self.db = np.sum(dout, axis=0)
        #注意这一句:db存储的是dout的每列的和
        dx = dx.reshape(*self.original_x_shape)  # 入力データの形状に戻す(テンソル対応)
        return dx

这段代码考虑了数据是张量的情况,俺不细究了!

Affine层的意义,是串联其他层。

Affine层并不算是神经网络的一层

5.6.3 Softmax with Loss 层

Softmax函数的作用:将输入值正规化后输出
什么是正规化?
把结果处理成0~1之间的数字

softmax和Cross Entrory Error函数比较复杂,但是两者结合后,经过简化的计算图如下图:

softmax计算图
反向传播时居然结果是y1-t1这样的值,妙~
(实际上,交叉熵误差就是为softmax函数设计的,得到y1-t1这样的值是有bear而来)

5.7误差反向传播法的实现

5.7.1 神经网络学习的全貌

学习:调整权重和偏置以便拟合训练数据的过程

step 1(mini-batch)

选取一个batch的数据作为训练数据

step 2(计算梯度)

step 3(更新参数)

step 4(重复)

5.7.2 代码实现:

(随书源码)

# coding: utf-8
import sys, os
sys.path.append(os.pardir) 
import numpy as np
from common.layers import *
from common.gradient import numerical_gradient
from collections import OrderedDict


class TwoLayerNet:

    def __init__(self, input_size, hidden_size, output_size, weight_init_std = 0.01):
        # 数据初始化
        self.params = {}
        self.params['W1'] = weight_init_std * np.random.randn(input_size, hidden_size)
        self.params['b1'] = np.zeros(hidden_size)
        self.params['W2'] = weight_init_std * np.random.randn(hidden_size, output_size) 
        self.params['b2'] = np.zeros(output_size)

        # 生成神经网络的各层
        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, t):
        y = self.predict(x)
        return self.lastLayer.forward(y, t)

    def accuracy(self, x, t):
        y = self.predict(x)
        y = np.argmax(y, axis=1)
        if t.ndim != 1 : t = np.argmax(t, axis=1)

        accuracy = np.sum(y == t) / float(x.shape[0])
        return accuracy

    # x:入力データ, t:教師データ
    def numerical_gradient(self, x, t):
        loss_W = lambda W: self.loss(x, t)

        grads = {}
        grads['W1'] = numerical_gradient(loss_W, self.params['W1'])
        grads['b1'] = numerical_gradient(loss_W, self.params['b1'])
        grads['W2'] = numerical_gradient(loss_W, self.params['W2'])
        grads['b2'] = numerical_gradient(loss_W, self.params['b2'])

        return grads

    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 = layer.backward(dout)
        grads = {}
        grads['W1'], grads['b1'] = self.layers['Affine1'].dW, self.layers['Affine1'].db
        grads['W2'], grads['b2'] = self.layers['Affine2'].dW, self.layers['Affine2'].db

        return grads

注意上面的layer是有序字典。(因为神经网络的层是有先后顺序的,有序字典可以依次调用各层)

5.7.3 梯度确认

使用误差反向传播法,虽然速度很快,但是可能会发生错误(为什么?)。

经常会比较误差反向传播法和数值微分法的结果,这个操作称为梯度确认