Date
:2021-12-20
Repositity
: Gitee
Reference: WIKI
反向传播(英语:Back Propagation,缩写为BP)是“误差反向传播”的简称,是一种与最优化方法(如梯度下降法)结合使用的,用来训练人工神经网络的常见方法。该方法对网络中所有权重计算损失函数的梯度。这个梯度会回馈给最佳化方法,用来更新权值以最小化损失函数。
反向传播要求对每个输入值得到已知输出,来计算损失函数梯度。因此,它通常被认为是一种监督式学习方法,虽然它也用在一些无监督网络(如自动编码器)中。它是多层前馈网络的Delta规则的推广,可以用链式法则对每层迭代计算梯度。反向传播要求人工神经元(或“节点”)的激励函数可微。
BP是神经网络中一个重要算法,他可以在图上面传播梯度,使模型结构更具弹性。
前述章节只有一个神经元,可以快速的算出其权重更新的解析解。但在复杂的多层神经网络下,手算不再适宜,因为参数太多过于复杂。那么可不可以将网络视为一张图,将计算节点的梯度存下来,然后按求导的链式法则计算对应的权重值
答案:反向传播。
这里以一个2阶的线性结构(两层的神经网络)为例,如下图所示:
h h h 为中间计算出来的隐层结果,MM
为矩阵乘法缩写;
绿色计算节点包含所有元素的梯度,后续展开说明;
图里的 b 1 b_1 b1 、 b 2 b_2 b2 向量都会被广播(broadcasting
)成前面隐层输出矩阵的维度;
因为神经网络支持展开,故前面的两层线性层支持展开合并,固有:
y ^ = W 2 ( W 1 ⋅ X + b 1 ) + b 2 = W 2 ⋅ W 1 ⋅ X + ( W 2 ⋅ b 1 + b 2 ) = W ⋅ X + b (1) \begin{aligned} \hat{y} &= W_2(W_1\cdot X + b_1) + b_2 \\ &= W_2\cdot W_1 \cdot X + (W_2 \cdot b_1 + b_2) \\ &= W \cdot X + b \end{aligned} \tag{1} y^=W2(W1⋅X+b1)+b2=W2⋅W1⋅X+(W2⋅b1+b2)=W⋅X+b(1)
那么上图的两层结构便被压成一层了,这样多线性层没有意义,复杂程度无法有效提高。怎么做?
每一层结束位置增加非线性函数,这里以sigmoid()
为例,如下图所示:
一图胜千言,看图即懂
这里以 y ^ = x ∗ ω + b \hat{y} = x * \omega + b y^=x∗ω+b 为模型,损失函数为MSE
: l o s s = ( x ∗ ω − y ) 2 loss = (x*\omega - y)^2 loss=(x∗ω−y)2 。为了方便画图演示,这里不考虑激活函数,模型的前馈计算和反向传播过程,如下图所示:
import torch
""" Formula: y = x * w """
x_data = [1., 2., 3., 4.]
y_data = [2., 4., 6., 8.]
w = torch.Tensor([1.0])
w.requires_grad = True # enable grad cal.
def forward(x):
return x * w # broadcasting
def loss_func(x, y):
y_pred = forward(x)
return (y_pred - y) ** 2
print('Predict (before training) input: {}, foward: {}'.format(5, forward(5).data.item()))
for epoch in range(100):
for x, y in zip(x_data, y_data):
# forward and cal loss
loss = loss_func(x, y)
# backward(cal all grads)
loss.backward()
print('\t x: {}, y: {}, grad: {}'.format(x, y, w.grad.item()))
w.data = w.data - 0.01 * w.grad.data
w.grad.data.zero_()
print('Progress: epoch={}, w={}, loss={}'.format(epoch, w.data.item(), loss.item()))
print('Predict (after training) input: {}, forward: {}'.format(5, forward(5).data.item()))
注意:
Q1: loss.backward()
执行后,为什么计算图会释放?
A1: 因为每次构建神经网络时,计算图可能是不一样的,从另一方面来看,这样更灵活动态?
Q2: w.grad.item()
和 w.grad.data
指什么?
A2: 前者直接将梯度数值拿出转成python
中的标量,后者是取权重参数中的梯度数值,只是取数值操作不进行模型相关操作。
Q3: 如何计算每个epoch
的平均loss
?
A3: 如果要计算每个epoch
的loss
,需要注意loss
的累加也使用loss.item()
,如果不使用则会导致计算图变大,最终爆掉显存。
Q4: 为什么要将梯度清零w.grad.data.zero_()
?
A4: 如果不对历史梯度进行清零,那么下一次循环重新计算权重时会将历史的梯度一并计算。当然特殊需求下,是不需要梯度清零的。
将前述模型更换为
y ^ = ω 1 x 2 + ω 2 x + b (2) \hat{y} = \omega_1 x^2 + \omega_2 x + b \tag{2} y^=ω1x2+ω2x+b(2)
损失函数还是MSE
l o s s = ( y ^ − y ) 2 = ( x ∗ ω − y ) 2 (3) loss =\left(\hat{y} - y\right)^2 = \left(x * \omega - y\right)^2 \tag{3} loss=(y^−y)2=(x∗ω−y)2(3)
首先画图,结果如下图所示,这里的初始值随意进行赋值,流程同前面。
import torch
x_data = [1., 2., 3., 4.]
y_data = [2., 4., 6., 8.]
w1, w2, b = torch.Tensor([0.05]), torch.Tensor([1.5]), torch.Tensor([0.05])
w1.requires_grad = True
w2.requires_grad = True
b.requires_grad = True
def forward(x):
return (x ** 2) * w1 + x * w2 + b
def loss_func(x, y):
y_pred = forward(x)
return (y_pred - y) ** 2
print('Predict (before training) input: {}, foward: {}'.format(5, forward(5).data.item()))
for epoch in range(200):
for x, y in zip(x_data, y_data):
loss = loss_func(x, y)
loss.backward()
# print('\t x: {}, y: {}, weight: {}, {}, {}\n'.format(x, y, w1.grad.item(), w2.grad.item(), b.grad.item()))
w1.data = w1.data - 0.001 * w1.grad.data
w2.data = w2.data - 0.05 * w2.grad.data
b.data = b.data - 0.001 * b.grad.data
w1.grad.data.zero_()
w2.grad.data.zero_()
b.grad.data.zero_()
print('Progress: epoch={}, w1, w2, b={}, {}, {}, loss={}'.format(epoch, w1.data.item(), w2.data.item(), b.data.item(), loss.item()))
print('Predict (after training) input: {}, forward: {}'.format(5, forward(5).data.item()))
结果调整学习率后的结果:
Progress: epoch=190, w1, w2, b=0.006666435860097408, 1.955682396888733, 0.06071145832538605, loss=7.891614950494841e-05
Progress: epoch=191, w1, w2, b=0.0066641089506447315, 1.9556971788406372, 0.06069079786539078, loss=7.888226537033916e-05
Progress: epoch=192, w1, w2, b=0.006661855150014162, 1.955712914466858, 0.0606701523065567, loss=7.879758777562529e-05
Progress: epoch=193, w1, w2, b=0.006659569218754768, 1.9557280540466309, 0.060649510473012924, loss=7.874680159147829e-05
Progress: epoch=194, w1, w2, b=0.006657279096543789, 1.9557431936264038, 0.060628872364759445, loss=7.869602995924652e-05
Progress: epoch=195, w1, w2, b=0.0066549829207360744, 1.9557582139968872, 0.060608237981796265, loss=7.864528015488759e-05
Progress: epoch=196, w1, w2, b=0.006652716547250748, 1.9557735919952393, 0.06058761477470398, loss=7.857763557694852e-05
Progress: epoch=197, w1, w2, b=0.006650409195572138, 1.955788254737854, 0.06056699529290199, loss=7.854382420191541e-05
Progress: epoch=198, w1, w2, b=0.006648148410022259, 1.955803632736206, 0.0605463907122612, loss=7.847622327972203e-05
Progress: epoch=199, w1, w2, b=0.006645866669714451, 1.955818772315979, 0.060525789856910706, loss=7.842553895898163e-05
Predict (after training) input: 5, forward: 10.005765914916992