三种方法我都写成直接调用的函数了,所以大家复制走拿去用就行了。
def adjust_learning_rate(optimizer, epoch, milestones=None):
"""Sets the learning rate: milestone is a list/tuple"""
def to(epoch):
if epoch <= args.warmup:
return 1
elif args.warmup < epoch <= milestones[0]:
return 0
for i in range(1, len(milestones)):
if milestones[i - 1] < epoch <= milestones[i]:
return i
return len(milestones)
n = to(epoch)
global lr
lr = args.base_lr * (0.2 ** n)
for param_group in optimizer.param_groups:
param_group['lr'] = lr
args.warmup和args.base_lr分别是1和初始学习率。函数to的目的就是求出当前epoch在milestone的哪个范围内,不同范围代表不同的衰减率,用返回的数字来区别epoch的范围。
之后声明lr是全局的,这样做可能是因为在函数外部有使用lr的地方,函数内容就直接改变的是全局的lr。
来看一下旷世开源的shuffleNet系列中使用的学习率变化策略。
用的学习率衰减策略是根据当前迭代次数选取的。
scheduler = torch.optim.lr_scheduler.LambdaLR(optimizer,
lambda step : (1.0-step/args.total_iters) if step <= args.total_iters else 0, last_epoch=-1)
每当运行一次 scheduler.step(),参数的学习率就会按照lambda公式衰减。
CCNet官方源码中改变学习率的方法。这个学习率衰减策略是最常用的,被称作多项式衰减法。
def lr_poly(base_lr, iter, max_iter, power):
return base_lr*((1-float(iter)/max_iter)**(power))
def adjust_learning_rate(optimizer, learning_rate, i_iter, max_iter, power):
"""Sets the learning rate to the initial LR divided by 5 at 60th, 120th and 160th epochs"""
lr = lr_poly(learning_rate, i_iter, max_iter, power)
optimizer.param_groups[0]['lr'] = lr
return lr
NOTE:
看到有些代码用for循环修改optimizer.param_groups,这个group的数目是model.parameters被分为几个字典,就是几个group,每个group有不同的学习率,weight_decay,等等
optimizer = torch.optim.SGD(model.parameters(),
lr=0.1,
momentum=0.9,
weight_decay=0.005)
for o in optimizer.param_groups:
# print(type(o)) # dict
for k,v in o.items():
print(k)
####
params
lr # optimizer.param_groups[0]['lr']就是这个
momentum
dampening
weight_decay
nesterov
如果定义optimizer只用了一组parameters,不是用形如:
optimizer = SGD([{params: params_1, 'lr':0.1},
{params:params_2, 'lr': 0.2}]
那么不需要for循环了,直接访问optimizer.param_groups[0]['lr']修改。
否则还是for循环吧,可读性高。
先搭建一个小网络。
import torch.nn as nn
class Net(nn.Module):
def __init__(self):
super(Net,self).__init__()
self.conv1 = nn.Conv2d(3,32,3)
self.conv2 = nn.Conv2d(32,24,3)
self.prelu = nn.PReLU()
for m in self.modules():
if isinstance(m,nn.Conv2d):
nn.init.xavier_normal_(m.weight.data)
nn.init.constant_(m.bias.data,0)
if isinstance(m,nn.Linear):
m.weight.data.normal_(0.01,0,1)
m.bias.data.zero_()
def forward(self, input):
out = self.conv1(input)
out = self.conv2(out)
out = self.prelu(out)
return out
我们现在看看这个模型的modules
model = Net()
for m in model.modules():
print(m)
'''
Net(
(conv1): Conv2d(3, 32, kernel_size=(3, 3), stride=(1, 1))
(conv2): Conv2d(32, 24, kernel_size=(3, 3), stride=(1, 1))
(prelu): PReLU(num_parameters=1)
)
Conv2d(3, 32, kernel_size=(3, 3), stride=(1, 1))
Conv2d(32, 24, kernel_size=(3, 3), stride=(1, 1))
PReLU(num_parameters=1)
'''
比如说我们想对前两个卷积层的学习率设置为0.2(注意是前两层),对PRelu激活函数中的参数设置为0.02。剩下的参数用0.3的学习率(假象网络比我写的更加大)。
model = Net()
conv_params = list(map(id,model.conv1.parameters())) #提出前两个卷积层存放参数的地址
conv_params += list(map(id,model.conv2.parameters()))
prelu_params = []
for m in model.modules(): #找到Prelu的参数
if isinstance(m, nn.PReLU):
prelu_params += m.parameters()
#假象网络比我写的很大,还有一部分参数,这部分参数使用另一个学习率
rest_params = filter(lambda x:id(x) not in conv_params+list(map(id,prelu_params)),model.parameters()) #提出剩下的参数
print(list(rest_params))
'''
>> [] #是空的,因为我举的例子没其他参数了
'''
import torch.optim as optim
optimizer = optim.Adam([{'params':model.conv1.parameters(),'lr':0.2},
{'params':model.conv2.parameters(),'lr':0.2},
{'params':prelu_params,'lr':0.02},
{'params':rest_params,'lr':0.3}
])
思路就是利用tensor的requires_grad,每一个tensor都有自己的requires_grad成员,值只能为True和False。
还是以上面搭建的简单网络为例,我们固定第一个卷积层的参数,训练其他层的所有参数。
model = Net()
for name, p in model.named_parameters():
if name.startswith('conv1'):
p.requires_grad = False
import torch.optim as optim
optimizer = optim.Adam(filter(lambda x: x.requires_grad is not False ,model.parameters()),lr= 0.2)
为了验证一下我们的设置是否正确,我们分别看看model中的参数的requires_grad和optim中的params_group()。
for p in model.parameters():
print(p.requires_grad)
'''
False
False
True
True
True
'''
for p in optimizer.param_groups[0]['params']:
print(p.requires_grad)
print(type(p))
'''
True
True
True
'''
能看出优化器仅仅对requires_grad为True的参数进行迭代优化。