当前位置:   article > 正文

Pytorch 预训练模型加载、修改网络结构并固定某层参数训练、不同层采用不同的学习率_预训练模型如何调整结构

预训练模型如何调整结构

目录

一、Pytorch中state_dict()、named_parameters()、model.parameter()、named_modules()区别

1、model.state_dict():

2、model.named_parameters():

3、model.parameter():

4、model.named_modules() 

二、Pytorch 载入预训练模型,并修改网络结构,固定某层参数学习

1、加载预训练模型、并修改网络结构

△ 本例子以Pytorch自带模型为例:

△ 本例子以自己保存的模型参数为例:

△ 修改某层后,快速加载之前的参数:

2、固定某层参数学习

以下为加深理解:

 1)、当不冻结层时

2)、冻结 fc1 层时

3)、只训练 fc1 层

总结:

三、对不同的层采用不同的学习率


一、Pytorch中state_dict()、named_parameters()、model.parameter()、named_modules()区别

1、model.state_dict():

        是将 layer_name 与 layer_param 以键的形式存储为 dict 。包含所有层的名字和参数,所存储的模型参数 tensor 的 require_grad 属性都是 False 。输出的值不包括 require_grad 。在固定某层时不能采用 model.state_dict() 来获取参数设置 require_grad 属性。

例子:

  1. # -*- coding: utf-8 -*-
  2. import torch.nn as nn
  3. class net(nn.Module):
  4. def __init__(self):
  5. super(net, self).__init__()
  6. self.conv1 = nn.Sequential(
  7. nn.Conv3d(1, 3,kernel_size=3, stride=1, padding=0, bias=False),
  8. )
  9. self.conv2 = nn.Sequential(
  10. nn.Conv3d(3, 6, kernel_size=3, stride=1, padding=0, bias=False),
  11. )
  12. self.conv3 = nn.Sequential(
  13. nn.Conv3d(6, 12,kernel_size=3, stride=1, padding=0, bias=False),
  14. )
  15. def forward(self, x):
  16. out = self.conv1(x)
  17. out = self.conv2(out)
  18. out = self.conv3(out)
  19. return out
  20. if __name__ == '__main__':
  21. model = net()
  22. for k,v in model.state_dict().items():
  23. print(k,"\t",v.shape)

输出:

  1. D:\anaconda3\python.exe ./code_.py
  2. conv1.0.weight tensor([[[[[-0.1776, -0.0824, -0.0484],
  3. [-0.0416, -0.1229, 0.0133],
  4. [-0.0035, -0.0472, 0.0650]],
  5. ... .... ....
  6. [[ 0.0080, -0.1849, -0.0697],
  7. [-0.0528, -0.0102, -0.0746],
  8. [ 0.1616, 0.0512, 0.0249]]]]]) # 没有 require_grad
  9. # D:\anaconda3\python.exe G:/me-zt/code_.py
  10. # conv1.0.weight torch.Size([3, 1, 3, 3, 3])
  11. # conv2.0.weight torch.Size([6, 3, 3, 3, 3])
  12. # conv3.0.weight torch.Size([12, 6, 3, 3, 3])
  13. # Process finished with exit code 0

2、model.named_parameters():

        是将 layer_name 与 layer_param 以打包成一个元祖然后再存到 list 当中。只保存可学习、可被更新的参数。model.named_parameters() 所存储的模型参数 tensor 的 require_grad 属性都是   True常用于固定某层的参数是否被训练,通常是通过 model.named_parameters() 来获取参数设置 require_grad 属性。

例子:

  1. # -*- coding: utf-8 -*-
  2. import torch.nn as nn
  3. class net(nn.Module):
  4. def __init__(self):
  5. super(net, self).__init__()
  6. self.conv1 = nn.Sequential(
  7. nn.Conv3d(1, 3,kernel_size=3, stride=1, padding=0, bias=False),
  8. )
  9. self.conv2 = nn.Sequential(
  10. nn.Conv3d(3, 6, kernel_size=3, stride=1, padding=0, bias=False),
  11. )
  12. self.conv3 = nn.Sequential(
  13. nn.Conv3d(6, 12,kernel_size=3, stride=1, padding=0, bias=False),
  14. )
  15. def forward(self, x):
  16. out = self.conv1(x)
  17. out = self.conv2(out)
  18. out = self.conv3(out)
  19. return out
  20. if __name__ == '__main__':
  21. model = net()
  22. for k,v in model.named_parameters():
  23. print(k,"\t",v)

