Pytorch中定义了所有优化器的基类即torch.optim.Optimizer(),它是所有优化器的基类。所以如果有必要,那么改写优化器时就要继承此类。
在初始化optimizer优化器时,第一个参数就是模型中的可微分参数,该参数必须是可迭代的,因为后续模型训练时候要更新嘛,一般是torch.Tensor或者字典对象。在自定义优化器时,子类需要重写基类中的成员函数_step()和_update_hyper_param(),目的是实现具体的优化算法和更新超参数的功能(要不你训练一遍以后模型怎么更新参数,就是靠这个)。其中,_step()通常用来计算参数的梯度并更新参数值,_update_hyper_param()方法用来更新优化器自身的超参数,比如学习率lr。需要注意的是,在使用Optimizer时, 传递的参数需要有序,并且保持一致, 对于无序的集合或者字典, 在运行时候可能会导致优化器在不同的运行中表现不一致(这块说的比较官方,但pytorch的源码解释部分是这么说的)。
本文的重点是要记录optimizer常见使用,比如SGD, Adam。
在使用时,通常是实例化模型后,创建优化器,然后将模型中的可训练参数传进优化器方便后续模型迭代训练的参数更新,所以常见的代码片段主要是:
import torch
model = 自定义模型实例化()#不要忘了这个(),有括号才成功调用实例化
params = model.parameters() #取出模型的可训练参数
optimizer = torch.optim.优化器名称(params, ...) #这里第一个参数就是模型中需要优化的可微分参数。
目前的优化器参数总共有8个,我以SGD,Adam为例,分别是:
SGD:dict_keys(['params', 'lr', 'momentum', 'dampening', 'weight_decay', 'nesterov', 'maximize', 'initial_lr'])
Adam:dict_keys(['params', 'lr', 'betas', 'eps', 'weight_decay', 'amsgrad', 'maximize', 'initial_lr'])
可以看出它是一个字典类型,关键字已经定义好,值就是需要定义的地方,params当然就是模型的参数,一般情况下,其他的常见使用需要定义的就是'lr', 'momentum','weight_decay'(因为我就使用过这三个)。具体的默认值可以自行print或者debug查看。
我也对网上的实验例子进行了测试,感觉就是将参数放在了优化器里面,然后在模型后续训练过程中就可以进行模型参数的训练更新了,但是因为是测试,优化器里的params显然没有更新,所以只是将参数放进优化器,放置优化器的前后实际是一样的。
import torch
w1 = torch.randn(3, 3)
w1.requires_grad = True # 这里不能将requires_grad直接写在上一行代码最后,否则会报错!
# 这里也不能将Adam以空参数实例化后再调用,因为Adam优化器在类定义时构造函数就要赋值,没有default
optis = torch.optim.Adam([w1])
print(optis.param_groups) # 这里w1其实和优化器后的一样,因为没训练,所以没更新。
w2 = torch.randn(3, 3)
w2.requires_grad = True
optis.add_param_group({'params': w2})
print(optis.param_groups)
如果要动态修改学习率,一般方法是:
for param_group in optimizer.param_groups:
param_group['lr'] = lr
这里其实就是将lr择出来,然后根据具体情况再进行设置。比如,在某个确定的时间点进行设定:
optimizer = torch.optim.Adam(model.parameters(), lr=0.1)
for epoch in range(10):
if epoch == 6: # 当模型训练到第6个epoch时,将学习率设定为0.01
optimizer.param_groups[0]['lr'] = 0.01
还有一种方法是使用调度器实现:常见的调度器有
torch.optim.lr_scheduler.StepLR
torch.optim.lr_scheduler.ReduceLROnPlateau
torch.optim.lr_scheduler.LamdaLR
optimizer = torch.optim.Adam(model.parameters(), lr=0.1)
scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step=5, gamma=0.1)
for epoch in range(10):
out = model(input)
loss = criterion(out, target)
optimizer.zero_grad()
loss.backward()
optimizer.step() #更新参数,我感觉这里主要更新的是params
scheduler.step() # 调度器更新学习率
当需要更细粒度的调参,即针对网络的某层进行学习率的调整,那么
import torch
optimizer = torch.optim.Adam( #模型的特征提取部分学习率为1e-5, 分类器为1e-2
[{'params': net.features.parameters()},
{'params': net.classifiter.parameters(), 'lr': 1e-2}], lr=1e-5
)
# 一般这种做法是为了将分类器的学习率调大加速训练
提取特定层,然后为其指定层的学习率:
# 提取指定层
special_layers = torch.nn.ModuleList([model.classifier[0], model.classifier[3]])
# 提取指定层的id号
special_layers_params = list(map(id, special_layers.parameters()))
# 提取其余层的id号
ordinary_layers_params = filter(lambda p: id(p) not in special_layers_params, model.parameters())
optimizer = torch.optim.Adam([
{'params': ordinary_layers_params},
{'params': special_layers.parameters(), 'lr': 0.01}], lr=0.001)
参考:
https://blog.csdn.net/xxmy7/article/details/125967239?spm=1001.2014.3001.5506