BP(Back Propagation)算法也叫误差反向传播算法,它用来求模型的参数梯度,进而配合梯度下降法更新网络参数。它的基本工作流程是这样的:
- 先通过正向传播得到误差。所谓正向传播,就是数据从输入层一路走到输出层,经过层层计算得到预测值,再用损失函数算出预测值和真实值之间的误差。
- 再通过反向传播把误差传回模型的各个参数,据此对网络参数做适当调整,一步步缩小预测值和真实值之间的误差。
反向传播算法是靠链式法则来求梯度、更新权重的。面对复杂的复合函数,我们先把它拆成加减乘除、指数、对数、三角函数这些初等函数,再用链式法则一步步完成复合函数的求导。
我们通过一个例子,来直观理解 BP 算法更新网络参数的过程:

为了把计算过程讲得更细,上图中每个矩形代表一个神经元,框里写的是该神经元的值、激活值的计算结果和对应公式。最终算出真实值和预测值之间的误差是 0.2984。图里需要说明的几点:
- 从下往上看,最下层两个绿色的圆代表两个输入值。
- 右侧那 8 个数字,最下面 4 个是 w1、w2、w3、w4 的初始值,最上面 4 个是 w5、w6、w7、w8 的初始值。
- b1 的值为 0.35,b2 的值为 0.60。
- 两个预测结果分别是:0.7514、0.7729。
我们先算 w5 和 w7 这两个权重的梯度,再用梯度下降更新这两个参数。


梯度值算出来之后,接下来就用梯度下降公式更新模型参数。假设学习率为 0.5,则:

接下来,我们算 w1 的梯度,并更新这个参数:

接下来更新该参数:

其余网络参数的更新过程和上面是一样的。下面我们用代码把这个网络搭出来,做一次正向传播和一次反向传播。
import torch
import torch.nn as nn
import torch.optim as optim
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.linear1 = nn.Linear(2, 2)
self.linear2 = nn.Linear(2, 2)
# 网络参数初始化
self.linear1.weight.data = torch.tensor([[0.15, 0.20], [0.25, 0.30]])
self.linear2.weight.data = torch.tensor([[0.40, 0.45], [0.50, 0.55]])
self.linear1.bias.data = torch.tensor([0.35, 0.35])
self.linear2.bias.data = torch.tensor([0.60, 0.60])
def forward(self, x):
x = self.linear1(x)
x = torch.sigmoid(x)
x = self.linear2(x)
x = torch.sigmoid(x)
return x
if __name__ == '__main__':
inputs = torch.tensor([[0.05, 0.10]])
target = torch.tensor([[0.01, 0.99]])
# 获得网络输出值
net = Net()
output = net(inputs)
# print(output) # tensor([[0.7514, 0.7729]], grad_fn=<SigmoidBackward>)
# 计算误差
loss = torch.sum((output - target) ** 2) / 2
# print(loss) # tensor(0.2984, grad_fn=<DivBackward0>)
# 优化方法
optimizer = optim.SGD(net.parameters(), lr=0.5)
# 梯度清零
optimizer.zero_grad()
# 反向传播
loss.backward()
# 打印 w5、w7、w1 的梯度值
print(net.linear1.weight.grad.data)
# tensor([[0.0004, 0.0009],
# [0.0005, 0.0010]])
print(net.linear2.weight.grad.data)
# tensor([[ 0.0822, 0.0827],
# [-0.0226, -0.0227]])
# 打印网络参数
optimizer.step()
print(net.state_dict())
# OrderedDict([('linear1.weight', tensor([[0.1498, 0.1996], [0.2498, 0.2995]])),
# ('linear1.bias', tensor([0.3456, 0.3450])),
# ('linear2.weight', tensor([[0.3589, 0.4087], [0.5113, 0.5614]])),
# ('linear2.bias', tensor([0.5308, 0.6190]))])
从这段代码可以看出,我们手算的结果和程序跑出来的结果是一致的。




冀公网安备13050302001966号