当前位置:   article > 正文

pytorch 预训练模型冻结层+添加层+不同层学习率调整——以resnet50为例_resnet50预训练模型

resnet50预训练模型

前言

网上完整的教程比较少,很多讲的都是局部操作,比如:如何冻结层、如何添加层、如何调整不同层的学习率,一旦组合起来,就会发现总是有bug。我在这篇文章里,尽可能地把遇到的bug写一下。

步骤

先定义一个新的类,这个类用来构造自己需要的模型。

  1. """
  2. 参数:
  3. num_classes:自己的分类任务要分的类别数
  4. 代码构造:
  5. BackBone:pytorch官方的预训练模型
  6. add_block:需要添加的fc层
  7. """
  8. class Net(nn.Module):
  9. def __init__(self , num_classes = 14):
  10. super(Net, self).__init__()
  11. BackBone = torchvision.models.__dict__['resnet50'](pretrained=True)
  12. # BackBone = nn.Sequential(*list(model.children())[:-1]) # 这里有个坑!
  13. add_block = []
  14. add_block += [nn.Linear(1000, 512)]
  15. add_block += [nn.ReLU(True)]
  16. add_block += [nn.Dropout(0.15)]
  17. add_block += [nn.Linear(512, 128)]
  18. add_block += [nn.ReLU(True)]
  19. add_block += [nn.Linear(128, num_classes)]
  20. add_block = nn.Sequential(*add_block)
  21. self.BackBone = BackBone
  22. self.add_block = add_block
  23. def forward(self, x):
  24. x = self.BackBone(x)
  25. x = self.add_block(x)
  26. return x
  27. """定义一个实例,名叫model_new"""
  28. model_new = Net()

查看model_new的网络架构:

  1. for name, value in model_new.named_parameters():
  2. print(name)
  3. """
  4. 输出:
  5. BackBone.conv1.weight
  6. BackBone.bn1.weight
  7. BackBone.bn1.bias
  8. BackBone.layer1.0.conv1.weight # 注意这里,是BackBone.layer1
  9. BackBone.layer1.0.bn1.weight
  10. BackBone.layer1.0.bn1.bias
  11. BackBone.layer1.0.conv2.weight
  12. BackBone.layer1.0.bn2.weight
  13. BackBone.layer1.0.bn2.bias
  14. ···
  15. """

注意:经过上面的操作后,网络结构会有一些微妙的变化,如下:

  1. # 对原模型
  2. for name, child in model.named_children():
  3. print(name)
  4. """
  5. 输出:
  6. conv1
  7. bn1
  8. relu
  9. maxpool
  10. layer1
  11. layer2
  12. layer3
  13. layer4
  14. avgpool
  15. fc
  16. """
  17. # 对修改后的模型
  18. for name, child in model_new.named_children():
  19. print(name)
  20. """
  21. 输出:
  22. BackBone
  23. add_block
  24. """

因此,对于model_new,需要进一步查看:(继续用 . 操作,进入到BackBone里)

  1. for name, child in model_new.BackBone.named_children():
  2. print(name)
  3. """
  4. 输出:
  5. conv1
  6. bn1
  7. relu
  8. maxpool
  9. layer1
  10. layer2
  11. layer3
  12. layer4
  13. avgpool
  14. fc
  15. """

坑1:

网上比较流行的做法是,用这段代码来修改网络:

  1. model = torchvision.models.__dict__['resnet50'](pretrained=True)
  2. BackBone = nn.Sequential(*list(model.children())[:-1])
  3. """
  4. 输出:
  5. BackBone.0.weight
  6. BackBone.1.weight
  7. BackBone.1.bias
  8. BackBone.4.0.conv1.weight
  9. BackBone.4.0.bn1.weight
  10. BackBone.4.0.bn1.bias
  11. BackBone.4.0.conv2.weight
  12. BackBone.4.0.bn2.weight
  13. BackBone.4.0.bn2.bias
  14. ···
  15. """

