优化器

Ascend GPU CPU 模型开发

在线运行下载Notebook下载样例代码查看源文件

概述

优化器在模型训练过程中,用于计算和更新网络参数,合适的优化器可以有效减少训练时间,提高最终模型性能。最基本的优化器是梯度下降(SGD),在此基础上,很多其他的优化器进行了改进,以实现目标函数能更快速更有效地收敛到全局最优点。

mindspore.nn.optim是MindSpore框架中实现各种优化算法的模块,包含常用的优化器、学习率等,接口具备较好的通用性,可以将以后更新、更复杂的方法集成到模块里。mindspore.nn.optim为模型提供常用的优化器,如mindspore.nn.SGDmindspore.nn.Adammindspore.nn.Ftrlmindspore.nn.LazyAdammindspore.nn.Momentummindspore.nn.RMSPropmindspore.nn.LARSmindspore.nn.ProximalAadagradmindspore.nn.Lamb等。同时mindspore.nn提供了动态学习率的模块,分为dynamic_lrlearning_rate_schedule,学习率的灵活设置可以有效支撑目标函数的收敛和模型的训练。

使用mindspore.nn.optim时,我们需要构建一个Optimizer实例。这个实例能够保持当前参数状态并基于计算得到的梯度进行参数更新。为了构建一个Optimizer,要指定需要优化的网络权重(必须是Parameter实例)的iterable,然后设置Optimizer的参数选项,比如学习率,权重衰减等。

以下内容分别从权重学习率、权重衰减、参数分组、混合精度等方面的配置分别进行详细介绍。

权重配置

在构建Optimizer实例时,通过params配置模型网络中要训练和更新的权重。params必须配置,常见的配置方法有以下两种。

使用Cell的网络权重获取函数

