Pytorch学习笔记(五):优化器

重要:本文最后更新于,某些文章具有时效性,若有错误或失效,请在下方留言

在准备好数据,设计好模型并选择了合适的损失函数后,应该采用合适的优化器进行优化(训练)模型。本篇笔记主要记录Pytorch的十个优化器和六个学习率调整方法。

部分学习资源链接:

之前的相关文章:

本篇笔记参考文章

优化器基类:Optimizer

PyTorch中所有的优化器(如:optim.Adadelta、optim.SGD、optim.RMSprop等)均是 Optimizer 的子类,Optimizer 中定义了一些常用的方法,有zero_grad()、step(closure)、state_dict()、load_state_dict(state_dict)和add_param_group(param_group),接下来进行介绍。

参数组(param_groups)的概念

认识 Optimizer 的方法之前,需要了解一个概念,叫做参数组(param_groups)。在 finetune,某层定制学习率,某层学习率置零操作中,都会设计参数组的概念,因此首先了解参数组的概念非常有必要。

optimizer对参数的管理是基于组的概念,可以为每一组参数配置特定的 lr , momentum , weight_decay 等等。

参数组在 optimizer 中表现为一个 list(self.param_groups) ,其中每个元素是 dict ,表示一个参数及其相应配置,在 dict 中包含 'params' 、'weight_decay' 、'lr' 、'momentum' 等字段。

代码示例:

import torch
import torch.optim as optim


w1 = torch.randn(2, 2)
w1.requires_grad = True

w2 = torch.randn(2, 2)
w2.requires_grad = True

w3 = torch.randn(2, 2)
w3.requires_grad = True

# 一个参数组
optimizer_1 = optim.SGD([w1, w3], lr=0.1)
print('len(optimizer.param_groups): ', len(optimizer_1.param_groups))
print(optimizer_1.param_groups, '\n')

# 两个参数组
optimizer_2 = optim.SGD([{'params': w1, 'lr': 0.1},
                         {'params': w2, 'lr': 0.001}])
print('len(optimizer.param_groups): ', len(optimizer_2.param_groups))
print(optimizer_2.param_groups)

输出结果:

len(optimizer.param_groups):  1
[{'params': [tensor([[ 1.2949, -0.3411],
        [ 1.1031,  1.0280]], requires_grad=True), tensor([[ 0.9430,  0.3461],
        [ 1.7684, -0.1433]], requires_grad=True)], 'lr': 0.1, 'momentum': 0, 'dampening': 0, 'weight_decay': 0, 'nesterov': False}] 

len(optimizer.param_groups):  2
[{'params': [tensor([[ 1.2949, -0.3411],
        [ 1.1031,  1.0280]], requires_grad=True)], 'lr': 0.1, 'momentum': 0, 'dampening': 0, 'weight_decay': 0, 'nesterov': False}, {'params': [tensor([[-0.6999,  0.2329],
        [ 0.4268, -0.4112]], requires_grad=True)], 'lr': 0.001, 'momentum': 0, 'dampening': 0, 'weight_decay': 0, 'nesterov': False}]

zero_grad()

功能:将梯度清零。

由于PyTorch不会自动清零梯度,所以在每一次更新前会进行此操作。

代码示例:

import torch
import torch.optim as optim

# ----------------------------------- zero_grad

w1 = torch.randn(2, 2)
w1.requires_grad = True

w2 = torch.randn(2, 2)
w2.requires_grad = True

optimizer = optim.SGD([w1, w2], lr=0.001, momentum=0.9)

optimizer.param_groups[0]['params'][0].grad = torch.randn(2, 2)

print('参数w1的梯度:')
print(optimizer.param_groups[0]['params'][0].grad, '\n')  # 参数组,第一个参数(w1)的梯度

optimizer.zero_grad()
print('执行zero_grad()之后,参数w1的梯度:')
print(optimizer.param_groups[0]['params'][0].grad)  # 参数组,第一个参数(w1)的梯度

