自定义优化器
模型训练过程中,使用优化器更新网络参数,合适的优化器可以有效减少训练时间,提高模型性能。
最基本的优化器是随机梯度下降算法(SGD),很多优化器在SGD的基础上进行了改进,以实现目标函数能更快速更有效地收敛到全局最优点。MindSpore中的nn
模块提供了常用的优化器,如nn.SGD
、nn.Adam
、nn.Momentum
等。本章主要介绍如何配置MindSpore提供的优化器以及如何自定义优化器。
MindSpore提供的优化器详细内容参见优化器API。
nn.optim
配置优化器
使用MindSpore提供的优化器时,首先需要指定待优化的网络参数params
,然后设置优化器的其他主要参数,如学习率learning_rate
和权重衰减weight_decay
等。
若要为不同网络参数单独设置选项,如对卷积参数和非卷积参数设置不同的学习率,则可使用参数分组的方法来设置优化器。
参数配置
在构建优化器实例时,需要通过优化器参数params
配置模型网络中要训练和更新的权重。Parameter
中包含了一个requires_grad
的布尔型的类属性,用于表示模型中的网络参数是否需要进行更新。
网络中大部分参数的requires_grad
默认值为True,少部分默认值为False,例如BatchNorm中的moving_mean
和moving_variance
。
MindSpore中的trainable_params
方法会屏蔽掉Parameter
中requires_grad
为False的属性,在为优化器配置 params
入参时,可使用net.trainable_params()
方法来指定需要优化和更新的网络参数。
[48]:
import numpy as np
import mindspore
from mindspore import nn, ops
from mindspore import Tensor, Parameter
class Net(nn.Cell):
def __init__(self):
super().__init__()
self.conv = nn.Conv2d(1, 6, 5, pad_mode="valid")
self.param = Parameter(Tensor(np.array([1.0], np.float32)), 'param')
def construct(self, x):
x = self.conv(x)
x = x * self.param
out = ops.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)]
用户可以手动修改网络权重中 Parameter
的 requires_grad
属性的默认值,来决定哪些参数需要更新。
如下例所示,使用 net.get_parameters()
方法获取网络中所有参数,并手动修改巻积参数的 requires_grad
属性为False,训练过程中将只对非卷积参数进行更新。
[49]:
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,示例如下:
[50]:
# 设置学习率为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
为例:
[51]:
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
为例:
[52]:
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 = Tensor(i, mindspore.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,此时不使用权重衰减策略。
[53]:
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在训练过程中进行指数衰减的一个示例。
[54]:
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
def construct(self, global_step):
# construct只能有一个输入,训练过程中,会自动传入global step进行计算
p = global_step / self.decay_steps
return self.weight_decay * ops.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有params
、lr
、weight_decay
和grad_centralizaiton
,value为对应的设定值。
其中,params
必须配置,其余参数可选择性配置,未配置参数项将采用定义优化器时设置的参数值。分组时,学习率既可使用固定学习率,又可使用动态学习率,weight_decay
可使用固定值。
下例分别对卷积参数和非卷积参数设置不同的学习率和权重衰减参数。
[55]:
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算法):
其中,\(grad\) 、\(lr\) 、\(p\) 、\(v\) 和 \(u\) 分别表示梯度、学习率、权重参数、动量参数(Momentum)和初始速度。
[56]:
class Momentum(nn.Optimizer):
"""定义优化器"""
def __init__(self, params, learning_rate, momentum=0.9):
super(Momentum, self).__init__(learning_rate, params)
self.momentum = Parameter(Tensor(momentum, ms.float32), name="momentum")
self.moments = self.parameters.clone(prefix="moments", init="zeros")
def construct(self, gradients):
"""construct输入为梯度,在训练中自动传入梯度gradients"""
lr = self.get_lr()
params = self.parameters # 待更新的权重参数
for i in range(len(params)):
# 更新moments值
ops.assign(self.moments[i], self.moments[i] * self.momentum + gradients[i])
update = params[i] - self.moments[i] * lr #带有动量的SGD算法
ops.assign(params[i], update)
return params
net = Net()
# 设置优化器待优化的参数和学习率为0.01
opt = Momentum(net.trainable_params(), 0.01)
mindspore.ops
也封装了优化器算子供用户自行定义优化器,如ops.ApplyCenteredRMSProp
、 ops.ApplyMomentum
和ops.ApplyRMSProp
等。下例使用ApplyMomentum
算子自定义优化器Momentum:
[57]:
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)
experimental.optim
除上述 mindspore.nn.optim
模块内的优化器外,MindSpore也提供了实验性的优化器模块 mindspore.experimental.optim
,旨在对优化器做功能扩展。
mindspore.experimental.optim
模块仍在开发中,目前此模块的优化器只适用于函数式编程场景,且仅适配 mindspore.experimental.optim.lr_scheduler 下的动态学习率类。
使用差异:
参数 |
nn.optim |
experimental.optim |
功能 |
---|---|---|---|
参数配置(超参不分组) |
配置入参为 |
配置入参为 |
常规场景下配置及功能一致,传入 |
学习率 |
配置入参为 |
配置入参为 |
动态学习率场景下配置及功能不同,详见动态学习率 |
权重衰减 |
配置入参为 |
配置入参为 |
动态weight_decay场景下配置不同,详见权重衰减 |
超参分组 |
配置入参为 |
配置入参为 |
分组场景下,即当 |
除上述异同外,mindspore.experimental.optim
下的优化器还支持查看参数组、运行中修改优化器参数等功能,详见下文。
配置优化器
参数配置
常规场景下,与 mindspore.nn.optim
的参数配置方式相同,传入 net.trainable_params
即可。
学习率
固定学习率:
与 mindspore.nn.optim
的固定学习率配置方式相同。
动态学习率:
mindspore.experimental.optim.lr_scheduler
下提供了动态学习率模块与 mindspore.experimental.optim
配合使用,使用方式与 mindspore.nn.optim
不同:
mindspore.nn.optim
:将动态学习率列表或实例传给优化器的入参 learning_rate
,使用方式请参考DynamicLR函数和LearningRateSchedule类。
mindspore.experimental.optim
:将优化器实例传给动态学习率类的入参 optimizer
,使用方式请参考LRScheduler类。
LRScheduler
提供的获取学习率的方式:
get_lr
方法。以 StepLR
为例,训练过程中可以直接使用scheduler.get_lr()
手动获取学习率。
[58]:
from mindspore.experimental import optim
net = Net()
optimizer = optim.Adam(net.trainable_params(), lr=0.1)
scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=30, gamma=0.1)
print(scheduler.get_last_lr())
[Tensor(shape=[], dtype=Float32, value= 0.1)]
权重衰减
mindspore.nn.optim
:weight_decay
除支持int和float类型外,也支持Cell类型用于动态weight_decay场景。
mindspore.experimental.optim
:weight_decay
数据类型仅支持int和float类型,但支持用户在PyNative模式下手动修改weight_decay的值。
超参分组
mindspore.nn.optim
:支持特定key分组:“params”、“lr”、“weight_decay”和“grad_centralizaiton”,具体使用方式详见上文。
mindspore.experimental.optim
:支持所有优化器参数分组。
代码样例:
[60]:
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()))
group_params = [
{'params': conv_params, 'weight_decay': 0.01, 'lr': 0.9, "amsgrad": True},
{'params': no_conv_params, 'lr': 0.66, "eps": 1e-6, "betas": (0.8, 0.88)}]
optimizer = optim.Adam(params=group_params, lr=0.01)
查看优化器配置
使用 ``param_group`` 属性查看参数组:
代码样例:
[61]:
print(optimizer.param_groups)
[{'params': [Parameter (name=conv.weight, shape=(6, 1, 5, 5), dtype=Float32, requires_grad=True)], 'weight_decay': 0.01, 'lr': Parameter (name=learning_rate_group_0, shape=(), dtype=Float32, requires_grad=True), 'amsgrad': True, 'betas': (0.9, 0.999), 'eps': 1e-08, 'maximize': False}, {'params': [Parameter (name=param, shape=(1,), dtype=Float32, requires_grad=True)], 'lr': Parameter (name=learning_rate_group_1, shape=(), dtype=Float32, requires_grad=True), 'eps': 1e-06, 'betas': (0.8, 0.88), 'weight_decay': 0.0, 'amsgrad': False, 'maximize': False}]
从上述输出中可以发现,优化器参数组中学习率为 Parameter
,mindspore中的 Parameter
不显示参数值,可以通过使用 .value()
的方式查看参数值。也可以使用上述动态学习率模块 mindspore.experimental.optim.lr_scheduler.LRScheduler
的 get_last_lr
方法获取。
[62]:
print(optimizer.param_groups[1]["lr"].value())
0.66
直接打印优化器实例查看参数组:
[63]:
print(optimizer)
Adam (
Parameter Group 0
amsgrad: True
betas: (0.9, 0.999)
eps: 1e-08
lr: 0.9
maximize: False
weight_decay: 0.01
Parameter Group 1
amsgrad: False
betas: (0.8, 0.88)
eps: 1e-06
lr: 0.66
maximize: False
weight_decay: 0.0
)
运行中修改优化器参数
运行中修改学习率
mindspore.experimental.optim.Optimizer
中学习率为 Parameter
,除通过上述动态学习率模块 mindspore.experimental.optim.lr_scheduler
动态修改学习率,也支持使用 assign
赋值的方式修改学习率。
例如下述样例,在训练step中,设置如果损失值相比上一个step变化小于0.1,将优化器第1个参数组的学习率调整至0.01:
[64]:
net = Net()
loss_fn = nn.MAELoss()
optimizer = optim.Adam(net.trainable_params(), lr=0.1)
scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.5)
last_step_loss = 0.1
def forward_fn(data, label):
logits = net(data)
loss = loss_fn(logits, label)
return loss
grad_fn = mindspore.value_and_grad(forward_fn, None, optimizer.parameters, has_aux=True)
def train_step(data, label):
(loss, _), grads = grad_fn(data, label)
optimizer(grads)
if ops.abs(loss - last_step_loss) < 0.1:
ops.assign(optimizer.param_groups[1]["lr"], Tensor(0.01))
return loss
运行中修改除lr以外的优化器参数
目前仅PyNative模式下支持运行中修改其他优化器参数,Graph模式下的修改将不生效或报错。
下述样例,在训练step中,设置如果损失值相比上一个step变化小于0.1,将优化器第1个参数组的 weight_decay
调整至0.02:
[65]:
net = Net()
loss_fn = nn.MAELoss()
optimizer = optim.Adam(net.trainable_params(), lr=0.1)
scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.5)
last_step_loss = 0.1
def forward_fn(data, label):
logits = net(data)
loss = loss_fn(logits, label)
return loss
grad_fn = mindspore.value_and_grad(forward_fn, None, optimizer.parameters, has_aux=True)
def train_step(data, label):
(loss, _), grads = grad_fn(data, label)
optimizer(grads)
if ops.abs(loss - last_step_loss) < 0.1:
optimizer.param_groups[1]["weight_decay"] = 0.02
return loss
自定义优化器
与上述自定义优化器方式相同,自定义优化器时也可以继承优化器基类experimental.optim.Optimizer,并重写__init__
方法和construct
方法以自行设定参数更新策略。