动手学深度学习 4.4 自定义层

前言

从零开始学习ai文章系列计划是个人在《动手学深度学习》和《磨菇书》两本书的学习中的个人笔记,文章也会以课本中的章节分开,即每个章节一片笔记。我会尽量的把主要内容以及遇到的难点进行记录与解决,如果哪里有错误的欢迎指正。或者不清晰的可以直接查看原文部分。

《动手学深度学习》原文(课本):https://tangshusen.me/Dive-into-DL-PyTorch/#/

《动手学深度学习》代码:https://github.com/ShusenTang/Dive-into-DL-PyTorch

(由于有时候公式太多,可能会直接贴图片)

本节将介绍如何使用Module来自定义层,从而可以被重复调用。

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

下面的CenteredLayer类通过继承Module类自定义了一个将输入减掉均值后输出的层,并将层的计算定义在了forward函数里。

1
2
3
4
5
6
class CenteredLayer(nn.Module):
def __init__(self, **kwargs):
super(CenteredLayer, self).__init__(**kwargs)

def forward(self, x):
return x - x.mean()

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

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

print(output)

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

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

2 含模型参数的自定义层

在4.2节(模型参数的访问、初始化和共享)中介绍了Parameter类其实是Tensor的子类,如果一个Tensor是Parameter,那么它会自动被添加到模型的参数列表里。

nn.Parameter 本质上是 Tensor,默认 requires_grad=True,因此parameter对象默认都是可求导的。

2.1 ParameterList 参数列表

ParameterList 接收一个Parameter实例的列表作为输入然后得到一个参数列表,可以使用append和extend在列表后面新增参数。

手动创建 3个4x4的w权重参数与1个4x1的w权重参数,通过ParameterList将所有参数放在一个列表中管理,然后进行手动计算的前向传播的网络。

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

def forward(self, x):
for i in range(len(self.params)):
x = torch.mm(x, self.params[i])
return x

net = MyDense()
print(net)

2.2 ParameterDict 参数字典

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

下面自定义模型包装多个网路层,模型构建时,用字符串进行网络层选择(这里的网络层是单纯的w权重参数)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class MyDictDense(nn.Module):
def __init__(self):
super(MyDictDense, self).__init__()
self.params = nn.ParameterDict({
'linear1': nn.Parameter(torch.randn(4, 4)),
'linear2': nn.Parameter(torch.randn(4, 1))
})
self.params.update({'linear3': nn.Parameter(torch.randn(4, 2))}) # 新增

def forward(self, x, choice='linear1'):
return torch.mm(x, self.params[choice])

net = MyDictDense()
print(net)

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

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