- resnet
model = models.resnet50(pretrained=True) - vgg
model = models.vgg16(pretrained=True)
2、预训练模型的修改
在实际的应用中,往往不能直接应用导出的网络模型,均需要对网络的模型进行一定的修改才能使用,例如在图像分类任务中,我们任务的分类数目不一定刚好与imagenet数据集模型类别一致(1000类),在使用时就需要自己对模型参数进行修改(或者直接修改最后一层,然后自己建立合适的层进行自己算法的分类操作)。 - -- coding:utf-8 --
- 调用模型
model = models.resnet50(pretrained=True,num_classes = 10)
print(model)
将最后一层fc的参数直接进行修改,对类的属性进行直接修改,替换掉fc这一层: - -- coding:utf-8 --
- 调用模型
model = models.resnet50(pretrained=True)
#提取fc层中固定的参数(这一层的输入节点数目
fc_features = model.fc.in_features
#修改类别为9,(直接对类的属性进行修改)
model.fc = nn.Linear(fc_features, 10)
2)增减卷积层
对于前一种方法,仅适用于在分类问题时对简单层进行修改,但是对于目标检测想要利用预训练模型进行fine-tune该怎么办呢?这就要学会利用增减卷积层进行预训练模型的使用了。这两天刚好需要 修改一个基础特征提取的网络结构,但是自己的数据集又太小,直接修改网络从头训练容易出现过拟合,必须使用预训练的网络模型进行训练,就研究了一下使用方法,总结如下: - -- coding:utf-8 --
- 然后读出预训练模型参数以resnet152为例,我不是利用程序下载的,我是习惯了下载好存储在文件夹中
pretrained_dict = torch.load(save_path)
model_dict = net.state_dict() (读出搭建的网络的参数,以便后边更新之后初始化) - 更新现有的model_dict的值
model_dict.update(pretrained_dict) - 加载模型需要的参数
net.load_state_dict(model_dict) - torch.nn.init.uniform(tensor, a=0, b=1)
#从均匀分布U(a, b)中生成值,填充输入的张量或变量
#参数:
#tensor - n维的torch.Tensor
#a - 均匀分布的下界
#b - 均匀分布的上界
#例子
w = torch.Tensor(3, 5)
nn.init.uniform(w) - torch.nn.init.normal(tensor, mean=0, std=1)
#从给定均值和标准差的正态分布N(mean, std)中生成值,填充输入的张量或变量
#参数:
#tensor – n维的torch.Tensor
#mean – 正态分布的均值
#std – 正态分布的标准差
#例子
w = torch.Tensor(3, 5)
nn.init.normal(w) - torch.nn.init.constant(tensor, val)
#用val的值填充输入的张量或变量 - torch.nn.init.eye_(tensor)
#用单位矩阵来填充2维输入张量或变量。在线性层尽可能多的保存输入特性 - torch.nn.init.xaviernormal(tensor, gain=1)
#根据Glorot, X.和Bengio, Y. 于2010年在“Understanding the difficulty of training deep feedforward neural networks”中描述的方法,用一个正态分布生成值,填充输入的张量或变量。结果张量中的值采样自均值为0,标准差为gain * sqrt(2/(fan_in + fan_out))的正态分布。也被称为Glorot initialisation - torch.nn.init.kaiminguniform(tensor, a=0, mode=’fanin’)
#根据He, K等人于2015年在“Delving deep into rectifiers: Surpassing human-level performance on ImageNet classification”中描述的方法,用一个均匀分布生成值,填充输入的张量或变量。结果张量中的值采样自U(-bound, bound),其中bound = sqrt(2/((1 + a^2) fanin)) sqrt(3)。也被称为He initialisation.
#tensor – n维的torch.Tensor或autograd.Variable
#a -这层之后使用的rectifier的斜率系数(ReLU的默认值为0)
#mode -可以为“fan_in”(默认)或“fan_out”。“fan_in”保留前向传播时权值方差的量级,“fan_out”保留反向传播时的量级。 - tensor – n维的torch.Tensor或 autograd.Variable
#a -这层之后使用的rectifier的斜率系数(ReLU的默认值为0)
#mode -可以为“fan_in”(默认)或“fan_out”。“fan_in”保留前向传播时权值方差的量级,“fan_out”保留反向传播时的量级。