这里是BackBone.4,这个.4就很迷,因为后面设置不同层学习率的话,需要用到
model_new.BackBone.谁.parameters(),问题出在这个“谁”身上,如果是layer4就没问题,但如果是4, model_new.BackBone.4就不符合Python语法规范,如下图:

(?)如果真要用这个方法,那么应该怎么定位到BackBone.4这里呢?求解答。 


 接着,冻结层操作,我需要冻结网络的前3个模块,只训练后面的模块:

  1. """
  2. 前面用model_new.BackBone.named_children()查看了有哪些“总的层”,那么就需要把这个当做标签。
  3. 比如,我只对最后一个模块(conv5),以及新添加的add_block做训练,那么就设置这些层的requires_grad = True
  4. """
  5. for name, child in model_new.named_children():
  6. if name in ['add_block']:
  7. for param in child.parameters():
  8. param.requires_grad = True
  9. for name, child in model_new.BackBone.named_children():
  10. if name in ['layer4','avgpool','fc']:
  11. for param in child.parameters():
  12. param.requires_grad = True
  13. else:
  14. for param in child.parameters():
  15. param.requires_grad = False
  16. print("模型冻结完成")

注意:.named_children()会忽略一些层, 比如最后的avgpool层,那么,我们可以直接看model_new长什么样子,然后把参数添加到列表里,如下图: 

 经过冻结操作后,查看一下所有层的冻结情况:

  1. for name, value in model_new.named_parameters():
  2. print(name, "\t冻结=\t",value.requires_grad)
  3. """
  4. 输出:
  5. BackBone.conv1.weight 冻结= False
  6. BackBone.bn1.weight 冻结= False
  7. BackBone.bn1.bias 冻结= False
  8. BackBone.layer1.0.conv1.weight 冻结= False
  9. ···
  10. add_block.3.weight 冻结= True
  11. add_block.3.bias 冻结= True
  12. add_block.5.weight 冻结= True
  13. add_block.5.bias 冻结= True
  14. """

接下来,设置不同层的学习率:(一般来说,进行微调的层的设置的小一些,新加入的层的学习率设置的大一些,比如后者是前者的10-100倍)

  1. """
  2. 这里有不少坑,见后面分析
  3. """
  4. optimizer = torch.optim.SGD([
  5. {'params': model.module.BackBone.fc.parameters(),'lr': 0.1},
  6. {'params': model.module.add_block.parameters(),'lr': 0.1},
  7. {'params': model.module.BackBone.layer4.parameters(),'lr': 0.01},
  8. ], momentum=0.9, weight_decay=1e-4)

坑2:如果用多GPU训练,会用到model=torch.nn.DataParallel(model)这句代码,那么需要把model.BackBone....改为model.module.BackBone....


 坑3:不能乱加 .parameters(),不然会报错。

网上关于如何冻结层、如何设置不同学习率的讲解,有不少用到这句代码:

filter(lambda p: ...... model_new.parameters())

如果把两个filter放到优化器里,那么就会得到这样的报错:

TypeError: __init__() got multiple values for argument 'lr'

这个错误的意思是,你定义了两个model_new.parameters(),但优化器只能接收一个。

例如:

  1. """ 代码块1 """
  2. for param in model_new.parameters():
  3. param.requires_grad = False
  4. for param in model_new.add_block.parameters():
  5. param.requires_grad = True
  6. """ 代码块2 """
  7. ignored_params = list(map(id, model_new.add_block.parameters()))
  8. base_params = filter(lambda p: id(p) not in ignored_params, model_new.parameters())
  9. optimizer = optim.SGD(
  10. # 对应代码块1
  11. filter(lambda p: p.requires_grad, model_new.parameters()),
  12. # 对应代码块2
  13. [
  14. {'params': base_params, 'lr': 0.01},
  15. {'params': model_new.add_block.parameters(), 'lr': 0.1},
  16. ],
  17. weight_decay=1e-5, momentum=0.9)

两个红框框都包含了model_new.parameters()了,所以就重复了。

简而言之,在没有找到合适解决办法之前,冻结层的操作不需要用 filter(...),设置学习率的时候用filter(...)就行。


 最后,可以训练了。

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

闽ICP备14008679号