目录
前言
在深度学习模型创建的时候,我们经常会碰到
nn.Sequential
,
nn.ModuleList
,
nn.ModuleDict
这三个东西,尤其是在迁移学习训练的时候经常碰到,他们到底是什么,怎么用的,使用的时候有哪些注意事项,通过这篇博文浅记一下。
一. nn.Module
在介绍这三个容器之前,我们需要先知道什么是
Module
。我们在创建模型的时候几乎所有的模型都是继承于这个类。他是所有网络的基类,用来管理网络的属性。有两个与他相关联的模块:
nn.Parameter
和
nn.functional
。所有这些模块都来自于
torch.nn
。下面我们先简要介绍下这几个模块。
1.1. nn.Parameter
首先是
nn.Parameter
, 在
Pytorch
中,
nn.Parameter
是用于创建模型参数的特殊类。在一个模型中,往往有许多的参数,要手动管理这些参数并不是一件容易的事情。
Pytorch
一般将参数用
nn.Parameter
来表示,并且用
nn.Module
来管理其结构下的所有参数。
## nn.Parameter 具有 requires_grad = True 属性
w = nn.Parameter(torch.randn(2,2))print(w)# tensor([[ 0.3544, -1.1643],[ 1.2302, 1.3952]], requires_grad=True)print(w.requires_grad)# True## nn.ParameterList 可以将多个nn.Parameter组成一个列表
params_list = nn.ParameterList([nn.Parameter(torch.rand(8,i))for i inrange(1,3)])print(params_list)print(params_list[0].requires_grad)## nn.ParameterDict 可以将多个nn.Parameter组成一个字典
params_dict = nn.ParameterDict({"a":nn.Parameter(torch.rand(2,2)),"b":nn.Parameter(torch.zeros(2))})print(params_dict)print(params_dict["a"].requires_grad)
可以通过Module把上面定义的参数管理起来:
# module.parameters()返回一个生成器,包括其结构下的所有parameters
module = nn.Module()
module.w = w
module.params_list = params_list
module.params_dict = params_dict
num_param =0for param in module.parameters():print(param,"\n")
num_param = num_param +1print("number of Parameters =",num_param)
实际使用的时候一般通过继承
nn.Module
来构建模块类,并将所有含有需要学习的参数的部分放在构造函数中。
#以下范例为Pytorch中nn.Linear的源码的简化版本#可以看到它将需要学习的参数放在了__init__构造函数中,并在forward中调用F.linear函数来实现计算逻辑。classLinear(nn.Module):
__constants__ =['in_features','out_features']def__init__(self, in_features, out_features, bias=True):super(Linear, self).__init__()
self.in_features = in_features
self.out_features = out_features
self.weight = nn.Parameter(torch.Tensor(out_features, in_features))if bias:
self.bias = nn.Parameter(torch.Tensor(out_features))else:
self.register_parameter('bias',None)defforward(self,input):return F.linear(input, self.weight, self.bias)
1.2. nn.functional
nn.functional
(一般引入后改名为F)有各种功能组件的函数实现。 比如:
- 激活函数系列(
F.relu, F.sigmoid, F.tanh, F.softmax
)- 模型层系列(
F.linear, F.conv2d, F.max_pool2d, F.dropout2d, F.embedding
)- 损失函数系列(
F.binary_cross_entropy, F.mse_loss, F.cross_entropy
)
为了便于对参数进行管理, 一般通过继承
nn.Module
转换为类的实现形式, 并直接封装在
nn
模块下:
- 激活函数变成(
nn.ReLu, nn.Sigmoid, nn.Tanh, nn.Softmax
)- 模型层(
nn.Linear, nn.Conv2d, nn.MaxPool2d, nn.Embedding
)- 损失函数(
nn.BCELoss, nn.MSELoss, nn.CrossEntorpyLoss
)
所以我们表面上用
nn
建立的这些激活函数, 层, 损失函数, 背后都在
functional
里面具体实现。继续往下看你就知道了,
nn.Module
这个模块确实非常强大, 除了可以管理其引用的各种参数,还可以管理其引用的子模块。
1.3. nn.Module
我们的重点是介绍这个
nn.Module
模块。
nn.Module
中有很多重要的字典属性:
self.training =True
self._parameters: Dict[str, Optional[Parameter]]= OrderedDict()
self._buffers: Dict[str, Optional[Tensor]]= OrderedDict()
self._non_persistent_buffers_set: Set[str]=set()
self._backward_hooks: Dict[int, Callable]= OrderedDict()
self._is_full_backward_hook =None
self._forward_hooks: Dict[int, Callable]= OrderedDict()
self._forward_pre_hooks: Dict[int, Callable]= OrderedDict()
self._state_dict_hooks: Dict[int, Callable]= OrderedDict()
self._load_state_dict_pre_hooks: Dict[int, Callable]= OrderedDict()
self._modules: Dict[str, Optional['Module']]= OrderedDict()
我们只需要重点关注其中的两个即可:
_parameters
和
_modules
_parameters
: 存储管理属于nn.Parameter
类的属性,例如权值,偏置这些参数_modules
: 存储管理nn.Module
类, 比如经典网络LeNet
中,会构建子模块,卷积层,池化层,就会存储在_modules中
这里提个问题:
nn.Parameter
和
nn.Module
中的
_parameters
的区别是什么?
nn.Parameter
:它是torch.Tensor
的子类,用于标记张量为模型的可学习参数。在定义模型的过程中,我们通常会使用nn.Parameter
来创建可学习参数,并将其作为模型的属性。这样做的好处是,nn.Parameter
对象会自动被注册为模型的参数,参与梯度计算和参数更新。_parameters
:它是nn.Module
类中的一个属性,是一个字典,用于存储模型的可学习参数。字典的键是参数的名称,值是对应的参数张量(nn.Parameter
类型)。_parameters
属性的值会自动从模型的属性中提取可学习参数,并将其添加到字典中。
可以将
_parameters
视为存储模型可学习参数的容器,而
nn.Parameter
则是用于创建和标记这些参数的特殊类。通过使用
nn.Parameter
创建参数,并将其作为模型属性,这些参数将被自动添加到
_parameters
字典中,方便对它们进行统一的管理和操作。即
nn.Parameter
是用于创建模型参数的特殊类,而
_parameters
是存储模型参数的字典属性。使用
nn.Parameter
创建的参数会自动添加到
_parameters
字典中,以便于管理和访问模型的参数。
nn.Module
构建构网络的过程是什么样的?以下面的网络为例:
import torch
from torch import nn
classNet(nn.Module):def__init__(self):super(Net, self).__init__()
self.conv1 = nn.Conv2d(1,1,1,1)
self.bn = nn.BatchNorm2d(1)
self.relu = nn.ReLU()
self.conv2 = nn.Conv2d(1,1,1,1)defforward(self, x):
x = self.conv1(x)
x = self.bn(x)
x = self.relu(x)
x = self.conv2(x)return x
if __name__ =="__main__":
dat = torch.rand(1,1,10,10)
net = Net().cuda()
构建过程如下:
我们是先有一个大的
Module
(上面创建的
Net
)继承
nn.Module
这个基类,比如上面的
Net
,然后这个
Net
里面又可以有很多的子模块,这些子模块同样也是继承于
nn.Module
,在这些
Module
的
__init__
方法中,会先通过调用父类的初始化方法进行父类属性的一个初始化。然后在构建每个子模块的时候,其实分为两步,第一步是初始化,然后被
__setattr__
这个方法通过判断
value
的类型将其保存到相应的属性字典里面去,然后再进行赋值给相应的成员。这样一个个的构建子模块,最终把整个
Net
构建完毕。具体过程可以自己调试看一下。
总结:
- 一个
module
可以包含多个子module
(Net
包含卷积层,BN
层,激活函数)- 一个
module
相当于一个运算, 必须实现forward()
函数(有些模块的forward需要自己去重新写,往下看你就知道了)- 每个
module
都有很多个字典管理它的属性(最常用的就是_parameters
,_modules
)
知道了网络的构建过程,我们就可以对别人创建好的模型进行解析,提取其中的若干部分,关于这部分的介绍可以参考这篇博文:Pytorch提取神经网络层结构、层参数及自定义初始化。
二. nn.Sequential
上面介绍完了
nn.Module
模块,我们开始介绍容器。首先我们来看下
nn.Sequential
,
nn.Sequential
是
PyTorch
中的一个模块容器,用于按顺序组合多个模块。它可以将一系列的模块按照顺序连接起来,形成一个串联的模型结构。我们来看下他在
pytorch
中是怎么实现的,这里我们只看构造函数和前向传播部分,其他部分的代码省略:
classSequential(Module):...def__init__(self,*args):super(Sequential, self).__init__()iflen(args)==1andisinstance(args[0], OrderedDict):for key, module in args[0].items():
self.add_module(key, module)else:for idx, module inenumerate(args):
self.add_module(str(idx), module)...defforward(self,input):for module in self:input= module(input)returninput
通过上面的代码可以看到,
nn.Sequential
是继承于
Module
,说明
Sequential
本身也是一个
Module
,所以它也会有那几个字典参数。可以看到
nn.Sequential
自己实现了
forward
方法。
nn.Sequential
中经常使用的主要有如下几个方法:
forward(input)
:定义模型的前向传播过程。在nn.Sequential
中,此方法会按照模块的顺序依次调用每个模块的forward
方法,将前一个模块的输出作为输入传递给下一个模块,从而计算最终的输出。add_module(name, module)
:向nn.Sequential
中添加一个子模块。name
是子模块的名称,module
是要添加的子模块对象。模块将按照添加的顺序依次进行前向传播。parameters()
:返回nn.Sequential
中所有可学习参数的迭代器。可以通过迭代器访问和操作模型的可学习参数。zero_grad()
:将nn.Sequential
中所有模块的参数梯度置零。通常在每次进行梯度更新之前调用该方法
就列举上面几个方法,其实还很对,平时用的最多的还是
add_module(name, module)
这个方法,用于添加模块。来看下
nn.Sequential
到底怎么使用的?
classNet(nn.Module):def__init__(self, classes):super(Net, self).__init__()
self.features = nn.Sequential(
nn.Conv2d(3,6,5),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2),
nn.Conv2d(6,16,5),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2),)
self.classifier = nn.Sequential(
nn.Linear(16*5*5,120),
nn.ReLU(),
nn.Linear(120,84),
nn.ReLU(),
nn.Linear(84, classes),)defforward(self, x):
x = self.features(x)
x = x.view(x.size()[0],-1)
x = self.classifier(x)return x
也可以使用add_module的方法进行创建:
import torch
import torch.nn as nn
classNet(nn.Module):def__init__(self, classes):super(Net, self).__init__()
self.features = nn.Sequential()
self.features.add_module('conv1', nn.Conv2d(3,6,5))
self.features.add_module('relu1', nn.ReLU())
self.features.add_module('pool1', nn.MaxPool2d(kernel_size=2, stride=2))
self.features.add_module('conv2', nn.Conv2d(6,16,5))
self.features.add_module('relu2', nn.ReLU())
self.features.add_module('pool2', nn.MaxPool2d(kernel_size=2, stride=2))
self.classifier = nn.Sequential()
self.classifier.add_module('fc1', nn.Linear(16*5*5,120))
self.classifier.add_module('relu3', nn.ReLU())
self.classifier.add_module('fc2', nn.Linear(120,84))
self.classifier.add_module('relu4', nn.ReLU())
self.classifier.add_module('fc3', nn.Linear(84, classes))defforward(self, x):
x = self.features(x)
x = x.view(x.size()[0],-1)
x = self.classifier(x)return x
通过上面的网络搭建,可以看到,
forward
函数中只用了一句
self.features(x)
即可完成六句话的执行。其之所以能完成这个操作地归功于
nn.Sequential
中的
forward
函数,程序在执行功的过程中会把参数传递给
nn.Sequential
中进行解析,具体实现过程可以调试代码观察。
总结:
nn.Sequential
是
nn.module
的容器, 用于按顺序包装一组网络层,主要有以下两个特点:
- 顺序性: 各网络层之间严格按照顺序构建,这时候一定要注意前后层数据的关系
- 自带
forward()
: 自带的forward
里,通过for
循环依次执行前向传播运算
三. nn.ModuleList
nn.ModuleList
也是
nn.module
的容器, 用于包装一组网络层, 以迭代方式调用网络层, 常用的方法有如下几个,跟list的使用很像:
append()
: 在ModuleList
后面添加网络层extend()
: 拼接两个ModuleList
insert()
: 指定在ModuleList
中位置插入网络层
直接看个例子,怎么使用
nn.ModuleList
搭建网络:
classModuleListNet(nn.Module):def__init__(self):super(ModuleListNet, self).__init__()
self.linears = nn.ModuleList([nn.Linear(10,10)for i inrange(10)])defforward(self, x):for i, linear inenumerate(self.linears):
x = linear(x)return x
上面的例子通过使用列表推导式创建了10个
nn.Linear
模块。整体上使用起来还是很简单的,具体实现过程感兴趣的小伙伴可以通过调试代码查看。
三. nn.ModuleDict
我们再来看下
nn.ModuleDict
这个模块。
nn.ModuleDict
也是
nn.module
的容器, 用于包装一组网络层, 以索引方式调用网络层, 常用的有如下方法,跟字典的操作比较像:
clear()
: 清空ModuleDict
items()
: 返回可迭代的键值对(key-value pairs
)keys()
: 返回字典的键(key
)values()
: 返回字典的值(value)
pop()
: 返回一对键值对, 并从字典中删除
看个例子:
import torch
import torch.nn as nn
classMyModel(nn.Module):def__init__(self):super(MyModel, self).__init__()
self.module_dict = nn.ModuleDict({'conv1': nn.Conv2d(3,64, kernel_size=3, stride=1, padding=1),'relu1': nn.ReLU(),'conv2': nn.Conv2d(64,128, kernel_size=3, stride=1, padding=1),'relu2': nn.ReLU(),'flatten': nn.Flatten(),'linear': nn.Linear(128*32*32,10)})defforward(self, x):for module in self.module_dict.values():
x = module(x)return x
# 创建模型实例
model = MyModel()# 随机生成输入
x = torch.randn(1,3,32,32)# 进行前向传播
output = model(x)print(output.shape)
上面通过
nn.ModuleDict
创建了一个网络,整体来说还是很简单的,跟字典操作差不多。
关于
nn.Sequential
,
nn.ModuleList
,
nn.ModuleDict
的基本使用基本上酒介绍完了,如有错误,敬请指正!
版权归原作者 I松风水月 所有, 如有侵权,请联系我们删除。