输出:

  1. D:\anaconda3\python.exe ./code_.py
  2. conv1.0.weight Parameter containing:
  3. tensor([[[[[-0.0079, 0.1026, 0.1596],
  4. [-0.1321, -0.0128, 0.1632],
  5. [-0.0125, -0.0934, -0.0056]],
  6. ... ... ...
  7. [[ 0.0135, 0.1755, 0.0007],
  8. [ 0.0541, 0.1801, -0.1160],
  9. [-0.0295, -0.0584, 0.1403]]]]], requires_grad=True) # 有 require_grad

3、model.parameter():

        返回的只是参数,不包括 layer_name 。返回结果包含 require_grad ,且均为 Ture ,这主要是网络在创建时,默认参数都是需要学习的,即 require_grad 都是 True。

例子:

  1. # -*- coding: utf-8 -*-
  2. import torch.nn as nn
  3. class net(nn.Module):
  4. def __init__(self):
  5. super(net, self).__init__()
  6. self.conv1 = nn.Sequential(
  7. nn.Conv3d(1, 3,kernel_size=3, stride=1, padding=0, bias=False),
  8. )
  9. self.conv2 = nn.Sequential(
  10. nn.Conv3d(3, 6, kernel_size=3, stride=1, padding=0, bias=False),
  11. )
  12. self.conv3 = nn.Sequential(
  13. nn.Conv3d(6, 12,kernel_size=3, stride=1, padding=0, bias=False),
  14. )
  15. def forward(self, x):
  16. out = self.conv1(x)
  17. out = self.conv2(out)
  18. out = self.conv3(out)
  19. return out
  20. if __name__ == '__main__':
  21. model = net()
  22. for k in model.parameters():
  23. print(k.shape)

输出:

  1. D:\anaconda3\python.exe ./code_.py
  2. Parameter containing:
  3. tensor([[[[[-0.1761, 0.1335, -0.1649],
  4. [ 0.0866, -0.0375, -0.0698],
  5. [-0.0400, -0.1483, -0.1746]],
  6. ... ... ...
  7. [[ 0.1708, -0.1187, 0.1409],
  8. [ 0.1374, 0.0351, -0.0047],
  9. [ 0.0362, -0.1237, 0.0445]]]]], requires_grad=True) # 默认为 Ture

4、model.named_modules() 

        返回每一层模型的名字和结构:

  1. # -*- coding: utf-8 -*-
  2. import torch.nn as nn
  3. class net(nn.Module):
  4. def __init__(self):
  5. super(net, self).__init__()
  6. self.conv1 = nn.Sequential(
  7. nn.Conv3d(1, 3,kernel_size=3, stride=1, padding=0, bias=False),
  8. )
  9. self.conv2 = nn.Sequential(
  10. nn.Conv3d(3, 6, kernel_size=3, stride=1, padding=0, bias=False),
  11. )
  12. self.conv3 = nn.Sequential(
  13. nn.Conv3d(6, 12,kernel_size=3, stride=1, padding=0, bias=False),
  14. )
  15. def forward(self, x):
  16. out = self.conv1(x)
  17. out = self.conv2(out)
  18. out = self.conv3(out)
  19. return out
  20. if __name__ == '__main__':
  21. model = net()
  22. for name, module in model.named_modules():
  23. print(name)

输出:

  1. # D:/02.py"
  2. #
  3. # conv1
  4. # conv1.0
  5. # conv2
  6. # conv2.0
  7. # conv3
  8. # conv3.0

二、Pytorch 载入预训练模型,并修改网络结构,固定某层参数学习

        注意:需要用到提取预训练模型的哪些层,那么自己创建的网络中,这些层的名字参数都不能改变,必须与之相同。但是,需要修改的层,名字不能与原来网络的相同。

1、加载预训练模型、并修改网络结构

