赞
踩
前言
网上完整的教程比较少,很多讲的都是局部操作,比如:如何冻结层、如何添加层、如何调整不同层的学习率,一旦组合起来,就会发现总是有bug。我在这篇文章里,尽可能地把遇到的bug写一下。
步骤
先定义一个新的类,这个类用来构造自己需要的模型。
- """
- 参数:
- num_classes:自己的分类任务要分的类别数
- 代码构造:
- BackBone:pytorch官方的预训练模型
- add_block:需要添加的fc层
- """
- class Net(nn.Module):
- def __init__(self , num_classes = 14):
- super(Net, self).__init__()
- BackBone = torchvision.models.__dict__['resnet50'](pretrained=True)
- # BackBone = nn.Sequential(*list(model.children())[:-1]) # 这里有个坑!
- add_block = []
- add_block += [nn.Linear(1000, 512)]
- add_block += [nn.ReLU(True)]
- add_block += [nn.Dropout(0.15)]
- add_block += [nn.Linear(512, 128)]
- add_block += [nn.ReLU(True)]
- add_block += [nn.Linear(128, num_classes)]
- add_block = nn.Sequential(*add_block)
-
- self.BackBone = BackBone
- self.add_block = add_block
-
- def forward(self, x):
-
- x = self.BackBone(x)
- x = self.add_block(x)
-
- return x
-
- """定义一个实例,名叫model_new"""
- model_new = Net()
查看model_new的网络架构:
- for name, value in model_new.named_parameters():
- print(name)
-
- """
- 输出:
- BackBone.conv1.weight
- BackBone.bn1.weight
- BackBone.bn1.bias
- BackBone.layer1.0.conv1.weight # 注意这里,是BackBone.layer1
- BackBone.layer1.0.bn1.weight
- BackBone.layer1.0.bn1.bias
- BackBone.layer1.0.conv2.weight
- BackBone.layer1.0.bn2.weight
- BackBone.layer1.0.bn2.bias
- ···
- """
注意:经过上面的操作后,网络结构会有一些微妙的变化,如下:
- # 对原模型
- for name, child in model.named_children():
- print(name)
-
- """
- 输出:
- conv1
- bn1
- relu
- maxpool
- layer1
- layer2
- layer3
- layer4
- avgpool
- fc
- """
-
- # 对修改后的模型
- for name, child in model_new.named_children():
- print(name)
-
- """
- 输出:
- BackBone
- add_block
- """
因此,对于model_new,需要进一步查看:(继续用 . 操作,进入到BackBone里)
- for name, child in model_new.BackBone.named_children():
- print(name)
-
- """
- 输出:
- conv1
- bn1
- relu
- maxpool
- layer1
- layer2
- layer3
- layer4
- avgpool
- fc
- """
坑1:
网上比较流行的做法是,用这段代码来修改网络:
- model = torchvision.models.__dict__['resnet50'](pretrained=True)
- BackBone = nn.Sequential(*list(model.children())[:-1])
-
- """
- 输出:
- BackBone.0.weight
- BackBone.1.weight
- BackBone.1.bias
- BackBone.4.0.conv1.weight
- BackBone.4.0.bn1.weight
- BackBone.4.0.bn1.bias
- BackBone.4.0.conv2.weight
- BackBone.4.0.bn2.weight
- BackBone.4.0.bn2.bias
- ···
- """
这里是BackBone.4,这个.4就很迷,因为后面设置不同层学习率的话,需要用到
model_new.BackBone.谁.parameters(),问题出在这个“谁”身上,如果是layer4就没问题,但如果是4, model_new.BackBone.4就不符合Python语法规范,如下图:
(?)如果真要用这个方法,那么应该怎么定位到BackBone.4这里呢?求解答。
接着,冻结层操作,我需要冻结网络的前3个模块,只训练后面的模块:
- """
- 前面用model_new.BackBone.named_children()查看了有哪些“总的层”,那么就需要把这个当做标签。
- 比如,我只对最后一个模块(conv5),以及新添加的add_block做训练,那么就设置这些层的requires_grad = True
- """
-
- for name, child in model_new.named_children():
- if name in ['add_block']:
- for param in child.parameters():
- param.requires_grad = True
-
- for name, child in model_new.BackBone.named_children():
- if name in ['layer4','avgpool','fc']:
- for param in child.parameters():
- param.requires_grad = True
- else:
- for param in child.parameters():
- param.requires_grad = False
- print("模型冻结完成")
注意:.named_children()会忽略一些层, 比如最后的avgpool层,那么,我们可以直接看model_new长什么样子,然后把参数添加到列表里,如下图:
经过冻结操作后,查看一下所有层的冻结情况:
- for name, value in model_new.named_parameters():
- print(name, "\t冻结=\t",value.requires_grad)
-
- """
- 输出:
- BackBone.conv1.weight 冻结= False
- BackBone.bn1.weight 冻结= False
- BackBone.bn1.bias 冻结= False
- BackBone.layer1.0.conv1.weight 冻结= False
- ···
- add_block.3.weight 冻结= True
- add_block.3.bias 冻结= True
- add_block.5.weight 冻结= True
- add_block.5.bias 冻结= True
- """
接下来,设置不同层的学习率:(一般来说,进行微调的层的设置的小一些,新加入的层的学习率设置的大一些,比如后者是前者的10-100倍)
- """
- 这里有不少坑,见后面分析
- """
-
- optimizer = torch.optim.SGD([
- {'params': model.module.BackBone.fc.parameters(),'lr': 0.1},
- {'params': model.module.add_block.parameters(),'lr': 0.1},
- {'params': model.module.BackBone.layer4.parameters(),'lr': 0.01},
- ], 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 """
- for param in model_new.parameters():
- param.requires_grad = False
- for param in model_new.add_block.parameters():
- param.requires_grad = True
-
- """ 代码块2 """
- ignored_params = list(map(id, model_new.add_block.parameters()))
- base_params = filter(lambda p: id(p) not in ignored_params, model_new.parameters())
-
-
- optimizer = optim.SGD(
-
- # 对应代码块1
- filter(lambda p: p.requires_grad, model_new.parameters()),
-
- # 对应代码块2
- [
- {'params': base_params, 'lr': 0.01},
- {'params': model_new.add_block.parameters(), 'lr': 0.1},
- ],
-
- weight_decay=1e-5, momentum=0.9)
两个红框框都包含了model_new.parameters()了,所以就重复了。
简而言之,在没有找到合适解决办法之前,冻结层的操作不需要用 filter(...),设置学习率的时候用filter(...)就行。
最后,可以训练了。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。