PyTorch快速入门1
在学习了PyTorch
的Tensor、Variable和autograd
之后,已经可以实现简单的深度学习模型,然而使用autograd
实现的深度学习模型,其抽象程度比较较低,如果用其来实现深度学习模型,则需要编写的代码量极大。在这种情况下,torch.nn
应运而生,其是专门为深度学习而设计的模块。torch.nn
的核心数据结构是Module
,它是一个抽象概念,既可以表示神经网络中的某个层(layer),也可以表示一个包含很多层的神经网络。在实际使用中,最常见的做法是继承nn.Module
,撰写自己的网络层。
- 自定义层
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的命名规范:
- 对于类似
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优化,其速度和性能都十分优异。更多的内容可参照官方文档或在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
图像相关层
图像相关层主要包括卷积层(Conv)、池化层(Pool)等,这些层在实际使用中可分为一维(1D)、二维(2D)、三维(3D),池化方式又分为平均池化(AvgPool)、最大值池化(MaxPool)、自适应池化(AdaptiveAvgPool)等。而卷积层除了常用的前向卷积之外,还有逆卷积(TransposeConv)。
深度学习当中,经常用到的网络层:
- Linear:全连接层。
- BatchNorm:批规范化层,分为1D、2D和3D。除了标准的BatchNorm之外,还有在风格迁移中常用到的InstanceNorm层。
- Dropout:dropout层,用来防止过拟合,同样分为1D、2D和3D。
- Conv:卷积层,分为1D、2D和3D
- Pool:池化层,分为1D、2D和3D
|
|
|
|
|
|
Sequential
和ModuleList
使用nn.module实现的神经网络,基本上都是将每一层的输出直接作为下一层的输入,这种网络称为前馈传播网络(
feedforward neural network
)。对于此类网络如果每次都写复杂的forward函数会有些麻烦,在此就有两种简化方式,ModuleList
和Sequential
。其中Sequential
是一个特殊的module
,它包含几个子Module
,前向传播时会将输入一层接一层的传递下去。ModuleList
也是一个特殊的module
,可以包含几个子module
,可以像用list一样使用它,但不能直接把输入传给ModuleList
。下面举例说明。
|
|
激活函数、损失函数与优化器
|
|
nn.Functional
和nn.Module
nn.Functional
nn
中还有一个很常用的模块:nn.functional
,nn
中的大多数layer,在functional
中都有一个与之相对应的函数。
nn.functional
中的函数和nn.Module
的主要区别在于,用nn.Module
实现的layers
是一个特殊的类,都是由class layer(nn.Module)
定义,会自动提取可学习的参数。而nn.functional
中的函数更像是纯函数,由def function(input)
定义。
|
|
在实际应用的时候,如果模型有可学习的参数,最好用
nn.Module
,否则既可以使用nn.functional
也可以使用nn.Module
,二者在性能上没有太大差异,具体的使用取决于个人的喜好。如激活函数(
ReLU、sigmoid、tanh
),池化(MaxPool
)等层由于没有可学习参数,则可以使用对应的functional
函数代替,而对于卷积、全连接等具有可学习参数的网络建议使用nn.Module
。虽然dropout操作也没有可学习操作,但建议还是使用
nn.Dropout
而不是nn.functional.dropout
,因为dropout在训练和测试两个阶段的行为有所差别,使用nn.Module
对象能够通过model.eval
操作加以区分。
|
|
nn.Module
nn.Module
基类的构造函数:
|
|
其中每个属性的解释如下:
_parameters
:字典,保存用户直接设置的parameter,self.param1 = nn.Parameter(t.randn(3, 3))
会被检测到,在字典中加入一个key为param1
,value为对应parameter
的item。而self.submodule = nn.Linear(3, 4)
中的parameter则不会存于此。_modules
:子module,通过self.submodel = nn.Linear(3, 4)
指定的子module会保存于此。_buffers
:缓存。如batchnorm
使用momentum机制,每次前向传播需用到上一次前向传播的结果。_backward_hooks
与_forward_hooks
:钩子技术,用来提取中间变量,类似variable的hook。training
:BatchNorm与Dropout层在训练阶段和测试阶段中采取的策略不同,通过判断training值来决定前向传播策略。上述几个属性中,
_parameters
、_modules
和_buffers
这三个字典中的键值,都可以通过self.key
方式获得,效果等价于self._parameters['key']
.
|
|
nn.Module
在实际使用中可能层层嵌套,一个module包含若干个子module,每一个子module又包含了更多的子module。为方便用户访问各个子module,nn.Module实现了很多方法,如函数children
可以查看直接子module,函数module
可以查看所有的子module(包括当前module)。与之相对应的还有函数named_childen
和named_modules
,其能够在返回module列表的同时返回它们的名字。对于
batchnorm、dropout、instancenorm
等在训练和测试阶段行为差距巨大的层,如果在测试时不将其training值设为True,则可能会有很大影响,这在实际使用中要千万注意。虽然可通过直接设置training
属性,来将子module设为train和eval
模式,但这种方式较为繁琐,因如果一个模型具有多个dropout层,就需要为每个dropout层指定training属性。更为推荐的做法是调用model.train()
函数,它会将当前module及其子module中的所有training属性都设为True,相应的,model.eval()
函数会把training属性都设为False。
|
|
nn.Module
对象在构造函数中的行为看起来有些怪异,如果想要真正掌握其原理,就需要看两个魔法方法__getattr__
和__setattr__
。在Python中有两个常用的buildin
方法getattr
和setattr
,getattr(obj, 'attr1')
等价于obj.attr
,如果getattr
函数无法找到所需属性,Python会转而调用obj.__getattr__('attr1')
方法,即getattr
函数无法找到的交给__getattr__
函数处理,没有实现__getattr__
或者__getattr__
也无法处理的就会raise AttributeError
。setattr(obj, 'name', value)
等价于obj.name=value
,如果obj对象实现了__setattr__
方法,setattr
会直接调用obj.__setattr__('name', value)
,否则调用buildin
方法。总结一下:
result = obj.name
会调用buildin
函数getattr(obj, 'name')
,如果该属性找不到,会调用obj.__getattr__('name')
obj.name = value
会调用buildin
函数setattr(obj, 'name', value)
,如果obj对象实现了__setattr__
方法,setattr
会直接调用obj.__setattr__('name', value')
nn.Module
实现了自定义的__setattr__
函数,当执行module.name=value
时,会在__setattr__
中判断value
是否为Parameter
或nn.Module
对象,如果是则将这些对象加到_parameters
和_modules
两个字典中,而如果是其它类型的对象,如Variable
、list
、dict
等,则调用默认的操作,将这个值保存在__dict__
中。因
_modules
和_parameters
中的item
未保存在__dict__
中,所以默认的getattr
方法无法获取它,因而nn.Module
实现了自定义的__getattr__
方法,如果默认的getattr
无法处理,就调用自定义的__getattr__
方法,尝试从_modules
、_parameters
和_buffers
这三个字典中获取。
|
|
Pytorch
模型保存与加载
在
PyTorch
中保存模型十分简单,所有的Module
对象都具有state_dict()
函数,返回当前Module
所有的状态数据。将这些状态数据保存后,下次使用模型时即可利用model.load_state_dict()
函数将状态加载进来。优化器(optimizer)也有类似的机制,不过一般并不需要保存优化器的运行状态。
|
|
在GPU
上运行
将Module放在GPU上运行十分简单,只需两步:
model = model.cuda()
:将模型的所有参数转存到GPUinput.cuda()
:将输入数据也放置到GPU上至于如何在多个GPU上并行计算,PyTorch也提供了两个函数,可实现简单高效的并行GPU计算
nn.parallel.data_parallel(module, inputs, device_ids=None, output_device=None, dim=0, module_kwargs=None)
class torch.nn.DataParallel(module, device_ids=None, output_device=None, dim=0)
可见二者的参数十分相似,通过
device_ids
参数可以指定在哪些GPU上进行优化,output_device指定输出到哪个GPU上。唯一的不同就在于前者直接利用多GPU并行计算得出结果,而后者则返回一个新的module,能够自动在多GPU上进行并行加速。
DataParallel
并行的方式,是将输入一个batch的数据均分成多份,分别送到对应的GPU进行计算,各个GPU得到的梯度累加。与Module相关的所有数据也都会以浅复制的方式复制多份,在此需要注意,在module中属性应该是只读的。
|
|
nn
与autograd
nn.Module
利用的也是autograd
技术,其主要工作是实现前向传播。在forward函数中,nn.Module
对输入的tensor进行的各种操作,本质上都是用到了autograd
技术。这里需要对比autograd.Function和nn.Module
之间的区别:
autograd.Function
利用了Tensor
对autograd
技术的扩展,为autograd
实现了新的运算op,不仅要实现前向传播还要手动实现反向传播nn.Module
利用了autograd
技术,对nn
的功能进行扩展,实现了深度学习中更多的层。只需实现前向传播功能,autograd
即会自动实现反向传播nn.functional
是一些autograd
操作的集合,是经过封装的函数作为两大类扩充
PyTorch
接口的方法,在实际使用中,如果某一个操作,在autograd
中尚未支持,那么只能实现Function
接口对应的前向传播和反向传播。如果某些时候利用autograd
接口比较复杂,则可以利用Function
将多个操作聚合,实现优化,而如果只是想在深度学习中增加某一层,使用nn.Module
进行封装则更为简单高效。