深度学习框架PyTorch入门与实践:第四章 神经网络工具箱nn

autograd实现了自动微分系统,然而对深度学习来说过于底层,本章将介绍的nn模块,是构建于autograd之上的神经网络模块。除了nn之外,我们还会介绍神经网络中常用的工具,比如优化器optim、初始化init等。

4.1 nn.Module

第3章中提到,使用autograd可实现深度学习模型,但其抽象程度较低,如果用其来实现深度学习模型,则需要编写的代码量极大。在这种情况下,torch.nn应运而生,其是专门为深度学习设计的模块。torch.nn的核心数据结构是Module,它是一个抽象的概念,既可以表示神经网络中的某个层(layer),也可以表示一个包含很多层的神经网络。在实际使用中,最常见的做法继承nn.Module,撰写自己的网络/层。下面先来看看如何使用nn.Module实现自己的全连接层。全连接层,又名仿射层,输入y和输入x满足y=Wx+b,W和b是可学习的参数。

import torch as t
from torch import nn
from torch.autograd import Variable as V

# 定义线性模型:y = w * x + b
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):
        xw = x.mm(self.w)
        y = xw + self.b.expand_as(xw)
        return y

net = Linear(4,3)
x = V(t.randn(2,4))
y = net(x)
y

输出如下:

tensor([[ 0.2732,  1.8660,  1.3620],
        [ 0.6374, -0.3646,  1.0089]], grad_fn=<AddBackward0>)
for name,parameter in layer.named_parameters():
    print(name,parameter)    # w and b

输出如下:

w Parameter containing:
tensor([[-0.3146,  2.5440,  0.0063],
        [ 0.6632, -1.5358,  0.1820],
        [-1.5990,  0.7136,  0.2463],
        [-1.8826,  1.4418,  0.7892]], requires_grad=True)
b Parameter containing:
tensor([ 0.1779,  0.2043, -0.3796], requires_grad=True)

可见,全连接层的实现非常简单,其代码量不超过10行,但需注意以下几点:

  • 自定义层Linear必须继承nn.Module,并且在其构造函数中需调用nn.Module的构造函数,即super(Linear,self).init()或nn.Module.init(self)。
  • 在构造函数__init__中必须自己定义可学习的参数,并封装成Parameter,如在本例中我们把w和b封装成Parameter。Parameter是一种特殊的Variable,但其默认需要求导(requires_grad=True),感兴趣的读者可以通过nn.Parameter??查看Parameter类的源代码。
  • forward函数实现前向传播过程,其输入可以是一个或多个variable,对x的任何操作也必须是variable支持的操作。
  • 无须写反向传播函数,因其前向传播都是对variable进行操作,nn.Module能够利用autograd自动实现反向传播,这一点比Function简单许多。
  • 使用时,直观上可将net看成数学概念中的函数,调用net(x)即可得到x对应的结果。它等价于net.call(x),在__call__函数中,主要调用的是net.forward(x),另外还对钩子做了一些处理。所以在实际使用中应尽量使用net(x)而不是使用net.forward(x),关于钩子技术的具体内容将在下文讲到。
  • Module中的可学习参数可以通过named_parameters()或者parameters()返回迭代器,前者会给每个parameter附上名字,使其更具有辨识度。

可见,利用Module实现的全连接层,比利用Function实现的更简单,因其不再需要写反向传播函数。

Module能够自动检测到自己的parameter,并将其作为学习参数。除了parameter,Module还包含子Module,主Module能够递归查找子Module中的parameter。下面再来看看稍微复杂一点的网络:多层感知机。

多层感知机的网络结构如图所示。它由两个全连接层组成,采用sigmoid函数作为激活函数(图中没有画出)。

image.png

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)
        x = self.layer2(x)
        return x
        
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的可学习参数。
  • 在前向传播函数中,我们有意识地将输出变量都命名为x,是为了能让Python回收一些中间层的输出,从而节省内存。但并不是所有的中间结果都会被回收,有些variable虽然名字被覆盖,但其在反向传播时仍需要用到,此时Python的内存回收模块将通过检查引用计数,不会回收这一部分内存。

