目录
本章将基于计算图(computational graph)学习一个能够高效计算权重参数的梯度的方法——误差反向传播法
1. 计算图
计算图将计算过程用图形表示出来,图形指数据结构图,通过多个节点和边表示
用计算图求解
-
示例:
-
流程:
- 构建计算图
- 在计算图上,从左向右进行计算
-
正向传播(forward propagation):从左向右进行计算,从计算图出发点到结束点的传播
-
相应的反向的传播称为反向传播(backward propagation)
局部计算
- 计算图的特征是可以通过传递”局部计算“获得最终结果
- 各个节点处的计算都是局部计算,只需要进行与自己有关的计算,不用考虑全局
- 因此,计算图可以集中精力于局部计算
- 例子:组装汽车是一个复杂的工作,但流水线上每个工人的工作都是简单的
为何用计算图解体(优点)
- 局部计算→简化问题
- 中间的计算结果可被全部保存
- 最大原因:可以通过反向传播高效计算导数
2. 链式法则
传递局部导数的原理基于链式法则(chain rule)
-
计算图的反向传播:
- 沿着与正方向相反的方向,乘上局部导数
- 正向:
- 反向:
-
链式法则:
- 如果某个函数由复合函数表示,则该复合函数的导数可以用构成复合函数的各个函数的导数的乘积表示
-
链式法则和计算图:
3. 反向传播
- 加法节点的反向传播:将上游的值原封不动输出到下游
- 因为导数为1
- 乘法节点的反向传播:将上游的值乘以正向传播时输入信号的”翻转值“再传递给下游
- 因此需要保存正向传播的输入信号
4.简单层的实现
- “层”是神经网络中功能的单位
- 层的实现有两个共通的方法(接口):forward() 和 backward()
乘法层(MulLayer)
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
加法层(AddLayer)
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
5. 激活函数层的实现
- 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
- sigmoid
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
6. Affine/Softmax层的实现
Affine层
-
神经网络的正向传播中进行的矩阵的乘积运算在几何学领域被称为”仿射变换“,包括一次线性变换和一次平移,也就是加权和偏置。这里将进行仿射变换的处理实现为“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): dx = np.dot(dout, self.W.T) self.dW = np.dot(self.x.T, dout) self.db = np.sum(dout, axis=0) return dx
Softmax-with-Loss层
-
softmax函数会将输入值正规化之后再输出,通常在学习阶段使用,不在推理阶段使用
-
同时考虑作为损失函数的交叉熵误差(cross entropy error)
-
简易版计算图
- 反向传播得到的结果时输出和监督标签的差分,直接表示了误差
- 神经网络的方向传播会把这个差分表示的误差传递给前面的层,这是神经网络学习中的重要性质
-
实现
class SoftmaxWithLoss: def __init__(self): self.loss = None # 损失 self.y = None # softmax的输出 self.t = None # 监督数据(one-hot向量) 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
7. 误差反向传播法的实现
from collections import OrderedDict
import numpy as np
from ch4_trainNeuralnet.gradient_method import numerical_gradient
from ch5_back_propagation.common_layers import Affine, Relu, SoftmaxWithLoss
class TwoLayerNet:
def __init__(self, input_size, hidden_size, output_size, weight_init_std=0.01):
# params 保存神经网络的参数
# 权重:通过randn(i, h),得到一组i*h的符合标准正态分布的随机数
# 偏置:通过zeros(h),得到一个全为0的矩阵
self.params = {'W1': weight_init_std * np.random.randn(input_size, hidden_size),
'b1': np.zeros(hidden_size),
'W2': weight_init_std * np.random.randn(hidden_size, output_size),
'b2': np.zeros(output_size)}
# 生成层
# 用有序字典OrderedDict保存神经网络的层
# 因此正向传播只需按顺序调用,反向传播用reverse()按相反的顺序调用即可
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()
# 推理,x:输入数据
# 直接按顺序调用字典中各层的forward()
def predict(self, x):
for layer in self.layers.values():
x = layer.forward(x)
return x
# 计算损失值,x:输入数据,t:监督数据
# 调用SoftmaxWithLoss层
def loss(self, x, t):
y = self.predict(x)
return self.lastLyer.forward(y, t)
# 计算识别精度, x:输入数据,t:监督数据
def accuracy(self, x, t):
y = self.predict(x)
y = np.argmax(y, axis=1) # 返回行最大值的索引的数组,长度为列数
t = np.argmax(t, axis=1)
accuracy = np.sum(y == t) / float(x.shape[0])
return accuracy
# 数值微分计算权重参数的梯度
def numerical_gradient(self, x, t):
loss_W = lambda W: self.loss(x, t) # def loss_W(x):...
# grads 保存梯度
grads = {'W1': numerical_gradient(loss_W, self.params['W1']),
'b1': numerical_gradient(loss_W, self.params['b1']),
'W2': numerical_gradient(loss_W, self.params['W2']),
'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)
# 先转换成list,然后用reverse()反转
layers = list(self.layers.values())
layers.reverse()
for layer in layers:
dout = layer.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
- 用层进行模块化的实现具有很大优点,想另外构建一个神经网络时只要添加必要的层就可以了
梯度确认
确认数值微分求出的梯度结果和误差反向传播法求出的结果是否一致
import sys, os
sys.path.append(os.pardir) # 为了导入父目录的文件而进行的设定
import numpy as np
from 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)
x_batch = x_train[:3]
t_batch = t_train[:3]
grad_numerical = network.numerical_gradient(x_batch, t_batch)
grad_backprop = network.gradient(x_batch, t_batch)
for key in grad_numerical.keys():
diff = np.average(np.abs(grad_backprop[key] - grad_numerical[key]))
print(key + ":" + str(diff))
使用误差反向传播法的学习
和上一章的实现相比,就是计算梯度时使用了误差反向传播法
import numpy as np
from matplotlib import pyplot as plt
from ch4_trainNeuralnet.two_layer_net import TwoLayerNet
from mnist import load_mnist
# 载入数据集,并将图像的像素值正规化为0.0~1.0,标签采用one-hot表示
# (训练图象,训练标签),(测试图象,测试标签)
(x_train, t_train), (x_test, t_test) = \\
load_mnist(normalize=True, one_hot_label=True)
# 超参数
iters_num = 10000 # 迭代次数
train_size = x_train.shape[0] # 训练数据条数
batch_size = 100 # 批数量
learing_rate = 0.1 # 学习率
network = TwoLayerNet(input_size=784, hidden_size=50, output_size=10)
# 记录学习过程中的损失值、训练数据的识别精度、测试数据的识别精度
train_loss_list = []
train_acc_list = []
test_acc_list = []
# 平均每个epoch的重复次数
iter_per_epoch = max(train_size / batch_size, 1)
for i in range(iters_num):
print("第", i, "次迭代")
# 获取mini-batch
batch_mask = np.random.choice(train_size, batch_size)
x_batch = x_train[batch_mask]
t_batch = t_train[batch_mask]
**# 计算梯度
# grad = network.numerical_grandient(x_batch, t_batch)
grad = network.gradient(x_batch, t_batch)**
# 更新参数,正方向更新梯度为负的权重,反方向更新梯度为正的权重
for key in ('W1', 'b1', 'W2', 'b2'):
network.params[key] -= learing_rate * grad[key]
# 记录学习过程
loss = network.loss(x_batch, t_batch)
train_loss_list.append(loss)
# 计算每个epoch的识别精度
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 | " + str(train_acc) + ", " + str(test_acc))
# 绘制损失函数值的图像
print("绘制损失函数值的图像")
x = np.arange(iters_num)
plt.plot(x, train_loss_list)
plt.xlabel("iters_num") # x轴标签
plt.ylabel("loss") # y轴标签
plt.show()