之前写了一遍文章 上文链接简单介绍用nn.Sequential来构造神经网络的方法,本文进一步介绍更灵活的方法来构造神经网络。
Pytorch中,模型的参数用nn.Parameter来表达,但是要手动去管理这些参数不容易,所以我们构建模型一般使用nn.Module,管理参数也是使用nn.Module和它的子类。
nn.Parameter 具有require_grad=True的属性
nn.ParameterList 可以将多个nn.Parameter组成一个列表
nn.ParameterDict 可以将多个nn.Parameter组成一个字典
例子如下
import torch
from torch import nn
w = troch.nn.Parameter(torch.randn(2,2))
print(w)
print(w.requires_grad)
输出结果:
Parameter containing:
tensor([[0.5738, 0.6093],
[0.1924, 0.0886]], requires_grad=True)
True
================================================================
w1 = nn.Parameter(torch.rand(2, 2))
w2 = nn.Parameter(torch.rand(2, 2))
para_l = nn.ParameterList([w1, w2])
print(para_l)
print(para_l[0])
输出结果:
ParameterList(
(0): Parameter containing: [torch.float32 of size 2x2]
(1): Parameter containing: [torch.float32 of size 2x2]
)
Parameter containing:
tensor([[0.9076, 0.4244],
[0.1826, 0.7291]], requires_grad=True)
================================================================
para_dict = nn.ParameterDict({
'a': nn.Parameter(torch.rand(2, 2)),
'b': nn.Parameter(torch.rand(2, 2))
})
print(para_dict)
print(para_dict['a'])
输出结果:
ParameterDict(
(a): Parameter containing: [torch.FloatTensor of size 2x2]
(b): Parameter containing: [torch.FloatTensor of size 2x2]
)
Parameter containing:
tensor([[0.8930, 0.3071],
[0.3121, 0.7276]], requires_grad=True)
# 可以用Module将它们管理起来
# module.parameters()返回一个生成器,包括其结构下的所有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)
输出结果
Parameter containing:
tensor([[0.1886, 0.7707],
[0.7998, 0.8668]], requires_grad=True)
Parameter containing:
tensor([[0.3607, 0.2619],
[0.0187, 0.2842]], requires_grad=True)
Parameter containing:
tensor([[0.0505, 0.9494],
[0.1531, 0.6120]], requires_grad=True)
Parameter containing:
tensor([[0.5063, 0.5944],
[0.0197, 0.2520]], requires_grad=True)
Parameter containing:
tensor([[0.1467, 0.2571],
[0.7267, 0.6117]], requires_grad=True)
number of Parameters = 5
一般将有学习参数的的函数放在构造函数中,
下面可以看看nn.Module的子类nn.Linear是如何与nn.Parameter还有nn.Functional互相作用的
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)
nn.Module提供了很多方法来管理子模块
其中比较重要的有下面几个
children() 方法:
返回生成器,包括模块下的所有子模块。
named_children() 方法:
返回一个生成器,包括模块下的所有子模块,以及它们的名字。
modules() 方法:
返回一个生成器,包括模块下的所有各个层级的模块,包括模块本身。
named_modules() 方法:
返回一个生成器,包括模块下的所有各个层级的模块以及它们的名字,包括模块本身。
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.embedding = nn.Embedding(num_embeddings = 10000,embedding_dim = 3,padding_idx = 1)
self.conv = nn.Sequential()
self.conv.add_module("conv_1",nn.Conv1d(in_channels = 3,out_channels = 16,kernel_size = 5))
self.conv.add_module("pool_1",nn.MaxPool1d(kernel_size = 2))
self.conv.add_module("relu_1",nn.ReLU())
self.conv.add_module("conv_2",nn.Conv1d(in_channels = 16,out_channels = 128,kernel_size = 2))
self.conv.add_module("pool_2",nn.MaxPool1d(kernel_size = 2))
self.conv.add_module("relu_2",nn.ReLU())
self.dense = nn.Sequential()
self.dense.add_module("flatten",nn.Flatten())
self.dense.add_module("linear",nn.Linear(6144,1))
self.dense.add_module("sigmoid",nn.Sigmoid())
def forward(self,x):
x = self.embedding(x).transpose(1,2)
x = self.conv(x)
y = self.dense(x)
return y
net = Net()
i = 0
for child in net.children():
i+=1
print(child,"\n")
print("child number",i)
# children是以Sequential为单位
Embedding(10000, 3, padding_idx=1) #children 1
Sequential( #children2
(conv_1): Conv1d(3, 16, kernel_size=(5,), stride=(1,))
(pool_1): MaxPool1d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(relu_1): ReLU()
(conv_2): Conv1d(16, 128, kernel_size=(2,), stride=(1,))
(pool_2): MaxPool1d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(relu_2): ReLU()
)
Sequential( #children3
(flatten): Flatten()
(linear): Linear(in_features=6144, out_features=1, bias=True)
(sigmoid): Sigmoid()
)
child number 3
i = 0
for name,child in net.named_children():
i+=1
print(name,":",child,"\n")
print("child number",i)
embedding : Embedding(10000, 3, padding_idx=1)
conv : Sequential(
(conv_1): Conv1d(3, 16, kernel_size=(5,), stride=(1,))
(pool_1): MaxPool1d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(relu_1): ReLU()
(conv_2): Conv1d(16, 128, kernel_size=(2,), stride=(1,))
(pool_2): MaxPool1d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(relu_2): ReLU()
)
dense : Sequential(
(flatten): Flatten()
(linear): Linear(in_features=6144, out_features=1, bias=True)
(sigmoid): Sigmoid()
)
child number 3
可以通过named_childred方法来索引相应的层
children_dict = {name:module for name,module in net.named_children()}
print(children_dict)
embedding = children_dict['embedding']
embedding.requires_grad_(False) #冻结其参数,这样可以控制训练参数数目
(1)model.children()和model.named_children()方法返回的是迭代器iterator;
(2)model.children():每一次迭代返回的每一个元素实际上是 Sequential 类型,而Sequential类型又可以使用下标index索引来获取每一个Sequenrial 里面的具体层,比如conv层、dense层等;
(3)model.named_children():每一次迭代返回的每一个元素实际上是 一个元组类型,元组的第一个元素是名称,第二个元素就是对应的层或者是Sequential。
类似的:
(1)model.modules()和model.named_modules()方法返回的是迭代器iterator;
(2)model的modules()方法和named_modules()方法都会将整个模型的所有构成(包括包装层、单独的层、自定义层等)由浅入深依次遍历出来,只不过modules()返回的每一个元素是直接返回的层对象本身,而named_modules()返回的每一个元素是一个元组,第一个元素是名称,第二个元素才是层对象本身。
(3)如何理解children和modules之间的这种差异性。注意pytorch里面不管是模型、层、激活函数、损失函数都可以当成是Module的拓展,所以modules和named_modules会层层迭代,由浅入深,将每一个自定义块block、然后block里面的每一个层都当成是module来迭代。而children就比较直观,就表示的是所谓的“孩子”,所以没有层层迭代深入。