系列文章目录
基础函数2——enumerate()、hasattr()、isinstance()
pytorch学习率设置——optimizer.param_groups、对不同层设置学习率、动态调整学习率。
文章目录
- 系列文章目录
- 前言
- 1、关于pytorch优化器
- 2、add_param_group()
- 3、pytorch优化器
- 4、pytorch优化器测试总代码
- 5、Yolov7 优化器代码示例
- 总结
前言
最近遇到了一个关于优化器的bug,困扰了我很多天,然后自己系统学习了以下pytorch优化器的知识,总结记录如下。
1、关于pytorch优化器
PyTorch优化器是一个用于优化神经网络模型的工具,它的作用是根据损失函数和模型参数来更新模型的参数,从而使模型的性能得到优化。PyTorch提供了多种优化器,包括SGD、Adam、Adagrad等。
PyTorch优化器的原理是通过反向传播算法计算损失函数对每个模型参数的梯度,然后根据梯度大小和学习率来更新模型参数。具体来说,优化器会根据一定的策略计算出一个梯度下降的方向,并根据这个方向对模型的参数进行调整。优化器的目标是使损失函数最小化,从而提高模型的性能。
在使用PyTorch优化器时,需要设置优化器的超参数,包括学习率、动量、权重衰减等。这些超参数的设置会影响优化器的性能和结果,因此需要根据具体的模型和数据集进行调整和优化。
总之,PyTorch优化器是神经网络模型优化的重要工具,它通过反向传播算法计算梯度,并根据一定的策略更新模型参数,从而使模型性能得到优化。
PyTorch是一种开源机器学习框架,提供了多种优化器来优化模型。以下是一些常用的优化器:
SGD:随机梯度下降优化器,是最基本的优化器之一,它计算每个样本的梯度并根据学习率更新模型参数。
Adam:自适应矩估计优化器,是一种自适应学习率优化器,它根据每个参数的梯度和梯度平方的移动平均值来计算自适应学习率。
Adagrad:自适应梯度算法优化器,是一种自适应学习率优化器,它根据每个参数的梯度平方和历史梯度的平方和来计算自适应学习率。
Adadelta:自适应学习率算法优化器,是一种自适应学习率优化器,它根据每个参数的梯度平方和历史梯度平方的平均值来计算自适应学习率。
RMSprop:均方根传播优化器,是一种自适应学习率优化器,它根据每个参数的梯度平方和历史梯度平方的移动平均值来计算自适应学习率。
这些优化器都可以在PyTorch中使用,并且可以通过设置各种参数来进行调整和优化。
2、add_param_group()
add_param_group()是PyTorch中的一个方法,它允许用户向优化器添加一个新的参数组。参数组是一个字典,描述了特定模型参数集的参数、超参数和优化选项。当我们想要为神经网络中的不同层或参数集使用不同的学习率、权重衰减或动量时,它很有用。
add_param_group()方法接受一个字典作为输入,该字典应包含以下键:
params:需要优化的参数张量列表。
lr:参数组的学习率。
weight_decay:参数组的权重衰减值。
momentum:参数组的动量因子。
dampening:参数组动量修正的衰减。
nesterov:是否使用nesterov动量作为参数组。
可以多次调用此方法以向优化器添加不同的参数组。
示例:
import torch.optim as optim
optimizer = optim.SGD(model.parameters(), lr=0.1)
# add a new parameter group with a different learning rate and weight decay
optimizer.add_param_group({'params': model.fc.parameters(), 'lr': 0.01, 'weight_decay': 0.001})
# add another parameter group with a different momentum
optimizer.add_param_group({'params': model.conv.parameters(), 'lr': 0.1, 'momentum': 0.9})
测试:
import torch
import torch.optim as optim
# ----------------------------------- add_param_group
w1 = torch.randn(2, 2)
w1.requires_grad = True
w2 = torch.randn(2, 2)
w2.requires_grad = True
w3 = torch.randn(2, 2)
w3.requires_grad = True
# 一个参数组
optimizer_1 = optim.SGD([w1, w2], lr=0.1)
print('当前参数组个数: ', len(optimizer_1.param_groups))
print(optimizer_1.param_groups, '\n')
# 增加一个参数组
print('增加一组参数 w3\n')
optimizer_1.add_param_group({'params': w3, 'lr': 0.001, 'momentum': 0.8})
print('当前参数组个数: ', len(optimizer_1.param_groups))
print(optimizer_1.param_groups, '\n')
Yolov7 调用示例:
3、pytorch优化器
以下函数用到了这几个函数。
hasattr() 函数用于判断对象是否包含对应的属性。
isinstance()检查对象是否是指定的类型。
append() 向列表末尾添加元素
详细介绍转:基础函数2——enumerate()、hasattr()、isinstance()
# 构造损失函数和优化函数
# 损失
criterion = torch.nn.CrossEntropyLoss()
pg0, pg1, pg2 ,pg3= [], [], [], []
for name, p in model.named_modules():
if hasattr(p, "bias") and isinstance(p.bias, nn.Parameter): # 把带有bias属性且性质为nn.Parameter的层选出来 添加到到pg2列表
pg2.append(p.bias)
if isinstance(p, nn.BatchNorm2d) or "bn" in name: # 把标准化层选出来 添加到到pg0列表
pg0.append(p.weight)
elif hasattr(p, "weight") and isinstance(p.weight, nn.Parameter): # 把带有weight属性且性质为nn.Parameter的层选出来 添加到到pg1列表
pg1.append(p.weight)
#print('22',name,p) # print打印出来 调试用
optimizer = torch.optim.SGD(pg0, lr=0.1, momentum=0.5) #初始化优化器,定义一个参数组
optimizer.add_param_group({"params": pg1}) # 增加一组参数 性质与pg0一样
optimizer.add_param_group({"params": pg2}) # 增加一组参数 性质与pg0一样
optimizer.add_param_group({"params": model.w,'lr': 0.12, 'momentum': 0.8}) # 这个是我网络中定义的自学习权重参数
可以看到,参数组是一个list,一个元素是一个dict,每个dict中都有lr, momentum等参数,这些都是可单独管理,单独设定。
train函数调用
outputs = model(inputs) #代入模型
loss = criterion(outputs, target) #计算损失值
loss.backward() #反向传播计算得到每个参数的梯度值
optimizer.step() #梯度下降参数更新
optimizer.zero_grad() #将梯度归零
4、pytorch优化器测试总代码
代码是以resnet18分类手写数字体识别mini数据集为例。
import torch
import torch.nn as nn
from torch.utils.data import DataLoader
from torchvision import datasets
from torchvision import transforms
import torch.nn.functional as F
import matplotlib.pyplot as plt
import numpy as np
import os # 添加代码①
os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE" # 添加代码②
batch_size = 256 #设置batch大小
transform = transforms.Compose([
transforms.ToTensor(), #转换为张量
transforms.Normalize((0.1307,), (0.3081,)) #设定标准化值
])
#训练集
train_dataset = datasets.MNIST(
root='../data/mnist',
train=True,
transform=transform,
download=True)
#测试集
test_dataset = datasets.MNIST(
root='../data/mnist',
train=False,
transform=transform,
download=True)
#训练集加载器
train_loader = DataLoader(dataset=train_dataset, batch_size=batch_size,shuffle=True)
#测试集加载器
test_loader = DataLoader(dataset=test_dataset,batch_size=batch_size, shuffle=False)
class resnet18(torch.nn.Module):
def __init__(self):
super(resnet18, self).__init__()
self.block1 = torch.nn.Sequential(
torch.nn.Conv2d(1, 10, 5),
torch.nn.MaxPool2d(2),
torch.nn.ReLU(True),
torch.nn.BatchNorm2d(10),
)
self.block2 = torch.nn.Sequential(
torch.nn.Conv2d(10, 20, 5),
torch.nn.MaxPool2d(2),
torch.nn.ReLU(True),
torch.nn.BatchNorm2d(20),
)
self.fc = torch.nn.Sequential(
torch.nn.Flatten(),
torch.nn.Linear(320, 10)
)
self.w = torch.nn.Parameter(torch.ones(2)) # 定义自学习参数
def forward(self, x):
x = self.block1(x)*(self.w[0])
x = self.block2(x)*(self.w[1])
x = self.fc(x)
return x
model = resnet18()
device=torch.device("cuda:0"if torch.cuda.is_available()else"cpu")#使用GPU进行计算
model.to(device)#把model模型放进去
#---------------------------------------------------------------------#
# 构造损失函数和优化函数
# 损失
criterion = torch.nn.CrossEntropyLoss()
pg0, pg1, pg2 ,pg3= [], [], [], []
for name, p in model.named_modules():
if hasattr(p, "bias") and isinstance(p.bias, nn.Parameter): # 把带有bias属性且性质为nn.Parameter的层选出来 添加到到pg2列表
pg2.append(p.bias)
if isinstance(p, nn.BatchNorm2d) or "bn" in name: # 把标准化层选出来 添加到到pg0列表
pg0.append(p.weight)
elif hasattr(p, "weight") and isinstance(p.weight, nn.Parameter): # 把带有weight属性且性质为nn.Parameter的层选出来 添加到到pg1列表
pg1.append(p.weight)
#print('22',name,p) # print打印出来 调试用
# hasattr() 函数用于判断对象是否包含对应的属性。
# isinstance()检查对象是否是指定的类型。
# append() 向列表末尾添加元素
optimizer = torch.optim.SGD(pg0, lr=0.1, momentum=0.5) #初始化优化器,定义一个参数组
optimizer.add_param_group({"params": pg1}) # 增加一组参数 性质与pg0一样
optimizer.add_param_group({"params": pg2}) # 增加一组参数 性质与pg0一样
optimizer.add_param_group({"params": model.w,'lr': 0.12, 'momentum': 0.8}) # 这个是我网络中定义的自学习权重参数
# 可以看到,参数组是一个list,一个元素是一个dict,每个dict中都有lr, momentum等参数,这些都是可单独管理,单独设定。
def train(epoch):
# adjust_learning_rate(optimizer, epoch, start_lr) # 动态调整学习率
# print("Lr:{}".format(optimizer.state_dict()['param_groups'][0]['lr'])) # 查看学习率
# print("Lr:{}".format(optimizer.state_dict()['param_groups'][1]['lr']))
# print("Lr:{}".format(optimizer.state_dict()['param_groups'][2]['lr']))
# print(optimizer.state_dict()["param_groups"]) # 查看优化器完整参数
running_loss = 0.0 #每一轮训练重新记录损失值
for batch_idx, data in enumerate(train_loader, 0): #提取训练集中每一个样本
inputs, target = data
inputs, target = inputs.to(device), target.to(device) # 这里的数据(原数据)也要迁移过去
# outputs输出为0-9的概率 256*10
outputs = model(inputs) #代入模型
loss = criterion(outputs, target) #计算损失值
loss.backward() #反向传播计算得到每个参数的梯度值
optimizer.step() #梯度下降参数更新
optimizer.zero_grad() #将梯度归零
running_loss += loss.item() #损失值累加
if batch_idx % 300 == 299: #每300个样本输出一下结果
print('[%d,%5d] loss: %.3f' % (epoch + 1, batch_idx + 1, running_loss / 300))
running_loss = 0.0 # (训练轮次, 该轮的样本次, 平均损失值)
return running_loss
def test():
correct = 0
total = 0
with torch.no_grad(): #执行计算,但不希望在反向传播中被记录
for data in test_loader: #提取测试集中每一个样本
images, labels = data
images, labels = images.to(device), labels.to(device)
# outputs输出为0-9的概率 256*10
outputs = model(images) #带入模型
# torch.max()这个函数返回的是两个值,第一个值是具体的value(我们用下划线_表示)
# 第二个值是value所在的index(也就是predicted)
_, pred = torch.max(outputs.data, dim=1) #获得结果中的最大值
total += labels.size(0) #测试数++
correct += (pred == labels).sum().item() #将预测结果pred与标签labels对比,相同则正确数++
print('%d %%' % (100 * correct / total)) #输出正确率
if __name__ == '__main__':
# 这两个数组主要是为了画图
lossy = [] #定义存放纵轴数据(损失值)的列表
epochx = [] #定义存放横轴数据(训练轮数)的列表
for epoch in range(10): #训练10轮
epochx.append(epoch) #将本轮轮次存入epochy列表
lossy.append(train(epoch)) #执行训练,将返回值loss存入lossy列表
test() #每轮训练完都测试一下正确率
path = "D:/code/text/model2.pth"
#torch.save(model,path)
torch.save(model.state_dict(),path) # 保存模型
model = torch.load("D:/code/text/model2.pth") # 加载模型
#可视化一下训练过程
plt.plot(epochx, lossy)
plt.grid()
plt.show()
5、Yolov7 优化器代码示例
#------------------------------------------------------------------#
# optimizer_type 使用到的优化器种类,可选的有adam、sgd
# 当使用Adam优化器时建议设置 Init_lr=1e-3
# 当使用SGD优化器时建议设置 Init_lr=1e-2
# momentum 优化器内部使用到的momentum参数
# weight_decay 权值衰减,可防止过拟合
# adam会导致weight_decay错误,使用adam时建议设置为0。
#------------------------------------------------------------------#
optimizer_type = "sgd"
momentum = 0.937
weight_decay = 5e-4
lr_decay_type = "cos" # 使用到的学习率下降方式,可选的有step、cos
UnFreeze_Epoch = 10 # eporch
nbs = 64
lr_limit_max = 1e-3 if optimizer_type == 'adam' else 5e-2
lr_limit_min = 3e-4 if optimizer_type == 'adam' else 5e-4
Init_lr_fit = min(max(batch_size / nbs * Init_lr, lr_limit_min), lr_limit_max)
Min_lr_fit = min(max(batch_size / nbs * Min_lr, lr_limit_min * 1e-2), lr_limit_max * 1e-2)
# 学习率下降公式函数
def get_lr_scheduler(lr_decay_type, lr, min_lr, total_iters, warmup_iters_ratio = 0.05, warmup_lr_ratio = 0.1, no_aug_iter_ratio = 0.05, step_num = 10):
def yolox_warm_cos_lr(lr, min_lr, total_iters, warmup_total_iters, warmup_lr_start, no_aug_iter, iters):
if iters <= warmup_total_iters:
# lr = (lr - warmup_lr_start) * iters / float(warmup_total_iters) + warmup_lr_start
lr = (lr - warmup_lr_start) * pow(iters / float(warmup_total_iters), 2
) + warmup_lr_start
elif iters >= total_iters - no_aug_iter:
lr = min_lr
else:
lr = min_lr + 0.5 * (lr - min_lr) * (
1.0
+ math.cos(
math.pi
* (iters - warmup_total_iters)
/ (total_iters - warmup_total_iters - no_aug_iter)
)
)
return lr
# 动态调整学习率函数
def set_optimizer_lr(optimizer, lr_scheduler_func, epoch):
lr = lr_scheduler_func(epoch)
for param_group in optimizer.param_groups:
param_group['lr'] = lr
#---------------------------------------#
# 根据optimizer_type选择优化器
#---------------------------------------#
pg0, pg1, pg2 = [], [], []
for k, v in model.named_modules():
if hasattr(v, "bias") and isinstance(v.bias, nn.Parameter):
pg2.append(v.bias)
if isinstance(v, nn.BatchNorm2d) or "bn" in k:
pg0.append(v.weight)
elif hasattr(v, "weight") and isinstance(v.weight, nn.Parameter):
pg1.append(v.weight)
optimizer = {
'adam' : optim.Adam(pg0, Init_lr_fit, betas = (momentum, 0.999)),
'sgd' : optim.SGD(pg0, Init_lr_fit, momentum = momentum, nesterov=True)
}[optimizer_type]
optimizer.add_param_group({"params": pg1, "weight_decay": weight_decay})
optimizer.add_param_group({"params": pg2})
# 以下两行代码不是初始化代码,放eporch for循环中 ,每个eporch执行一次
lr_scheduler_func = get_lr_scheduler(lr_decay_type, Init_lr_fit, Min_lr_fit, UnFreeze_Epoch)
# 学习率调整
set_optimizer_lr(optimizer, lr_scheduler_func, epoch) # 根据迭代epoch更新学习率
这段代码参考了http://t.csdn.cn/cVhoQ