【PyTorch】torch.nn 模块笔记

简介

pytorch中其实一般没有特别明显的Layer和Module的区别,不管是自定义层、自定义块、自定义模型,都是通过继承Module类完成的。其实Sequential类也是继承自Module类的。

torcn.nn是专门为神经网络设计的模块化接口。构建于autograd之上,可以用来定义和运行神经网络。

torch.nn.Module 是所有神经网络单元的基类,包含网络各层的定义及forward方法。

pytorch里面一切自定义操作基本上都是继承nn.Module类来实现的。

源码手册:TORCH.NN

下文中为了简洁:> import torch.nn as nn

nn.Module - Neural network module. Convenient way of encapsulating parameters, with helpers for moving them to GPU, exporting, loading, etc.

部分内容有参考pytorch教程之nn.Module类详解——使用Module类来自定义网络层

在pytorch里面自定义层也是通过继承自nn.Module类来实现的。pytorch里面一般是没有层的概念,层也是当成一个模型来处理的,这里和keras是不一样的。keras更加注重的是层Layer、pytorch更加注重的是模型Module。

Pytorch基于nn.Module构建的模型中,只支持mini-batch的Variable输入方式。比如,只有一张输入图片,也需要变成N*C*H*W的形式。

torch.nn only supports mini-batches. The entire torch.nn package only supports inputs that are a mini-batch of samples, and not a single sample.

For example, nn.Conv2d will take in a 4D Tensor of nSamples * nChannels * Height * Width.

If you have a single sample, just use input.unsqueeze(0) to add a fake batch dimension.

自定义模型

pytorch框架中自定义一个模型:通过继承nn.Module类来实现,在__init__构造函数中申明各个层的定义,在forward中实现层之间的连接关系,实际上就是前向传播的过程。

如何定义自己的网络:

  1. 需要继承nn.Module类,并实现forward方法。继承nn.Module类之后,在构造函数中要调用Module的构造函数, super(Linear, self).init()。
  2. 一般把网络中具有可学习参数的层放在构造函数__init__()中。
  3. 不具有可学习参数的层(如ReLU)可放在构造函数中,也可不放在构造函数中(而在forward中使用nn.functional来代替)。可学习参数放在构造函数中,并且通过nn.Parameter()使参数以parameters(一种tensor,默认是自动求导)的形式存在Module中,并且通过parameters()或者named_parameters()以迭代器的方式返回可学习参数。
  4. 只要在nn.Module中定义了forward函数,backward函数就会被自动实现(利用Autograd)。而且一般不是显式的调用forward(layer.forward), 而是layer(input), 会自执行forward()。
  5. 在forward中可以使用任何Variable支持的函数,毕竟在整个pytorch构建的图中,是Varible在流动。还可以使用if, for, print, log等python语法。

所有放在构造函数__init__里面的层的都是这个模型的“固有属性”。

forward方法是必须要重写的,它是实现模型的功能,实现各个层之间的连接关系的核心。

神经网络基本单元

可参考pytorch中实现循环神经网络的基本单元RNN、LSTM、GRU的输入、输出、参数详细理解

rnn的源码(RNNBase)github-pytorch

nn.Conv1d && nn.Conv1d

具体计算方式参考:

博客PyTorch中的nn.Conv1d与nn.Conv2d.

博客pytorch之nn.Conv1d详解.

一般来说,一维卷积nn.Conv1d用于文本数据,只对宽度进行卷积,对高度不卷积,输入为3维张量\((N, C_{in}, L)\)。通常,(忽略了N)输入大小为word_embedding_dim * max_length,其中,word_embedding_dim为词向量的维度,max_length为句子的最大长度。卷积核窗口在句子长度的方向上滑动,进行卷积操作。

二维卷积nn.Conv2d用于图像数据,对宽度和高度都进行卷积,输入为4维张量\((N, C_{in}, H, W )\)。

源码:CONV1DCONV2D

nn.Sequential

可以快速搭组件的一个类,细节参见pytorch源码

示例见pytorch教程

一个有序的Sequential容器,神经网络模块将按照在传入构造器的顺序依次被添加到计算图中执行,同时以神经网络模块为元素的有序字典也可以作为传入参数。

nn.Sequential是包含其他模块的模块,并按顺序应用这些模块来产生其输出。

每个线性模块使用线性函数从输入计算输出,并保存其内部的权重和偏差张量。

在构造模型之后,我们使用.to()方法将其移动到所需的设备。

nn.Linear

线性变换层,参考源码

class torch.nn.Linear(in_features, out_features, bias=True)

Applies a linear transformation to the incoming data: $ y = x \cdot {A^T} + b $

in_features - 每个输入样本的大小

out_features - 每个输出样本的大小

bias - 如果设置为False,则图层不会学习附加偏差。默认值:True

nn.LSTM

