本文是 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
来源: https://www.cnblogs.com/wangguchangqing/p/11058525.html