第十一课:梯度下降和反向传播

在之前的课程中,我们已经完成了从0建立深层神经网络,并介绍了各类神经网络所使用的损失函数。本节课开始,我们将以分类深层神经网络为例,为大家展示神经网络的学习和训练过程。在介绍PyTorch的基本工具AutoGrad库时,我们系统地介绍过数学中的优化问题和优化思想,我们介绍了最小二乘法以及梯度下降法这两个入门级优化算法的具体操作,并使用AutoGrad库实现了他们。在本节课中,我们将从梯度下降法向外拓展,介绍更常用的优化算法,实现神经网络的学习和迭代。在本节课结束的时候,你将能够完整地实现一个神经网络训练的全流程。

Lesson 11 神经网络的学习

一 梯度下降中的两个关键问题
1 找出梯度向量的方向和大小
2 让坐标点移动起来(进行一次迭代)
二、找出距离和方向:反向传播
1 反向传播的定义与价值
2 PyTorch实现反向传播
三、移动坐标点
1 走出第一步
2 从第一步到第二步:动量法Momentum
3 torch.optim实现带动量的梯度下降
四、开始迭代:batch_size与epoches
1 为什么要有小批量?
2 batch_size与epoches
3 TensorDataset与DataLoader
五、在MINST-FASHION上实现神经网络的学习流程
1 导库,设置各种初始值
2 导入数据,分割小批量
3 定义神经网络的架构
4 定义训练函数
5 进行训练与评估

在我们的优化流程之中,我们使用损失函数定义预测值与真实值之间的差异,也就是模型的优劣。当损失函数越小,就说明模型的效果越好,我们要追求的时损失函数最小时所对应的权重向量 。对于凸函数而言,导数为0的点就是极小值点,因此在数学中,我们常常先对权重w ww求导,再令导数为0来求解极值和对应的w ww。但是对于像神经网络这样的复杂模型,可能会有数百个w ww的存在,同时如果我们使用的是像交叉熵这样复杂的损失函数(机器学习中还有更多更加复杂的函数),令所有权重的导数为0并一个个求解方程的难度很大、工作量也很大。因此我们转换思路,不追求一步到位,而使用迭代的方式逐渐接近损失函数的最小值。这就是优化算法的具体工作,优化算法的相关知识也都是关于“逐步迭代到损失函数最小值”的具体操作。

在讲解AutoGrad的时候,九天老师给大家详细阐述了梯度下降的细节,因此本篇章会假设你对梯度下降是熟悉的。在这里,你可以问自己以下的问题来进行自查:

  • 1、梯度下降的基本流程是什么,它是怎么找到损失函数的最小值的(写出流程)?
  • 2、梯度下降中是如何迭代权重w ww的(写出公式)?
  • 3、什么是梯度?什么是步长?

如果你还不能回答这些问题,那我建议你回到Lesson 6中仔细复习一下梯度下降的细节,因为上面这些问题对于之后使用PyTorch实现神经网络的优化至关重要。

一 梯度下降中的两个关键问题

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这个过程中,每步的方向就是当前坐标点对应的梯度向量的反方向,每步的距离就是步长 * 当前坐标点所对应的梯度向量的大小(也就是梯度向量的模长),梯度向量的性质保证了沿着其反方向、按照其大小进行移动,就能够接近损失函数的最小值。在这个过程中,存在两个关键问题:

  • 1、怎么找出梯度向量的方向和大小?
  • 2、怎么让坐标点按照梯度向量的反方向,移动与梯度向量大小相等的距离?

许多基于梯度下降算法的变化都是围绕这两个问题来进行。我们先来解决第一个问题。

1.找出梯度向量的方向和大小

在这里插入图片描述
在这里插入图片描述

2、让坐标点移动起来(进行一次迭代)

有了大小和方向,来看第二个问题:怎么让坐标点按照梯度向量的反方向,移动与梯度向量大小相等的距离?

在这里插入图片描述
在这里插入图片描述
这就是我们迭代权重的迭代公式,其中偏导数的大小影响整体梯度向量的大小,偏导数前的减号影响整体梯度向量的方向。当我们将η \etaη设置得很大,梯度下降的每一个步子就迈得很大,走得很快,当我们将步长设置较小,梯度下降就走得很慢。我们使用一个式子,就同时控制了大小和方向,不得不说大道至简,很复杂的目的在数学的世界里可以变得极其简单。

当然了,一旦迭代w ww,我们的损失函数也会发生变化。在整个损失函数的图像上,红色箭头就是梯度向量的反方向,灰色箭头就是每个权重对应的迭代。(这个损失函数就是九天老师在AutoGrad课程中使用的SSE)。