Parameter类中包含了一个requires_grad的布尔型的类属性,表征了模型网络中的权重是否需要梯度来进行更新(详情可参考:https://gitee.com/mindspore/mindspore/blob/r1.6/mindspore/python/mindspore/common/parameter.py )。其中大部分权重的requires_grad的默认值都为True;少数默认为False,例如BatchNormalize中的moving_meanmoving_variance。用户可以根据需要,自行对requires_grad的值进行修改。

MindSpore提供了get_parameters方法来获取模型网络中所有权重,该方法返回了Parameter类型的网络权重;trainable_params方法本质是一个filter,过滤了requires grad=TrueParameter。用户在构建优化器时,可以通过配置paramsnet.trainable_params()来指定需要优化和更新的权重。

代码样例如下:

[1]:
import numpy as np
import mindspore.ops as ops
from mindspore import nn, Model, Tensor, Parameter

class Net(nn.Cell):
    def __init__(self):
        super(Net, self).__init__()
        self.matmul = ops.MatMul()
        self.conv = nn.Conv2d(1, 6, 5, pad_mode="valid")
        self.param = Parameter(Tensor(np.array([1.0], np.float32)))

    def construct(self, x):
        x = self.conv(x)
        x = x * self.param
        out = self.matmul(x, x)
        return out

net = Net()
optim = nn.Adam(params=net.trainable_params())

自定义筛选

用户也可以设定筛选条件,在使用get_parameters获取到网络全部参数后,通过限定参数名字等方法,自定义filter来决定哪些参数需要更新。例如下面的例子,训练过程中将只对非卷积参数进行更新:

[2]:
from mindspore import nn

params_all = net.get_parameters()
no_conv_params = list(filter(lambda x: 'conv' not in x.name, params_all))
optim = nn.Adam(params=no_conv_params, learning_rate=0.1, weight_decay=0.0)

学习率

学习率作为机器学习及深度学习中常见的超参,对目标函数能否收敛到局部最小值及何时收敛到最小值有重要作用。学习率过大容易导致目标函数波动较大,难以收敛到最优值,太小则会导致收敛过程耗时长,除了基本的固定值设置,很多动态学习率的设置方法也在深度网络的训练中取得了显著的效果。

固定学习率

使用固定学习率时,优化器传入的learning_rate为浮点类型或标量Tensor。

以Momentum为例,固定学习率为0.01,用法如下:

[3]:
from mindspore import nn

net = Net()
optim = nn.Momentum(params=net.trainable_params(), learning_rate=0.01, momentum=0.9)
loss = nn.SoftmaxCrossEntropyWithLogits()
model = Model(net, loss_fn=loss, optimizer=optim)

动态学习率

模块提供了动态学习率的两种不同的实现方式,dynamic_lrlearning_rate_schedule

  • dynamic_lr: 预生成长度为total_step的学习率列表,将列表传入优化器中使用, 训练过程中, 第i步使用第i个学习率的值作为当前step的学习率,其中,total_step的设置值不能小于训练的总步数;

  • learning_rate_schedule: 优化器学习率指定一个LearningRateSchedule的Cell实例,学习率会和训练网络一起组成计算图,在执行过程中,根据step计算出当前学习率。

预生成学习率列表

本模块中的动态学习率为函数,配合优化器使用时,调用函数并将结果列表lr传递给优化器。在训练过程中,优化器将 lr[current_step]作为当前学习率。

mindspore.nn.dynamic_lr支持以下不同的实现方式:

  • piecewise_constant_lr:基于得到分段不变的学习速率。

  • exponential_decay_lr:基于指数衰减函数计算学习率。

  • natural_exp_decay_lr:基于自然指数衰减函数计算学习率。

  • inverse_decay_lr:基于反时间衰减函数计算学习速率。

  • cosine_decay_lr:基于余弦衰减函数计算学习率。

  • polynomial_decay_lr:基于多项式衰减函数计算学习率。

  • warmup_lr:提高学习率。

piecewise_constant_lr为例:

[4]:
from mindspore import nn

milestone = [2, 5, 10]
learning_rates = [0.1, 0.05, 0.01]
lr = nn.dynamic_lr.piecewise_constant_lr(milestone, learning_rates)
print(lr)

net = Net()
optim = nn.SGD(net.trainable_params(), learning_rate=lr)
[0.1, 0.1, 0.05, 0.05, 0.05, 0.01, 0.01, 0.01, 0.01, 0.01]

定义学习率计算图

本模块中的动态学习率为LearningRateSchedule 的子类,配合优化器使用时,将对应的LearningRateSchedule子类的实例传递给优化器。在训练过程中,优化器调用以current_step为输入的实例,以获得当前的学习率。

mindspore.nn.learning_rate_schedule模块包含以下实现方式:

  • ExponentialDecayLR:基于指数衰减函数计算学习率。

  • NaturalExpDecayLR:基于自然指数衰减函数计算学习率。

  • InverseDecayLR:基于反时间衰减函数计算学习速率。

  • CosineDecayLR:基于余弦衰减函数计算学习率。

  • PolynomialDecayLR:基于多项式衰减函数计算学习率。

  • WarmUpLR:提高学习率。

ExponentialDecayLR为例:

ExponentialDecayLR仅支持GPU和Ascend后端。

[5]:
from mindspore import nn
from mindspore import Tensor

polynomial_decay_lr = nn.learning_rate_schedule.PolynomialDecayLR(learning_rate=0.1,
                                                                  end_learning_rate=0.01,
                                                                  decay_steps=4,
                                                                  power=0.5)

net = Net()
optim = nn.SGD(net.trainable_params(), learning_rate=polynomial_decay_lr)

权重衰减

一般情况下,weight_decay取值范围为[0, 1),实现对(BatchNorm以外的)参数使用权重衰减的策略,以避免模型过拟合问题;weight_decay的默认值为0.0,此时不使用权重衰减策略。

[6]:
net = Net()
optimizer = nn.Momentum(net.trainable_params(), learning_rate=0.01, momentum=0.9, weight_decay=0.9)

参数分组

优化器也支持为不同参数单独设置选项,此时不直接传入变量,而是传入一个字典的列表,每个字典定义一个参数组别的设置值,key可以为“params”,“lr”,“weight_decay”,”grad_centralizaiton”,value为对应的设定值。params必须配置,其余参数可以选择配置,未配置的参数项,将采用定义优化器时设置的参数值。

分组时,学习率可以使用固定学习率,也可以使用dynamic_lr和learningrate_schedule动态学习率。

当前MindSpore除个别优化器外(例如AdaFactor,FTRL),均支持对学习率进行分组,详情参考各优化器的说明。

例如下面的例子:

  • conv_params组别的参数,使用固定学习率0.01, weight_decay为字典传入的数值0.01;

  • no_conv_params组别使用learning_rate_schedule的动态学习率PolynomialDecayLRweight_decay使用优化器配置的值0.0;

  • group_params还提供了order_params配置项;一般情况下order_params无需配置。group_params根据分组情况会改变parameter和梯度的计算顺序。如果使用自动并行策略,并通过set_all_reduce_fusion_split_indices配置了梯度更新的切分点,group_params引起的顺序变化会影响梯度广播的并行效果,此时可以通过order_params指定预期的参数顺序,例如指定为net.trainable_params(),使参数顺序与网络中定义权重的原始顺序保持一致。

[7]:
from mindspore import nn

net = Net()

# Use parameter groups and set different values
conv_params = list(filter(lambda x: 'conv' in x.name, net.trainable_params()))
no_conv_params = list(filter(lambda x: 'conv' not in x.name, net.trainable_params()))

fix_lr = 0.01
polynomial_decay_lr = nn.learning_rate_schedule.PolynomialDecayLR(learning_rate=0.1,
                                                                  end_learning_rate=0.01,
                                                                  decay_steps=4,
                                                                  power=0.5)

group_params = [{'params': conv_params, 'weight_decay': 0.01, 'lr': fix_lr},
                {'params': no_conv_params, 'lr': polynomial_decay_lr},
                {'order_params': net.trainable_params()}]

optim = nn.Momentum(group_params, learning_rate=0.1, momentum=0.9, weight_decay=0.0)

混合精度

深度神经网络存在使用混合精度训练的场景,这种方法通过混合使用单精度和半精度数据格式来加速网络训练,同时保持了单精度训练所能达到的网络精度。混合精度训练能够加速计算过程,减少内存使用和存取,并使得在特定的硬件上可以训练更大的模型或batch size。

在混合精度训练过程中,会使用float16类型来替代float32类型存储数据,但由于float16类型数据比float32类型数据范围小很多,所以当某些参数(例如梯度)在训练过程中变得很小时,就会发生数据下溢。为避免半精度float16类型数据下溢,MindSpore提供了FixedLossScaleManagerDynamicLossScaleManager方法。其主要思想是计算loss时,将loss扩大一定的倍数,由于链式法则的存在,梯度也会相应扩大,然后在优化器更新权重时再缩小相应的倍数,从而避免了数据下溢的情况又不影响计算结果。

一般情况下优化器不需要与LossScale功能配合使用,但使用FixedLossScaleManager,并且drop_overflow_update为False时,优化器需设置loss_scale的值,且loss_scale值与FixedLossScaleManager的相同,具体用法详见:https://www.mindspore.cn/docs/programming_guide/zh-CN/r1.6/lossscale.html