神经网络 BP 算法

神经网络 BP 算法

BP(Back Propagation)算法也叫误差反向传播算法,它用来求模型的参数梯度,进而配合梯度下降法更新网络参数。它的基本工作流程是这样的:

  1. 先通过正向传播得到误差。所谓正向传播,就是数据从输入层一路走到输出层,经过层层计算得到预测值,再用损失函数算出预测值和真实值之间的误差。
  2. 再通过反向传播把误差传回模型的各个参数,据此对网络参数做适当调整,一步步缩小预测值和真实值之间的误差。

反向传播算法是靠链式法则来求梯度、更新权重的。面对复杂的复合函数,我们先把它拆成加减乘除、指数、对数、三角函数这些初等函数,再用链式法则一步步完成复合函数的求导。

我们通过一个例子,来直观理解 BP 算法更新网络参数的过程:

为了把计算过程讲得更细,上图中每个矩形代表一个神经元,框里写的是该神经元的值、激活值的计算结果和对应公式。最终算出真实值和预测值之间的误差是 0.2984。图里需要说明的几点:

  1. 从下往上看,最下层两个绿色的圆代表两个输入值。
  2. 右侧那 8 个数字,最下面 4 个是 w1、w2、w3、w4 的初始值,最上面 4 个是 w5、w6、w7、w8 的初始值。
  3. b1 的值为 0.35,b2 的值为 0.60。
  4. 两个预测结果分别是: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]))])

从这段代码可以看出,我们手算的结果和程序跑出来的结果是一致的。