微调的部分使用更小的学习率:
params_1x = [param for name, param in net.named_parameters()if name not in ["fc.weight", "fc.bias"]]trainer = torch.optim.SGD([{'params': params_1x},{'params': net.fc.parameters(),'lr': learning_rate * 10}],lr=learning_rate, weight_decay=0.001)
如果不采用torchvision.models库里的模型,而是从外部提供的模型进行fine tuning,需要怎么操作?谢谢
首先你要构建一个和你所说的外部模型一样结构的模型(你想要载入的那部分要一致,无需载入的部分如最后的输出层倒无所谓),然后加载那个模型预训练的state_dict,要注意的是你构建的模型与预训练的模型各层的命名要一致,否则load_state_dict会报错。load_state_dict中参数设置strict=False,仅加载一致部分(如下代码)# 构建模型# Build your model# 加载预训练的state_dictpretrained_state_dict = torch.load(pretrained_path)# 检验参数数量,确保能匹配matched_state_dict = {k: v for k, v in pretrained_state_dict.items()if model.state_dict()[k].numel() == v.numel()}# 不严格载入,加载模型中一致的部分model.load_state_dict(matched_state_dict, strict=False)# 后续微调# To be implemented
ImageNet数据集中有一个“热狗”类别。我们可以通过以下代码获取其输出层中的相应权重参数,但是我们怎样才能利用这个权重参数?
weight = pretrained_net.fc.weighthotdog_w = torch.split(weight.data, 1, dim=0)[934]hotdog_w.shape
可以直接使用预训练模型,但是它的最终输出不是特征,而是每个类别的得分。本文以resnet18为例。
import torchvision
model = torchvision.models.resnet18(pretrained=True)
为了使用预训练模型提取特征,需要自己将网络复制过来,进行修改。
import torch
import torch.nn as nn
from torchvision.models.resnet import Bottleneck, BasicBlock, conv1x1, conv3x3
class MyResNet(nn.Module):
def init(self, block, layers, num_classes=1000, zero_init_residual=False,
groups=1, width_per_group=64, replace_stride_with_dilation=None,
norm_layer=None):
super(MyResNet, self).init()
if norm_layer is None:
norm_layer = nn.BatchNorm2d
self._norm_layer = norm_layer
self.inplanes = 64self.dilation = 1if replace_stride_with_dilation is None:# each element in the tuple indicates if we should replace# the 2x2 stride with a dilated convolution insteadreplace_stride_with_dilation = [False, False, False]if len(replace_stride_with_dilation) != 3:raise ValueError("replace_stride_with_dilation should be None ""or a 3-element tuple, got {}".format(replace_stride_with_dilation))self.groups = groupsself.base_width = width_per_groupself.conv1 = nn.Conv2d(3, self.inplanes, kernel_size=7, stride=2, padding=3,bias=False)self.bn1 = norm_layer(self.inplanes)self.relu = nn.ReLU(inplace=True)self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)self.layer1 = self._make_layer(block, 64, layers[0])self.layer2 = self._make_layer(block, 128, layers[1], stride=2,dilate=replace_stride_with_dilation[0])self.layer3 = self._make_layer(block, 256, layers[2], stride=2,dilate=replace_stride_with_dilation[1])self.layer4 = self._make_layer(block, 512, layers[3], stride=2,dilate=replace_stride_with_dilation[2])self.avgpool = nn.AdaptiveAvgPool2d((1, 1))# self.fc = nn.Linear(512 * block.expansion, num_classes) 这里将全连接层注释掉了for m in self.modules():if isinstance(m, nn.Conv2d):nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')elif isinstance(m, (nn.BatchNorm2d, nn.GroupNorm)):nn.init.constant_(m.weight, 1)nn.init.constant_(m.bias, 0)# Zero-initialize the last BN in each residual branch,# so that the residual branch starts with zeros, and each residual block behaves like an identity.# This improves the model by 0.2~0.3% according to https://arxiv.org/abs/1706.02677if zero_init_residual:for m in self.modules():if isinstance(m, Bottleneck):nn.init.constant_(m.bn3.weight, 0)elif isinstance(m, BasicBlock):nn.init.constant_(m.bn2.weight, 0)def _make_layer(self, block, planes, blocks, stride=1, dilate=False):norm_layer = self._norm_layerdownsample = Noneprevious_dilation = self.dilationif dilate:self.dilation *= stridestride = 1if stride != 1 or self.inplanes != planes * block.expansion:downsample = nn.Sequential(conv1x1(self.inplanes, planes * block.expansion, stride),norm_layer(planes * block.expansion),)layers = []layers.append(block(self.inplanes, planes, stride, downsample, self.groups,self.base_width, previous_dilation, norm_layer))self.inplanes = planes * block.expansionfor _ in range(1, blocks):layers.append(block(self.inplanes, planes, groups=self.groups,base_width=self.base_width, dilation=self.dilation,norm_layer=norm_layer))return nn.Sequential(*layers)def _forward_impl(self, x):# See note [TorchScript super()]x = self.conv1(x)x = self.bn1(x)x = self.relu(x)x = self.maxpool(x)x = self.layer1(x)x = self.layer2(x)x = self.layer3(x)x = self.layer4(x)x = self.avgpool(x)x = torch.flatten(x, 1)# x = self.fc(x) 这里注释掉了最后一个全连接层,直接输出提取的特征return xdef forward(self, x):return self._forward_impl(x)
修改完网络后需要对网络参数进行初始化,由于修改了网络,需要逐层进行初始化。
import torchvision as tv
model = MyResNet(BasicBlock, [2, 2, 2, 2]) #这里具体的参数参考库中源代码
resnet18 = tv.models.resnet18(pretrained=True)
pretrained_dict = resnet18.state_dict()
model_dict = model.state_dict()
pretrained_dict = {k: v for k, v in pretrained_dict.items() if k in model_dict}
model_dict.update(pretrained_dict)
model.load_state_dict(model_dict)
这样便实现了对模型的初始化,可以利用resnet18预训练模型来提取图片特征了。
1、pytorch中的预训练模型
在上边的连接地址中有各个基础网络模型的程序源码,还有预训练模型参数文件的下载地址,使用时主要采用下边代码块中的文件直接引用
import torchvision.models as models
resnet
model = models.resnet50(pretrained=True)
vgg
model = models.vgg16(pretrained=True)
2、预训练模型的修改
在实际的应用中,往往不能直接应用导出的网络模型,均需要对网络的模型进行一定的修改才能使用,例如在图像分类任务中,我们任务的分类数目不一定刚好与imagenet数据集模型类别一致(1000类),在使用时就需要自己对模型参数进行修改(或者直接修改最后一层,然后自己建立合适的层进行自己算法的分类操作)。
在目标检测中往往自己需要建立特征提取的基础网络,需要有选择的建立适合自己特定需求的网络,然后利用与预训练模型结构相同的部分进行初始化,加快模型的收敛速度。
1)参数的修改(分类网络最后一层参数)
以自己的分类数据集10类为例,直接传入入类别数目这一参数
-- coding:utf-8 --
import torchvision.models as models
调用模型
model = models.resnet50(pretrained=True,num_classes = 10)
print(model)
将最后一层fc的参数直接进行修改,对类的属性进行直接修改,替换掉fc这一层:
-- 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, 10)
2)增减卷积层
对于前一种方法,仅适用于在分类问题时对简单层进行修改,但是对于目标检测想要利用预训练模型进行fine-tune该怎么办呢?这就要学会利用增减卷积层进行预训练模型的使用了。这两天刚好需要 修改一个基础特征提取的网络结构,但是自己的数据集又太小,直接修改网络从头训练容易出现过拟合,必须使用预训练的网络模型进行训练,就研究了一下使用方法,总结如下:
基本思想就是:先建立好自己的网络(与预训练的模型类似,要不谈何fine-tune),然后将
削减卷积层
基本思想就是:
1、先建立好自己的网络(与预训练的模型类似,要不谈何fine-tune)
2、然后将预训练模型参数与自己搭建的网络不一致的部分参数去掉
3、将保留的合适的参数读入网络初始化,实现fine-tune的效果
基础网络准备使用resnet152的前143层(为什么是143,这里直接去掉了最后一个残差块)
-- coding:utf-8 --
###############
#建立自己的网络模型net
然后读出预训练模型参数以resnet152为例,我不是利用程序下载的,我是习惯了下载好存储在文件夹中
pretrained_dict = torch.load(save_path)
model_dict = net.state_dict() (读出搭建的网络的参数,以便后边更新之后初始化)
去除不属于model_dict的键值
pretrained_dict={ k : v for k, v in pretrained_dict.items() if k in model_dict}
更新现有的model_dict的值
model_dict.update(pretrained_dict)
加载模型需要的参数
net.load_state_dict(model_dict)
介绍到这儿,相信应该对模型读入参数的方法load_state_dict()有了清晰的了解,就是利用键值的对应关系读入参数进行初始化网络,键值不对应会报错
知道了如何读入参数,那么增加卷积层的操作应该已经能够理解了。
增加卷积层
上边介绍了削减卷积层,那么如果我想在后边累加一些卷积层怎么办呢,这个更简单了,直接对应将前边的卷积层参数利用预训练模型初始化,后边添加的卷积层利用nn.init.kaimingnormal进行初始化
torch.nn.init.uniform(tensor, a=0, b=1)
#从均匀分布U(a, b)中生成值,填充输入的张量或变量
#参数:
#tensor - n维的torch.Tensor
#a - 均匀分布的下界
#b - 均匀分布的上界
#例子
w = torch.Tensor(3, 5)
nn.init.uniform(w)
torch.nn.init.normal(tensor, mean=0, std=1)
#从给定均值和标准差的正态分布N(mean, std)中生成值,填充输入的张量或变量
#参数:
#tensor – n维的torch.Tensor
#mean – 正态分布的均值
#std – 正态分布的标准差
#例子
w = torch.Tensor(3, 5)
nn.init.normal(w)
torch.nn.init.constant(tensor, val)
#用val的值填充输入的张量或变量
torch.nn.init.eye_(tensor)
#用单位矩阵来填充2维输入张量或变量。在线性层尽可能多的保存输入特性
###############################这个用的比较多
#torch.nn.init.xavieruniform(tensor, gain=1)
#根据Glorot, X.和Bengio, Y.在“Understanding the difficulty of training deep feedforward neural networks”中描述的方法,用一个均匀分布生成值,填充输入的张量或变量。结果张量中的值采样自U(-a, a),其中a= gain sqrt( 2/(fan_in + fan_out)) sqrt(3). 该方法也被称为Glorot initialisation
#参数:
#tensor – n维的torch.Tensor
#gain - 可选的缩放因子
#例子:
w = torch.Tensor(3, 5)
nn.init.xavieruniform(w, gain=math.sqrt(2.0))
torch.nn.init.xaviernormal(tensor, gain=1)
#根据Glorot, X.和Bengio, Y. 于2010年在“Understanding the difficulty of training deep feedforward neural networks”中描述的方法,用一个正态分布生成值,填充输入的张量或变量。结果张量中的值采样自均值为0,标准差为gain * sqrt(2/(fan_in + fan_out))的正态分布。也被称为Glorot initialisation
torch.nn.init.kaiminguniform(tensor, a=0, mode=’fanin’)
#根据He, K等人于2015年在“Delving deep into rectifiers: Surpassing human-level performance on ImageNet classification”中描述的方法,用一个均匀分布生成值,填充输入的张量或变量。结果张量中的值采样自U(-bound, bound),其中bound = sqrt(2/((1 + a^2) fanin)) sqrt(3)。也被称为He initialisation.
#tensor – n维的torch.Tensor或autograd.Variable
#a -这层之后使用的rectifier的斜率系数(ReLU的默认值为0)
#mode -可以为“fan_in”(默认)或“fan_out”。“fan_in”保留前向传播时权值方差的量级,“fan_out”保留反向传播时的量级。
.
################################这个用的较多
#torch.nn.init.kaimingnormal(tensor, a=0, mode=’fan_in’)
#根据He, K等人在“Delving deep into rectifiers: Surpassing human-level performance on ImageNet classification”中描述的方法,用一个正态分布生成值,填充输入的张量或变量。结果张量中的值采样自均值为0,标准差为sqrt(2/((1 + a^2) * fan_in))的正态分布。
tensor – n维的torch.Tensor或 autograd.Variable
#a -这层之后使用的rectifier的斜率系数(ReLU的默认值为0)
#mode -可以为“fan_in”(默认)或“fan_out”。“fan_in”保留前向传播时权值方差的量级,“fan_out”保留反向传播时的量级。
在中间插入卷积层呢,又该怎么办呢?
利用state-dict的键值对应读入原始的参数,新添加的层利用合适的方式直接初始化就可以。
总之,增加卷积层的操作,就是将原有的卷积层参数按照对应的键值读入,将新添加的层利用nn.init.kaimingnormal进行初始化就可以
看到这儿,应该已经可以利用torchvision的预训练模型轻松的进行fine-tune了。
编辑于 2020-07-08 17:47
