5.1 层和块
为了实现复杂的网络,我们引入了神经网络块的概念。块(block)可以描述单个层,或者描述多个层组成的组件,又或者是整个模型都可以称之为一个块。
使用块进行抽象的一个好处是,可以将一些块组成更大的组件。有利于通过简洁的代码实现复杂的神经网络。
从编程的角度看,块就是一个类。它必须定义一个将输入转换为输出的前向传播函数,而且,还要存储所有用到的参数(有些块可能不需要参数)。
最后,为了计算梯度,块还需要有反向传播函数。但是在定义我们自己的块时,由于自动微分提供了一些后端实现,所以我们只需要考虑前向传播函数和必需的参数即可。
先回顾一下多层感知机
import torch
from torch import nn
from torch.nn import functional as F
net = nn.Sequential(nn.Linear(20, 256), nn.ReLU(), nn.Linear(256, 10))
X = torch.rand(2, 20)
net(X)
tensor([[ 0.2275, -0.0931, 0.0570, 0.0855, -0.0524, 0.0325, 0.1676, -0.1014,
0.0794, -0.1586],
[ 0.2249, -0.0549, -0.0864, -0.1089, -0.1159, -0.0089, 0.1040, -0.2234,
0.1652, -0.2738]], grad_fn=<AddmmBackward0>)
5.1.1 自定义块
接下来,我们将从零开始编写一个块。它包含一个多层感知机,其具有256个隐藏单元的隐藏层和一个10维输出层。
注意,下面的MLP
类继承了表示块的类。 我们的实现只需要提供我们自己的构造函数(Python中的__init__
函数)和前向传播函数。
class MLP(nn.Module):
# 用模型参数声明层。这里,我们声明两个全连接的层
def __init__(self):
# 调用MLP的父类Module的构造函数来执行必要的初始化。
# 这样,在类实例化时也可以指定其他函数参数,例如模型参数params(稍后将介绍)
super().__init__()
self.hidden = nn.Linear(20, 256) # 隐藏层
self.out = nn.Linear(256, 10) # 输出层
# 定义模型的前向传播,即如何根据输入X返回所需的模型输出
def forward(self, X):
# 注意,这里我们使用ReLU的函数版本,其在nn.functional模块中定义。
return self.out(F.relu(self.hidden(X)))
5.1.2 顺序块
class MySequential(nn.Module):
def __init__(self, *args):
super().__init__()
for idx, module in enumerate(args):
# 这里,module是Module子类的一个实例。我们把它保存在'Module'类的成员
# 变量_modules中。_module的类型是OrderedDict
self._modules[str(idx)] = module
def forward(self, X):
# OrderedDict保证了按照成员添加的顺序遍历它们
for block in self._modules.values():
X = block(X)
return X
5.1.3 在前向传播函数中执行代码
class FixedHiddenMLP(nn.Module):
def __init__(self):
super().__init__()
# 不计算梯度的随机权重参数。因此其在训练期间保持不变
self.rand_weight = torch.rand((20, 20), requires_grad=False)
self.linear = nn.Linear(20, 20)
def forward(self, X):
X = self.linear(X)
# 使用创建的常量参数以及relu和mm函数
X = F.relu(torch.mm(X, self.rand_weight) + 1)
# 复用全连接层。这相当于两个全连接层共享参数
X = self.linear(X)
# 控制流
while X.abs().sum() > 1:
X /= 2
return X.sum()
注意,在返回输出之前,模型做了一些不寻常的事情: 它运行了一个while循环,在1范数大于1的条件下, 将输出向量除以2,直到它满足条件为止。 最后,模型返回了X
中所有项的和。 注意,此操作可能不会常用于在任何实际任务中, 我们只展示如何将任意代码集成到神经网络计算的流程中。
5.4 自定义层
5.4.1 不带参数的层
import torch
import torch.nn.functional as F
from torch import nn
class CenteredLayer(nn.Module):
def __init__(self):
super().__init__()
def forward(self, X):
return X - X.mean()
5.4.2 带参数的层
class MyLinear(nn.Module):
def __init__(self, in_units, units):
super().__init__()
self.weight = nn.Parameter(torch.randn(in_units, units))
self.bias = nn.Parameter(torch.randn(units,))
def forward(self, X):
linear = torch.matmul(X, self.weight.data) + self.bias.data
return F.relu(linear)
5.5 读写文件
5.5.1 加载和保存张量
import torch
from torch import nn
from torch.nn import functional as F
x = torch.arange(4)
torch.save(x, 'x-file')
x2 = torch.load('x-file')
我们可以存储一个张量列表,然后把它们读回内存。
y = torch.zeros(4)
torch.save([x, y],'x-files')
x2, y2 = torch.load('x-files')
(x2, y2)
5.5.2 加载和保存模型参数
class MLP(nn.Module):
def __init__(self):
super().__init__()
self.hidden = nn.Linear(20, 256)
self.output = nn.Linear(256, 10)
def forward(self, x):
return self.output(F.relu(self.hidden(x)))
net = MLP()
X = torch.randn(size=(2, 20))
Y = net(X)
接下来,我们将模型的参数存储在一个叫做“mlp.params”的文件中。
torch.save(net.state_dict(), 'mlp.params')
clone = MLP()
clone.load_state_dict(torch.load('mlp.params'))
5.5.3 小结
save
和load
函数可用于张量对象的文件读写。- 我们可以通过参数字典保存和加载网络的全部参数。
- 保存架构必须在代码中完成,而不是在参数中完成。