最近在浏览yolox的代码
咋yolox/exp/yolox_base.py
中的def get_optimizer方法,趁机补充一下pytorch中的优化器知识
def get_optimizer(self, batch_size):
if "optimizer" not in self.__dict__:
if self.warmup_epochs > 0:
lr = self.warmup_lr
else:
lr = self.basic_lr_per_img * batch_size
pg0, pg1, pg2 = [], [], [] # optimizer parameter groups,把需要更新的参数,加入到这三个列表中
# 之所以要搞这三个列表,是因为要三个列表中的权重更新方式是不一样的
for k, v in self.model.named_modules():
# 关于model.named_modules(),可以看这篇文章:https://blog.csdn.net/Pl_Sun/article/details/106978171
# k是模块的名字,v是模型的模块
if hasattr(v, "bias") and isinstance(v.bias, nn.Parameter): # isinstance判断对象是否为某个类的实例
# 如果是nn.Parameter,则表示可以在训练时跟新的参数
# 关于nn.Parameter,可以看这篇文章:https://www.jianshu.com/p/d8b77cc02410
pg2.append(v.bias) # biases
if isinstance(v, nn.BatchNorm2d) or "bn" in k: # 这里k指向一个字符串,如果字符串中带有bn
# 在以前所学的深度学习中,BN层的参数都是不更新的,这里试图更新BN曾的参数
pg0.append(v.weight) # no decay
elif hasattr(v, "weight") and isinstance(v.weight, nn.Parameter):
pg1.append(v.weight) # apply decay
optimizer = torch.optim.SGD(
pg0, lr=lr, momentum=self.momentum, nesterov=True # nesterov表示是否使用nesterov momentum
# 关于nesterov momentum可以看这篇文章:https://blog.csdn.net/u012328159/article/details/80311892
)
optimizer.add_param_group(
{"params": pg1, "weight_decay": self.weight_decay} # 对pg中的参数,在更新的时候使用权重衰减
# 这里的所谓权重衰减,指的是L2正则化,weight_decay即为正则化时使用的λ
# 关于权重衰减,可以看这篇文章:https://zhuanlan.zhihu.com/p/225606205
) # add pg1 with weight_decay
optimizer.add_param_group({"params": pg2})
self.optimizer = optimizer
return self.optimizer
现在来逐行讲解这个函数,如果yolox_base中有名为“optimizer”的属性,那么就不用定义优化器了,直接返回
如果没有,再来定义优化器
下面这段代码是调整学习率
if self.warmup_epochs > 0:
lr = self.warmup_lr
else:
lr = self.basic_lr_per_img * batch_size
这里的warmup_epochs指的是执行warmup算法剩下的遍历次数,如果这个等于0,说明之后不再继续执行warmup算法了。
接下来创建了三个列表,并把模型中的参数按照一定的规则加入到这三个列表中
pg0, pg1, pg2 = [], [], [] # optimizer parameter groups,把需要更新的参数,加入到这三个列表中
# 之所以要搞这三个列表,是因为要三个列表中的权重更新方式是不一样的
for k, v in self.model.named_modules():
# 关于model.named_modules(),可以看这篇文章:https://blog.csdn.net/Pl_Sun/article/details/106978171
# k是模块的名字,v是模型的模块
if hasattr(v, "bias") and isinstance(v.bias, nn.Parameter): # isinstance判断对象是否为某个类的实例
# 如果是nn.Parameter,则表示可以在训练时跟新的参数
# 关于nn.Parameter,可以看这篇文章:https://www.jianshu.com/p/d8b77cc02410
pg2.append(v.bias) # biases
if isinstance(v, nn.BatchNorm2d) or "bn" in k: # 这里k指向一个字符串,如果字符串中带有bn
# 在以前所学的深度学习中,BN层的参数都是不更新的,这里试图更新BN曾的参数
pg0.append(v.weight) # no decay
elif hasattr(v, "weight") and isinstance(v.weight, nn.Parameter):
pg1.append(v.weight) # apply decay
之所以把参数分成三类,是因为需要使用不同的更新方式
接下来定义优化器,并将参数加入到优化器中
optimizer = torch.optim.SGD(
pg0, lr=lr, momentum=self.momentum, nesterov=True # nesterov表示是否使用nesterov momentum
# 关于nesterov momentum可以看这篇文章:https://blog.csdn.net/u012328159/article/details/80311892
)
optimizer.add_param_group(
{"params": pg1, "weight_decay": self.weight_decay} # 对pg中的参数,在更新的时候使用权重衰减
# 这里的所谓权重衰减,指的是L2正则化,weight_decay即为正则化时使用的λ
# 关于权重衰减,可以看这篇文章:https://zhuanlan.zhihu.com/p/225606205
) # add pg1 with weight_decay
optimizer.add_param_group({"params": pg2})
self.optimizer = optimizer
这边需要讲解一下pytorch中优化器的用法,首先看看基本用法
# 如果要对一个模型的参数进行更新,则使用
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9)
# 如果要对指定参数进行更新,则
optimizer = optim.Adam([var1, var2], lr=0.0001) # var1和var2都是需要更新的参数,它们的requires_grad必须是True,放入可迭代对象中
假如我们想对不同的参数使用不同的学习率,则可以这样
optim.SGD([
{'params': model.base.parameters()},
{'params': model.classifier.parameters(), 'lr': 1e-3}
], lr=1e-2, momentum=0.9)
列表(或其他可迭代对象)中封装字典,在字典中对该参数的学习率进行设置,如果字典里没有设置(如果上段代码中的model.base.parameters()),将使用默认学习率(即 lr=1e-2),而momentum=0.9
则同时作用于model.base.parameters和model.classifier.parameters。
除了为特定的参数指定学习率,还可以给特定的参数指定权重衰减系数weight_decay,动量momentum和该优化器的其他参数。
以Adam算法为例,这些参数都在一个名为的param_groups列表中
import torch
import torch.optim as optim
w1 = torch.randn(3, 3)
w1.requires_grad = True
w2 = torch.randn(3, 3)
w2.requires_grad = True
optimizer = optim.Adam([w1, w2])
print(optimizer.param_groups)
输出
[{'params': [tensor([[-1.4738, 1.0450, 1.2211],
[-1.2189, 0.7307, -0.4714],
[-0.8499, 0.5304, -1.0945]], requires_grad=True),
tensor([[ 0.8003, 0.2559, -0.7001],
[ 1.2911, -0.5790, 0.4431],
[ 1.6924, 0.1126, 0.4403]], requires_grad=True)], 'lr': 0.001, 'betas': (0.9, 0.999), 'eps': 1e-08, 'weight_decay': 0, 'amsgrad': False}]
这个列表有个字典,这个字典中的信息为该优化器的相关参数
假如我已经定义了优化器,并在定义的时候把[w1, w2]
的参数放入到了优化器中,如果我还想把w3放入到优化器,并且指定其学习率和权重衰减因子,那么可以使用add_param_group,例如:
import torch
import torch.optim as optim
w1 = torch.randn(3, 3)
w1.requires_grad = True
w2 = torch.randn(3, 3)
w2.requires_grad = True
optimizer = optim.Adam([w1, w2], lr=1e-3)
w3 = torch.randn(3, 3)
w3.requires_grad = True
optimizer.add_param_group({"params": w3, "weight_decay": 5e-4, "lr": 1e-5})
print(optimizer.param_groups)
输出
[{'params': [tensor([[ 0.2659, -0.0605, -0.1793],
[ 0.6018, 0.5354, 0.0096],
[ 2.0476, -0.1480, -0.4830]], requires_grad=True), tensor([[ 0.9057, -1.2513, 0.2970],
[ 0.2442, -1.5792, 1.6637],
[ 1.0540, -0.2506, 0.2723]], requires_grad=True)], 'lr': 0.001, 'betas': (0.9, 0.999), 'eps': 1e-08, 'weight_decay': 0, 'amsgrad': False},
{'params': [tensor([[-0.5128, 1.1488, 0.4640],
[-0.3684, 2.1852, -0.5718],
[ 0.7953, 0.8427, 1.0651]], requires_grad=True)], 'weight_decay': 0.0005, 'lr': 1e-05, 'betas': (0.9, 0.999), 'eps': 1e-08, 'amsgrad': False}]
add_param_group相当于给param_group增加了一个字典,它的参数更新规则按照这个字典中的键值对来进行。