输出结果:

参数w1的梯度:
tensor([[ 0.6742,  0.0191],
        [-0.8342, -0.0218]]) 

执行zero_grad()之后,参数w1的梯度:
tensor([[0., 0.],
        [0., 0.]])

state_dict()

功能:获取模型当前的参数,以一个有序字典形式返回。

这个有序字典中,key是各层参数名,value就是参数。

代码示例:

import torch.nn as nn
import torch.nn.functional as F


# ----------------------------------- state_dict
class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(3, 1, 3)
        self.pool = nn.MaxPool2d(2, 2)
        self.fc1 = nn.Linear(1 * 3 * 3, 2)

    def forward(self, x):
        x = self.pool(F.relu(self.conv1(x)))
        x = x.view(-1, 1 * 3 * 3)
        x = F.relu(self.fc1(x))
        return x


net = Net()

# 获取网络当前参数
net_state_dict = net.state_dict()

print('net_state_dict类型:', type(net_state_dict))
print('net_state_dict管理的参数: ', net_state_dict.keys())
for key, value in net_state_dict.items():
    print('参数名: ', key, '\t大小: ',  value.shape)

输出结果:

net_state_dict类型: <class 'collections.OrderedDict'>
net_state_dict管理的参数:  odict_keys(['conv1.weight', 'conv1.bias', 'fc1.weight', 'fc1.bias'])
参数名:  conv1.weight  大小:  torch.Size([1, 3, 3, 3])
参数名:  conv1.bias  大小:  torch.Size([1])
参数名:  fc1.weight  大小:  torch.Size([2, 9])
参数名:  fc1.bias  大小:  torch.Size([2])

load_state_dict(state_dict)

功能:将 state_dict 中的参数加载到当前网络,常用于 finetune 。

代码示例:

import torch
import torch.nn as nn
import torch.nn.functional as F


# ----------------------------------- load_state_dict

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(3, 1, 3)
        self.pool = nn.MaxPool2d(2, 2)
        self.fc1 = nn.Linear(1 * 3 * 3, 2)

    def forward(self, x):
        x = self.pool(F.relu(self.conv1(x)))
        x = x.view(-1, 1 * 3 * 3)
        x = F.relu(self.fc1(x))
        return x

    def zero_param(self):
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                torch.nn.init.constant_(m.weight.data, 0)
                if m.bias is not None:
                    m.bias.data.zero_()
            elif isinstance(m, nn.Linear):
                torch.nn.init.constant_(m.weight.data, 0)
                m.bias.data.zero_()
net = Net()

