目录
一、Pytorch中state_dict()、named_parameters()、model.parameter()区别
1、model.state_dict():
2、model.named_parameters():
3、model.parameter():
二、Pytorch 载入预训练模型,并修改网络结构,固定某层参数学习
1、加载预训练模型、并修改网络结构
△ 本例子以Pytorch自带模型为例:
△ 本例子以自己保存的模型参数为例:
△ 修改某层后,快速加载之前的参数:
2、固定某层参数学习
以下为加深理解:
1)、当不冻结层时
2)、冻结 fc1 层时
3)、只训练 fc1 层
总结:
三、对不同的层采用不同的学习率
是将 layer_name 与 layer_param 以键的形式存储为 dict 。包含所有层的名字和参数,所存储的模型参数 tensor 的 require_grad 属性都是 False 。输出的值不包括 require_grad 。在固定某层时不能采用 model.state_dict() 来获取参数设置 require_grad 属性。
例子:
# -*- coding: utf-8 -*-
import torch.nn as nn
class net(nn.Module):
def __init__(self):
super(net, self).__init__()
self.conv1 = nn.Sequential(
nn.Conv3d(1, 3,kernel_size=3, stride=1, padding=0, bias=False),
)
self.conv2 = nn.Sequential(
nn.Conv3d(3, 6, kernel_size=3, stride=1, padding=0, bias=False),
)
self.conv3 = nn.Sequential(
nn.Conv3d(6, 12,kernel_size=3, stride=1, padding=0, bias=False),
)
def forward(self, x):
out = self.conv1(x)
out = self.conv2(out)
out = self.conv3(out)
return out
if __name__ == '__main__':
model = net()
for k,v in model.state_dict().items():
print(k,"\t",v.shape)
输出:
D:\anaconda3\python.exe ./code_.py
conv1.0.weight tensor([[[[[-0.1776, -0.0824, -0.0484],
[-0.0416, -0.1229, 0.0133],
[-0.0035, -0.0472, 0.0650]],
... .... ....
[[ 0.0080, -0.1849, -0.0697],
[-0.0528, -0.0102, -0.0746],
[ 0.1616, 0.0512, 0.0249]]]]]) # 没有 require_grad
# D:\anaconda3\python.exe G:/me-zt/code_.py
# conv1.0.weight torch.Size([3, 1, 3, 3, 3])
# conv2.0.weight torch.Size([6, 3, 3, 3, 3])
# conv3.0.weight torch.Size([12, 6, 3, 3, 3])
# Process finished with exit code 0
是将 layer_name 与 layer_param 以打包成一个元祖然后再存到 list 当中。只保存可学习、可被更新的参数。model.named_parameters() 所存储的模型参数 tensor 的 require_grad 属性都是 True。常用于固定某层的参数是否被训练,通常是通过 model.named_parameters() 来获取参数设置 require_grad 属性。
例子:
# -*- coding: utf-8 -*-
import torch.nn as nn
class net(nn.Module):
def __init__(self):
super(net, self).__init__()
self.conv1 = nn.Sequential(
nn.Conv3d(1, 3,kernel_size=3, stride=1, padding=0, bias=False),
)
self.conv2 = nn.Sequential(
nn.Conv3d(3, 6, kernel_size=3, stride=1, padding=0, bias=False),
)
self.conv3 = nn.Sequential(
nn.Conv3d(6, 12,kernel_size=3, stride=1, padding=0, bias=False),
)
def forward(self, x):
out = self.conv1(x)
out = self.conv2(out)
out = self.conv3(out)
return out
if __name__ == '__main__':
model = net()
for k,v in model.named_parameters():
print(k,"\t",v)
输出:
D:\anaconda3\python.exe ./code_.py
conv1.0.weight Parameter containing:
tensor([[[[[-0.0079, 0.1026, 0.1596],
[-0.1321, -0.0128, 0.1632],
[-0.0125, -0.0934, -0.0056]],
... ... ...
[[ 0.0135, 0.1755, 0.0007],
[ 0.0541, 0.1801, -0.1160],
[-0.0295, -0.0584, 0.1403]]]]], requires_grad=True) # 有 require_grad
返回的只是参数,不包括 layer_name 。返回结果包含 require_grad ,且均为 Ture ,这主要是网络在创建时,默认参数都是需要学习的,即 require_grad 都是 True。
例子:
# -*- coding: utf-8 -*-
import torch.nn as nn
class net(nn.Module):
def __init__(self):
super(net, self).__init__()
self.conv1 = nn.Sequential(
nn.Conv3d(1, 3,kernel_size=3, stride=1, padding=0, bias=False),
)
self.conv2 = nn.Sequential(
nn.Conv3d(3, 6, kernel_size=3, stride=1, padding=0, bias=False),
)
self.conv3 = nn.Sequential(
nn.Conv3d(6, 12,kernel_size=3, stride=1, padding=0, bias=False),
)
def forward(self, x):
out = self.conv1(x)
out = self.conv2(out)
out = self.conv3(out)
return out
if __name__ == '__main__':
model = net()
for k in model.parameters():
print(k.shape)
输出:
D:\anaconda3\python.exe ./code_.py
Parameter containing:
tensor([[[[[-0.1761, 0.1335, -0.1649],
[ 0.0866, -0.0375, -0.0698],
[-0.0400, -0.1483, -0.1746]],
... ... ...
[[ 0.1708, -0.1187, 0.1409],
[ 0.1374, 0.0351, -0.0047],
[ 0.0362, -0.1237, 0.0445]]]]], requires_grad=True) # 默认为 Ture
注意:需要用到提取预训练模型的哪些层,那么自己创建的网络中,这些层的名字参数都不能改变,必须与之相同。但是,需要修改的层,名字不能与原来网络的相同。
import torchvision.models as models
# 创建model
resnet50 = models.resnet50(pretrained=True) # 创建预训练模型,并加载参数
net_later = net() # 创建我们的网络
# 读取网络参数
pretrained_dict = resnet50 ().state_dict() # 读取预训练模型参数
net_dict = net_later().state_dict() # 读取我们的网络参数
# 将pretrained_dict里不属于net_dict的键剔除掉
pretrained_dict = {k: v for k, v in pretrained_dict.items() if k in net_dict}
# 更新修改之后的net_dict
net_dict.update(pretrained_dict) # 将与 pretrained_dict 中 layer_name 相同的参数更新为 pretrained_dict 的
# 加载我们真正需要的state_dict
net_later.load_state_dict(net_dict)
model = net() # 创建自己的网络,该网络是修改之后的
# 读取修改之前自己模型保存的参数权重
logdir = "2021_08-24_14-56-58_"
NET_PARAMS_PATH = os.path.join(os.getcwd(), "log", logdir, "net_params(5).pkl")
net_params = torch.load(NET_PARAMS_PATH)
# 读取网络参数
model_dict = model.state_dict() # 读取修改之后的网络参数
# 将之前保存的模型参数(net_params )里不属于 net_dict 的键剔除掉
pretrained_dict = {k: v for k, v in net_params["model_state_dict"].items() if k in net_dict}
# 更新修改之后的 model_dict
model_dict.update(pretrained_dict)
# 加载我们真正需要的 state_dict
model.load_state_dict(net_dict)
model = net() # 创建自己的网络,该网络是修改之后的
# 读取修改之前自己模型保存的参数权重
logdir = "2021_08-24_14-56-58_"
NET_PARAMS_PATH = os.path.join(os.getcwd(), "log", logdir, "net_params(5).pkl")
net_params = torch.load(NET_PARAMS_PATH)
# 加载模型参数
model.load_state_dict(net_params['model_state_dict'], strict=False) # 加载时需要设置 strict=None 为 False 属性。这样加载时就会忽略加载某层参数名与创建的网络某一层参数名字的不同而引起的报错,此时该层的参数不会使用所保存的参数,以达到修改的效果。
如果载入的这些权重参数中,有些权重参数需要被更新,即固定不变,不参与训练,需要手动设置这些参数的梯度属性为 Fasle ,并且在 Optimizer 传参时筛选掉这些参数(注意顺序,不能改变):
代码:
model = net()
# 例如冻结 fc1 层的参数
for name, param in model.named_parameters():
if "fc1" in name:
param.requires_grad = False
# 定义一个 fliter ,只传入 requires_grad=True 的模型参数
optimizer = optim.SGD(filter(lambda p : p.requires_grad, model.parameters()), lr=1e-2)
训练一段时间后如果想再打开之前冻结的层,只要 model 的 reauire_grade 设置为 True 。
同时,不要忘记将优化器再重新加载一遍,否则虽然设置为 True ,依然还是固定训练。
for k,v in model.named_parameters():
v.requires_grad=True # 固定层打开
optimizer = optim.Adam(model.parameters(),lr=0.01)
scheduler = torch.optim.lr_scheduler.StepLR(optimizer,step_size=10, gamma=0.5)
此类情况,在训练过程中,模型的所有层参数都会被学习改变
model = net()
# 不冻结参数时
optimizer = optim.SGD(model.parameters(), lr=1e-2) # 将所有的参数传入
loss = nn.CrossEntropyLoss()
此类情况只要设置某层的 requires_grad=False ,虽然传入模型所有的参数,但仍然只更新requires_grad=True 的层的参数。
optimizer = optim.SGD(model.parameters(), lr=1e-2) # 将所有的参数传入
# 冻结 fc1 层参数
for name, param in model.named_parameters():
if "fc1" in name:
param.requires_grad = False
此种情况,只需要将 fc1 层的参数传入优化器即可。不需要将要冻结层参数的 requires_grad 属性设
置为 False 。
此时,只会更新优化器传入的参数,对于没有传入的参数可以求导,但是仍然不会更新参数。
# 情况一:优化器只传入 fc2 的参数
optimizer = optim.SGD(model.fc2.parameters(), lr=1e-2)
# 情况二:优化器只传入混合层中某一层的参数(依次写即可)
optimizer = optim.SGD(model.features.denseblock3.denselayer1.conv2_2.parameters(), lr=1e-2)
通过分析发现,应先设置冻结不更新参数的层,再在优化器里传入需要学习的参数的层。由此,可以节省显存(不将不更新的参数传入 Optimizer
)和提升速度(将不更新的参数的 requires_grad
设置为 False
,节省了计算这部分参数梯度的时间)。
此时,需要知道某一块层的名字:
for k, v in model.features.denseblock3.denselayer1.named_parameters():
print(k)
# 输出:
# D:\anaconda3\python.exe G:/me-zt/02.py
# DenseLayer.0.weight
# DenseLayer.0.bias
# DenseLayer.2.weight
# DenseLayer.3.weight
# DenseLayer.3.bias
# conv2_1.0.weight
# conv2_1.1.weight
# conv2_2.0.weight
# conv2_2.1.weight
# conv3.0.weight
# Process finished with exit code 0
假设要求 DenseLayer 层的学习率为0.001, 其余的学习率为0.01,那么在将参数传入优化器时:
# 读取 DenseLayer 层的参数并放入列表
ignored_params = list(map(id, model.features.denseblock3.denselayer1.DenseLayer.parameters()))
# 将该 DenseLayer 层参数忽略,提取其他层参数
base_params = filter(lambda p: id(p) not in ignored_params, model.parameters())
# 按不同的层设置优化器
optimizer = torch.optim.Adam([{'params':base_params,'lr':0.01},
{'params':model.features.denseblock3.denselayer1.DenseLayer.parameters()}],
lr=0.001, momentum=0.9)
注意:此时除 DenseLayerr 层参数的 learning_rate=0.001 外,其他参数的 learning_rate=0.01。 如果 list 中没有设置 lr 则应用 Adam 的 lr 属性。Adam的属性除了 lr, 其他参数都是共用的(例如 momentum )。
参考:
1、https://blog.csdn.net/qq_34351621/article/details/79967463
2、https://blog.csdn.net/qq_36429555/article/details/118547133
3、https://blog.csdn.net/qq_34108714/article/details/90169562
4、https://blog.csdn.net/weixin_41712499/article/details/110198423