在这里插入图片描述
你可以使用这一段代码,在Jupyter Notebook中实现这个3D图的绘制。注意,这段代码只在jupyter notebook中有效,jupyterlab不可用。

二、找出距离和方向:反向传播

1 反向传播的定义与价值

在梯度下降的最初,我们需要先找出坐标点对应的梯度向量。梯度向量是各个自变量求偏导后的表达式再带入坐标点计算出来的,在这一步骤中,最大的难点在于如何获得梯度向量的表达式——也就是损失函数对各个自变量求偏导后的表达式。
在单层神经网络,例如逻辑回归(二分类单层神经网络)中,我们有如下计算:
在这里插入图片描述

其中BCEloss是二分类交叉熵损失函数。在这个计算图中,从左向右计算以此的过程就是正向传播,因此进行以此计算后,我们会获得所有节点上的张量的值(z、sigma以及loss)。根据梯度向量的定义,在这个计算过程中我们要求的是损失函数对w 的导数,所以求导过程需要涉及到的链路如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
同样的,进行从左到右的正向传播之后,我们会获得所有节点上的张量。其中涉及到的求导链路如下:
在这里插入图片描述

在这里插入图片描述
对于需要对这个式子求导,大家感受如何?而这只是一个两层的二分类神经网络,对于复杂神经网络来说,所需要做得求导工作是无法想象的。求导过程的复杂是神经网络历史上的一大难题,这个难题直到1986年才真正被解决。1986年,Rumelhart、Hinton和Williams提出了反向传播算法(Backpropagation algorithm,又叫做Delta法则),利用链式法则成功实现了复杂网络求导过程的简单化。(值得一提的是,多层神经网络解决XOR异或门问题是在1985年被提出的)。接下来,我们就来看看反向传播是怎么解决复杂求导问题的。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2 PyTorch实现反向传播 (重点)

在梯度下降中,每走一步都需要更新梯度,所以计算量是巨大的。幸运的是,PyTorch可以帮助我们自动计算梯度,我们只需要提取梯度向量的值来进行迭代就可以了。在PyTorch中,我们有两种方式实现梯度计算。一种是使用我们之前已经学过的AutoGrad。在使用AutoGrad时,我们可以使用torch.autograd.grad()函数计算出损失函数上具体某个点/某个变量的导数,当我们需要了解具体某个点的导数值时autograd会非常关键,比如:

import torch
x = torch.tensor(1.,requires_grad = True,dtype=torch.float32) #requires_grad,表示允许对X进行梯度计算
y = x ** 2
torch.autograd.grad(y, x) #这里返回的是在函数y=x**2上,x=1时的导数值。

对于单层神经网络,autograd.grad会非常有效。但深层神经网络就不太适合使用grad函数了。对于深层神经网络,我们需要一次性计算大量权重对应的导数值,并且这些权重是以层为单位阻止成一个个权重的矩阵,要一个个放入autograd来进行计算有点麻烦。所以我们会直接使PyTorch提供的基于autograd的反向传播功能,lossfunction.backward()来进行计算。

注意,在实现反向传播之前,首先要完成模型的正向传播,并且要定义损失函数,因此我们会借助之前的课程中我们完成的三层神经网络的类和数据(500行,20个特征的随机数据)来进行正向传播。

#导入库、数据、定义神经网络类,完成正向传播
#继承nn.Module类完成正向传播
import torch
import torch.nn as nn
from torch.nn import functional as F #确定数据
torch.manual_seed(420) 
X = torch.rand((500,20),dtype=torch.float32) * 100
y = torch.randint(low=0,high=3,size=(500,),dtype=torch.float32) #定义神经网路的架构
"""
注意:这是一个三分类的神经网络,因此我们需要调用的损失函数多分类交叉熵函数CEL
CEL类已经内置了sigmoid功能,因此我们需要修改一下网络架构,删除forward函数中输出层上的sigmoid
函数,并将最终的输出修改为zhat
"""
class Model(nn.Module):
    def __init__(self,in_features=10,out_features=2):
        super(Model,self).__init__() #super(请查找这个类的父类,请使用找到的父类替换现在的类)
        self.linear1 = nn.Linear(in_features,13,bias=True) #输入层不用写,这里是隐藏层的第一层
        self.linear2 = nn.Linear(13,8,bias=True)
        self.output = nn.Linear(8,out_features,bias=True)
    def forward(self, x):
        z1 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值