# 保存,并加载模型参数(仅保存模型参数)
torch.save(net.state_dict(), 'net_params.pkl')   # 假设训练好了一个模型net
pretrained_dict = torch.load('net_params.p

输出结果:

conv1层的权值为:
 tensor([[[[0., 0., 0.],
          [0., 0., 0.],
          [0., 0., 0.]],

         [[0., 0., 0.],
          [0., 0., 0.],
          [0., 0., 0.]],

         [[0., 0., 0.],
          [0., 0., 0.],
          [0., 0., 0.]]]]) 

加载之后,conv1层的权值变为:
 tensor([[[[ 0.1033,  0.1682, -0.1399],
          [ 0.1844,  0.0281,  0.1014],
          [-0.0952, -0.0616, -0.1238]],

         [[ 0.0714,  0.0280, -0.1818],
          [ 0.1266, -0.1768, -0.0706],
          [ 0.1776,  0.0392,  0.1849]],

         [[-0.1133, -0.1129,  0.0132],
          [ 0.1472,  0.0888,  0.1082],
          [ 0.0226, -0.0079, -0.0573]]]])

add_param_group()

功能:给 optimizer 管理的参数组中增加一组参数,可为该组参数定制 lr , momentum , weight_decay 等,在 finetune 中常用。

例如:

optimizer_1.add_param_group({'params': w3, 'lr': 0.001, 'momentum': 0.8})

代码示例:

import torch
import torch.optim as optim

# ----------------------------------- add_param_group

w1 = torch.randn(2, 2)
w1.requires_grad = True

w2 = torch.randn(2, 2)
w2.requires_grad = True

w3 = torch.randn(2, 2)
w3.requires_grad = True

# 一个参数组
optimizer_1 = optim.SGD([w1, w2], lr=0.1)
print('当前参数组个数: ', len(optimizer_1.param_groups))
print(optimizer_1.param_groups, '\n')

# 增加一个参数组
print('增加一组参数 w3\n')
optimizer_1.add_param_group({'params': w3, 'lr': 0.001, 'momentum': 0.8})

print('当前参数组个数: ', len(optimizer_1.param_groups))
print(optimizer_1.param_groups, '\n')

print('可以看到,参数组是一个list,一个元素是一个dict,每个dict中都有lr, momentum等参数,这些都是可单独管理,单独设定,十分灵活!')

输出结果:

当前参数组个数:  1
[{'params': [tensor([[-1.3566,  0.3295],
        [-0.7923,  1.1479]], requires_grad=True), tensor([[-1.0733, -0.3530],
        [-1.4594,  1.4481]], requires_grad=True)], 'lr': 0.1, 'momentum': 0, 'dampening': 0, 'weight_decay': 0, 'nesterov': False}] 

增加一组参数 w3

当前参数组个数:  2
[{'params': [tensor([[-1.3566,  0.3295],
        [-0.7923,  1.1479]], requires_grad=True), tensor([[-1.0733, -0.3530],
        [-1.4594,  1.4481]], requires_grad=True)], 'lr': 0.1, 'momentum': 0, 'dampening': 0, 'weight_decay': 0, 'nesterov': False}, {'params': [tensor([[ 1.0496, -0.2211],
        [-0.2596, -0.4344]], requires_grad=True)], 'lr': 0.001, 'momentum': 0.8, 'dampening': 0, 'weight_decay': 0, 'nesterov': False}] 

可以看到,参数组是一个list,一个元素是一个dict,每个dict中都有lr, momentum等参数,这些都是可单独管理,单独设定,十分灵活!

step()

所有的 optimizer 都实现了 step() 方法,这个方法会更新所有的参数。一旦梯度被如 backward() 之类的函数计算好后,我们就可以调用这个函数。

for input, target in dataset:    optimizer.zero_grad()    output = model(input)    loss = loss_fn(output, target)    loss.backward()    optimizer.step()

step(closure)

功能:执行一步权值更新, 其中可传入参数 closure(一个闭包)。如,当采用 LBFGS 优化方法时,需要多次计算,因此需要传入一个闭包去允许它们重新计算loss , 这个闭包应当清空梯度, 计算损失,然后返回。

代码示例:

for input, target in dataset:    def closure():        optimizer.zero_grad()        output = model(input)        loss = loss_fn(output, target)        loss.backward()        return loss    optimizer.step(closure)

优化方法的基本使用

import optimptimizer=optim.SGD(params=net.parameters(),lr=0.1)optimizer.zero_grad()  //梯度清零output=net(input)output.backward(output)optimizer.step()#或者optimizer = optim.SGD(model.parameters(), lr = 0.01, momentum=0.9)optimizer = optim.Adam([var1, var2], lr = 0.0001)

当然 你也可以为每个参数单独设置选项:

不直接传入Variable的iterable,而是传入dict的iterable。 在我们想为每一层设置学习率时很有用。

optim.SGD([
               {'params': model.base.parameters()},
               {'params': model.classifier.parameters(), 'lr': 1e-3}
          ], 
          lr=1e-2, momentum=0.9)

这意味着 model.base 的参数将会使用 1e-2 的学习率, model.classifier 的参数将会使用 1e-3 的学习率,并且0.9的 momentum 将会被用于所有的参数。

PyTorch的优化器

之前虽然了解了 Optimizer 类,但是还无法构建一个能优化网络的优化器。

接下来将会介绍PyTorch提供的十种优化器,有常见的 SGD 、ASGD 、Rprop 、RMSprop 、Adam 等等。这里都说的是优化器,并没有提优化方法,因为PyTorch中给出的优化器与原始论文中提出的优化方法,多多少少有改动,详细还需看优化器源码。

1. torch.optim.SGD

CLASS torch.optim.SGD(params, lr=<required parameter>, momentum=0, dampening=0, weight_decay=0, nesterov=False)

功能:

可实现随机梯度下降优化算法,带动量 SGD 优化算法,带NAG(Nesterov accelerated gradient)动量 SGD 优化算法,并且均可拥有 weight_decay 项。

参数:

params(iterable):参数组(参数组的概念请查看 3.2 优化器基类:Optimizer),优化器要管理的那部分参数。

lr(float):初始学习率,可按需随着训练过程不断调整学习率。

momentum(float):动量,通常设置为0.9,0.8

dampening(float):dampening for momentum ,暂时不了其功能,在源码中是这样用的:buf.mul_(momentum).add_(1 - dampening, d_p),值得注意的是,若采用nesterov,dampening必须为 0.

weight_decay(float):权值衰减系数,也就是L2正则项的系数

nesterov(bool):bool选项,是否使用NAG(Nesterov accelerated gradient)

代码示例:

optimizer = torch.optim.SGD(model.parameters(), lr=0.1, momentum=0.9)
optimizer.zero_grad()
loss_fn(model(input), target).backward()
optimizer.step()

注意事项:

Pytroch中使用SGD十分需要注意的是,更新公式与其他框架略有不同!

Pytorch中是这样的:

\[v=\rho*v+g\]

\[p=p-lr*v\]

其他框架中是这样的:

\[v=\rho*v+lr*g\]

\[p=p-v\]

ρ 是动量,v 是速率,g 是梯度,p 是参数,其实差别就是在 ρ∗v 这一项,PyTorch中将此项也乘了一个学习率。

2. torch.optim.ASGD

CLASS torch.optim.ASGD(params, lr=0.01, lambd=0.0001, alpha=0.75, t0=1000000.0, weight_decay=0)

功能:

ASGD 也成为 SAG,均表示随机平均梯度下降(Averaged Stochastic Gradient Descent),简单地说 ASGD 就是用空间换时间的一种 SGD,详细可参看论文:《Accelerating Stochastic Gradient Descent using
Predictive Variance Reduction》

参数:

params(iterable):参数组(参数组的概念请查看 3.1 优化器基类:Optimizer),优化器要优化的那些参数。

lr(float):初始学习率,可按需随着训练过程不断调整学习率。

lambd(float):衰减项,默认值1e-4。 alpha(float)- power for eta update ,默认值0.75。 t0(float)- point at which to start averaging,默认值1e6

weight_decay(float):权值衰减系数,也就是L2正则项的系数。

3. torch.optim.Rprop

CLASS torch.optim.Rprop(params, lr=0.01, etas=(0.5, 1.2), step_sizes=(1e-06, 50))

功能:

实现Rprop优化方法(弹性反向传播),优化方法原文《Martin Riedmiller und Heinrich Braun: Rprop - A Fast Adaptive Learning Algorithm. Proceedings of the International Symposium on Computer and Information Science VII, 1992》,该优化方法适用于full-batch,不适用于mini-batch,因而在mini-batch大行其道的时代里,很少见到。

4. torch.optim.Adagrad

CLASS torch.optim.Adagrad(params, lr=0.01, lr_decay=0, weight_decay=0, initial_accumulator_value=0)

功能:

实现Adagrad优化方法(Adaptive Gradient),Adagrad是一种自适应优化方法,是自适应的为各个参数分配不同的学习率。这个学习率的变化,会受到梯度的大小和迭代次数的影响。梯度越大,学习率越小;梯度越小,学习率越大。缺点是训练后期,学习率过小,因为Adagrad累加之前所有的梯度平方作为分母。

论文:《Adaptive Subgradient Methods for Online Learning and Stochastic Optimization》 John Duchi, Elad Hazan, Yoram Singer; 12(Jul):2121−2159, 2011

5. torch.optim.Adadelta

CLASS torch.optim.Adadelta(params, lr=1.0, rho=0.9, eps=1e-06, weight_decay=0)

功能:

实现 Adadelta 优化方法。Adadelta 是 Adagrad 的改进。Adadelta 分母中采用距离当前时间点比较近的累计项,这可以避免在训练后期,学习率过小。

论文: 《 ADADELTA: AN ADAPTIVE LEARNING RATE METHOD 》

6. torch.optim.RMSprop

CLASS torch.optim.RMSprop(params, lr=0.01, alpha=0.99, eps=1e-08, weight_decay=0, momentum=0, centered=False)

功能: 实现RMSprop优化方法(Hinton提出),RMS是均方根(root meam square)的意思。RMSprop和Adadelta一样,也是对Adagrad的一种改进。RMSprop采用均方根作为分母,可缓解Adagrad学习率下降较快的问题,并且引入均方根,可以减少摆动。

详细讲解:

7. torch.optim.Adam(AMSGrad)

CLASS torch.optim.SparseAdam(params, lr=0.001, betas=(0.9, 0.999), eps=1e-08)

功能:

实现Adam(Adaptive Moment Estimation))优化方法。Adam是一种自适应学习率的优化方法,Adam利用梯度的一阶矩估计和二阶矩估计动态的调整学习率。吴老师课上说过,Adam是结合了Momentum和RMSprop,并进行了偏差修正。

