1、Adam优化器简介

Adam 是一种可以替代传统随机梯度下降过程的一阶优化算法,它能基于训练数据迭代地更新神经网络权重。Adam 最开始是由 OpenAI 的 Diederik Kingma 和多伦多大学的 Jimmy Ba 在提交到 2015 年 ICLR 论文(Adam: A Method for Stochastic Optimization)中提出的。该算法名为「Adam」,其并不是首字母缩写,也不是人名,它的名称来源于适应性矩估计(adaptive moment estimation)。

2、Adam提出动机

在 Adam 提出之前,人们已经在使用像 Momentum 和 RMSProp 这样的优化算法来加速深度神经网络的训练过程。然而,这些算法各有优势和局限。Momentum 善于处理梯度的方向和大小,而 RMSProp 善于调整学习率以应对数据的稀疏性。Adam 的提出是为了结合这两种算法的优点,同时减少它们的缺点,提供一种更加鲁棒的优化解决方案。

3、Adam使用过程

首先,我们要定义一个神经网络,如下所示:

class MyModel(nn.Module):
    def __init__(self):
        super().__init__()
        self.nn1 = nn.Linear(2, 3)
        self.nn2 = nn.Linear(3, 6)

    def forward(self, x):
        x = F.relu(self.nn1(x))
        return F.relu(self.nn2(x))

接着,我们可以通过model.named_parameters()函数查看一下这个网络内部的权重的名称和内部参数是什么?

model = MyModel()
for name, param in model.named_parameters():
    print(name)
    print('-' * 24)
    print(param)
    print('=' * 24)

结果如下所示:

nn1.weight
------------------------
Parameter containing:
tensor([[ 0.4568,  0.2095],
        [-0.6798,  0.1150],
        [-0.6007, -0.0654]], requires_grad=True)
========================
nn1.bias
------------------------
Parameter containing:
tensor([-0.2651,  0.1305,  0.4654], requires_grad=True)
========================
nn2.weight
------------------------
Parameter containing:
tensor([[-0.1951, -0.1204,  0.1378],
        [-0.1610, -0.4839, -0.0099],
        [ 0.3376, -0.1957,  0.5075],
        [-0.1851, -0.2538, -0.0336],
        [ 0.5438, -0.1405, -0.1241],
        [ 0.4294,  0.0081, -0.2110]], requires_grad=True)
========================
nn2.bias
------------------------
Parameter containing:
tensor([-0.5548,  0.1145,  0.1029, -0.2623, -0.0360, -0.0181],
       requires_grad=True)
========================

最后,可以针对上面的权重参数,设置Adam优化器进行迭代更新:

optimizer_Adam = torch.optim.Adam(model.parameters(), lr=0.1)

至此,我们完成了Adam优化器的设置,下一步可以在训练中使用它进行权重参数的更新了。优化器的固定用法如下:

loss.backward()
optimizer.step()
optimizer.zero_grad()

其中backward()是反向计算各参数梯度,step()表示更新网络参数,zero_grad()表示清空当前的梯度更新,防止影响下一轮更新。

4、Adam进阶使用

(1)只训练模型的一部分参数,排除bias层训练

param_optimizer = list(model.named_parameters())
no_decay = ['nn1.bias', 'nn2.bias']
param_optimizer2 = [p for n, p in param_optimizer if n not in no_decay]
optimizer_grouped_parameters = [{'params': param_optimizer2, 'weight_decay': 0.01}]
optimizer_Adam = torch.optim.Adam(optimizer_grouped_parameters, lr=0.1)

(2)不同部分的参数设置不同的学习率。

optimizer_grouped_parameters = [{'params': model.nn1.parameters(), 'lr': 0.1},{'params': model.nn2.parameters(), 'lr': 0.2}]
optimizer_Adam = torch.optim.Adam(optimizer_grouped_parameters)

(3)根据迭代次数,手动设置学习率。

optimizer_Adam = torch.optim.Adam(model.parameters(), lr=0.1)
for epoch in range(100):    
    if epoch % 5 == 0:        
        for params in optimizer_Adam.param_groups:             
            # 遍历Optimizer中的每一组参数,将该组参数的学习率 * 0.9            
            params['lr'] *= 0.9            

备注:Optimizer优化器的底层介绍

优化器在构建完成后,具备一个param_groups列表,这个列表包含了哪些变量需要更新,这些变量更新的学习率如何,这个属性列表可通过param_groups()方法获取。

param_groups本质就是一个List列表,它的每个元素都是一组独立的参数,以dict的方式进行存储,结构参见下图:

-param_groups    
    -0(dict)  # 第一组参数        
        params:        # 维护要更新的参数        
        lr:            # 该组参数的学习率               
        weight_decay:  # 该组参数的权重衰减系数     
    -1(dict)  # 第二组参数    
    -2(dict)  # 第三组参数    
    ...

注意,每个dict中会因为optimizer的不同,参数有所差别,比如Adam optimizer中存在betas参数,而SGD Optimizer中存在moumentum参数。下面可以看看本例子的Adam优化器的内部参数:

[
    {

        'params':   [Parameter containing:
                    tensor([[-0.0386,  0.3979],
                            [ 0.2451, -0.5477],
                            [ 0.2848, -0.6663]], requires_grad=True), Parameter containing:
                    tensor([-0.6260,  0.6027,  0.0412], requires_grad=True), Parameter containing:
                    tensor([[-0.2931, -0.3993,  0.1601],
                            [ 0.1608,  0.1821,  0.4538],
                            [ 0.3516, -0.4239, -0.5256],
                            [ 0.4598,  0.1838, -0.4019],
                            [-0.4469,  0.4455,  0.1316],
                            [-0.1232,  0.3769, -0.1184]], requires_grad=True), Parameter containing:
                    tensor([ 0.1404, -0.0542, -0.0085,  0.0995,  0.3741, -0.0223],
                           requires_grad=True)], 
       'lr': 0.1, 
       'betas': (0.9, 0.999), 
       'eps': 1e-08, 
       'weight_decay': 0, 
       'amsgrad': False, 
       'maximize': False, 
       'foreach': None, 
       'capturable': False, 
       'differentiable': False, 
       'fused': None
       
    }
       
]