第五章 误差反向传播法
1.计算图
计算图主要集中精力于局部计算,可以将中间结果保存,通过反向传播高效计算导数。
2.反向传播
如图所示,反向传播的计算顺序是,将信号E乘以节点的局部导数,然后将结果传递给下一个节点。这里所说的局部导数是指正向传播中y = f(x),的导数,也就是y关于x的导数。
假设y = f(x)=x^{2},,则局部导数为
把这个局部导数乘以上游传过来的值(本例中为E),然后传递给前面的节点。
- 乘法规则:乘法的反向传播会将上游的值乘以正向传播时的输入信号的“翻转值”后传递给下游。
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 # 翻转x和y
dy = dout * self.x
return dx, dy
调用backward()的顺序与调用forward()的顺序相反。此外,要注意backward()的参数中需要输入“关于正向传播时的输出变量的导数”。
- 加法规则:backward()将上游传来的导数(dout)原封不动地传递给下游。
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
3.链式法则和计算图
如图所示,计算图的反向传播从右到左传播信号。反向传播的计算顺序是,先将节点的输入信号乘以节点的局部导数(偏导数),然后再传递给下一
个节点。比如,反向传播时,“**2”节点的输入是z对z的偏导 ,将其乘以局部导数 (因为正向传播时输入是t、输出是z,所以这个节点的局部导数是z对t ),然后传递给下一个节点。另外,图5-7中反向传播最开始的信号 在前面的数学式中没有出现,这是因为 ,所以在刚才的式子中被省略了。
4.激活函数层的实现
在神经网络的层的实现中,一般假定forward()和backward()的参数是NumPy数组。
- ReLU
Relu类有实例变量mask。这个变量mask是由True/False构成的NumPy数组,它会把正向传播时的输入x的元素中小于等于0的地方保存为True,其他地方(大于0的元素)保存为False。
如果正向传播时的输入值小于等于0,则反向传播的值为0。因此,反向传播中会使用正向传播时保存的mask,将从上游传来的dout的mask中的元素为True的地方设为0。
代码示例:
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
- Sigmoid
公式:
代码示例:
class Sigmoid:
def __init__(self):
self.out = None
def forward(self, x):
out = 1 / (1 + np.exp(-x))
self.out = out
return out
def backward(self, dout):
dx = dout * (1.0 - self.out) * self.out
return dx
5.Affine/Softmax层的实现
- Affine层
定义:神经网络的正向传播中进行的矩阵的乘积运算在几何学领域被称为“仿射变换”。因此,这里将进行仿射变换的处理实现为“Affine层”。
代码示例:
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):#dout是输出梯度
dx = np.dot(dout, self.W.T)#使用输入 x 的转置 self.x.T 与输出梯度 dout 计算输入梯度
self.dW = np.dot(self.x.T, dout)#计算权重矩阵梯度
self.db = np.sum(dout, axis=0)#偏执矩阵梯度
return dx
- Softmax-with-Loss 层
softmax函数会将输入值正规化之后再输出。
神经网络中进行的处理有推理(inference)和学习两个阶段。神经网络的学习阶段则需要 Softmax层。
6.神经网络学习
- 前提
神经网络中有合适的权重和偏置,调整权重和偏置以便拟合训练数据的过程称为学习。神经网络的学习分为下面4个步骤。
- 步骤1(mini-batch)
从训练数据中随机选择一部分数据。
- 步骤2(计算梯度)
计算损失函数关于各个权重参数的梯度。
- 步骤3(更新参数)
将权重参数沿梯度方向进行微小的更新。
- 步骤4(重复)
重复步骤1、步骤2、步骤3。
7.4.误差反向传播算法的实现
数值微分需要花费较多的时间,误差反向传播算法可以高效的计算梯度。
import sys, os
sys.path.append(os.pardir)
import numpy as np
from dataset.mnist import load_mnist
from two_layer_net import TwoLayerNet
# 读入数据
(x_train, t_train), (x_test, t_test) =load_mnist(normalize=True, one_hot_label=True)
network = TwoLayerNet(input_size=784, hidden_size=50, output_size=10)
iters_num = 10000#训练次数
train_size = x_train.shape[0]#获取训练数据集 x_train 的样本数量,确定训练集的大小。
batch_size = 100
learning_rate = 0.1
train_loss_list = []
train_acc_list = []
test_acc_list = []
iter_per_epoch = max(train_size / batch_size, 1)
#计算每个迭代中要遍历的训练数据的数量
#采用 max 函数来确保即使 train_size 不是 batch_size 的整数倍,iter_per_epoch 也至少是 1,以确保至少遍历一次整个数据集。
for i in range(iters_num):
batch_mask = np.random.choice(train_size, batch_size)
x_batch = x_train[batch_mask]
t_batch = t_train[batch_mask]
#这两行使用 batch_mask 来从训练数据中选择相应的输入数据 x_batch 和目标数据 t_batch,构成一个小批量的训练数据。这个小批量数据将用于计算梯度和更新模型参数。
# 通过误差反向传播法求梯度
grad = network.gradient(x_batch, t_batch)
# 更新
for key in ('W1', 'b1', 'W2', 'b2'):
network.params[key] -= learning_rate * grad[key]#使用梯度下降法更新这四个参数
loss = network.loss(x_batch, t_batch)
train_loss_list.append(loss)
if i % iter_per_epoch == 0:
train_acc = network.accuracy(x_train, t_train)#计算训练集的正确率并存储
test_acc = network.accuracy(x_test, t_test)
train_acc_list.append(train_acc)#记录训练集上的准确率随时间的变化
test_acc_list.append(test_acc)
print(train_acc, test_acc)#记录训练集上的准确率随时间的变化
在正向传播的时候使用OrderdDict(有序字典)来添加元素,反向传播的时候按照相反的方顺序调用即可。
梯度确认:确认数值微分和误差反向传播算法求出的结果是否一致。(计算各个权重参数中对应元素差的绝对值,并计算其平均值)
# 求各个权重的绝对误差的平均值
for key in grad_numerical.keys():
diff = np.average( np.abs(grad_backprop[key] - grad_numerical[key]) )
print(key + ":" + str(diff))