文章目录
- 参考文献
- 什么是优化器
- optimizer的定义
- optimizer的属性
- defaults
- state
- param_groups
- optimizer的方法
- zero_grad()
- step()
- add_param_group()
- state_dict()、load_state_dict()
- 优化一个网络
- 同时优化多个网络
- 当成一个网络优化
- 当成多个网络优化
- 只优化网络的某些指定的层
- 调整学习率
个人学习总结,持续更新中……
参考文献
官方教程
【学习笔记】Pytorch深度学习—优化器(一)
【学习笔记】Pytorch深度学习—优化器(二)
什么是优化器
Pytorch的优化器:
管理并更新模型中可学习参数的值,使得模型输出更接近真实标签。
分析
其中,可学习参数指 权值 和 偏置bias;
其次,优化器最主要的2大功能:
(1)管理:指优化器管理哪一部分参数;
(2)更新:优化器当中具有一些优化策略,优化器可采用这些优化策略更新模型中可学习参数的值;这一更新策略,在神经网络中通常都会采用梯度下降法。
什么是梯度下降法
<总结>
Pytorch中优化器optimizer 管理着模型中的可学习参数,并采用梯度下降法 更新着可学习参数的值。
optimizer的定义
以Adam为例:
class Adam(Optimizer):
def __init__(self, params, lr=1e-3, betas=(0.9, 0.999), eps=1e-8,
weight_decay=0, amsgrad=False):
import torch
# 构建1个2×2大小的随机张量,并开放"梯度"
weight = torch.tensor([[1., 2.], [3., 4.]], requires_grad=True)
# 构建1个2×2大小的全1梯度张量
weight.grad = torch.ones((2, 2))
# 将可学习参数[weight]传入优化器
optimizer1 = torch.optim.Adam([weight], lr=0.01, betas=(0.9, 0.999), eps=1e-08, weight_decay=0, amsgrad=False)
# optimizer2 = torch.optim.Adam([weight])
import torch
import torch.nn as nn
initial_lr = 0.1
class model(nn.Module):
def __init__(self):
super().__init__()
self.conv1 = nn.Conv2d(in_channels=3, out_channels=3, kernel_size=(3, 3))
self.conv2 = nn.Conv2d(in_channels=3, out_channels=3, kernel_size=(3, 3))
def forward(self, x):
pass
net = model()
optimizer = torch.optim.Adam(net.parameters(), lr=initial_lr)
optimizer的属性
defaults
优化器超参数,用来存储学习率、momentum的值等等;
import torch
import torch.nn as nn
initial_lr = 0.1
class model(nn.Module):
def __init__(self):
super().__init__()
self.conv1 = nn.Conv2d(in_channels=3, out_channels=3, kernel_size=(3, 3))
self.conv2 = nn.Conv2d(in_channels=3, out_channels=3, kernel_size=(3, 3))
def forward(self, x):
pass
net = model()
optimizer = torch.optim.Adam(net.parameters(), lr=0.01, betas=(0.9, 0.999), eps=1e-08, weight_decay=0, amsgrad=False)
print(optimizer.defaults)
'''
{'lr': 0.01, 'betas': (0.9, 0.999), 'eps': 1e-08, 'weight_decay': 0, 'amsgrad': False}
'''
state
参数的缓存,如momentum的缓存;采用momentum时会使用前几次更新时使用的梯度,也就是前几次的梯度,把前几次的梯度值缓存下来,在本次更新中使用;
import torch
import torch.nn as nn
initial_lr = 0.1
class model(nn.Module):
def __init__(self):
super().__init__()
self.conv1 = nn.Conv2d(in_channels=3, out_channels=3, kernel_size=(3, 3))
self.conv2 = nn.Conv2d(in_channels=3, out_channels=3, kernel_size=(3, 3))
def forward(self, x):
pass
net = model()
optimizer = torch.optim.Adam(net.parameters(), lr=0.01, betas=(0.9, 0.999), eps=1e-08, weight_decay=0, amsgrad=False)
print(optimizer.state)
'''
defaultdict(<class 'dict'>, {})
'''
param_groups
管理的参数组;优化器最重要的属性,已经知道优化器是管理可学习参数,这一系列可学习参数就放在param_groups这一属性中,同时,这一参数组定义为list。
param_groups=[{‘params’:param_groups,‘lr’: 0.01,}]
因此,param_groups是1个list。而在list[ ] 中,每一个元素又是1个字典{ } ,这些字典中有很多key,其中最重要的key是-‘params’,只有’params’当中才会存储训练模型的参数。
import torch
# 构建1个2×2大小的随机张量,并开放"梯度"
weight = torch.tensor([[1., 2.], [3., 4.]], requires_grad=True)
# 构建1个2×2大小的全1梯度张量
weight.grad = torch.ones((2, 2))
# 将可学习参数[weight]传入优化器
optimizer = torch.optim.Adam([weight], lr=0.01, betas=(0.9, 0.999), eps=1e-08, weight_decay=0, amsgrad=False)
print(optimizer.param_groups)
'''
[{'params': [tensor([[1., 2.],
[3., 4.]], requires_grad=True)], 'lr': 0.01, 'betas': (0.9, 0.999), 'eps': 1e-08, 'weight_decay': 0, 'amsgrad': False}]
'''
optimizer的方法
zero_grad()
清空所管理参数的梯度
Pytorch中tensor特性:tensor张量梯度不自动清零
已知参数param是1个特殊的张量,张量当中都会有梯度grad。由于Pytorch中张量tensor的梯度grad是不会自动清零的,它会在每一次backward反向传播时采用autograd计算梯度,并把梯度值累加到张量的grad属性中的。
由于Pytorch中的grad属性不自动清零,因此每计算1次梯度就自动累加到grad属性中造成错误;因此,一定要在使用完梯度后或者进行梯度求导(反向传播)之间通过zero_grad进行清零。
import torch
# 构建1个2×2大小的随机张量,并开放"梯度"
weight = torch.tensor([[1., 2.], [3., 4.]], requires_grad=True)
# 构建1个2×2大小的全1梯度张量
weight.grad = torch.ones((2, 2))
# 将可学习参数[weight]传入优化器
optimizer = torch.optim.Adam([weight], lr=0.01, betas=(0.9, 0.999), eps=1e-08, weight_decay=0, amsgrad=False)
print(weight.grad)
'''
tensor([[1., 1.],
[1., 1.]])
'''
optimizer.zero_grad()
print(weight.grad)
'''
tensor([[0., 0.],
[0., 0.]])
'''
step()
执行一步更新
当计算得到Loss,利用Loss进行backward反向传播计算各个参数的梯度之后,采用step()进行一步更新,更新权值参数。step()会采用梯度下降的策略,具体方法有很多种,比如随机梯度下降法、momentum+动量方法、autograd自适应学习率等等一系列优化方法。
参数_new = 参数_old + 负梯度值 * 学习率
import torch
# 构建1个2×2大小的随机张量,并开放"梯度"
weight = torch.tensor([[2., 3.], [4., 5.]], requires_grad=True)
# 构建1个2×2大小的全1梯度张量
weight.grad = torch.ones((2, 2))
# 将可学习参数[weight]传入优化器
optimizer = torch.optim.Adam([weight], lr=1.0, betas=(0.9, 0.999), eps=1e-08, weight_decay=0, amsgrad=False)
print(weight.data)
'''
tensor([[2., 3.],
[4., 5.]])
'''
optimizer.step() # 修改lr=0.1观察结果
print(weight.data)
'''
tensor([[1.0000, 2.0000],
[3.0000, 4.0000]])
'''
add_param_group()
添加参数组
add_param_group()添加一组参数到优化器中。已知优化器管理很多参数,这些参数是可以分组;对于不同组的参数,有不同的超参数设置,例如在某一模型中,希望特征提取部分的权值参数的学习率小一点,学习更新慢一点,这时可以把特征提取的参数设置为一组参数,而对于后面全连接层,希望其学习率大一点,学习快一点。这时,可以把整个模型参数设置为两组,一组为特征提取部分的参数,另一部分是全连接层的参数,对这两组设置不同的学习率或超参数,这时就需要用到参数组概念。
import torch
# 构建1个2×2大小的随机张量,并开放"梯度"
weight = torch.tensor([[2., 3.], [4., 5.]], requires_grad=True)
# 构建1个2×2大小的全1梯度张量
weight.grad = torch.ones((2, 2))
# 将可学习参数[weight]传入优化器
optimizer = torch.optim.Adam([weight], lr=1.0, betas=(0.9, 0.999), eps=1e-08, weight_decay=0, amsgrad=False)
print(optimizer.param_groups)
'''
[{'params': [tensor([[2., 3.],
[4., 5.]], requires_grad=True)], 'lr': 1.0, 'betas': (0.9, 0.999), 'eps': 1e-08, 'weight_decay': 0, 'amsgrad': False}]
'''
w2 = torch.randn((3, 3), requires_grad=True)
optimizer.add_param_group({"params": w2, 'lr': 0.0001})
print(optimizer.param_groups)
'''
[{'params': [tensor([[2., 3.],
[4., 5.]], requires_grad=True)], 'lr': 1.0, 'betas': (0.9, 0.999), 'eps': 1e-08, 'weight_decay': 0, 'amsgrad': False}, {'params': [tensor([[ 0.2155, 1.3953, -0.2814],
[ 1.3192, 2.0449, 1.6898],
[ 2.0740, -1.5179, -0.1514]], requires_grad=True)], 'lr': 0.0001, 'betas': (0.9, 0.999), 'eps': 1e-08, 'weight_decay': 0, 'amsgrad': False}]
'''
import torch
# 构建1个2×2大小的随机张量,并开放'梯度'
weight = torch.tensor([[1., 2.], [3., 4.]], requires_grad=True)
# 构建1个2×2大小的全1梯度张量
weight.grad = torch.ones((2, 2))
# 将可学习参数[weight]传入优化器
optimizer = torch.optim.Adam([weight], lr=1) # 0.1
w2 = torch.tensor([[11., 12.], [13., 14.]], requires_grad=True)
w3 = torch.tensor([[11., 12.], [13., 14.]], requires_grad=True)
# 构建字典,key中 params中放置参数 w2,利用方法 add_param_group把这一组参数加进来
optimizer.add_param_group({"params": [w2,w3], 'lr': 0.0001})
# optimizer.add_param_group({"params": w3, 'lr': 0.0001})
state_dict()、load_state_dict()
import torch
# 构建1个2×2大小的随机张量,并开放'梯度'
weight = torch.tensor([[1., 2.], [3., 4.]], requires_grad=True)
# 构建1个2×2大小的全1梯度张量
weight.grad = torch.ones((2, 2))
# 将可学习参数[weight]传入优化器
optimizer = torch.optim.Adam([weight], lr=1) # 0.1
w2 = torch.tensor([[11., 12.], [13., 14.]], requires_grad=True)
w3 = torch.tensor([[11., 12.], [13., 14.]], requires_grad=True)
# 构建字典,key中 params中放置参数 w2,利用方法 add_param_group把这一组参数加进来
optimizer.add_param_group({"params": [w2,w3], 'lr': 0.0001})
# optimizer.add_param_group({"params": w3, 'lr': 0.0001})
opt_state_dict = optimizer.state_dict()
# 打印step()更新之前的状态信息字典
print("state_dict before step:\n", opt_state_dict)
optimizer.step()
# 打印step()更新之后的状态信息字典
print("state_dict after step:\n", optimizer.state_dict())
# 保存更新之后的状态信息字典在当前文件夹下名为 optimizer_state_dict.pkl的文件
torch.save(optimizer.state_dict(), 'optimizer_state_dict.pkl')
# -------------------------load state_dict --------------
# 重新构建优化器
optimizer = torch.optim.Adam([weight], lr=0.1)
optimizer.add_param_group({"params": [w2,w3], 'lr': 0.0001})
# optimizer.add_param_group({"params": w3, 'lr': 0.0001})
# 创建加载状态名
state_dict = torch.load("optimizer_state_dict.pkl")
print("state_dict before load state:\n", optimizer.state_dict())
# 利用load_state_dict方法加载状态信息,接着当前状态往下训练
optimizer.load_state_dict(state_dict)
print("state_dict after load state:\n", optimizer.state_dict())
优化一个网络
import torch
import torch.nn as nn
initial_lr = 0.1
class model(nn.Module):
def __init__(self):
super().__init__()
self.conv1 = nn.Conv2d(in_channels=1, out_channels=1, kernel_size=(3, 3))
self.conv2 = nn.Conv2d(in_channels=1, out_channels=1, kernel_size=(3, 3))
def forward(self, x):
pass
net_1 = model()
optimizer_1 = torch.optim.Adam(net_1.parameters(), lr=initial_lr)
print("******************optimizer_1*********************")
print("optimizer_1.defaults:\n", optimizer_1.defaults)
print("optimizer_1.param_groups:\n", optimizer_1.param_groups)
'''
******************optimizer_1*********************
optimizer_1.defaults:
{'lr': 0.1, 'betas': (0.9, 0.999), 'eps': 1e-08, 'weight_decay': 0, 'amsgrad': False}
optimizer_1.param_groups:
[{'params': [Parameter containing:
tensor([[[[-0.2143, -0.3299, 0.0063],
[ 0.1602, -0.0350, 0.0579],
[-0.1537, 0.0446, 0.0909]]]], requires_grad=True), Parameter containing:
tensor([-0.2204], requires_grad=True), Parameter containing:
tensor([[[[ 0.1074, -0.1432, 0.0954],
[ 0.1079, 0.3233, -0.2487],
[-0.1410, 0.1557, 0.0839]]]], requires_grad=True), Parameter containing:
tensor([-0.1368], requires_grad=True)], 'lr': 0.1, 'betas': (0.9, 0.999), 'eps': 1e-08, 'weight_decay': 0, 'amsgrad': False}]
'''
同时优化多个网络
当成一个网络优化
这种方法,每个网络的学习率是相同的。
optimizer = torch.optim.Adam([*net_1.parameters(), *net_2.parameters()], lr = initial_lr)
当成多个网络优化
这样可以很容易的让多个网络的学习率各不相同。
optimizer_3 = torch.optim.Adam([{"params": net_1.parameters()}, {"params": net_2.parameters()}], lr = initial_lr)
optimizer_3 = torch.optim.Adam( [{"params": net_1.parameters(), 'lr': initial_lr}, {"params": net_2.parameters(), 'lr': initial_lr}])
import torch
import torch.nn as nn
initial_lr = 0.1
class model(nn.Module):
def __init__(self):
super().__init__()
self.conv1 = nn.Conv2d(in_channels=1, out_channels=1, kernel_size=(3,3))
self.conv2 = nn.Conv2d(in_channels=1, out_channels=1, kernel_size=(3,3))
def forward(self, x):
pass
net_1 = model()
net_2 = model()
optimizer_2 = torch.optim.Adam([*net_1.parameters(), *net_2.parameters()],
lr=initial_lr)
# optimizer_2 = torch.opotim.Adam(itertools.chain(net_1.parameters(), net_2.parameters())) # 和上一行作用相同
print("******************optimizer_2*********************")
print("optimizer_2.defaults:", optimizer_2.defaults)
print("optimizer_2.param_groups长度:", len(optimizer_2.param_groups))
print("optimizer_2.param_groups一个元素包含的键:", optimizer_2.param_groups[0].keys())
print()
optimizer_3 = torch.optim.Adam([{
"params": net_1.parameters()
}, {
"params": net_2.parameters()
}],
lr=initial_lr)
print("******************optimizer_3*********************")
print("optimizer_3.defaults:", optimizer_3.defaults)
print("optimizer_3.param_groups长度:", len(optimizer_3.param_groups))
print("optimizer_3.param_groups一个元素包含的键:", optimizer_3.param_groups[1].keys())
optimizer_4 = torch.optim.Adam([{
"params": net_1.parameters(),
'lr': initial_lr
}, {
"params": net_2.parameters(),
'lr': initial_lr
}])
print("******************optimizer_4*********************")
print("optimizer_4.defaults:", optimizer_4.defaults)
print("optimizer_4.param_groups长度:", len(optimizer_4.param_groups))
print("optimizer_4.param_groups一个元素包含的键:", optimizer_4.param_groups[1].keys())
'''
******************optimizer_2*********************
optimizer_2.defaults: {'lr': 0.1, 'betas': (0.9, 0.999), 'eps': 1e-08, 'weight_decay': 0, 'amsgrad': False}
optimizer_2.param_groups长度: 1
optimizer_2.param_groups一个元素包含的键: dict_keys(['params', 'lr', 'betas', 'eps', 'weight_decay', 'amsgrad'])
******************optimizer_3*********************
optimizer_3.defaults: {'lr': 0.1, 'betas': (0.9, 0.999), 'eps': 1e-08, 'weight_decay': 0, 'amsgrad': False}
optimizer_3.param_groups长度: 2
optimizer_3.param_groups一个元素包含的键: dict_keys(['params', 'lr', 'betas', 'eps', 'weight_decay', 'amsgrad'])
******************optimizer_4*********************
optimizer_4.defaults: {'lr': 0.001, 'betas': (0.9, 0.999), 'eps': 1e-08, 'weight_decay': 0, 'amsgrad': False}
optimizer_4.param_groups长度: 2
optimizer_4.param_groups一个元素包含的键: dict_keys(['params', 'lr', 'betas', 'eps', 'weight_decay', 'amsgrad'])
'''
只优化网络的某些指定的层
optimizer = optim.SGD( [{'params': model.layer0.parameters(), "lr": 0.01}, {'params': model.layer2.parameters(), "lr": 0.01}])
from torch import optim
from torch import nn
import torch
class MLP(nn.Module):
def __init__(self, in_dim, hid_dim1, hid_dim2, out_dim):
super(MLP, self).__init__()
self.layer0 = nn.Linear(in_dim, hid_dim1)
self.layer1 = nn.ReLU()
self.layer2 = nn.Linear(hid_dim1, hid_dim2)
self.layer3 = nn.ReLU()
self.layer4 = nn.Linear(hid_dim2, out_dim)
self.layer5 = nn.ReLU()
def forward(self, x):
x = self.layer0(x)
x = self.layer1(x)
x = self.layer2(x)
x = self.layer3(x)
x = self.layer4(x)
x = self.layer5(x)
return x
model = MLP(10, 3, 3, 10)
optimizer = optim.SGD(
[{'params': model.layer0.parameters(), "lr": 0.01}, {'params': model.layer2.parameters(), "lr": 0.01}])
data = torch.randn(10, 10)
label = torch.Tensor([1, 0, 4, 7, 9, 2, 4, 5, 3, 2]).long()
criterion = nn.CrossEntropyLoss()
for i in range(100):
output = model(data)
loss = criterion(output, label)
optimizer.zero_grad()
loss.backward()
optimizer.step()
if i == 0:
print('model.layer0.parameters(): \n', [x for x in model.layer0.parameters()])
print('model.layer4.parameters(): \n', [x for x in model.layer4.parameters()])
optimizer.step()
if i == 99:
print('model.layer0.parameters(): \n', [x for x in model.layer0.parameters()])
print('model.layer4.parameters(): \n', [x for x in model.layer4.parameters()])
调整学习率
torch.optim.lr_scheduler:调整学习率