自定义层
引用翻译:《动手学深度学习》
深度学习成功的原因之一是可以在深度网络中使用的广泛的层中找到。这允许了巨大程度的定制和适应。例如,科学家们已经发明了图像、文本、集合、循环、动态编程,甚至计算机程序的层。你迟早会遇到一个在Torch中还不存在的层,甚至更好的是,你最终会发明一个新的层,对你手头的问题很有效。这时就需要建立一个自定义层。本节将告诉你如何做。
没有参数的图层 由于这略显复杂,我们从一个没有任何固有参数的自定义图层(又称模块)开始。我们的第一步与我们之前介绍模块时非常相似。下面的CenteredLayer类构建了一个从输入中减去平均值的层。我们通过继承模块类并实现forward方法来构建它。
import torch
from torch import nn
from torch.autograd import Variable
import torch.nn.functional as F
class CenteredLayer(nn.Module):
def __init__(self):
super().__init__()
def forward(self, x):
return x - x.mean()
为了看看它是如何工作的,让我们把一些数据送入该层。
layer = CenteredLayer()
layer(torch.FloatTensor([1, 2, 3, 4, 5])) # 平均值是3,一起减去平均值
输出:
tensor([-2., -1., 0., 1., 2.])
我们还可以用它来构建更复杂的模型。
net = nn.Sequential(nn.Linear(8,128), CenteredLayer())
让我们看看居中层是否完成了它的工作。为此,我们通过网络发送随机数据,检查平均值是否消失。请注意,由于我们处理的是浮点数,我们将看到一个非常小的、但通常是非零的数字。
print('随机数:: ',torch.rand(4,8)) # 得到四个八维的样本
y=net(torch.rand(4,8)) # 传入8维数据,输出128维度的结果
print('经过网络得到的y: ',y[0]) # 取第一个样本
print('y的平均值: ',y.mean())
随机数:: tensor([[0.9474, 0.7621, 0.0908, 0.4475, 0.0655, 0.2067, 0.2735, 0.9855],
[0.0898, 0.6685, 0.6798, 0.6926, 0.7772, 0.9956, 0.4350, 0.8951],
[0.4441, 0.7469, 0.3220, 0.5045, 0.6867, 0.7187, 0.7266, 0.6981],
[0.3967, 0.7567, 0.8612, 0.1394, 0.4738, 0.2163, 0.9063, 0.5320]])
经过网络得到的y: tensor([-0.4227, 0.9753, -0.2304, 0.1427, 0.6200, -0.3179, -0.2510, -0.0763,
0.3112, 0.5809, 0.4178, -0.1608, -0.2836, -0.3096, 0.7073, -0.0547,
-0.4640, -0.2917, -0.2480, -0.2010, -0.0594, -0.6192, 0.3383, -0.6951,
-0.1228, 0.0096, 0.1976, 0.6609, 0.3885, 0.6092, 0.0528, -0.0063,
-0.0064, -0.1550, -0.7137, -0.7469, 0.8388, 0.0048, -0.2906, -0.6625,
-0.3065, 0.2936, 0.7682, -0.1084, -0.0343, -0.6003, 1.0665, 0.4369,
0.1631, 0.2068, 0.7544, 0.2955, -0.5232, 0.1614, -0.3146, 0.3101,
-0.1627, -0.7610, -0.2237, -0.6014, 0.0238, -0.0439, 0.1345, -0.3612,
0.2725, -0.1909, 0.4594, 0.3303, 0.3599, -0.9597, 0.2161, -0.1744,
-0.1242, 0.3042, 0.3561, -0.0429, 0.0094, 0.3374, -0.9072, -0.2880,
0.2086, -0.6053, 0.1549, 0.2416, 0.4736, -0.2889, -0.1418, 0.5008,
0.5315, -0.3954, 0.3227, 0.2385, -0.6166, 0.3209, 0.5652, 0.2410,
-0.3893, 0.3971, -0.3881, -0.1892, -0.2318, -0.1591, -0.3089, -0.3150,
0.2635, -1.0352, -0.3869, -0.2507, 0.3372, 0.0183, -0.0309, 0.2499,
-0.1257, -0.6759, 0.0159, 0.4498, 0.1256, -0.1119, -0.1908, 0.0991,
0.3073, 0.2553, -0.3267, 0.8463, 0.2715, 0.0056, -0.1366, -0.0237],
grad_fn=<SelectBackward>)
y的平均值: tensor(-3.7253e-09, grad_fn=<MeanBackward0>)
一、带参数的图层
现在我们知道了如何在原则上定义层,让我们来定义带有参数的层。这些参数可以通过训练来调整。为了简化狂热的深度学习研究者的事情,Parameter类和ParameterDict字典提供了一些基本的内部管理功能。特别是,他们管理访问、初始化、共享、保存和加载模型参数。
例如,这样我们就不需要为每个新的自定义层编写自定义序列化例程。例如,我们可以使用模块类附带的ParameterDict类型的成员变量params。
它是一个字典,将字符串类型的参数名称映射到Parameter类型中的模型参数。我们可以通过get函数从ParameterDict创建一个Parameter实例。
params=torch.nn.ParameterDict()
params.update({"param2":nn.Parameter(Variable(torch.ones(2,3)))})
params # 参数包含2x3个
输出:
ParameterDict( (param2): Parameter containing: [torch.FloatTensor of size 2x3])
让我们用它来实现我们自己版本的密集层。它有两个参数–偏置和权重。为了使它有点非标准,我们把ReLU激活作为默认值。接下来,我们实现一个同时具有权重和偏置参数的全连接层。它使用ReLU作为激活函数,其中in_units和units分别是输入的数量和输出的数量。
class MyDense(nn.Module):
def __init__(self, units, in_units):
super().__init__()
self.weight = Variable(torch.ones(in_units, units))
self.bias = Variable(torch.ones(units,))
def forward(self, x):
linear = torch.matmul(x, self.weight.data) + self.bias.data
return F.relu(linear)
命名参数使我们以后可以通过字典查询来访问它们的名字。给它们起个有指导意义的名字是个好主意。接下来,我们实例化MyDense类并访问其模型参数。
dense = MyDense(units=3, in_units=5) # 初始化神经网络
dense.parameters
输出:
<bound method Module.parameters of MyDense()>
我们可以直接使用自定义层进行正向计算。
print('随机值大小: ',torch.rand(2,5))
dense(torch.rand(2,5))
print('经过神经网络后的结果: ',dense(torch.rand(2,5)))
随机值大小: tensor([[0.9884, 0.9673, 0.9450, 0.5284, 0.8671],
[0.6098, 0.3284, 0.4989, 0.2574, 0.0102]])
经过神经网络后的结果: tensor([[3.8029, 3.8029, 3.8029],
[3.7187, 3.7187, 3.7187]])
我们还可以使用自定义图层构建模型。一旦我们有了这个,我们就可以像内置的密集层一样使用它。唯一的例外是,在我们的案例中,尺寸推理不是自动的。请查阅PyTorch文档,以了解如何做到这一点的细节。
net = nn.Sequential(
MyDense(8, in_units=64),# 8是输出,in_units是输入
MyDense(1, in_units=8)) # 1是输出,in_units是上一层的输出
net(torch.rand(2,64))
输出:
tensor([[246.0841],
[288.1516]])
二、摘要
-
我们可以通过模块类来设计自定义层。这比定义一个模块工厂更强大,因为它可以在很多情况下被调用。
-
模块可以有本地参数。
三、练习
1、设计一个学习数据的仿射变换的层,也就是说,它去掉平均数并学习一个加性参数。
2、设计一个层来返回数据的傅里叶系数的前一半。提示–在PyTorch中查找fft函数。