优化器

下载Notebook下载样例代码查看源文件

模型训练过程中,使用优化器计算梯度并更新网络参数,合适的优化器可以有效减少训练时间,提高模型性能。

最基本的优化器是随机梯度下降算法(SGD),很多优化器在SGD的基础上进行了改进,以实现目标函数能更快速更有效地收敛到全局最优点。MindSpore中的nn模块提供了常用的优化器,如nn.SGDnn.Adamnn.Momentum等。本章主要介绍如何配置MindSpore提供的优化器以及如何自定义优化器。

learningrate.png

MindSpore提供的优化器详细内容参见优化器API

配置优化器

使用MindSpore提供的优化器时,首先需要指定待优化的网络参数params,然后设置优化器的其他主要参数,如学习率learning_rate和权重衰减weight_decay等。

若要为不同网络参数单独设置选项,如对卷积参数和非卷积参数设置不同的学习率,则可使用参数分组的方法来设置优化器。

参数配置

在构建优化器实例时,需要通过优化器参数params配置模型网络中要训练和更新的权重。

Parameter中包含了一个requires_grad的布尔型的类属性,用于表示模型中的网络参数是否需要进行更新,网络中大部分参数的requires_grad 默认值为True,少部分默认值为False,例如BatchNorm中的 moving_meanmoving_variance

MindSpore中的trainable_params方法会屏蔽掉Parameterrequires_grad为False的属性,在为优化器配置 params 入参时,可使用net.trainable_params()方法来指定需要优化和更新的网络参数。

[1]:
import numpy as np
import mindspore.ops as ops
from mindspore import nn
import mindspore as ms

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 = ms.Parameter(ms.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())
print(net.trainable_params())
[Parameter (name=param, shape=(1,), dtype=Float32, requires_grad=True), Parameter (name=conv.weight, shape=(6, 1, 5, 5), dtype=Float32, requires_grad=True)]

用户可以手动修改网络权重中 Parameterrequires_grad 属性的默认值,来决定哪些参数需要更新。

如下例所示,使用 net.get_parameters() 方法获取网络中所有参数,并手动修改巻积参数的 requires_grad 属性为False,训练过程中将只对非卷积参数进行更新。

[2]:
conv_params = [param for param in net.get_parameters() if 'conv' in param.name]
for conv_param in conv_params:
    conv_param.requires_grad = False
print(net.trainable_params())
optim = nn.Adam(params=net.trainable_params())
[Parameter (name=param, shape=(1,), dtype=Float32, requires_grad=True)]

学习率

学习率作为机器学习及深度学习中常见的超参,对目标函数能否收敛到局部最小值及何时收敛到最小值有重要影响。学习率过大容易导致目标函数波动较大,难以收敛到最优值,太小则会导致收敛过程耗时过长。除了设置固定学习率,MindSpore还支持设置动态学习率,这些方法在深度学习网络中能明显提升收敛效率。

固定学习率

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

nn.Momentum为例,固定学习率为0.01,示例如下:

[3]:
# 设置学习率为0.01
optim = nn.Momentum(params=net.trainable_params(), learning_rate=0.01, momentum=0.9)

动态学习率

mindspore.nn提供了动态学习率的模块,分为Dynamic LR函数和LearningRateSchedule类。其中Dynamic LR函数会预先生成长度为total_step的学习率列表,将列表传入优化器中使用,训练过程中,第i步使用第i个学习率的值作为当前step的学习率,其中total_step的设置值不能小于训练的总步数;LearningRateSchedule类将实例传递给优化器,优化器根据当前step计算得到当前的学习率。

  • Dynamic LR函数

Dynamic LR函数目前有基于余弦衰减函数计算学习率(nn.cosine_decay_lr)、基于指数衰减函数计算学习率(nn.exponential_decay_lr)、基于逆时衰减函数计算学习率(nn.inverse_decay_lr)、基于自然指数衰减函数计算学习率(nn.natural_exp_decay_lr)、获取分段常量学习率(nn.piecewise_constant_lr)、基于多项式衰减函数计算学习率(nn.polynomial_decay_lr)和预热学习率(nn.warmup_lr)。

下例以分段常量学习率nn.piecewise_constant_lr为例:

[4]:
from mindspore import nn

milestone = [1, 3, 10]
learning_rates = [0.1, 0.05, 0.01]
lr = nn.piecewise_constant_lr(milestone, learning_rates)

# 打印学习率
print(lr)

net = Net()
# 优化器设置待优化的网络参数和分段常量学习率
optim = nn.SGD(net.trainable_params(), learning_rate=lr)
[0.1, 0.05, 0.05, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]
  • LearningRateSchedule类

LearningRateSchedule类目前有基于余弦衰减函数计算学习率(nn.CosineDecayLR)、基于指数衰减函数计算学习率(nn.ExponentialDecayLR)、基于逆时衰减函数计算学习率(nn.InverseDecayLR)、基于自然指数衰减函数计算学习率(nn.NaturalExpDecayLR)、基于多项式衰减函数计算学习率(nn.PolynomialDecayLR)和预热学习率(nn.WarmUpLR)。

下例基于指数衰减函数计算学习率nn.ExponentialDecayLR为例:

[5]:
import mindspore as ms

learning_rate = 0.1  # 学习率的初始值
decay_rate = 0.9     # 衰减率
decay_steps = 4      # 衰减的step数
step_per_epoch = 2

exponential_decay_lr = nn.ExponentialDecayLR(learning_rate, decay_rate, decay_steps)

for i in range(decay_steps):
    step = ms.Tensor(i, ms.int32)
    result = exponential_decay_lr(step)
    print(f"step{i+1}, lr:{result}")

net = Net()