△ 本例子以Pytorch自带模型为例:

  1. import torchvision.models as models
  2. # 创建model
  3. resnet50 = models.resnet50(pretrained=True) # 创建预训练模型,并加载参数
  4. net_later = net() # 创建我们的网络
  5. # 读取网络参数
  6. pretrained_dict = resnet50 ().state_dict() # 读取预训练模型参数
  7. net_dict = net_later().state_dict() # 读取我们的网络参数
  8. # 将pretrained_dict里不属于net_dict的键剔除掉
  9. pretrained_dict = {k: v for k, v in pretrained_dict.items() if k in net_dict}
  10. # 更新修改之后的net_dict
  11. net_dict.update(pretrained_dict) # 将与 pretrained_dict 中 layer_name 相同的参数更新为 pretrained_dict 的
  12. # 加载我们真正需要的state_dict
  13. net_later.load_state_dict(net_dict)

△ 本例子以自己保存的模型参数为例:

  1. model = net() # 创建自己的网络,该网络是修改之后的
  2. # 读取修改之前自己模型保存的参数权重
  3. logdir = "2021_08-24_14-56-58_"
  4. NET_PARAMS_PATH = os.path.join(os.getcwd(), "log", logdir, "net_params(5).pkl")
  5. net_params = torch.load(NET_PARAMS_PATH)
  6. # 读取网络参数
  7. model_dict = model.state_dict() # 读取修改之后的网络参数
  8. # 将之前保存的模型参数(net_params )里不属于 net_dict 的键剔除掉
  9. pretrained_dict = {k: v for k, v in net_params["model_state_dict"].items() if k in net_dict}
  10. # 更新修改之后的 model_dict
  11. model_dict.update(pretrained_dict)
  12. # 加载我们真正需要的 state_dict
  13. model.load_state_dict(net_dict)

△ 修改某层后,快速加载之前的参数:

  1. model = net() # 创建自己的网络,该网络是修改之后的
  2. # 读取修改之前自己模型保存的参数权重
  3. logdir = "2021_08-24_14-56-58_"
  4. NET_PARAMS_PATH = os.path.join(os.getcwd(), "log", logdir, "net_params(5).pkl")
  5. net_params = torch.load(NET_PARAMS_PATH)
  6. # 加载模型参数
  7. model.load_state_dict(net_params['model_state_dict'], strict=False) # 加载时需要设置 strict=None 为 False 属性。这样加载时就会忽略加载某层参数名与创建的网络某一层参数名字的不同而引起的报错,此时该层的参数不会使用所保存的参数,以达到修改的效果。

2、固定某层参数学习

        如果载入的这些权重参数中,有些权重参数需要被更新,即固定不变,不参与训练,需要手动设置这些参数的梯度属性为 Fasle ,并且在 Optimizer 传参时筛选掉这些参数注意顺序,不能改变)

        代码:

  1. model = net()
  2. # 例如冻结 fc1 层的参数
  3. for name, param in model.named_parameters():
  4. if "fc1" in name:
  5. param.requires_grad = False
  6. # 定义一个 fliter ,只传入 requires_grad=True 的模型参数
  7. optimizer = optim.SGD(filter(lambda p : p.requires_grad, model.parameters()), lr=1e-2)

        训练一段时间后如果想再打开之前冻结的层,只要 model 的 reauire_grade 设置为 True 。
