第四章 神经网络工具箱nn
上一章中提到,使用autograd可实现深度学习模型,但其抽象程度较低,如果用其来实现深度学习模型,则需要编写的代码量极大。在这种情况下,torch.nn应运而生,其是专门为深度学习而设计的模块。torch.nn的核心数据结构是Module
,它是一个抽象概念,既可以表示神经网络中的某个层(layer),也可以表示一个包含很多层的神经网络。在实际使用中,最常见的做法是继承nn.Module
,撰写自己的网络/层。下面先来看看如何用nn.Module实现自己的全连接层。全连接层,又名仿射层,输出y和输入x满足y = Wx+b,W和b是可学习的参数。
In [6]:
import torch as t from torch import nn
In [7]:
class Linear(nn.Module): # 继承nn.Module def __init__(self, in_features, out_features): super(Linear, self).__init__() # 等价于nn.Module.__init__(self) self.w = nn.Parameter(t.randn(in_features, out_features)) self.b = nn.Parameter(t.randn(out_features)) def forward(self, x): x = x.mm(self.w) # x.@(self.w) return x + self.b.expand_as(x)
In [8]:
layer = Linear(4,3) input = t.randn(2,4) output = layer(input) output
Out[8]:
tensor([[-2.4421, -0.2355, 0.5636], [-1.4299, -1.0916, -1.9299]])
In [9]:
for name, parameter in layer.named_parameters(): print(name, parameter) # w and b
w Parameter containing: tensor([[ 0.0481, -0.5937, -1.0651], [ 0.5725, 0.1317, -0.0920], [ 0.7409, 0.3163, -1.3940], [ 0.1598, -0.3302, 0.3646]]) b Parameter containing: tensor([-1.5881, -0.8368, -0.5220])
可见,全连接层的实现非常简单,其代码量不超过10行,但需注意以下几点:
- 自定义层
Linear
必须继承nn.Module
,并且在其构造函数中需调用nn.Module
的构造函数,即super(Linear, self).__init__()
或nn.Module.__init__(self)
,推荐使用第一种用法,尽管第二种写法更直观。 - 在构造函数
__init__
中必须自己定义可学习的参数,并封装成Parameter
,如在本例中我们把w
和b
封装成parameter
。parameter
是一种特殊的Tensor
,但其默认需要求导(requires_grad = True),感兴趣的读者可以通过nn.Parameter??
,查看Parameter
类的源代码。 forward
函数实现前向传播过程,其输入可以是一个或多个tensor。- 无需写反向传播函数,nn.Module能够利用autograd自动实现反向传播,这点比Function简单许多。
- 使用时,直观上可将layer看成数学概念中的函数,调用layer(input)即可得到input对应的结果。它等价于
layers.__call__(input)
,在__call__
函数中,主要调用的是layer.forward(x)
,另外还对钩子做了一些处理。所以在实际使用中应尽量使用layer(x)
而不是使用layer.forward(x)
,关于钩子技术将在下文讲解。 Module
中的可学习参数可以通过named_parameters()
或者parameters()
返回迭代器,前者会给每个parameter都附上名字,使其更具有辨识度。
可见利用Module实现的全连接层,比利用Function
实现的更为简单,因其不再需要写反向传播函数。
Module能够自动检测到自己的Parameter
,并将其作为学习参数。除了parameter
之外,Module还包含子Module
,主Module能够递归查找子Module
中的parameter
。下面再来看看稍微复杂一点的网络,多层感知机。
多层感知机的网络结构如图4-1所示,它由两个全连接层组成,采用函数作为激活函数,图中没有画出。
In [10]:
class Perceptron(nn.Module): def __init__(self, in_features, hidden_features, out_features): nn.Module.__init__(self) self.layer1 = Linear(in_features, hidden_features) # 此处的Linear是前面自定义的全连接层 self.layer2 = Linear(hidden_features, out_features) def forward(self,x): x = self.layer1(x) x = t.sigmoid(x) return self.layer2(x)
In [11]:
perceptron = Perceptron(3,4,1) for name, param in perceptron.named_parameters(): print(name, param.size())
layer1.w torch.Size([3, 4]) layer1.b torch.Size([4]) layer2.w torch.Size([4, 1]) layer2.b torch.Size([1])
可见,即使是稍复杂的多层感知机,其实现依旧很简单。 构造函数__init__
中,可利用前面自定义的Linear层(module),作为当前module对象的一个子module,它的可学习参数,也会成为当前module的可学习参数。
module中parameter的命名规范:
- 对于类似
self.param_name = nn.Parameter(t.randn(3, 4))
,命名为param_name
- 对于子Module中的parameter,会其名字之前加上当前Module的名字。如对于
self.sub_module = SubModel()
,SubModel中有个parameter的名字叫做param_name,那么二者拼接而成的parameter name 就是sub_module.param_name
。
为方便用户使用,PyTorch实现了神经网络中绝大多数的layer,这些layer都继承于nn.Module,封装了可学习参数parameter
,并实现了forward函数,且很多都专门针对GPU运算进行了CuDNN优化,其速度和性能都十分优异。本书不准备对nn.Module中的所有层进行详细介绍,具体内容读者可参照官方文档1或在IPython/Jupyter中使用nn.layer?来查看。阅读文档时应主要关注以下几点:
- 构造函数的参数,如nn.Linear(in_features, out_features, bias),需关注这三个参数的作用。
- 属性、可学习参数和子module。如nn.Linear中有
weight
和bias
两个可学习参数,不包含子module。 - 输入输出的形状,如nn.linear的输入形状是(N, input_features),输出为(N,output_features),N是batch_size。
这些自定义layer对输入形状都有假设:输入的不是单个数据,而是一个batch。输入只有一个数据,则必须调用tensor.unsqueeze(0)
或 tensor[None]
将数据伪装成batch_size=1的batch