深度学习(一)优化算法之动量法详解

动量法

使用梯度下降法,每次都会朝着目标函数下降最快的方向,这也称为最速下降法。这种更新方法看似非常快,实际上存在一些问题。

1. 梯度下降法的问题

考虑一个二维输入, [ x 1 , x 2 ] [x_1, x_2] [x1,x2],输出的损失函数 L : R 2 → R L: R^2 \rightarrow R L:R2R,下面是这个函数的等高线:

在这里插入图片描述

可以想象成一个很扁的漏斗,这样在竖直方向上,梯度就非常大,在水平方向上,梯度就相对较小,所以我们在设置学习率的时候就不能设置太大,为了防止竖直方向上参数更新太过了,这样一个较小的学习率又导致了水平方向上参数在更新的时候太过于缓慢,所以就导致最终收敛起来非常慢。

2. 动量法

动量法的提出就是为了应对这个问题,我们梯度下降法做一个修改如下:

v i = γ v i − 1 + η ∇ L ( θ ) v_i = \gamma v_{i-1} + \eta \nabla L(\theta) vi=γvi1+ηL(θ)

θ i = θ i − 1 − v i \theta _i = \theta_{i-1} - v_i θi=θi1vi

其中 v i v_i vi 是当前速度, γ \gamma γ 是动量参数,是一个小于 1的正数, η \eta η 是学习率

相当于每次在进行参数更新的时候,都会将之前的速度考虑进来,每个参数在各方向上的移动幅度不仅取决于当前的梯度,还取决于过去各个梯度在各个方向上是否一致,如果一个梯度一直沿着当前方向进行更新,那么每次更新的幅度就越来越大,如果一个梯度在一个方向上不断变化,那么其更新幅度就会被衰减,这样我们就可以使用一个较大的学习率,使得收敛更快,同时梯度比较大的方向就会因为动量的关系每次更新的幅度减少,如下图

在这里插入图片描述

比如我们的梯度每次都等于 g,而且方向都相同,那么动量法在该方向上使参数加速移动,有下面的公式:

v 0 = 0 v_0 = 0 v0=0

v 1 = γ v 0 + η g = η g v_1 = \gamma v_0 + \eta g = \eta g v1=γv0+ηg=ηg

v 2 = γ v 1 + η g = ( 1 + γ ) η g v_2 = \gamma v_1 + \eta g = (1 + \gamma) \eta g v2=γv1+ηg=(1+γ)ηg

v 3 = γ v 2 + η g = ( 1 + γ + γ 2 ) η g v_3 = \gamma v_2 + \eta g = (1 + \gamma + \gamma^2) \eta g v3=γv2+ηg=(1+γ+γ2)ηg

⋯ \cdots

v + ∞ = ( 1 + γ + γ 2 + γ 3 + ⋯   ) η g = 1 1 − γ η g v_{+ \infty} = (1 + \gamma + \gamma^2 + \gamma^3 + \cdots) \eta g = \frac{1}{1 - \gamma} \eta g v+=(1+γ+γ2+γ3+)ηg=1γ1ηg

如果我们把 γ \gamma γ 定为 0.9,那么更新幅度的峰值就是原本梯度乘学习率的 10 倍。

本质上说,动量法就仿佛我们从高坡上推一个球,小球在向下滚动的过程中积累了动量,在途中也会变得越来越快,最后会达到一个峰值,对应于我们的算法中就是,动量项会沿着梯度指向方向相同的方向不断增大,对于梯度方向改变的方向逐渐减小,得到了更快的收敛速度以及更小的震荡。

下面我们手动实现一个动量法,公式已经在上面了:

def sgd_momentum(parameters, vs, lr, gamma):
    for param, v in zip(parameters, vs):
        v[:] = gamma * v + lr * param.grad.data
        param.data = param.data - v
import numpy as np
import torch
from torch.utils.data import DataLoader
from torchvision.datasets import MNIST
import torch.nn as nn
import torch.nn.functional as F

import time
import matplotlib.pyplot as plt
%matplotlib inline
def data_tf(x):
    x = np.array(x, dtype='float32') / 255
    x = (x - 0.5) / 0.5 # 标准化,这个技巧之后会讲到
    x = x.reshape((-1,)) # 拉平
    x = torch.from_numpy(x)
    return x

# 使用MNIST数据集,没有的话需要下载
train_set = MNIST('./data', train=True, transform=data_tf) # 载入数据集,申明定义的数据变换
test_set = MNIST('./data', train=False, transform=data_tf)

# 定义 loss 函数
criterion = nn.CrossEntropyLoss()
# DataLoader 对数据进行加载打包
train_data = DataLoader(train_set, batch_size=64, shuffle=True)
# 使用 Sequential 定义 3 层神经网络
net = nn.Sequential(
    nn.Linear(784, 200),
    nn.ReLU(),
    nn.Linear(200, 10),
)

# 将速度初始化为和参数形状相同的零张量
vs = []
for param in net.parameters():
    vs.append(torch.zeros_like(param.data))

# 开始训练
losses = []

start = time.time() # 记时开始
for e in range(5):
    train_loss = 0
    for im, label in train_data:

        # 前向传播
        out = net(im)
        loss = criterion(out, label)
        # 反向传播
        net.zero_grad()
        loss.backward()
        sgd_momentum(net.parameters(), vs, 1e-2, 0.9) # 使用的动量参数为 0.9,学习率 0.01
        # 记录误差
        train_loss += loss.item()

        losses.append(loss.item())
    print('epoch: {}, Train Loss: {:.6f}'
          .format(e, train_loss / len(train_data)))
end = time.time() # 计时结束
print('使用时间: {:.5f} s'.format(end - start))
epoch: 0, Train Loss: 0.363505
epoch: 1, Train Loss: 0.173747
epoch: 2, Train Loss: 0.124349
epoch: 3, Train Loss: 0.101248
epoch: 4, Train Loss: 0.084900
使用时间: 21.00233 s