# 优化器设置学习率为基于指数衰减函数计算学习率
optim = nn.Momentum(net.trainable_params(), learning_rate=exponential_decay_lr, momentum=0.9)
step1, lr:0.1
step2, lr:0.097400375
step3, lr:0.094868325
step4, lr:0.09240211

权重衰减

权重衰减(weight decay),通常也被称为L2正则化,是一种缓解深度神经网络过拟合的方法。

一般情况下,weight_decay取值范围为\([0, 1)\),其默认值为0.0,此时不使用权重衰减策略。

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

此外,MindSpore还支持动态weight decay。此时weight_decay是用户自定义的一个Cell,称之为weight_decay_schedule。在训练过程中,优化器内部会调用该Cell的实例,传入global_step计算当前step的weight_decay值。其中global_step是内部维护的变量,每训练一个step,global_step都会自加1。注意,自定义的weight_decay_schedule的construct仅接收一个输入。如下是weight_decay在训练过程中进行指数衰减的一个示例。

[ ]:
from mindspore.nn import Cell
from mindspore import ops, nn
import mindspore as ms

class ExponentialWeightDecay(Cell):

    def __init__(self, weight_decay, decay_rate, decay_steps):
        super(ExponentialWeightDecay, self).__init__()
        self.weight_decay = weight_decay
        self.decay_rate = decay_rate
        self.decay_steps = decay_steps
        self.pow = ops.Pow()
        self.cast = ops.Cast()

    def construct(self, global_step):
        # construct只能有一个输入,训练过程中,会自动传入global step进行计算
        p = self.cast(global_step, ms.float32) / self.decay_steps
        return self.weight_decay * self.pow(self.decay_rate, p)

net = Net()

weight_decay = ExponentialWeightDecay(weight_decay=0.0001, decay_rate=0.1, decay_steps=10000)
optimizer = nn.Momentum(net.trainable_params(), learning_rate=0.01,
                        momentum=0.9, weight_decay=weight_decay)

超参分组

优化器也支持为不同参数单独设置选项,此时不直接传入变量,而是传入一个字典列表,每个字典都对应一组参数的设置值,字典内可用的key有paramslrweight_decaygrad_centralizaiton,value为对应的设定值。

其中,params必须配置,其余参数可选择性配置,未配置参数项将采用定义优化器时设置的参数值。分组时,学习率既可使用固定学习率,又可使用动态学习率,weight_decay可使用固定值。

下例分别对卷积参数和非卷积参数设置不同的学习率和权重衰减参数。

[7]:
net = Net()

# 卷积参数
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.PolynomialDecayLR(learning_rate=0.1,      # 学习率初始值
                                           end_learning_rate=0.01, # 学习率最终值
                                           decay_steps=4,          # 衰减的step数
                                           power=0.5)              # 多项式幂

# 卷积参数使用固定学习率0.001,权重衰减为0.01
# 非卷积参数使用动态学习率,权重衰减为0.0
group_params = [{'params': conv_params, 'weight_decay': 0.01, 'lr': fix_lr},
                {'params': no_conv_params, 'lr': polynomial_decay_lr}]

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

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

自定义优化器

除使用MindSpore提供的优化器外,用户还可自定义优化器。

自定义优化器时需继承优化器基类nn.Optimizer,并重写__init__方法和construct方法以自行设定参数更新策略。

下例实现自定义优化器Momentum(带有动量的SGD算法):

\[v_{t+1} = v_t×u+grad \tag{1}\]
\[p_{t+1} = p_t - lr*v_{t+1} \tag{2}\]

其中,\(grad\)\(lr\)\(p\)\(v\)\(u\) 分别表示梯度、学习率、权重参数、动量参数(Momentum)和初始速度。

[8]:
import mindspore as ms
from mindspore import nn, ops

class Momentum(nn.Optimizer):
    """定义优化器"""
    def __init__(self, params, learning_rate, momentum=0.9):
        super(Momentum, self).__init__(learning_rate, params)
        self.momentum = ms.Parameter(ms.Tensor(momentum, ms.float32), name="momentum")
        self.moments = self.parameters.clone(prefix="moments", init="zeros")
        self.assign = ops.Assign()

    def construct(self, gradients):
        """construct输入为梯度,在训练中自动传入梯度gradients"""
        lr = self.get_lr()
        params = self.parameters # 待更新的权重参数

        for i in range(len(params)):
            # 更新moments值
            self.assign(self.moments[i], self.moments[i] * self.momentum + gradients[i])
            update = params[i] - self.moments[i] * lr  #带有动量的SGD算法
            self.assign(params[i], update)
        return params

net = Net()
# 设置优化器待优化的参数和学习率为0.01
opt = Momentum(net.trainable_params(), 0.01)

mindSpore.ops也封装了优化器算子供用户自行定义优化器,如ops.ApplyCenteredRMSPropops.ApplyMomentumops.ApplyRMSProp等。下例使用ApplyMomentum算子自定义优化器Momentum:

[9]:
class Momentum(nn.Optimizer):
    """定义优化器"""
    def __init__(self, params, learning_rate, momentum=0.9):
        super(Momentum, self).__init__(learning_rate, params)
        self.moments = self.parameters.clone(prefix="moments", init="zeros")
        self.momentum = momentum
        self.opt = ops.ApplyMomentum()

    def construct(self, gradients):
        # 待更新的权重参数
        params = self.parameters
        success = None
        for param, mom, grad in zip(params, self.moments, gradients):
            success = self.opt(param, mom, self.learning_rate, grad, self.momentum)
        return success

net = Net()
# 设置优化器待优化的参数和学习率为0.01
opt = Momentum(net.trainable_params(), 0.01)