目录
  1. 1. 1. 直接加载预训练模型
  2. 2. 2. 修改某一层
  3. 3. 3.加载部分预训练模型,可用于修改现有模型的结构
  4. 4. 4. 微调FineTune
pytorch之加载欲训练模型

Pytorch有很多方便易用的包,今天要谈的是torchvision包,它包括3个子包,分别是: torchvison.datasets ,torchvision.models ,torchvision.transforms ,分别是预定义好的数据集(比如MNIST、CIFAR10等)、预定义好的经典网络结构(比如AlexNet、VGG、ResNet等)和预定义好的数据增强方法(比如Resize、ToTensor等)。这些方法可以直接调用,简化我们建模的过程,也可以作为我们学习或构建新的模型的参考。

1. 直接加载预训练模型

1
2
3
4

import torchvision.models as models

resnet50 = models.resnet50(pretrained=True)

这样就导入了Resnet50的预训练模型了,如果只要网络结构,不需要预训练的参数来初始化,就填FALSE

值得注意的是,当你将模型设置为预训练模式的时候,模型输入的图片最好是经过相同方式归一化的3通道数据,即(3* H* W)H与W至少是224,图像必须被加载到(0,1)的范围内,然后再归一化到mean = [0.485, 0.456, 0.406]且std=[0.229,0.224,0.225]。可以使用pytorch自带的归一化函数

normalize = transforms.Normalize(mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225])
Pytorch加载模型后,模型有两个状态,model.train()or model.eval()

当你在训练模型的时候,将模型置为model.train()

当你在测试模型的时候,将模型置为 model.eval(),设置为此种模式的时候,模型内部的权值不会改变.

2. 修改某一层

修改之后的模型的参数一般是默认初始化好了的,均值为0方差为1

1
2
3
4
5
6
7
 以resnet为例,默认的是ImageNet的1000类,比如我们要做二分类,分类猫和狗

resnet.fc = nn.Linear(2048, 2)
resnet 第一层卷积的卷积核是7,我们可能想改成5,那么可以通过以下方法修改:

#修改需要有理论依据,计算featuremap维度使之匹配。
resnet.conv1 = nn.Conv2d(3, 64,kernel_size=5, stride=2, padding=3, bias=False)

如果是只想修改Resnet中的最后一层FC

1
2
3
4
5
6
7
8
9
# coding=UTF-8
import torchvision.models as models

