在阅读本章节之前,请先阅读MindSpore官网教程优化器。
这里就MindSpore的优化器的一些特殊使用方式和学习率衰减策略的原理做一个介绍。
PyTorch和MindSpore同时支持的优化器异同比较详见API映射表。MindSpore暂不支持的优化器:LBFGS,NAdam,RAdam。
PyTorch单步执行优化器时,一般需要手动执行 方法将历史梯度设置为0(或None),然后使用 计算当前训练step的梯度,最后调用优化器的 方法实现网络权重的更新;
MindSpore中优化器的使用,只需要直接对梯度进行计算,然后使用 执行网络权重的更新。
PyTorch | MindSpore |
|
|
超参名称?
网络权重和学习率入参名称异同:
参数 |
PyTorch |
MindSpore |
差异 |
---|---|---|---|
网络权重 |
params |
params |
参数名相同 |
学习率 |
lr |
learning_rate |
参数名不同 |
PyTorch | MindSpore |
|
|
超参配置方式?
参数不分组:
入参支持类型不同: PyTorch入参类型为 和 ,支持迭代器类型; MindSpore入参类型为 ,,不支持迭代器。
其他超参配置及支持差异详见API映射表。
参数分组:
PyTorch支持所有参数分组;MindSpore仅支持特定key分组:”params”,”lr”,”weight_decay”,”grad_centralization”,”order_params”。
PyTorch MindSpore optim.SGD([ {'params': model.base.parameters()}, {'params': model.classifier.parameters(), 'lr': 1e-3} ], lr=1e-2, momentum=0.9)
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.02}, {'params': no_conv_params}] optim = nn.Momentum(group_params, learning_rate=0.1, momentum=0.9)
运行时超参修改?
PyTorch支持在训练过程中修改任意的优化器参数,并提供了 用于动态修改学习率;
MindSpore当前不支持训练过程中修改优化器参数,但提供了修改学习率和权重衰减的方式,使用方式详见学习率和权重衰减章节。
PyTorch中修改 示例如下;
MindSpore中实现动态weight decay:用户可以继承 自定义动态weight decay的类,传入优化器中。
PyTorch | MindSpore |
|
|
PyTorch的优化器模块提供了 用于优化器状态的查看及保存, 用于优化器状态的加载。
MindSpore的优化器模块继承自 ,优化器的保存与加载和网络的保存与加载方式相同,通常情况下配合 与 使用。
PyTorch | MindSpore |
|
|
PyTorch中定义了 类用于对学习率进行管理。使用动态学习率时,将 实例传入 子类中,通过循环调用 执行学习率修改,并将修改同步至优化器中。
MindSpore中的动态学习率有 和 两种实现方式,两种类型的动态学习率使用方式一致,都是在实例化完成之后传入优化器,前者在内部的 中进行每一步学习率的计算,后者直接按照计算逻辑预生成学习率列表,训练过程中内部实现学习率的更新。具体请参考动态学习率。
PyTorch | MindSpore |
|
|
PyTorch的动态学习率模块 提供了 接口供用户自定义学习率调整规则,用户通过传入lambda表达式或自定义函数实现学习率指定。
MindSpore未提供类似的lambda接口,自定义学习率调整策略可以通过自定义函数或自定义 来实现。
PyTorch | MindSpore |
|
|
PyTorch:
固定学习率情况下,通常通过 进行学习率的查看和打印,例如参数分组时,对于第n个参数组,使用 ,参数不分组时,使用 ;
动态学习率情况下,可以使用 的 方法获取当前学习率,或使用 方法打印学习率。
MindSpore:
目前未提供直接查看学习率的接口,后续版本中会针对此问题进行修复。
PyTorch:
PyTorch提供了包用于动态修改lr,使用的时候需要显式地调用和来更新lr,详情请参考如何调整学习率。
MindSpore:
MindSpore的学习率是包到优化器里面的,每调用一次优化器,学习率更新的step会自动更新一次。
MindSpore的优化器支持一些特别的操作,比如对网络里所有的可训练的参数可以设置不同的学习率(lr)、权重衰减(weight_decay)和梯度中心化(grad_centralization)策略,如:
from mindspore import nn
# 定义模型
class Network(nn.Cell):
def __init__(self):
super().__init__()
self.layer1 = nn.SequentialCell([
nn.Conv2d(3, 12, kernel_size=3, pad_mode='pad', padding=1),
nn.BatchNorm2d(12),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2)
])
self.layer2 = nn.SequentialCell([
nn.Conv2d(12, 4, kernel_size=3, pad_mode='pad', padding=1),
nn.BatchNorm2d(4),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2)
])
self.pool = nn.AdaptiveMaxPool2d((5, 5))
self.fc = nn.Dense(100, 10)
def construct(self, x):
x = self.layer1(x)
x = self.layer2(x)
x = self.pool(x)
x = x.view((-1, 100))
out = nn.Dense(x)
return out
def params_not_in(param, param_list):
# 利用Parameter的id来判断一个param是否不在param_list中
param_id = id(param)
for p in param_list:
if id(p) == param_id:
return False
return True
net = Network()
trainable_param = net.trainable_params()
conv_weight, bn_weight, dense_weight = [], [], []
for _, cell in net.cells_and_names():
# 判断是什么API,将对应参数加到不同列表里
if isinstance(cell, nn.Conv2d):
conv_weight.append(cell.weight)
elif isinstance(cell, nn.BatchNorm2d):
bn_weight.append(cell.gamma)
bn_weight.append(cell.beta)
elif isinstance(cell, nn.Dense):
dense_weight.append(cell.weight)
other_param = []
# 所有分组里的参数不能重复,并且其交集是需要做参数更新的所有参数
for param in trainable_param:
if params_not_in(param, conv_weight) and params_not_in(param, bn_weight) and params_not_in(param, dense_weight):
other_param.append(param)
group_param = [{'order_params': trainable_param}]
# 每一个分组的参数列表不能是空的
if conv_weight:
conv_weight_lr = nn.cosine_decay_lr(0., 1e-3, total_step=1000, step_per_epoch=100, decay_epoch=10)
group_param.append({'params': conv_weight, 'weight_decay': 1e-4, 'lr': conv_weight_lr})
if bn_weight:
group_param.append({'params': bn_weight, 'weight_decay': 0., 'lr': 1e-4})
if dense_weight:
group_param.append({'params': dense_weight, 'weight_decay': 1e-5, 'lr': 1e-3})
if other_param:
group_param.append({'params': other_param})
opt = nn.Momentum(group_param, learning_rate=1e-3, weight_decay=0.0, momentum=0.9)
需要注意以下几点:
每一个分组的参数列表不能是空的;
如果没有设置和则使用优化器里设置的值,设置了的话使用分组参数字典里的值;
每个分组里的都可以是静态或动态的,但不能再分组;
每个分组里的都需要是符合规范的浮点数;
所有分组里的参数不能重复,并且其交集是需要做参数更新的所有参数。
在训练过程中,MindSpore的学习率是以参数的形式存在于网络里的,在执行优化器更新网络可训练参数前,MindSpore会调用get_lr 方法获取到当前step需要的学习率的值。
MindSpore的学习率支持静态、动态、分组三种,其中静态学习率在网络里是一个float32类型的Tensor。
动态学习率有两种,一种在网络里是一个长度为训练总的step数,float32类型的Tensor,如Dynamic LR函数。在优化器里有一个的参数,每经过一次优化器更新参数会+1,MindSpore内部会根据和这两个参数来获取当前step的学习率的值; 另一种是通过构图来生成学习率的值的,如LearningRateSchedule类。
分组学习率如上一小节参数分组中介绍的。
因为MindSpore的学习率是参数,我们也可以通过给参数赋值的方式修改训练过程中学习率的值,如LearningRateScheduler Callback,这种方法只支持优化器中传入静态的学习率。关键代码如下:
import mindspore as ms
from mindspore import ops, nn
net = nn.Dense(1, 2)
optimizer = nn.Momentum(net.trainable_params(), learning_rate=0.1, momentum=0.9)
print(optimizer.learning_rate.data.asnumpy())
new_lr = 0.01
# 改写learning_rate参数的值
ops.assign(optimizer.learning_rate, ms.Tensor(new_lr, ms.float32))
print(optimizer.learning_rate.data.asnumpy())
运行结果:
0.1
0.01