动量法Momentum

理解

动量法的理解:在历史方向与现有方向相同的情况下,迈出大步子,在历史方向与现有 方向相反的情况下,迈出小步子。

让上一步的梯度向量与现在这一点的梯度向量以加权的方式求和,求解出受到上一步大小和方向影响的真实下降方向,再让坐标点向真实下降方向移动。在坐标轴上,可以表示为:

其中,对上一步的梯度向量加上的权重被称为动量参数(也叫做衰减力度,通常使用\gamma进行表示),对这一点的梯度向量加上的权重就是步长(学习率,表示为\eta),真实移动的向量为v,被称为动量(Momentum)。

用公式表示为:

在第一步中,没有历史梯度方向,因此第一步的真实方向就是起始点梯度的反方向,v_{0} = 0。其中 v_{(t-1)}代表了之前所有步骤所累积的动量和。在这种情况下,梯度下降的方向有了“惯性”,受到历史累计动量的影响,当新坐标点的梯度反方向与历史累计动量的方向一致时,历史累计动量会加大实际方向的步子,相反,当新坐标点的梯度反方向与历史累计动量的方向不一致时,历史累计动量会减小实际方向的步子。

Pytorch实现动量法

所用数据来源于上个笔记的数据设计。

深度学习笔记1:实现第一个深度神经网络的正向传播

lr = 0.1  #学习率
gamma = 0.9  #动量参数
dw = net.linear1.weight.grad   #当前参数梯度
w = net.linear1.weight.data   #权重数据
v = torch.zeros(dw.shape[0],dw.shape[1])
#动量变量,用于累积历史更新方向(初始化为与梯度同形状的零张量)

动量v要能够跟dw相减,因此必须和dw保持相同的结构,初始v为0,但后续v会越来越大。

核心动量更新公式:

v = gamma * v - lr * dw
w -=v
#每运行一次,迭代更新一次
#动量v可看为:gamma*v + (-lr*dw)

注意点:

结果:

torch.optim实现带动量的梯度下降

流程
1->神经网络向前传播
2->得到本轮向前传播的损失函数值
3->反向传播,得到梯度
4->更新权重(和动量)
5->更新完权重后,清空梯度:清除原来计算出的基于上一个点的坐标计算的梯度

在PyTorch库的架构中,拥有专门实现优化算法的模块torch.optim。接下来使用torch.optim实现带动量的梯度下降,所使用的数据设计和神经网络架构

import torch
import torch.nn as nn
from torch.nn import functional as F
import torch.optim as optim
torch.random.manual_seed(420)
X=torch.rand((500,20),dtype=torch.float32) 
y=torch.randint(low=0,high=3,size=(500,),dtype=torch.float32)
lr=0.1
gamma=0.9
#神经网络架构
class Model(nn.Module):
    def __init__(self,in_features=10,out_features=2):
        super().__init__()
        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):
        sigma1=torch.relu(self.linear1(x))
        sigma2=torch.sigmoid(self.linear2(sigma1))
        zhat=self.output(sigma2)
        return zhat
#实例化
torch.random.manual_seed(420)
net = Model(in_features=input_,out_features=output_)
net.parameters()#一次性导出现在网络架构下的所有权重和截距
#定义损失函数
criterion = nn.CrossEntropyLoss()  #交叉熵损失函数
#定义优化算法
#opt = optim.SGD(需要进行迭代的权重,学习率,动量参数)
opt = optim.SGD(net.parameters(),lr=lr,momentum=gamma)

进行一轮梯度下降

zhat=net.forward(X)      #最后一个线性层的输出结果,向前传播
loss=criterion(zhat,y.reshape(500,).long())  #计算损失函数
loss.backward()   #反向传播,计算梯度
opt.step()   #走一步,实现  w-= v这一步,更新权重w,更新动量v
opt.zero_grad()   #清空梯度

print(loss)
print(net.linear1.weight.data[0][:10])
#[0][10]是对权重向量的索引操作,取权重的第0行,前10列元素

结果

这一系列代码就完成了梯度下降的一步,多运行几次就实现了梯度下降本身。

小批量梯度下降

为什么会有小批量梯度下降?

在深度学习中,神经网络的训练对象往往是图像、文字、语音、视频等非结构化数据,这些数据的特点之一,就是特征张量一般都是大量的高维的数据。

在深度学习中,如果梯度下降的每次迭代都使用全部数据,将会非常耗费计算资源,且样本量越大,计算开销越高。虽然PyTorch被设计成天生能够处理巨量数据,但还是需要在数据量这一点上下功夫。因此就有了小批量随机梯度下降(mini-batch stochastic gradient descent,简写为mini-batch SGD)

为什么使用小批量梯度下降?

传统梯度下降在每次进行权重迭代时都会使用全部数据,每次迭代所使用的数据也都完全一致。而mini batch SGD是每次迭代前,都会从整体样本中采样一批固定数目的样本组成批次(batch),并用批次中的样本进行梯度计算,以减少样本量。

比起传统梯度下降,小批量梯度下降更容易找到全局最小值。

不同梯度下降之间的对比

传统梯度下降:传统梯度下降是每次迭代时都使用全部数据的梯度下降,所以每次使用的数据是一致的,因此梯度向量的方向和大小都只受到权重的影响,所以梯度方向的变化相对较小,很多时候看起来梯度甚至是指向一个方向这样带来的优势是可以使用较大的步长,快速迭代直到找到最小值。但是缺点也很明显,由于梯度方向不容易发生巨大变化,所以一旦在迭代过程中落入局部最优的范围,传统梯 度下降就很难跳出局部最优,再去寻找全局最优解了。