Long Short Term Memory,称为长短期记忆网络,意思就是长的短时记忆,其解决的仍然是短时记忆问题,这种短时记忆比较长,能一定程度上解决长时依赖。

LSTM由3个门来控制,分别是输入门、遗忘门和输出门。输入门控制网络的输入,遗忘门控制着记忆单元,输出门控制着网络的输出。最为重要的就是遗忘门,可以决定哪些记忆被保留,由于遗忘门的作用,使得LSTM具有长时记忆的功能。对于给定的任务,遗忘门能够自主学习保留多少之前的记忆,网络能够自主学习。

class torch.nn.LSTM(*args, **kwargs)

参数列表:
   -- input_size: x 的特征维度
   -- hidden_size: 隐层的特征维度
   -- num_layers: LSTM 层数,默认为1
   -- bias: 是否采用 bias, 如果为False,则不采用。默认为True
   -- batch_first: True, 则输入输出的数据格式为 [batch_size, seq_len, feature_dim],默认为False
   -- dropout: dropout会在除最后一层外都进行dropout, 默认为0
   -- bidirectional: 是否采用双向,默认为False
输入数据:
   -- input: [seq_len, batch_size, input_size], 输入的特征矩阵
   -- h_0: [num_layers * num_directions, batch_size, hidden_size], 初始时 h 状态, 默认为0
   -- c_0: [num_layers * num_directions, batch_size, hidden_size], 初始时 cell 状态, 默认为0
输出数据:
   -- output: [seq_len, batch_size, num_directions * hidden_size], 最后一层的所有隐层输出
   -- h_n : [num_layers * num_directions, batch, hidden_size], 所有层的最后一个时刻隐层状态
   -- c_n : [num_layers * num_directions, batch, hidden_size], 所有层的最后一格时刻的 cell 状态
W,b参数:
  -- weight_ih_l[k]: 与输入x相关的第k层权重 W 参数, W_ii, W_if, W_ig, W_io
  -- weight_hh_l[k]: 与上一时刻 h 相关的第k层权重参数, W_hi, W_hf, W_hg, W_ho
  -- bias_ih_l[k]: 与输入x相关的第k层 b 参数, b_ii, b_if, b_ig, b_io
  -- bias_hh_l[k]: 与上一时刻 h 相关的第k层 b 参数, b_hi, b_hf, b_hg, b_ho

需要注意的一点是, LSTM/GRU 中所有的W,b 参数默认采用均匀分布。

可参考个人博客LSTM:Pytorch实现 和 个人博客【pytorch】pytorch-LSTM

torch.nn包下实现了LSTM函数,实现LSTM层。多个LSTMcell组合起来是LSTM。

LSTM自动实现了前向传播,不需要自己对序列进行迭代。创建需要指定如下参数,并至少指定前三个参数。

input_size:输入特征维数

hidden_size:隐层状态的维数

num_layers:RNN层的个数,在图中竖向的是层数,横向的是seq_len

bias:隐层状态是否带bias,默认为true

batch_first:是否输入输出的第一维为batch_size,因为pytorch中batch_size维度默认是第二维度,故此选项可以将batch_size放在第一维度。如input是(4,1,5),中间的1是batch_size,指定batch_first=True后就是(1,4,5)

dropout:是否在除最后一个RNN层外的RNN层后面加dropout层

bidirectional:是否是双向RNN,默认为false,若为true,则num_directions=2,否则为1

lstm = torch.nn.LSTM(input_size,hidden_size,num_layers,batch_first=True)   # 创建LSTM
out,hidden = lstm(input,hidden)      # forward使用LSTM层,其中,hidden=(h0,c0)是个tuple, 最终得到out,hidden

nn.GRU

GRU是Gated Recurrent Unit的缩写,与LSTM最大的不同之处在于GRU将遗忘门和输入门合成一个“更新门”,同时网络不再额外给出记忆状态Ct​,而是将输出结果ht​作为记忆状态不断向后传递,网络的输入和输出都简化。

隐藏状态参数不再是标准RNN的4倍,而是3倍,也就是GRU的参数要比LSTM的参数量要少,但是性能差不多。

具体原理可参考博客/Gated Recurrent Unit(GRU)

LSTM存在一个问题,就是计算开销比较大,因为其内部结构相对复杂。GRU 也是为了旨在解决标准 RNN 中出现的梯度消失问题,可以看做是LSTM的一种变种。其实在大多数情况下GRU的性能和LSTM几乎相差无几(甚至有时候LSTM效果更好,且LSTM是1997年提出的,经受了更多的历史考验),但GRU最大的优势就是 简单(因为只有两个门),计算开销小,更加适用于大规模数据集。

class torch.nn.RNN(args, kwargs)*  

nn.Parameter

参考PyTorch里面的torch.nn.Parameter():