同时,不要忘记将优化器再重新加载一遍,否则虽然设置为 True ,依然还是固定训练。

  1. for k,v in model.named_parameters():
  2. v.requires_grad=True # 固定层打开
  3. optimizer = optim.Adam(model.parameters(),lr=0.01)
  4. scheduler = torch.optim.lr_scheduler.StepLR(optimizer,step_size=10, gamma=0.5)

        或者用下面的代码更好

  1. if epoch==50:
  2. for parameter in deeplabv3.classifier.parameters():
  3. if parameter.requires_grad==False:
  4. parameter.requires_grad = True
  5. optimizer.add_param_group({'params': parameter})
  6. '''
  7. 冻结的是deeplabv3的classifier层中的卷积,所以只循环这一层即可。
  8. 其实也可以直接optimizer.add_param_group({'params': deeplabv3.classifier.xx.parameters()})
  9. 但是,如果有数字就只能用上面的方法
  10. '''

        冻结时的网络层名如何查看

  1. for name, param in deeplabv3.named_parameters():
  2. print(name)
  3. # D:\Anaconda3\python.exe "D:/03.py"
  4. # backbone.conv1.weight
  5. # backbone.bn1.weight
  6. # backbone.bn1.bias
  7. # backbone.layer1.0.conv1.weight
  8. # backbone.layer1.0.bn1.weight
  9. # backbone.layer1.0.bn1.bias
  10. # backbone.layer1.0.conv2.weight
  11. # backbone.layer1.0.bn2.weight
  12. # backbone.layer1.0.bn2.bias
  13. # backbone.layer1.0.conv3.weight
  14. # backbone.layer1.0.bn3.weight
  15. # backbone.layer1.0.bn3.bias
  16. # backbone.layer1.0.downsample.0.weight
  17. # backbone.layer1.0.downsample.1.weight
  18. # backbone.layer1.0.downsample.1.bias
  19. # backbone.layer1.1.conv1.weight

以下为加深理解:

 1)、当不冻结层时

       此类情况,在训练过程中,模型的所有层参数都会被学习改变

  1. model = net()
  2. # 不冻结参数时
  3. optimizer = optim.SGD(model.parameters(), lr=1e-2) # 将所有的参数传入
  4. loss = nn.CrossEntropyLoss()

2)、冻结 fc1 层时

        此类情况只要设置某层的 requires_grad=False ,虽然传入模型所有的参数,但仍然只更新requires_grad=True 的层的参数。

  1. optimizer = optim.SGD(model.parameters(), lr=1e-2) # 将所有的参数传入
  2. # 冻结 fc1 层参数
  3. for name, param in model.named_parameters():
  4. if "fc1" in name:
  5. param.requires_grad = False

3)、只训练 fc1 层

        此种情况,只需要将 fc1 层的参数传入优化器即可。不需要将要冻结层参数的 requires_grad 属性设置为 False 。此时,只会更新优化器传入的参数,对于没有传入的参数可以求导,但是仍然不会更新参数。

  1. # 情况一:优化器只传入 fc2 的参数
  2. optimizer = optim.SGD(model.fc2.parameters(), lr=1e-2)
  3. # 情况二:优化器只传入混合层中某一层的参数(依次写即可)
  4. optimizer = optim.SGD(model.features.denseblock3.denselayer1.conv2_2.parameters(), lr=1e-2)

总结:

        通过分析发现,应先设置冻结不更新参数的层,再在优化器里传入需要学习的参数的层。由此,可以节省显存(不将不更新的参数传入 Optimizer)和提升速度(将不更新的参数的 requires_grad 设置为 False,节省了计算这部分参数梯度的时间)。

三、对不同的层采用不同的学习率

        此时,需要知道某一块层的名字:

  1. for k, v in model.features.denseblock3.denselayer1.named_parameters():
  2. print(k)
  3. # 输出:
  4. # D:\anaconda3\python.exe G:/me-zt/02.py
  5. # DenseLayer.0.weight
  6. # DenseLayer.0.bias
  7. # DenseLayer.2.weight
  8. # DenseLayer.3.weight
  9. # DenseLayer.3.bias
  10. # conv2_1.0.weight
  11. # conv2_1.1.weight
  12. # conv2_2.0.weight
  13. # conv2_2.1.weight
  14. # conv3.0.weight
  15. # Process finished with exit code 0

        假设要求 DenseLayer 层的学习率为0.001, 其余的学习率为0.01,那么在将参数传入优化器时:

  1. # 读取 DenseLayer 层的参数并放入列表
  2. ignored_params = list(map(id, model.features.denseblock3.denselayer1.DenseLayer.parameters()))
  3. # 将该 DenseLayer 层参数忽略,提取其他层参数
  4. base_params = filter(lambda p: id(p) not in ignored_params, model.parameters())
  5. # 按不同的层设置优化器
  6. optimizer = torch.optim.Adam([{'params':base_params,'lr':0.01},
  7. {'params':model.features.denseblock3.denselayer1.DenseLayer.parameters()}],
  8. 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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/羊村懒王/article/detail/217624?site
推荐阅读
相关标签
  

闽ICP备14008679号