PyTorch-网络的创建,预训练模型的加载
本文是PyTorch使用过程中的的一些总结,有以下内容:
- 构建网络模型的方法
- 网络层的遍历
- 各层参数的遍历
- 模型的保存与加载
- 从预训练模型为网络参数赋值
主要涉及到以下函数的使用
add_module
,ModulesList
,Sequential
模型创建modules()
,named_modules()
,children()
,named_children()
访问模型的各个子模块parameters()
,named_parameters()
网络参数的遍历save()
,load()
,state_dict()
模型的保存与加载
构建网络
torch.nn.Module
是所有网络的基类,在Pytorch实现的Model都要继承该类。而且,Module
是可以包含其他的Module
的,以树形的结构来表示一个网络结构。
简单的定义一个网络Model
class Model(nn.Module):
def __init__(self):
super(Model,self).__init__()
self.conv1 = nn.Conv2d(3,64,3)
self.conv2 = nn.Conv2d(64,64,3)
def forward(self,x):
x = self.conv1(x)
x = self.conv2(x)
return x
Model
中两个属性conv1
和conv2
是两个卷积层,在正向传播的过程中,再依次调用这两个卷积层。
除了使用Model
的属性来为网络添加层外,还可以使用add_module
将网络层添加到网络中。
class Model(nn.Module):
def __init__(self):
super(Model,self).__init__()
self.conv1 = nn.Conv2d(3,64,3)
self.conv2 = nn.Conv2d(64,64,3)
self.add_module("maxpool1",nn.MaxPool2d(2,2))
self.add_module("covn3",nn.Conv2d(64,128,3))
self.add_module("conv4",nn.Conv2d(128,128,3))
def forward(self,x):
x = self.conv1(x)
x = self.conv2(x)
x = self.maxpool1(x)
x = self.conv3(x)
x = self.conv4(x)
return x
add_module(name,layer)
在正向传播的过程中可以使用添加时的name
来访问改layer。
这样一个个的添加layer,在简单的网络中还行,但是对于负责的网络层很多的网络来说就需要敲很多重复的代码了。 这就需要使用到torch.nn.ModuleList
和torch.nn.Sequential
。
使用ModuleList
和Sequential
可以方便添加子网络到网络中,但是这两者还是有所不同的。
ModuleList
ModuleList
是以list
的形式保存sub-modules
或者网络层,这样就可以先将网络需要的layer
构建好保存到一个list
,然后通过ModuleList
方法添加到网络中。
class MyModule(nn.Module):
def __init__(self):
super(MyModule,self).__init__()
# 构建layer的list
self.linears = nn.ModuleList([nn.Linear(10, 10) for i in range(10)])
def forward(self,x):
# 正向传播,使用遍历每个Layer
for i, l in enumerate(self.linears):
x = self.linears[i // 2](x) + l(x)
return x
使用[nn.Linear(10, 10) for i in range(10)]
构建要给Layer的list,然后使用ModuleList
添加到网络中,在正向传播的过程中,遍历该list
。
更为方便的是,可以提前配置后,所需要的各个Layer
的属性,然后读取配置创建list
,然后使用ModuleList
将配置好的网络层添加到网络中。 以VGG
为例:
vgg_cfg = [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 'C', 512, 512, 512, 'M',
512, 512, 512, 'M']
def vgg(cfg, i, batch_norm=False):
layers = []
in_channels = i
for v in cfg:
if v == 'M':
layers += [nn.MaxPool2d(kernel_size=2, stride=2)]
elif v == 'C':
layers += [nn.MaxPool2d(kernel_size=2, stride=2, ceil_mode=True)]
else:
conv2d = nn.Conv2d(in_channels, v, kernel_size=3, padding=1)
if batch_norm:
layers += [conv2d, nn.BatchNorm2d(v), nn.ReLU(inplace=True)]
else:
layers += [conv2d, nn.ReLU(inplace=True)]
in_channels = v
return layers
class Model1(nn.Module):
def __init__(self):
super(Model1,self).__init__()
self.vgg = nn.ModuleList(vgg(vgg_cfg,3))
def forward(self,x):
for l in self.vgg:
x = l(x)
m1 = Model1()
print(m1)
读取配置好的网络结构vgg_cfg
然后,创建相应的Layer List
,使用ModuleList
加入到网络中。这样就可以很灵活的创建不同的网络。
这里需要注意的是,ModuleList
是将Module
加入网络中,需要自己手动的遍历进行每一个Module
的forward
。
Sequential
一个时序容器。Modules 会以他们传入的顺序被添加到容器中。当然,也可以传入一个OrderedDict一个时序容器。Modules
会以他们传入的顺序被添加到容器中。当然,也可以传入一个OrderedDict
。
Sequential
也是一次加入多个Module到网络中中,和ModuleList
不同的是,它接受多个Module依次加入到网络中,还可以接受字典作为参数,例如:
# Example of using Sequential
model = nn.Sequential(
nn.Conv2d(1,20,5),
nn.ReLU(),
nn.Conv2d(20,64,5),
nn.ReLU()
)
# Example of using Sequential with OrderedDict
model = nn.Sequential(OrderedDict([
('conv1', nn.Conv2d(1,20,5)),
('relu1', nn.ReLU()),
('conv2', nn.Conv2d(20,64,5)),
('relu2', nn.ReLU())
]))
另一个是,Sequential
中实现了添加Module的forward
,不需要手动的循环调用了。这点相比ModuleList
较为方便。
总结
常见的有三种方法来添加子Module到网络中
- 单独添加一个Module,可以使用属性或者
add_module
方法。 ModuleList
可以将一个Module的List
加入到网络中,自由度较高,但是需要手动的遍历ModuleList
进行forward
。Sequential
按照顺序将将Module加入到网络中,也可以处理字典。 相比于ModuleList
不需要自己实现forward
遍历网络结构
可以使用以下2对4个方法来访问网络层所有的Modules
modules()
和named_modules()
children()
和named_children()
modules
方法
简单的定义一个如下网络:
class Model(nn.Module):
def __init__(self):
super(Model,self).__init__()
self.conv1 = nn.Conv2d(in_channels=3,out_channels=64,kernel_size=3)
self.conv2 = nn.Conv2d(64,64,3)
self.maxpool1 = nn.MaxPool2d(2,2)
self.features = nn.Sequential(OrderedDict([
('conv3', nn.Conv2d(64,128,3)),
('conv4', nn.Conv2d(128,128,3)),
('relu1', nn.ReLU())
]))
def forward(self,x):
x = self.conv1(x)
x = self.conv2(x)
x = self.maxpool1(x)
x = self.features(x)
return x
modules()
方法,返回一个包含当前模型所有模块的迭代器,这个是递归的返回网络中的所有Module。使用如下语句
m = Model()
for idx,m in enumerate(m.modules()):
print(idx,"-",m)
其结果为:
0 - Model(
(conv1): Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1))
(conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1))
(maxpool1): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(features): Sequential(
(conv3): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1))
(conv4): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1))
(relu1): ReLU()
)
)
1 - Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1))
2 - Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1))
3 - MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
4 - Sequential(
(conv3): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1))
(conv4): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1))
(relu1): ReLU()
)
5 - Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1))
6 - Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1))
7 - ReLU()
输出结果解析:
0-Model
整个网络模块1-2-3-4
为网络的4个子模块,注意4 - Sequential
仍然包含有子模块5-6-7
为模块4 - Sequential
的子模块
可以看出modules()
是递归的返回网络的各个module,从最顶层直到最后的叶子module。
named_modules()
的功能和modules()
的功能类似,不同的是它返回内容有两部分:module的名称以及module。
children()
方法
和modules()
不同,children()
只返回当前模块的子模块,不会递归子模块。
for idx,m in enumerate(m.children()):
print(idx,"-",m)
其输出为:
0 - Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1))
1 - Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1))
2 - MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
3 - Sequential(
(conv3): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1))
(conv4): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1))
(relu1): ReLU()
)
子模块3-Sequential
仍然有子模块,children()
没有递归的返回。
named_children()
和children()
的功能类似,不同的是其返回两部分内容:模块的名称以及模块本身。
网络的参数
方法parameters()
返回一个包含模型所有参数的迭代器。一般用来当作optimizer的参数。
for p in m.parameters():
print(type(p.data),p.size())
其输出为:
<class 'torch.Tensor'> torch.Size([128, 64, 3, 3])
<class 'torch.Tensor'> torch.Size([128])
<class 'torch.Tensor'> torch.Size([128, 128, 3, 3])
<class 'torch.Tensor'> torch.Size([128])
包含网络中的所有的权值矩阵参数以及偏置参数。 对网络进行训练时需要将parameters()
作为优化器optimizer
的参数。
optimizer = torch.optim.SGD(m1.parameters(),lr = args.lr,momentum=args.momentum,weight_decay=args.weight_decay)
parameters()
返回网络的所有参数,主要是提供给optimizer
用的。而要取得网络某一层的参数或者参数进行一些特殊的处理(如fine-tuning),则使用named_parameters()
更为方便些。
named_parameters()
返回参数的名称及参数本身,可以按照参数名对一些参数进行处理。
以上面的vgg
网络为例:
for k,v in m1.named_parameters():
print(k,v.size())
named_parameters
返回的是键值对,k
为参数的名称 ,v
为参数本身。输出结果为:
vgg.0.weight torch.Size([64, 3, 3, 3])
vgg.0.bias torch.Size([64])
vgg.2.weight torch.Size([64, 64, 3, 3])
vgg.2.bias torch.Size([64])
vgg.5.weight torch.Size([128, 64, 3, 3])
vgg.5.bias torch.Size([128])
vgg.7.weight torch.Size([128, 128, 3, 3])
vgg.7.bias torch.Size([128])
vgg.10.weight torch.Size([256, 128, 3, 3])
vgg.10.bias torch.Size([256])
vgg.12.weight torch.Size([256, 256, 3, 3])
vgg.12.bias torch.Size([256])
vgg.14.weight torch.Size([256, 256, 3, 3])
vgg.14.bias torch.Size([256])
vgg.17.weight torch.Size([512, 256, 3, 3])
vgg.17.bias torch.Size([512])
vgg.19.weight torch.Size([512, 512, 3, 3])
vgg.19.bias torch.Size([512])
vgg.21.weight torch.Size([512, 512, 3, 3])
vgg.21.bias torch.Size([512])
vgg.24.weight torch.Size([512, 512, 3, 3])
vgg.24.bias torch.Size([512])
vgg.26.weight torch.Size([512, 512, 3, 3])
vgg.26.bias torch.Size([512])
vgg.28.weight torch.Size([512, 512, 3, 3])
vgg.28.bias torch.Size([512])
参数名的命名规则属性名称.参数属于的层的编号.weight/bias
。 这在fine-tuning
的时候,给一些特定的层的参数赋值是非常方便的,这点在后面在加载预训练模型时会看到。
模型的保存与加载
PyTorch使用torch.save
和torch.load
方法来保存和加载网络,而且网络结构和参数可以分开的保存和加载。
- 保存网络结构及其参数
torch.save(model,'model.pth') # 保存
model = torch.load("model.pth") # 加载
- 只加载模型参数,网络结构从代码中创建
torch.save(model.state_dict(),"model.pth") # 保存参数
model = model() # 代码中创建网络结构
params = torch.load("model.pth") # 加载参数
model.load_state_dict(params) # 应用到网络结构中
加载预训练模型
PyTorch中的torchvision里有很多常用网络的预训练模型,例如:vgg
,resnet
,googlenet
等,可以方便的使用这些预训练模型进行微调。
# PyTorch中的torchvision里有很多常用的模型,可以直接调用:
import torchvision.models as models
resnet101 = models.resnet18(pretrained=True)
alexnet = models.alexnet()
squeezenet = models.squeezenet1_0()
有时候只需要加载预训练模型的部分参数,可以使用参数名作为过滤条件,如下
resnet152 = models.resnet152(pretrained=True)
pretrained_dict = resnet152.state_dict()
"""加载torchvision中的预训练模型和参数后通过state_dict()方法提取参数
也可以直接从官方model_zoo下载:
pretrained_dict = model_zoo.load_url(model_urls['resnet152'])"""
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
model_dict.update(pretrained_dict)
# 加载我们真正需要的state_dict
model.load_state_dict(model_dict)
model.state_dict()
返回一个python的字典对象,将每一层与它的对应参数建立映射关系(如model的每一层的weights及偏置等等)。注意,只有有参数训练的层才会被保存。
上述的加载方式,是按照参数名类匹配过滤的,但是对于一些参数名称无法完全匹配,或者在预训练模型的基础上新添加的一些层,这些层无法从预训练模型中获取参数,需要初始化。
仍然以上述的vgg
为例,在标准的vgg16
的特征提取后面,新添加两个卷积层,这两个卷积层的参数需要进行初始化。
vgg = torch.load("vgg.pth") # 加载预训练模型
for k,v in m1.vgg.named_parameters():
k = "features.{}".format(k) # 参数名称
if k in vgg.keys():
v.data = vgg[k].data # 直接加载预训练参数
else:
if k.find("weight") >= 0:
nn.init.xavier_normal_(v.data) # 没有预训练,则使用xavier初始化
else:
nn.init.constant_(v.data,0) # bias 初始化为0
PyTorch-网络的创建,预训练模型的加载的更多相关文章
- js动态创建的select2标签样式加载不上解决办法
js动态创建的select2标签样式加载不上:调用select2的select2()函数来初始化一下: js抛出了Uncaught query function not defined for Sel ...
- pytorch中修改后的模型如何加载预训练模型
问题描述 简单来说,比如你要加载一个vgg16模型,但是你自己需要的网络结构并不是原本的vgg16网络,可能你删掉某些层,可能你改掉某些层,这时你去加载预训练模型,就会报错,错误原因就是你的模型和原本 ...
- 【小白学PyTorch】5 torchvision预训练模型与数据集全览
文章来自:微信公众号[机器学习炼丹术].一个ai专业研究生的个人学习分享公众号 文章目录: 目录 torchvision 1 torchvision.datssets 2 torchvision.mo ...
- Swift微博项目--Swift中通过类名字符串创建类以及动态加载控制器的实现
Swift中用类名字符串创建类(用到了命名空间) OC中可以直接通过类名的字符串转换成对应的类来操作,但是Swift中必须用到命名空间,也就是说Swift中通过字符串获取类的方式为NSClassFro ...
- Android Handler 异步消息处理机制的妙用 创建强大的图片加载类(转)
转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/38476887 ,本文出自[张鸿洋的博客] 最近创建了一个群,方便大家交流,群号: ...
- React(九)create-react-app创建项目 + 按需加载Ant Design
(1)create-react-app如何创建项目我前面第一章介绍过了,这里就不过多写了, (2)我们主要来说说按需加载的问题 1. 引入antd npm install antd --save 2. ...
- 从整体上理解进程创建、可执行文件的加载和进程执行进程切换,重点理解分析fork、execve和进程切换
学号后三位<168> 原创作品转载请注明出处https://github.com/mengning/linuxkernel/ 1.分析fork函数对应的内核处理过程sys_clone,理解 ...
- 如何用Swift创建一个复杂的加载动画
现在在苹果应用商店上有超过140万的App,想让你的app事件非常具有挑战的事情.你有这样一个机会,在你的应用的数据完全加载出来之前,你可以通过一个很小的窗口来捕获用户的关注. 没有比这个更好的地方让 ...
- DLL动态库的创建,隐式加载和显式加载
动态库的创建 打开VS,创建如下控制台工程,工程命名为DllTest: 在弹出的对话框中选择"DLL"后单击"完成"按钮: 在工程中新建DllTest.h和Dl ...
随机推荐
- java的回调和C#的委托
曾经有人对我说java的回调很巧妙. 今天我自己看了一下,回调的关键就是一个接口的事情. 也许是因为用了一定的手法,一开始不好懂吧,所以看懂了会感觉巧妙. 但是我心里的想法却是,真啰嗦! 回调的实例 ...
- 2019-11-28:ssrf基础学习,笔记
ssrf服务端请求伪造ssrf是一种由恶意访问者构造形成由服务端发起请求的一个安全漏洞,一般情况下,ssrf访问的目标是从外网无法访问的内部系统,正式因为它是由服务端发起的,所以它能请求到它相连而外网 ...
- 01 jQuery配置、jQuery语法结构、jQuery对象与DOM对象的互相转换
配置jQuery环境 下载jQuery 官网:jquery.com 学习或开发建议选择未压缩版,便于学习,发布建议选择压缩版,便于用户极速体验(点击下载若出现的是代码页 面,Ctrl+A全选复制 ...
- 关于新浪和腾讯短网址API接口的调用
最新新浪t.cn短网址和腾讯url.cn短网址生成api接口,快速生成t.cn及url.cn超短链接,接口都可以正常调用,觉得不错可以收藏一下. 新浪短网址api接口:1. http://yldwz. ...
- CCNA 之 六 路由协议 二 EIGRP
EIGRP(Enhanced IGRP) 增强型内部网关路由协议 注意:这是cisco私有协议:也就是说,该协议只能运行在思科的设备上,如果有其他的厂家的设备,则不能保证能运行此协议: EIGRP的特 ...
- split分割(拆分)文件
split分割(拆分)文件 需求:指定文件大小拆分文件 # ll -h test/ |grep vmcore -rw-r--r-- 1 root root 12G 12月 7 00:20 vmco ...
- 小白的springboot之路(二)、集成swagger
0-前言 现在的项目开发,基本都是前后端分离,后端专注于API接口开发,都需要编写和维护API接口文档.如果你还在用Word来编写接口文档,那你就out了,这个时候,当当当当~神兵利器swagger隆 ...
- 【10分钟学Spring】:@Profile、@Conditional实现条件化装配
根据不同的环境来装配不同的bean 企业级开发中,我们一般有多种环境,比如开发环境.测试环境.UAT环境和生产环境.而系统中有些配置是和环境强相关的,比如数据库相关的配置,与其他外部系统的集成等. 如 ...
- DS1302时钟芯片驱动程序
/***************************************************************************** FileName : DS1302.c F ...
- Snack3 一个新的微型JSON框架
Snack3 一个新的微型JSON框架 一个作品,一般表达作者的一个想法.因为大家想法不同,所有作品会有区别.就做技术而言,因为有很多有区别的框架,所以大家可以选择的框架很丰富. snack3.基于j ...