参数:

amsgrad:是否采用AMSGrad优化方法,asmgrad优化方法是针对Adam的改进,通过添加额外的约束,使学习率始终为正值。(AMSGrad,ICLR-2018 Best-Pper之一,《On the convergence of Adam and Beyond》)。

论文:《 Adam: A Method for Stochastic Optimization

8. torch.optim.Adamax

CLASS torch.optim.Adamax(params, lr=0.002, betas=(0.9, 0.999), eps=1e-08, weight_decay=0)

功能:

实现Adamax优化方法。Adamax是对Adam增加了一个学习率上限的概念,所以也称之为Adamax。具体依然可以阅读上面那篇论文。

9. torch.optim.SparseAdam

CLASS torch.optim.SparseAdam(params, lr=0.001, betas=(0.9, 0.999), eps=1e-08)

功能:

针对稀疏张量的一种“阉割版”Adam优化方法。

10. torch.optim.LBFGS

CLASS torch.optim.LBFGS(params, lr=1, max_iter=20, max_eval=None, tolerance_grad=1e-05, tolerance_change=1e-09, history_size=100, line_search_fn=None)

功能:

实现L-BFGS(Limited-memory Broyden–Fletcher–Goldfarb–Shanno)优化方法。L-BFGS属于拟牛顿算法。L-BFGS是对BFGS的改进,特点就是节省内存。

使用注意事项:

  1. 此优化器不支持每个参数选项和参数组(只能有一个)
  2. 现在所有参数都必须在一台设备上。这将在未来得到改善。

关于其他的优化器,详情请参考官方文档: https://pytorch.org/docs/stable/optim.html

展开阅读更多
来源:孟繁阳的博客
本篇文章来源于孟繁阳的博客,如需转载,请注明出处,谢谢配合。
孟繁阳的博客 » Pytorch学习笔记(五):优化器
Loading...

发表评论

表情
图片 链接 代码

孟繁阳的博客

博客首页 文章归档