4.4 自定义层

深度学习的一个魅力在于神经网络中各式各样的层,例如全连接层和后面章节中将要介绍的卷积层、池化层与循环层。虽然PyTorch提供了大量常用的层,但有时候我们依然希望自定义层。本节将介绍如何使用Module来自定义层,从而可以被重复调用。

4.4.1 不含模型参数的自定义层

我们先介绍如何定义一个不含模型参数的自定义层。事实上,这和4.1节(模型构造)中介绍的使用Module类构造模型类似。下面的CenteredLayer类通过继承Module类自定义了一个将输入减掉均值后输出的层,并将层的计算定义在了forward函数里。这个层里不含模型参数。

  1. import torch
  2. from torch import nn
  3. class CenteredLayer(nn.Module):
  4. def __init__(self, **kwargs):
  5. super(CenteredLayer, self).__init__(**kwargs)
  6. def forward(self, x):
  7. return x - x.mean()

我们可以实例化这个层,然后做前向计算。

  1. layer = CenteredLayer()
  2. layer(torch.tensor([1, 2, 3, 4, 5], dtype=torch.float))

输出:

  1. tensor([-2., -1., 0., 1., 2.])

我们也可以用它来构造更复杂的模型。

  1. net = nn.Sequential(nn.Linear(8, 128), CenteredLayer())

下面打印自定义层各个输出的均值。因为均值是浮点数,所以它的值是一个很接近0的数。

  1. y = net(torch.rand(4, 8))
  2. y.mean().item()

输出:

  1. 0.0

4.4.2 含模型参数的自定义层

我们还可以自定义含模型参数的自定义层。其中的模型参数可以通过训练学出。

在4.2节(模型参数的访问、初始化和共享)中介绍了Parameter类其实是Tensor的子类,如果一个TensorParameter,那么它会自动被添加到模型的参数列表里。所以在自定义含模型参数的层时,我们应该将参数定义成Parameter,除了像4.2.1节那样直接定义成Parameter类外,还可以使用ParameterListParameterDict分别定义参数的列表和字典。

ParameterList接收一个Parameter实例的列表作为输入然后得到一个参数列表,使用的时候可以用索引来访问某个参数,另外也可以使用appendextend在列表后面新增参数。

  1. class MyDense(nn.Module):
  2. def __init__(self):
  3. super(MyDense, self).__init__()
  4. self.params = nn.ParameterList([nn.Parameter(torch.randn(4, 4)) for i in range(3)])
  5. self.params.append(nn.Parameter(torch.randn(4, 1)))
  6. def forward(self, x):
  7. for i in range(len(self.params)):
  8. x = torch.mm(x, self.params[i])
  9. return x
  10. net = MyDense()
  11. print(net)

输出:

  1. MyDense(
  2. (params): ParameterList(
  3. (0): Parameter containing: [torch.FloatTensor of size 4x4]
  4. (1): Parameter containing: [torch.FloatTensor of size 4x4]
  5. (2): Parameter containing: [torch.FloatTensor of size 4x4]
  6. (3): Parameter containing: [torch.FloatTensor of size 4x1]
  7. )
  8. )

ParameterDict接收一个Parameter实例的字典作为输入然后得到一个参数字典,然后可以按照字典的规则使用了。例如使用update()新增参数,使用keys()返回所有键值,使用items()返回所有键值对等等,可参考官方文档

  1. class MyDictDense(nn.Module):
  2. def __init__(self):
  3. super(MyDictDense, self).__init__()
  4. self.params = nn.ParameterDict({
  5. 'linear1': nn.Parameter(torch.randn(4, 4)),
  6. 'linear2': nn.Parameter(torch.randn(4, 1))
  7. })
  8. self.params.update({'linear3': nn.Parameter(torch.randn(4, 2))}) # 新增
  9. def forward(self, x, choice='linear1'):
  10. return torch.mm(x, self.params[choice])
  11. net = MyDictDense()
  12. print(net)

输出:

  1. MyDictDense(
  2. (params): ParameterDict(
  3. (linear1): Parameter containing: [torch.FloatTensor of size 4x4]
  4. (linear2): Parameter containing: [torch.FloatTensor of size 4x1]
  5. (linear3): Parameter containing: [torch.FloatTensor of size 4x2]
  6. )
  7. )

这样就可以根据传入的键值来进行不同的前向传播:

  1. x = torch.ones(1, 4)
  2. print(net(x, 'linear1'))
  3. print(net(x, 'linear2'))
  4. print(net(x, 'linear3'))

输出:

  1. tensor([[1.5082, 1.5574, 2.1651, 1.2409]], grad_fn=<MmBackward>)
  2. tensor([[-0.8783]], grad_fn=<MmBackward>)
  3. tensor([[ 2.2193, -1.6539]], grad_fn=<MmBackward>)

我们也可以使用自定义层构造模型。它和PyTorch的其他层在使用上很类似。

  1. net = nn.Sequential(
  2. MyDictDense(),
  3. MyListDense(),
  4. )
  5. print(net)
  6. print(net(x))

输出:

  1. Sequential(
  2. (0): MyDictDense(
  3. (params): ParameterDict(
  4. (linear1): Parameter containing: [torch.FloatTensor of size 4x4]
  5. (linear2): Parameter containing: [torch.FloatTensor of size 4x1]
  6. (linear3): Parameter containing: [torch.FloatTensor of size 4x2]
  7. )
  8. )
  9. (1): MyListDense(
  10. (params): ParameterList(
  11. (0): Parameter containing: [torch.FloatTensor of size 4x4]
  12. (1): Parameter containing: [torch.FloatTensor of size 4x4]
  13. (2): Parameter containing: [torch.FloatTensor of size 4x4]
  14. (3): Parameter containing: [torch.FloatTensor of size 4x1]
  15. )
  16. )
  17. )
  18. tensor([[-101.2394]], grad_fn=<MmBackward>)

小结

  • 可以通过Module类自定义神经网络中的层,从而可以被重复调用。

注:本节与原书此节有一些不同,原书传送门