#调用模型
model = models.resnet50(pretrained=True)
#提取fc层中固定的参数
fc_features = model.fc.in_features
#修改类别为9
model.fc = nn.Linear(fc_features, 9

如果是想修改Densenet中的分类器,可以通过获得原模型中该层的名字来进行修改

如果层在sequential中。因为sequential类型没有定义setitem,只有getitem 所以不能直接获取某一层并进行修改。就是sequential[0]=nn.Linear(fc_features, 9)是会报错的。

1
2
3
self.densenet161 = models.densenet161(pretrained=True)
self.densenet161.classifier = torch.nn.Linear(2208, 4)#classifier由来是在原模型中分类层的名字
#同理,想修改Resnet中的最后一层分类层的话换成model.fc

可以自己对初始化方式做修改:

1
2
3
# Initialize the weights/bias with identity transformation 额外的初始化
self.fc_loc[2].weight.data.zero_()
self.fc_loc[2].bias.data.copy_(torch.tensor([1, 0, 0, 0, 1, 0], dtype=torch.float))

3.加载部分预训练模型,可用于修改现有模型的结构

新定义的网络不能和pretrained的网络有相同名字的层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
对于具体的任务,很难保证模型和公开的模型完全一样,但是预训练模型的参数确实有助于提高训练的准确率,为了结合二者的优点,就需要我们加载部分预训练模型。

#加载model,model是自己定义好的模型
resnet50 = models.resnet50(pretrained=True)
model =Net(...)

#读取参数
pretrained_dict =resnet50.state_dict()
model_dict = model.state_dict()

#将pretrained_dict里不属于model_dict的键剔除掉
pretrained_dict = {k: v for k, v in pretrained_dict.items() if k in model_dict}

# 更新现有的model_dict ,对其中相应的key值对应的值进行跟新,在model_dict中保留了Resnet部分用的到的参数
model_dict.update(pretrained_dict)

# 加载我们真正需要的state_dict
model.load_state_dict(model_dict)
# 冻结部分模型参数,进行fine-tuning
# 第一种方式
for p in freeze.parameters(): # 将需要冻结的参数的 requires_grad 设置为 False
p.requires_grad = False
for p in no_freeze.parameters(): # 将fine-tuning 的参数的 requires_grad 设置为 True
p.requires_grad = True
optimizer.SGD(filter(lambda p: p.requires_grad, model.parameters()), lr=1e-3) # 将需要 fine-tuning 的参数放入optimizer 中

# 第二种方式
optim_param = []
for p in freeze.parameters(): # 将需要冻结的参数的 requires_grad 设置为 False
p.requires_grad = False
for p in no_freeze.parameters(): # 将fine-tuning 的参数的 requires_grad 设置为 True
p.requires_grad = True
optim_param.append(p)
optimizer.SGD(optim_param, lr=1e-3) # 将需要 fine-tuning 的参数放入optimizer 中

删除原本模型中的层,并添加新层:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Net(nn.Module):
def __init__(self , model):
super(Net, self).__init__()
#一个继承nn.module的model它包含一个叫做children()的函数,这个函数可以用来提取出model每一层的网络结构,
#在此基础上进行修改即可,修改方法如下(去除后两层):
self.resnet_layer = nn.Sequential(*list(model.children())[:-2])

self.transion_layer = nn.ConvTranspose2d(2048, 2048, kernel_size=14, stride=3)
self.pool_layer = nn.MaxPool2d(32)
self.Linear_layer = nn.Linear(2048, 8)

def forward(self, x):
x = self.resnet_layer(x)

x = self.transion_layer(x)

x = self.pool_layer(x)

x = x.view(x.size(0), -1)

x = self.Linear_layer(x)

return x
  1. 加载自己的模型

其实这个是保存和恢复模型,比如我们训练好的模型保存,然后加载用于测试。

方法一(推荐):

第一种方法也是官方推荐的方法,只保存和恢复模型中的参数。

使用这种方法,我们需要自己导入模型的结构信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
1)保存

torch.save(model.state_dict(), PATH)

#example
torch.save(resnet50.state_dict(),'ckp/model.pth')
2)恢复

model = ModelClass(*args, **kwargs)
model.load_state_dict(torch.load(PATH))

#example
resnet=resnet50(pretrained=True)
resnet.load_state_dict(torch.load('ckp/model.pth'))

方法二:

1
2
3
4
5
6
7
8
使用这种方法,将会保存模型的参数和结构信息。

(1)保存

torch.save (model, PATH)
(2)恢复

model = torch.load(PATH)

模型训练知识点:

with torch.no_grad():PyTorch 将不再计算梯度,这将使得模型 forward 的时候,显存的需求大幅减少,速度大幅提高 。一般用在测试的时候。

4. 微调FineTune

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import torch.optim as optim
import torch.nn as nn
# 局部微调
# 有时候我们加载了训练模型后,只想调节最后的几层,
# 其他层不训练。其实不训练也就意味着不进行梯度计算,PyTorch中提供的requires_grad使得对训练的控制变得非常简单。
model = torchvision.models.resnet18(pretrained=True)
for param in model.parameters():
param.requires_grad = False
# 替换最后的全连接层, 改为训练100类
# 新构造的模块的参数默认requires_grad为True
model.fc = nn.Linear(512, 100)

# 只优化最后的分类层
optimizer = optim.SGD(model.fc.parameters(), lr=1e-2, momentum=0.9)
###########################################################################################
# 全局微调
# 有时候我们需要对全局都进行finetune,只不过我们希望改换过的层和其他层的学习速率不一样,
# 这时候我们可以把其他层和新层在optimizer中单独赋予不同的学习速率。比如:


ignored_params = list(map(id, model.fc.parameters()))#layerfc need to be trained
base_params = filter(lambda p: id(p) not in ignored_params,model.parameters())
#this is the new way to use Optimd 通常预训练层的学习率会低一些,下方代码中预训练层fc的学习率0.001,其余层0.01
optimizer = optim.SGD([
{'params': base_params},
{'params': model.fc.parameters(), 'lr': 1e-3}
], lr=1e-2, momentum=0.9)
文章作者: HazardFY
文章链接: http://hazardfy.github.io/2019/11/16/pytorch之加载欲训练模型/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 HazardFY's BLOG
打赏
  • 微信
  • 支付寶