【对于self.v = torch.nn.Parameter(torch.FloatTensor(hidden_size)), 可以把这个函数理解为类型转换函数,将一个不可训练的类型Tensor转换成可以训练的类型parameter并将这个parameter绑定到这个module里面(net.parameter()中就有这个绑定的parameter,所以在参数优化的时候可以进行优化的),所以经过类型转换这个self.v变成了模型的一部分,成为了模型中根据训练可以改动的参数了。使用这个函数的目的也是想让某些变量在学习的过程中不断的修改其值以达到最优化。】

是Tensor的子类,当与modules一起使用时,会有一个非常特殊的属性:当它们被指定为模块属性时,它们被自动添加到它的参数列表中,并且会出现在Parameters()迭代器中。

nn.Parameter - A kind of Tensor, that is automatically registered as a parameter when assigned as an attribute to a Module.

Assigning a Tensor doesn't have such effect. This is because one might want to cache some temporary state, like last hidden state of the RNN, in the model. If there was no such class as :class:Parameter, these temporaries would get registered too.

很多代码采用self.v = torch.nn.Parameter(torch.FloatTensor(hidden_size)),首先可以把这个函数理解为类型转换函数,将一个不可训练的类型Tensor转换成可以训练的类型parameter并将这个parameter绑定到这个module里面。

所以经过类型转换这个self.v变成了模型的一部分,成为了模型中根据训练可以改动的参数了。

net.parameter()中就有这个绑定的parameter,所以在参数优化的时候可以进行优化的,使用这个函数的目的也是想让某些变量在学习的过程中不断的修改其值以达到最优化。

源码参考

class Parameter(torch.Tensor)

loss-functions

参考:

源码:SOURCE CODE FOR TORCH.NN.MODULES.LOSS

总结:pytorch loss function 总结

【nn.BCELoss】二分类用的交叉熵,用的时候需要在该层前面加上 Sigmoid 函数。

【nn.CrossEntropyLoss】多分类用的交叉熵损失函数,用这个 loss 前面不需要加 Softmax 层。

【nn.NLLLoss】用于多分类的负对数似然损失函数(Negative Log Likelihood)。在前面接上一个 nn.LogSoftMax 层就等价于交叉熵损失了。事实上,nn.CrossEntropyLoss 也是调用这个函数。

【nn.CosineEmbeddingLoss】余弦相似度的损失,目的是让两个向量尽量相近。注意这两个向量都是有梯度的。

nn.utils

工具箱????

pytorch对变长序列处理

参考个人博客/pytorch对可变长度序列的处理,主要是用函数torch.nn.utils.rnn.PackedSequence()和torch.nn.utils.rnn.pack_padded_sequence()以及torch.nn.utils.rnn.pad_packed_sequence()来进行的。

torch.nn.utils.rnn.PackedSequence() 【这个类的实例不能手动创建,只能被 pack_padded_sequence() 实例化。】

PackedSequence对象包括:

一个data对象:一个torch.Variable(令牌的总数,每个令牌的维度)

一个batch_sizes对象:每个时间步长的令牌数列表

PackedSequence对象有一个很不错的特性,就是无需对序列解包(这一步操作非常慢)即可直接在PackedSequence数据变量上执行许多操作。特别是可以对令牌执行任何操作(即对令牌的顺序/上下文不敏感)。当然,也可以使用接受PackedSequence作为输入的任何一个pyTorch模块(pyTorch 0.2)。

torch.nn.utils.rnn.pack_padded_sequence() 【将一个填充过的变长序列压紧(填充时会有冗余,所以压紧一下)】

输入的形状可以是(T×B×* )。T是最长序列长度,B是batch size,*代表任意维度(可以是0)。如果batch_first=True的话,那么相应的 input size 就是 (B×T×*)。

Variable中保存的序列,应该按序列长度的长短排序,长的在前,短的在后。即input[:,0]代表的是最长的序列,input[:, B-1]保存的是最短的序列。

参数说明:

input (Variable) – 变长序列 被填充后的 batch

lengths (list[int]) – Variable 中 每个序列的长度。

batch_first (bool, optional) – 如果是True,则input的形状应该是B×T×*。

返回值: 一个PackedSequence 对象。

只要是维度大于等于2的input都可以作为上述函数的参数。可以用它来打包labels,然后用RNN的输出和打包后的labels来计算loss。通过PackedSequence对象的.data属性可以获取 Variable。

torch.nn.utils.rnn.pad_packed_sequence() 【填充packed_sequence】

这个操作和pack_padded_sequence()是相反的。把压紧的序列再填充回来。

返回的Varaible的值的size是 T×B×*, T 是最长序列的长度,B 是 batch_size,如果 batch_first=True,那么返回值是B×T×*。

Batch中的元素将会以它们长度的逆序排列。

参数说明:

sequence (PackedSequence) – 将要被填充的batch

batch_first (bool, optional) – 如果为True,返回的数据的格式为 B×T×*。

返回值: 一个tuple,包含被填充后的序列,和batch中序列的长度列表。

参考

1、torch.nn - PyTorch中文文档