torch.nn包含了大量的函数及类,如nn.Linear(); nn.ReLU()等等,如想了解nn构造块包含了哪些函数,文档可参考:torch.nn.
nn.Module的nn模块提供了模型构造类,你可以通过继承它来搭建你自己的网络层。torch.nn.Module 这个类的内部有多达 48 个函数,这个类是 PyTorch 中所有 neural network module的基类,可以通过继承nn.Module来完成自己的网络搭建,文档可参考:torch.nn.Module; 知乎
nn.Module中的函数forward()和__init__()需要通过子类来实现,不然就会报错,__init__()主要作用是定义基础的网络层,forward()则是实现各层网络的连接,由于nn.Module模块中自带了__call__()函数,所以当项搭建好后的网络传递数据的时候,forward()函数会自动运行;
一个简单的例子:
import torch
from torch import nn
class MLP(nn.Module):
def __init__(self):
super(MLP, self).__init__()
self.hidden = nn.Linear(784, 256)
self.act = nn.ReLU()
self.output = nn.Linear(256, 10)
def forward(self, x):
a = self.act(self.hidden(x))
return self.output(a)
X = torch.rand(2, 784)
net = MLP()
print(net)
net(X)
上一个例子是将各网络层添加到__init__()中来搭建网络,nn.Module父类中自带了函数add_module(),可以通过调用该函数,来添加网络层;添加完的网络层都存放在self._modules中,这里请注意forward()函数的写法:
"""
函数功能:构建一个容器,用于存放模块
语法笔记:
1.与python自带的字典相比较,OrderedDict表示有序的字典;
2.enumerate() 函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标;
"""
from collections import OrderedDict
class MySequential(nn.Module):
def __init__(self, *args):
super(MySequential, self).__init__()
# 如果传入的是一个有序的字典
# isinstance(args[0], OrderdDict)判断args[0]的类型是不是有序字典
if len(args) == 1 and isinstance(args[0], OrderdDict):
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._modules.values():
# 每层网络输入后,都会返回一个输出
# 当前层的输出作为下一层的输入
input = module(input)
return input
下面两种方式的输出结果一致
X = torch.rand(2, 784)
# 通过传有序的字典
Dict = OrderedDict([
('0', nn.Linear(784, 256)),
('1', nn.ReLU()),
('2', nn.Linear(256, 10))
])
net1 = MySequential(Dict)
print(net1)
net1(X)
print('----------------------------------------')
# 通过传可迭代的对象
net2 = MySequential(
nn.Linear(784, 256),
nn.ReLU(),
nn.Linear(256, 10),
)
print(net2)
net2(X)
nn.Sequential作为容易也是用于存放网络层,但要求是按照顺序进行排列的,所以必须确保上一层的输出与下一层的输入的size保持一致,实例如下:
import torch
import torch.nn as nn
import torch.nn.functional as F
class net_seq(nn.Module):
def __init__(self):
super(net2, self).__init__()
self.seq = nn.Sequential(
nn.Conv2d(1,20,5),
nn.ReLU(),
nn.Conv2d(20,64,5),
nn.ReLU()
)
def forward(self, x):
return self.seq(x)
net = net_seq()
print(net)
也可以通过OrderedDict来指定每个module的名字:
from collections import OrderedDict
class net_seq(nn.Module):
def __init__(self):
super(net_seq, self).__init__()
self.seq = nn.Sequential(OrderedDict([
('conv1', nn.Conv2d(1,20,5)),
('relu1', nn.ReLU()),
('conv2', nn.Conv2d(20,64,5)),
('relu2', nn.ReLU())
]))
def forward(self, x):
return self.seq(x)
net = net_se:q()
print(net)
ModuleList接收一个子模块的列表作为输入,也可以进行append和extend操作;
net = nn.ModuleList([nn.Linear(784, 256), nn.ReLU()])
net.append(nn.Linear(256, 10)) # # 类似List的append操作
print(net[-1]) # 类似List的索引访问
print(net)
# net(torch.zeros(1, 784)) # 会报NotImplementedError
ModuleList仅仅是一个储存各种模块的列表,这些模块之间没有联系也没有顺序;
ModuleList没有实现forward功能,如果对net进行输入操作会报错;意思就是你需要自己将各层网络连接起来,相比较于sequential更有定制性;
ModuleList相比较于python的list会自动添加网络的参数parameters;
nn.Sequential与nn.ModuleList的区别
- nn.Sequential实现了forward函数,可以见上面的例子。ModuleList需要在类的内部自己实现forward函数;
def forward(self, x):
for m in self.modlist:
x = m(x)
return x
如果完全使用nn.Sequential是可以的,只是会失去部分灵活性,不可进行定制了;
- nn.Sequential可以使用OrderedDict进行命名;
- 有时候网络中会有许多相似或者重复的层,这时会考虑通过for循环来创建它们,而不是一行一行地写;
linears = [nn.Linear(10, 10) for i in range(5)]
但是问题是,这样创建出来的层它们之间的参数是一样的。
参见:知乎
方式5:通过ModuleDict搭建网络
目前感觉ModuleDict和ModuleList的区别是它可以自己命名网络层的名字而已;
net = nn.ModuleDict({
'linear': nn.Linear(784, 256),
'act': nn.ReLU(),
})
net['output'] = nn.Linear(256, 10) # 添加
print(net['linear']) # 访问
print(net.output)
print(net)
# net(torch.zeros(1, 784)) # 会报NotImplementedError
其他补充
- 如果某层的某个参数的requires_grad为False,则该层的这个参数不会被更新;
- torch.nn.ReLU()和torch.nn.functional.relu()本质上没什么区别;源代码显示,nn.ReLU()是通过调用nn.functional.relu()来实现的;
二、模型参数的初始化
2.1 添加参数
方式1:model.state_dict()
model.state_dict()返回的是一个有序的字典,分别对应参数的名称及具体参数;
# 如果一个网络各层已经定义好了参数,可以通过遍历的方式来访问它;
for params, value in net.state_dict().items():
print(f'params:{params} \n value.size:{value.size()}')
输出如下:
params:conv1.weight
value.size:torch.Size([32, 3, 3, 3])
params:conv1.bias
value.size:torch.Size([32])
params:conv2.weight
value.size:torch.Size([32, 3, 3, 3])
params:conv2.bias
value.size:torch.Size([32])
params:dense1.weight
value.size:torch.Size([128, 288])
params:dense1.bias
value.size:torch.Size([128])
params:dense2.weight
value.size:torch.Size([10, 128])
params:dense2.bias
value.size:torch.Size([10])
方式2:model.named_parameters
model.named_parameters除了返回参数Tensor外,还会返回对应的名字;它与前面提到的model.state_dict()不同的是,它是一个迭代器。
print(type(net.named_parameters()))
for name, param in net.named_parameters():
print(name, param.size())
输出如下:
<class 'generator'>
0.weight torch.Size([3, 4])
0.bias torch.Size([3])
2.weight torch.Size([1, 3])
2.bias torch.Size([1])
方式3:model.parameters()
先说功能,model.parameters()是一个迭代器,但是只能返回参数的值,不可返回名字;准确来说,model.parameters()是通过model.named_parameters来实现的,定义如下:
def parameters(self, recurse: bool = True) -> Iterator[Parameter]:
r"""Returns an iterator over module parameters.
This is typically passed to an optimizer.
Args:
recurse (bool): if True, then yields parameters of this module
and all submodules. Otherwise, yields only parameters that
are direct members of this module.
Yields:
Parameter: module parameter
Example::
>>> for param in model.parameters():
>>> print(type(param), param.size())
<class 'torch.Tensor'> (20L,)
<class 'torch.Tensor'> (20L, 1L, 5L, 5L)
"""
for name, param in self.named_parameters(recurse=recurse):
yield param
2.2 初始化模型的参数
pytorch的nn.init提供了多种预设的初始化方式,可以访问:nn.init;下面以将权重参数以均值为0,标准差为0.01的正态分布来设置:
for name, param in net.named_parameters():
if 'weight' in name:
init.normal_(param, mean=0, std=0.01)
print(name, param.data)
使用常数填充nn.init.constant_(tensor, val):
w = torch.empty(3, 5)
nn.init.constant_(w, 0.3)