参考
4.1 模型构造
让我们回顾以下多重感知机的简洁实现中包含单隐藏层的多重感知机的实现方法。我们首先构造Sequential
实例,然后依次添加两个全连接层。其中第一层的输出大小为256,即隐藏层单元个数是256;第二层的输出大小为10,即输出层单元个数是10.
4.1.1 继承Module
类来构造模型
Module
类是nn
模块里提供的一个模型构造类,是所有神经网络模块的基类,我们可以继承它来定义我们想要的模型。下面继承Module
类构造本节开头提到的多层感知机。这里定义的MLP
类重载了Module
类的__init__
函数。它们分别用于创建模型和定义前向计算。
import torch
from torch import nnclass MLP(nn.Module):# 声明带有模型参数的层,这里声明了两个全连接层def __init__(self, **kwargs):# 调用MLP父类Module的构造函数来进行必要的初始化。这样在构造实例时还可以指定其他函数# 参数,如"模型参数的访问、初始化和共享"super(MLP, self).__init__(**kwargs)self.hidden = nn.Linear(784, 256)self.act = nn.ReLU()self.output = nn.Linear(256,10)# 定义模型的向前运算,即如何根据输入x计算返回所需要的模型输出def forward(self, x):a = self.act(self.hidden(x))return self.output(a)
以上MLP
类中无须定义反向传播函数。系统将通过自动求梯度而生成反向传播所需的backward
函数。
我们可以实例化MLP
类得到模型变量net
。下面的代码初始化net
并传入数据X
做一次前向计算。
X = torch.rand(2, 784)
net = MLP()
print(net)
注意,这里并没有将Module
类命名为Layer
(层)或者Model
(模型)之类的名字,这是因为该类是一个可供自由组建的部件。它的子类既可以是一个层(如PyTorch提供的Linear
类),又可以是一个模型(如这里定义的MLP
类),或者是模型的一个部分。我们下面通过两个例子来展示它的灵活性。
4.1.2 Module
的子类
我们刚刚提到,Module
类是一个通用的部件。事实上,PyTorch还实现了继承自Module
的可以方便构建模型的类: 如Sequential
、ModuleList
和ModuleDict
等等。
4.1.2.1 Sequential
类
它可以接收一个子模块的有序字典(OrderedDict)或者一系列子模块作为参数来逐一添加Module
的实例,而模型的前向计算就是将这些实例添加的顺序逐一计算。
下面我们实现一个与Sequential
类有相同功能的MySequential
类。这或许可以帮助读者更加清晰地理解Sequential
类的工作机制
class MySequential(nn.Module):from collections import OrderedDictdef __init__(self, *args):super(MySequential, self).__init__() # 调用父类if len(args) == 1 and isinstance(args[0], OrderedDict): # 如果传入的是一个OrderedDictfor key, module in args[0].items():self.add_module(key, module) # add_module方法会将module添加进self._modules(一个OrderedDict)else: # 传入的是一些Modulefor idx, module in enumerate(args):self.add_module(str(idx), module)def forward(self, input):# self._modules返回一个 OrderedDict(),保证会按照成员添加时的顺序遍历成员for module in self._modules.values():input = module(input)return input
# 利用 MySequential类定义网络
net = MySequential(nn.Linear(784, 256),nn.ReLU(),nn.Linear(256, 10)
)
print(net)
net(X)
4.1.2.2 ModuleList
类
ModuleList
接收一个子模块的列表作为输入,然后也可以类似List那样进行append和extend操作:
net = nn.ModuleList([nn.Linear(784, 256), nn.ReLU()])
net.append(nn.Linear(256, 10)) # 类似List的append操作
print(net[-1])
print(net)
既然Sequential
和ModuleList
都可以进行列表化构造函数,那二者区别是什么呢。
ModuleList
仅仅是一个存储各种模块的列表,这些模块之间没有联系也没有顺序(所以不用保证相邻层的输入输出维度匹配),而且没有实现forward
功能需要自己实现,所以上面执行net(torch.zeros(1, 784))
会报NotImplementedError
;而Sequential
内的模块需要按照顺序排列,要保证相邻层的输入输出大小匹配,内部forward
功能已经实现。
class MyModule(nn.Module):def __init__(self):super(MyModule, self).__init__()self.linears = nn.ModuleList([nn.Linear(10, 10) for i in range(10)])def forward(self, x):for i, l in enumerate(self.linears):x = self.linears[i // 2](x) + l(x)return x
# 另外, ModuleList不同于一般的Python的list,加入到ModuleList里面的所有模块的参数会被自动添加到整个网络中
class Module_ModuleList(nn.Module):def __init__(self):super(Module_ModuleList, self).__init__()self.linears = nn.ModuleList([nn.Linear(10, 10)])class Module_List(nn.Module):def __init__(self):super(Module_List, self).__init__()self.linears = [nn.Linear(10, 10)]net1 = Module_ModuleList()
net2 = Module_List()print("net1: ")
for p in net1.parameters():print(p.size())print("net2: ")
for p in net2.parameters():print(p)
4.1.2.3 ModuleDict
类
ModuleDict接收一个子模块的字典作为输入,然后也可以类似字典那样添加访问操作
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)
4.1.3 构造复杂的模型
下面构造一个相对复杂的网络FancyMLP
。在这个网络中,我们通过get_constant
函数创建训练中不被迭代的参数,即常数参数。在前向计算中,除了使用创建的常数参数外,我们还使用Tensor
的函数和Python的控制流,并多次调用相同的层。
class FancyMLP(nn.Module):def __init__(self, **kwargs):super(FancyMLP, self).__init__(**kwargs)self.rand_weight = torch.rand((20, 20), requires_grad=False)self.linear = nn.Linear(20, 20)def forward(self, x):print(x)x = self.linear(x)# 使用创建的常数参数,以及nn.functional中的relu函数和mm函数# torch.mm矩阵的乘法x = nn.functional.relu(torch.mm(x, self.rand_weight.data) + 1)# 复用全连接层。x = self.linear(x)# 控制流,这里我们需要调用item函数来返回标量进行比较while x.norm().item() > 1:x /= 2if x.norm().item() < 0.8:x *= 10return x.sum()
X = torch.rand(2, 20)
net = FancyMLP()
net(X)
# 嵌套调用 FancyMLP和 Sequential类
class NestMLP(nn.Module):def __init__(self, **kwargs):super(NestMLP, self).__init__(**kwargs)self.net = nn.Sequential(nn.Linear(40, 30), nn.ReLU())def forward(self, x):return self.net(x)net = nn.Sequential(NestMLP(), nn.Linear(30, 20), FancyMLP())X = torch.rand(2, 40)
print(net)
net(X)