PyTorch 深度学习实践-04-[Back Propagation]

Date: 2021-12-20

Repositity: Gitee

0. 前言

Reference: WIKI

反向传播(英语:Back Propagation,缩写为BP)是“误差反向传播”的简称,是一种与最优化方法(如梯度下降法)结合使用的,用来训练人工神经网络的常见方法。该方法对网络中所有权重计算损失函数的梯度。这个梯度会回馈给最佳化方法,用来更新权值以最小化损失函数。

反向传播要求对每个输入值得到已知输出,来计算损失函数梯度。因此,它通常被认为是一种监督式学习方法,虽然它也用在一些无监督网络(如自动编码器)中。它是多层前馈网络的Delta规则的推广,可以用链式法则对每层迭代计算梯度。反向传播要求人工神经元(或“节点”)的激励函数可微。

1. 正文

BP是神经网络中一个重要算法,他可以在图上面传播梯度,使模型结构更具弹性。

前述章节只有一个神经元,可以快速的算出其权重更新的解析解。但在复杂的多层神经网络下,手算不再适宜,因为参数太多过于复杂。那么可不可以将网络视为一张图,将计算节点的梯度存下来,然后按求导的链式法则计算对应的权重值

答案:反向传播。

1.1 计算图

这里以一个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(W1X+b1)+b2=W2W1X+(W2b1+b2)=WX+b(1)
那么上图的两层结构便被压成一层了,这样多线性层没有意义,复杂程度无法有效提高。怎么做?

每一层结束位置增加非线性函数,这里以sigmoid()为例,如下图所示:

上图给出了包含全部计算的两层线性神经网络示意图。接下来我们回顾一下链式求导法则

1.2 链式求导

一图胜千言,看图即懂

1.3 前馈计算和反向传播

这里以 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 。为了方便画图演示,这里不考虑激活函数,模型的前馈计算和反向传播过程,如下图所示:

  • 红色箭头为前馈计算结果
  • 橙色箭头为反向传播过程的偏导数计算
  • 灰色block为初始输入值
PyTorch 深度学习实践-04-[Back Propagation]_第1张图片
注意:`PyTorch`中的`Tensor`这个类,有两个成员一个是`data`:张量值、一个是`grad`:损失函数对权重的导数。因此我们定义了`Tensor`,就可以构建计算图。

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: 如果要计算每个epochloss,需要注意loss的累加也使用loss.item(),如果不使用则会导致计算图变大,最终爆掉显存。

Q4: 为什么要将梯度清零w.grad.data.zero_()

A4: 如果不对历史梯度进行清零,那么下一次循环重新计算权重时会将历史的梯度一并计算。当然特殊需求下,是不需要梯度清零的。

3 作业

将前述模型更换为
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)
首先画图,结果如下图所示,这里的初始值随意进行赋值,流程同前面。

PyTorch 深度学习实践-04-[Back Propagation]_第2张图片
代码:
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

你可能感兴趣的:(pytorch,pytorch深度学习实践)