小批量梯度下降:在每次迭代前都会随机抽取一批数据,所以每次迭代时带入梯度向量表达式的数据是不同的,梯度的方向同时受到系数wb和带入的训练数据的影响,因此每次迭代时梯度向量的方向都会 发生较大变化。并且,当抽样的数据量越小,本次迭代中使用的样本数据与上一次迭代中使用的样本数据之间的差异就可能越大,这也导致本次迭代中梯度的方向与上一次迭代中梯度的方向有巨大差异。所以对于mini-batch SGD而言,它的梯度下降路线看起来往往是曲折的折线。

随机梯度下降:小批量梯度下降的一种极端情况,每次随机选取的批量中只有一个样本时,梯度下降的迭代轨迹就会变得异常不稳定。

小批量梯度下降的优势

mini-batch SGD的优势是算法不会轻易陷入局部最优,由于每次梯度向量的方向都会发生巨大变化,因此一旦有机会,算法就能够跳出局部最优,走向全局最优(当然也有可能是跳出一个局部最优,走向另一个局部最优)。不过缺点是,需要的迭代次数变得不明。如果最开始就在全局最优的范围内,那可能 只需要非常少的迭代次数就收敛,但是如果最开始落入了局部最优的范围,或全局最优与局部最优的差异很小,那可能需要花很长的时间、经过很多次迭代才能够收敛,毕竟不断改变的方向会让迭代的路线变得曲折。

mini-batch SGD可以提升神经网络的计算效率,让神经网络计算更快。

batch_size和epoch

batch_size: 在mini-batch SGD中,我们选择的批量batch含有的样本数被称为"batch_size",批量尺寸,这个尺寸一定是小于数据量的某个正整数值。每次迭代之前需要从数据集中抽取batch_size个数据用于训练。

epoch: SGD中,因为每次迭代时都只使用了一小部分数据,所以它迭代的次数并不能代表全体数 据一共被学习了多少次,所以需要epoch来定义全体数据一共被学习了多少次。

在深度学习中,常常定义num_epoches作为梯度下降的最外层循环,batch_size作为内层循环。有 时候使数据被多学习几次,用来增加模型对数据的理解。有时候也会控制模型对数据的训练。总之往往会使用epoch和batch_size来控制训练的节奏。

for epochs in range(epoch):
    for batch in range(batch):
        zhat=net.forward(X)      #最后一个线性层的输出结果,向前传播
        loss=criterion(zhat,y.reshape(500,).long())  #计算损失函数
        loss.backward()   #反向传播,计算梯度
        opt.step()   #走一步,实现  w-= v这一步,更新权重w,更新动量v
        opt.zero_grad()  

TensorDataset和DataLoader

TensorDataset

使用小批量随机梯度下降,就需要对数据进行采样、分割等操作。在PyTorch中,操作数据所需要使用的模块是torch.utils,其中utils.data类下面有大量用来执行数据预处理的工具。在MBSGD中,需要将数据划分为许多组“特征张量+对应标签”的形式,因此最开始我们要将数据的特征张量与标签打包成一个对象。之前提到过,深度学习中的特征张量维度很少是二维,因此其特征张量与标签几乎总是分开的,不像机器学习中标签常常出现在特征矩阵的最后一列或第一列。

合并张量与标签,所使用的类是utils.data.TensorDataset,这个功能类似于python中的zip,可以 将最外面的维度一致的tensor进行打包,也就是将第一个维度一致的tensor进行打包。

具体操作如下:

import torch
from torch.utils.data import TensorDataset
a = torch.randn(500,2,3)  #三维
b = torch.randn(500,3,4,5)  #四维
c = torch.randn(500,1)  #二维
#定义三个不同维度的数据集
TensorDataset(a,b,c)  #合并三个数据集
for x in TensorDataset(a,b,c):
    print(x)
    break        #与上面一一对应,一共500个组,其中一个减一个维度展示
#此处仅展示打包后的第一个元素就跳出

上图结果展示了:a的500个2行3列的张量的第1个,随后紧跟b的500个3维模样是4行5列的张量的第一个,再紧跟c的500个1列的张量的第一个。

for x in TensorDataset(b,c):
    print(x)
    break        #与上面一一对应,一共500个组,其中一个减一个维度展示

DataLoader

当将数据打包成一个对象之后,需要使用划分小批量的功能DataLoader。DataLoader是处理训练前专用的功能,它可以接受任意形式的数组、张量作为输入,并把他们一次性转换为神经网络可以 接入的tensor。

from torch.utils.data import DataLoader
data = TensorDataset(b,c) #打包b,c数据集
DataLoader(data)
for x in DataLoader(data):
    print(x)
    break   #只是换成了一个列表

注意:此时DataLoader生成了与上述TensorDataset相同的对象,但是将他转换成了一个列表(注意开头的tensor左边的“(”换成了“[” ),这个列表有两个元素,分别是:

b的500个3维模样是4行5列的张量的第一个。

c的500个1列的张量的第一个。

重要参数

batch_size、shuffle、drop_last

bs=120  
dataset=DataLoader(data
           ,batch_size=bs
           ,shuffle = True  #划分小批量之前随机打乱数据
           ,drop_last=False  #你要舍弃最后一个batch吗?默认false
          )
for i in dataset:
    print(i[0].shape)

不舍弃最后一个batch VS 舍弃最后一个batch

len(dataset)  #多少个batch?
len(dataset.dataset)    #展示里面全部的数据

dataset.dataset[0]#batch里第一个样本 ,左上
dataset.dataset[0][0]#第一个样本的特征,右上
dataset.dataset[0][1]   #第一个样本的标签,左下
dataset.batch_size   #查看现有的batch_size,右下

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