前言
??在深度學(xué)習(xí)模型創(chuàng)建的時(shí)候,我們經(jīng)常會(huì)碰到nn.Sequential
, nn.ModuleList
, nn.ModuleDict
這三個(gè)東西,尤其是在遷移學(xué)習(xí)訓(xùn)練的時(shí)候經(jīng)常碰到,他們到底是什么,怎么用的,使用的時(shí)候有哪些注意事項(xiàng),通過(guò)這篇博文淺記一下。
一. nn.Module
??在介紹這三個(gè)容器之前,我們需要先知道什么是Module
。我們?cè)趧?chuàng)建模型的時(shí)候幾乎所有的模型都是繼承于這個(gè)類(lèi)。他是所有網(wǎng)絡(luò)的基類(lèi),用來(lái)管理網(wǎng)絡(luò)的屬性。有兩個(gè)與他相關(guān)聯(lián)的模塊:nn.Parameter
和nn.functional
。所有這些模塊都來(lái)自于torch.nn
。下面我們先簡(jiǎn)要介紹下這幾個(gè)模塊。
1.1. nn.Parameter
??首先是nn.Parameter
, 在Pytorch
中,nn.Parameter
是用于創(chuàng)建模型參數(shù)的特殊類(lèi)。在一個(gè)模型中,往往有許多的參數(shù),要手動(dòng)管理這些參數(shù)并不是一件容易的事情。Pytorch
一般將參數(shù)用nn.Parameter
來(lái)表示,并且用nn.Module
來(lái)管理其結(jié)構(gòu)下的所有參數(shù)。
## 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 可以將多個(gè)nn.Parameter組成一個(gè)列表
params_list = nn.ParameterList([nn.Parameter(torch.rand(8,i)) for i in range(1,3)])
print(params_list)
print(params_list[0].requires_grad)
## nn.ParameterDict 可以將多個(gè)nn.Parameter組成一個(gè)字典
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)
可以通過(guò)Module把上面定義的參數(shù)管理起來(lái):
# module.parameters()返回一個(gè)生成器,包括其結(jié)構(gòu)下的所有parameters
module = nn.Module()
module.w = w
module.params_list = params_list
module.params_dict = params_dict
num_param = 0
for param in module.parameters():
print(param,"\n")
num_param = num_param + 1
print("number of Parameters =",num_param)
??實(shí)際使用的時(shí)候一般通過(guò)繼承nn.Module
來(lái)構(gòu)建模塊類(lèi),并將所有含有需要學(xué)習(xí)的參數(shù)的部分放在構(gòu)造函數(shù)中。
#以下范例為Pytorch中nn.Linear的源碼的簡(jiǎn)化版本
#可以看到它將需要學(xué)習(xí)的參數(shù)放在了__init__構(gòu)造函數(shù)中,并在forward中調(diào)用F.linear函數(shù)來(lái)實(shí)現(xiàn)計(jì)算邏輯。
class Linear(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)
def forward(self, input):
return F.linear(input, self.weight, self.bias)
1.2. nn.functional
nn.functional
(一般引入后改名為F)有各種功能組件的函數(shù)實(shí)現(xiàn)。 比如:
- 激活函數(shù)系列(
F.relu, F.sigmoid, F.tanh, F.softmax
)- 模型層系列(
F.linear, F.conv2d, F.max_pool2d, F.dropout2d, F.embedding
)- 損失函數(shù)系列(
F.binary_cross_entropy, F.mse_loss, F.cross_entropy
)
??為了便于對(duì)參數(shù)進(jìn)行管理, 一般通過(guò)繼承nn.Module
轉(zhuǎn)換為類(lèi)的實(shí)現(xiàn)形式, 并直接封裝在nn
模塊下:
- 激活函數(shù)變成(
nn.ReLu, nn.Sigmoid, nn.Tanh, nn.Softmax
)- 模型層(
nn.Linear, nn.Conv2d, nn.MaxPool2d, nn.Embedding
)- 損失函數(shù)(
nn.BCELoss, nn.MSELoss, nn.CrossEntorpyLoss
)
??所以我們表面上用nn
建立的這些激活函數(shù), 層, 損失函數(shù), 背后都在functional
里面具體實(shí)現(xiàn)。繼續(xù)往下看你就知道了, nn.Module
這個(gè)模塊確實(shí)非常強(qiáng)大, 除了可以管理其引用的各種參數(shù),還可以管理其引用的子模塊。
1.3. nn.Module
我們的重點(diǎn)是介紹這個(gè)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()
我們只需要重點(diǎn)關(guān)注其中的兩個(gè)即可:_parameters
和_modules
_parameters
: 存儲(chǔ)管理屬于nn.Parameter
類(lèi)的屬性,例如權(quán)值,偏置這些參數(shù)_modules
: 存儲(chǔ)管理nn.Module
類(lèi), 比如經(jīng)典網(wǎng)絡(luò)LeNet
中,會(huì)構(gòu)建子模塊,卷積層,池化層,就會(huì)存儲(chǔ)在_modules中
這里提個(gè)問(wèn)題:nn.Parameter
和nn.Module
中的_parameters
的區(qū)別是什么?
nn.Parameter
:它是torch.Tensor
的子類(lèi),用于標(biāo)記張量為模型的可學(xué)習(xí)參數(shù)。在定義模型的過(guò)程中,我們通常會(huì)使用nn.Parameter
來(lái)創(chuàng)建可學(xué)習(xí)參數(shù),并將其作為模型的屬性。這樣做的好處是,nn.Parameter
對(duì)象會(huì)自動(dòng)被注冊(cè)為模型的參數(shù),參與梯度計(jì)算和參數(shù)更新。_parameters
:它是nn.Module
類(lèi)中的一個(gè)屬性,是一個(gè)字典,用于存儲(chǔ)模型的可學(xué)習(xí)參數(shù)。字典的鍵是參數(shù)的名稱(chēng),值是對(duì)應(yīng)的參數(shù)張量(nn.Parameter
類(lèi)型)。_parameters
屬性的值會(huì)自動(dòng)從模型的屬性中提取可學(xué)習(xí)參數(shù),并將其添加到字典中。
??可以將 _parameters
視為存儲(chǔ)模型可學(xué)習(xí)參數(shù)的容器,而 nn.Parameter
則是用于創(chuàng)建和標(biāo)記這些參數(shù)的特殊類(lèi)。通過(guò)使用 nn.Parameter
創(chuàng)建參數(shù),并將其作為模型屬性,這些參數(shù)將被自動(dòng)添加到 _parameters
字典中,方便對(duì)它們進(jìn)行統(tǒng)一的管理和操作。即nn.Parameter
是用于創(chuàng)建模型參數(shù)的特殊類(lèi),而 _parameters
是存儲(chǔ)模型參數(shù)的字典屬性。使用 nn.Parameter
創(chuàng)建的參數(shù)會(huì)自動(dòng)添加到 _parameters
字典中,以便于管理和訪問(wèn)模型的參數(shù)。
nn.Module
構(gòu)建構(gòu)網(wǎng)絡(luò)的過(guò)程是什么樣的?以下面的網(wǎng)絡(luò)為例:
import torch
from torch import nn
class Net(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)
def forward(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()
構(gòu)建過(guò)程如下:
??我們是先有一個(gè)大的
Module
(上面創(chuàng)建的Net
)繼承nn.Module
這個(gè)基類(lèi),比如上面的Net
,然后這個(gè)Net
里面又可以有很多的子模塊,這些子模塊同樣也是繼承于nn.Module
,在這些Module
的__init__
方法中,會(huì)先通過(guò)調(diào)用父類(lèi)的初始化方法進(jìn)行父類(lèi)屬性的一個(gè)初始化。然后在構(gòu)建每個(gè)子模塊的時(shí)候,其實(shí)分為兩步,第一步是初始化,然后被__setattr__
這個(gè)方法通過(guò)判斷value
的類(lèi)型將其保存到相應(yīng)的屬性字典里面去,然后再進(jìn)行賦值給相應(yīng)的成員。這樣一個(gè)個(gè)的構(gòu)建子模塊,最終把整個(gè)Net
構(gòu)建完畢。具體過(guò)程可以自己調(diào)試看一下。
總結(jié):
- 一個(gè)
module
可以包含多個(gè)子module
(Net
包含卷積層,BN
層,激活函數(shù))- 一個(gè)
module
相當(dāng)于一個(gè)運(yùn)算, 必須實(shí)現(xiàn)forward()
函數(shù)(有些模塊的forward需要自己去重新寫(xiě),往下看你就知道了)- 每個(gè)
module
都有很多個(gè)字典管理它的屬性(最常用的就是_parameters
,_modules
)
??知道了網(wǎng)絡(luò)的構(gòu)建過(guò)程,我們就可以對(duì)別人創(chuàng)建好的模型進(jìn)行解析,提取其中的若干部分,關(guān)于這部分的介紹可以參考這篇博文:Pytorch提取神經(jīng)網(wǎng)絡(luò)層結(jié)構(gòu)、層參數(shù)及自定義初始化。
二. nn.Sequential
??上面介紹完了nn.Module
模塊,我們開(kāi)始介紹容器。首先我們來(lái)看下nn.Sequential
,nn.Sequential
是 PyTorch
中的一個(gè)模塊容器,用于按順序組合多個(gè)模塊。它可以將一系列的模塊按照順序連接起來(lái),形成一個(gè)串聯(lián)的模型結(jié)構(gòu)。我們來(lái)看下他在pytorch
中是怎么實(shí)現(xiàn)的,這里我們只看構(gòu)造函數(shù)和前向傳播部分,其他部分的代碼省略:
class Sequential(Module):
...
def __init__(self, *args):
super(Sequential, self).__init__()
if len(args) == 1 and isinstance(args[0], OrderedDict):
for key, module in args[0].items():
self.add_module(key, module)
else:
for idx, module in enumerate(args):
self.add_module(str(idx), module)
...
def forward(self, input):
for module in self:
input = module(input)
return input
??通過(guò)上面的代碼可以看到,nn.Sequential
是繼承于Module
,說(shuō)明Sequential
本身也是一個(gè)Module
,所以它也會(huì)有那幾個(gè)字典參數(shù)??梢钥吹?code>nn.Sequential自己實(shí)現(xiàn)了forward
方法。nn.Sequential
中經(jīng)常使用的主要有如下幾個(gè)方法:
forward(input)
:定義模型的前向傳播過(guò)程。在nn.Sequential
中,此方法會(huì)按照模塊的順序依次調(diào)用每個(gè)模塊的forward
方法,將前一個(gè)模塊的輸出作為輸入傳遞給下一個(gè)模塊,從而計(jì)算最終的輸出。add_module(name, module)
:向nn.Sequential
中添加一個(gè)子模塊。name
是子模塊的名稱(chēng),module
是要添加的子模塊對(duì)象。模塊將按照添加的順序依次進(jìn)行前向傳播。parameters()
:返回nn.Sequential
中所有可學(xué)習(xí)參數(shù)的迭代器??梢酝ㄟ^(guò)迭代器訪問(wèn)和操作模型的可學(xué)習(xí)參數(shù)。zero_grad()
:將nn.Sequential
中所有模塊的參數(shù)梯度置零。通常在每次進(jìn)行梯度更新之前調(diào)用該方法
??就列舉上面幾個(gè)方法,其實(shí)還很對(duì),平時(shí)用的最多的還是add_module(name, module)
這個(gè)方法,用于添加模塊。來(lái)看下 nn.Sequential
到底怎么使用的?
class Net(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),)
def forward(self, x):
x = self.features(x)
x = x.view(x.size()[0], -1)
x = self.classifier(x)
return x
也可以使用add_module的方法進(jìn)行創(chuàng)建:
import torch
import torch.nn as nn
class Net(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))
def forward(self, x):
x = self.features(x)
x = x.view(x.size()[0], -1)
x = self.classifier(x)
return x
??通過(guò)上面的網(wǎng)絡(luò)搭建,可以看到,forward
函數(shù)中只用了一句self.features(x)
即可完成六句話的執(zhí)行。其之所以能完成這個(gè)操作地歸功于nn.Sequential
中的forward
函數(shù),程序在執(zhí)行功的過(guò)程中會(huì)把參數(shù)傳遞給nn.Sequential
中進(jìn)行解析,具體實(shí)現(xiàn)過(guò)程可以調(diào)試代碼觀察。
總結(jié):nn.Sequential
是nn.module
的容器, 用于按順序包裝一組網(wǎng)絡(luò)層,主要有以下兩個(gè)特點(diǎn):
- 順序性: 各網(wǎng)絡(luò)層之間嚴(yán)格按照順序構(gòu)建,這時(shí)候一定要注意前后層數(shù)據(jù)的關(guān)系
- 自帶
forward()
: 自帶的forward
里,通過(guò)for
循環(huán)依次執(zhí)行前向傳播運(yùn)算
三. nn.ModuleList
??nn.ModuleList
也是nn.module
的容器, 用于包裝一組網(wǎng)絡(luò)層, 以迭代方式調(diào)用網(wǎng)絡(luò)層, 常用的方法有如下幾個(gè),跟list的使用很像:
append()
: 在ModuleList
后面添加網(wǎng)絡(luò)層extend()
: 拼接兩個(gè)ModuleList
insert()
: 指定在ModuleList
中位置插入網(wǎng)絡(luò)層
直接看個(gè)例子,怎么使用nn.ModuleList
搭建網(wǎng)絡(luò):
class ModuleListNet(nn.Module):
def __init__(self):
super(ModuleListNet, self).__init__()
self.linears = nn.ModuleList([nn.Linear(10, 10) for i in range(10)])
def forward(self, x):
for i, linear in enumerate(self.linears):
x = linear(x)
return x
??上面的例子通過(guò)使用列表推導(dǎo)式創(chuàng)建了10個(gè)nn.Linear
模塊。整體上使用起來(lái)還是很簡(jiǎn)單的,具體實(shí)現(xiàn)過(guò)程感興趣的小伙伴可以通過(guò)調(diào)試代碼查看。
三. nn.ModuleDict
我們?cè)賮?lái)看下nn.ModuleDict
這個(gè)模塊。nn.ModuleDict
也是nn.module
的容器, 用于包裝一組網(wǎng)絡(luò)層, 以索引方式調(diào)用網(wǎng)絡(luò)層, 常用的有如下方法,跟字典的操作比較像:
clear()
: 清空ModuleDict
items()
: 返回可迭代的鍵值對(duì)(key-value pairs
)keys()
: 返回字典的鍵(key
)values()
: 返回字典的值(value)
pop()
: 返回一對(duì)鍵值對(duì), 并從字典中刪除
看個(gè)例子:文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-477375.html
import torch
import torch.nn as nn
class MyModel(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)
})
def forward(self, x):
for module in self.module_dict.values():
x = module(x)
return x
# 創(chuàng)建模型實(shí)例
model = MyModel()
# 隨機(jī)生成輸入
x = torch.randn(1, 3, 32, 32)
# 進(jìn)行前向傳播
output = model(x)
print(output.shape)
上面通過(guò)nn.ModuleDict
創(chuàng)建了一個(gè)網(wǎng)絡(luò),整體來(lái)說(shuō)還是很簡(jiǎn)單的,跟字典操作差不多。
??關(guān)于nn.Sequential
, nn.ModuleList
, nn.ModuleDict
的基本使用基本上酒介紹完了,如有錯(cuò)誤,敬請(qǐng)指正!文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-477375.html
到了這里,關(guān)于pytorch容器之nn.Sequential, nn.ModuleList, nn.ModuleDict介紹的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!