可以看到,加完动量之后 loss 能下降非常快,但是一定要小心学习率和动量参数,这两个值会直接影响到参数每次更新的幅度,所以可以多试几个值

当然,pytorch 内置了动量法的实现,非常简单,直接在torch.optim.SGD(momentum=0.9) 即可,下面实现一下

train_data = DataLoader(train_set,batch_size = 64,shuffle=True)

# 使用nn.Module定义3层网络

class my_net(nn.Module):
    def __init__(self):
        super(my_net,self).__init__()
        self.L1 = nn.Linear(784,200)
        self.L2 = nn.Linear(200,10)
        
    def forward(self,x):
        x = F.relu(self.L1(x))
        x = F.relu(self.L2(x))
        return x

net=my_net()
print(net)
my_net(
  (L1): Linear(in_features=784, out_features=200, bias=True)
  (L2): Linear(in_features=200, out_features=10, bias=True)
)
import torch.optim as optim
optimizer=optim.SGD(net.parameters(),lr=1e-2,momentum=0.9)

criterion = nn.CrossEntropyLoss()
## 开始训练
losses = []
idx=0
start = time.time()# 计时开始
for e in range(5):
    train_loss=0
    for im,label in train_data:
        # 前向传播
        out = net(im)
        loss = criterion(out,label)
        
        # 反向传播
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        
        # 记录误差
        train_loss += loss.item()
        if idx%30==0: # 30步记录一次
            losses.append(loss.item())
        idx += 1
        
    print(f'epoch:{e},Train Loss:{train_loss/len(train_data):.6f}')
end = time.time()
print(f'使用时间:{end-start:.6f}s')
epoch:0,Train Loss:0.253888
epoch:1,Train Loss:0.236733
epoch:2,Train Loss:0.221444
epoch:3,Train Loss:0.206973
epoch:4,Train Loss:0.193939
使用时间:19.242222s
x_axis = np.linspace(0, 5, len(losses), endpoint=True)
plt.semilogy(x_axis, losses, label='momentum: 0.9')
plt.legend(loc='best')
<matplotlib.legend.Legend at 0x19ab8e64808>

在这里插入图片描述

我们可以对比一下不加动量的随机梯度下降法

# 使用 Sequential 定义 3 层神经网络
net = nn.Sequential(
    nn.Linear(784, 200),
    nn.ReLU(),
    nn.Linear(200, 10),
)

optimizer = torch.optim.SGD(net.parameters(), lr=1e-2) # 不加动量
# 开始训练
losses1 = []
idx = 0
start = time.time() # 记时开始
for e in range(5):
    train_loss = 0
    for im, label in train_data:

        # 前向传播
        out = net(im)
        loss = criterion(out, label)
        # 反向传播
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        # 记录误差
        train_loss += loss.item()
        if idx % 30 == 0: # 30 步记录一次
            losses1.append(loss.item())
        idx += 1
    print('epoch: {}, Train Loss: {:.6f}'
          .format(e, train_loss / len(train_data)))
end = time.time() # 计时结束
print('使用时间: {:.5f} s'.format(end - start))
epoch: 0, Train Loss: 0.748911
epoch: 1, Train Loss: 0.366224
epoch: 2, Train Loss: 0.320932
epoch: 3, Train Loss: 0.294529
epoch: 4, Train Loss: 0.272778
使用时间: 18.94132 s

补充:格式化输出

a= 1.23333233313

  • print(‘a:{:.6f}’.format(a))
  • print(f’a:{a:.6f}’)
    两种输出形式是一样的
x_axis = np.linspace(0, 5, len(losses), endpoint=True)
plt.semilogy(x_axis, losses, label='momentum: 0.9')
plt.semilogy(x_axis, losses1, label='no momentum')
plt.legend(loc='best')
<matplotlib.legend.Legend at 0x19aba401048>

在这里插入图片描述

可以看到加完动量之后的 loss 下降的程度更低了,可以将动量理解为一种惯性作用,所以每次更新的幅度都会比不加动量的情况更多。

参考:PyTorch中文手册

  • 14
    点赞
  • 73
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
深度学习优化算法有很多种,以下是一些常用的优化算法: 1. 梯度下降(Gradient Descent):是深度学习中最基本的优化算法之一,通过计算损失函数的梯度来更新模型参数,从而最小化损失函数的值。 2. 随机梯度下降(Stochastic Gradient Descent):是梯度下降的一种变体,每次更新时只随机选取一部分样本进行计算,可以加速模型的收敛速度。 3. 批量梯度下降(Batch Gradient Descent):同样是梯度下降的一种变体,每次更新时选取全部样本进行计算,可以减少随机性,但计算开销较大。 4. 动量(Momentum):通过给梯度增加一个动量项,可以在更新时保持方向一致,从而加速模型的收敛速度,减少震荡。 5. 自适应学习率算法(Adaptive Learning Rate):如Adagrad、Adam等,根据梯度的大小自适应地调整学习率,可以加速收敛速度,提高模型的泛化能力。 6. L-BFGS算法:是一种基于拟牛顿优化算法,可以有效地处理大规模数据和高维参数空间的优化问题。 7. RMSProp算法:是一种自适应学习率算法,可以根据历史梯度的大小自适应地调整学习率,有效地解决了Adagrad算法学习率下降过快的问题。 8. Adadelta算法:是一种自适应学习率算法,可以根据历史梯度的大小自适应地调整学习率和动量项,可以更加稳定地进行模型优化。 总之,深度学习优化算法有很多种,不同的算法有不同的优缺点,需要根据具体问题选择合适的算法来进行模型优化。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值