Module中parameter的全局命名规范如下:

  • Parameter直接命名。例如self.param_name = nn.Parameter(t.randn(3,4)),命名为param_name。
  • 子Module中的parameter,会在其名字之前加上当前Module的名字。例如self.sub_module = SubModule(),SubModule中有个parameter的名字也叫作param_name,那么二者拼接而成的parameter name就是sub_module.param_name。

为了方便用户使用,PyTorch实现了神经网络中绝大多数的layer,这些layer都继承于nn.Module,封装了可学习参数parameter,并实现了forward函数,且专门针对GPU运算进行了CuDNN优化,其速度和性能都十分优异。本书不准备对nn.Module中的所有层进行详细介绍,具体内容读者可参照官方文档或在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。若想输入一个数据,必须调用unsqueeze(0)函数将数据伪装成batch_size=1的batch。

下面将从应用层面出发,对一些常用的layer做简单介绍,更详细的用法请查看官方文档。

4.2 常用的神经网络层

4.2.1 图像相关层

图像相关层主要包括卷积层(Conv)、池化层(Pool)等,这些层在实际使用中分为一维(1D)、二维(2D)和三维(3D),池化方式又分为平均池化(AvgPool)、最大值池化(MaxPool)、自适应池化(AdaptiveAvgPool)等。卷积层除了常用的前向卷积外,还有逆卷积(TransposeConv)。下面举例说明。

from PIL import Image
from torchvision.transforms import ToTensor, ToPILImage
to_tensor = ToTensor() # img -> tensor
to_pil = ToPILImage()
lena = Image.open('imgs/lena.png')
lena

输出如下:

image.png

# 输入是一个batch,batch_size=1
input = to_tensor(lena).unsqueeze(0) 

# 锐化卷积核
kernel = t.ones(3, 3)/-9.
kernel[1][1] = 1
conv = nn.Conv2d(1, 1, (3, 3), 1, bias=False)
conv.weight.data = kernel.view(1, 1, 3, 3)

out = conv(input)
to_pil(out.data.squeeze(0))

处理后的Lena图如下:

image.png

图像的卷积操作还有各种变体,有关各种变体的介绍具体可以参照此处的介绍

池化层可以看成是一种特殊的卷积层,用来下采样。但池化层没有可学习的参数,其weight是固定的。

pool = nn.AvgPool2d(2,2)
list(pool.parameters())

输出如下:

[]
out = pool(input)
to_pil(out.data.squeeze(0))

处理后的Lena图如下:

image.png

除了卷积层和池化层,深度学习中还将常用到以下几个层。

  • Linear:全连接层。
  • BatchNorm:批规范化层,分为1D、2D和3D。除了标准的BatchNorm之外,还有在风格迁移中常用到的InstanceNorm层。
  • Dropout:dropout层,用于防止过拟合,同样分为1D、2D和3D。

下面通过例子讲解它们的使用方法。

# 输入 batch_size=2,维度3
input = t.randn(2, 3)
linear = nn.Linear(3, 4)
h = linear(input)
h

输出:

tensor([[-0.3437,  0.3086,  0.3261, -1.3908],
        [ 0.3508, -0.7137,  0.8659, -0.5121]], grad_fn=<AddmmBackward>)
# 4 channel,初始化标准差为4,均值为0
bn = nn.BatchNorm1d(4)
bn.weight.data = t.ones(4) * 4
bn.bias.data = t.zeros(4)

bn_out = bn(h)
# 注意输出的均值和方差
# 方差是标准差的平方,计算无偏方差分母会减1
# 使用unbiased=False 分母不减1
bn_out.mean(0), bn_out.var(0, unbiased=False)

输出:

(tensor([ 0.0000e+00,  0.0000e+00, -2.3842e-07,  0.0000e+00],
        grad_fn=<MeanBackward2>),
 tensor([15.9987, 15.9994, 15.9978, 15.9992], grad_fn=<VarBackward1>))
# 每个元素以0.5的概率舍弃
dropout = nn.Dropout(0.5)
o = dropout(bn_out)
o # 有一半左右的数变为0
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值