pytorch学习笔记(7):RNN 和 LSTM 实现分类和回归


在第三篇文章中,咱们介绍了 pytorch 中的一些常见网络层。可是这些网络层都是在 CNN 中比较常见的一些层,关于深度学习,咱们确定最了解的两个知识点就是 CNN 和 RNN。那么如何实现一个 RNN 呢?这篇文章咱们用 RNN 实现一个分类器和一个回归器。
javascript

本文须要你最好对 RNN 相关的知识有一个初步的认识,而后我会尽量的让你明白在 pytorch 中是如何去实现这一点的。java

1. pytorch 提供了哪些 RNN?

若是咱们对 RNN 有所了解,就会知道 RNN 有不少变种,就像 CNN 也有不少变种同样。只要带了 recurrent 的功能,就都属于 RNN 的范畴。那么在 pytorch 中提供了哪些 RNN 呢?git

上面这幅图是 pytorch 源码中的结构,能够看到除了一个 RNNBase() 类,下面还有 RNN,LSTM,GRU 分别继承了 RNNBase() 类,实现了三个 RNN 子类。这三个也就是 pytorch 提供的 RNN 类型。github

今天的文章,咱们分别经过源码中的 doc 和一些介绍来了解 RNN 和 LSTM,而后分别用它们实现一个回归器和一个分类。关于 GRU 的部分,就留给你们本身去展开啦。web

2. RNN,以及实现一个回归器

这一部分,咱们先从 RNN 开始进行介绍,分别简单介绍一下 RNN 的原理,在 pytorch 中使用它的一些参数要求,最后是一个回归器,代码示例参考了 @莫烦 的视频教程,用 sim 曲线做为输入,cos 曲线做为 label,判断函数的拟合能力。在文章末尾有莫烦视频教程的介绍和地址。ruby

2.1 简单介绍 RNN

首先看一下 RNN 的内容,常见的介绍 RNN 的文章中都会有这样一幅图:微信

X0,X1 等等分别是输入序列的一个维度上的数据,X0 首先传进去,生成 h0 做为第一个隐状态。而后 h0 和 X1 一块儿做为下一个时间序列上的输入,它们的输出再和 X2 做为下下个时间序列的输入,以此类推。网络

具体的细节咱们就不展开讲了,默认你们对理论层面已经有了了解。在 pytorch 的源码 doc 中也给出了,对下面的公式进行计算:app


这个式子也是对于 RNN 的常见描述。W_ih 表示对输入数据进行处理的权重,而 W_hh 则表示对上一个时间序列的隐状态进行处理的权重。b_ih 和 b_hh 则分别是两个偏置项,有的公式里面不会给出这两项,也就是默认偏置为 0。机器学习

2.2 RNN 类实现的参数要求

接下来咱们看一下 pytorch 中的 RNN 类有什么参数吧。咱们主要是对这几个参数进行介绍:

input_size:这个参数表示的输入数据的维度。好比输入一个句子,这里表示的就是每一个单词的词向量的维度。

hidden_size :能够理解为在 CNN 中,一个卷积层的输出维度同样。这里表示将前面的 input_size 映射到一个什么维度上。

num_layers:表示循环的层数。举个栗子,将 num_layers 设置为  2,也就是将如前面图所示的两个 RNN 堆叠在一块儿,第一层的输出做为第二层的输入。默认为 1。

nonlinearity:这个参数对激活函数进行选择,目前 pytorch 支持 tanh 和 relu,默认的激活函数是 tanh。

bias:这个参数就是对前面公式中的 b_ih 和 b_hh。来选择是否须要偏置项,默认为 True。

batch_first:这个是咱们数据的格式描述,在 pytorch 中咱们常常以 batch 分组来训练数据。这里的 batch_size 表示 batch 是否在输入数据的第一个维度,若是在第一个维度则为 True,默认为 False,也就是第二个维度。

dropout:这里就是对每一层的输出是否加一个 dropout 层,若是参数非 0,那么就会加上这个 dropout 层。值得注意的是,对最后的输出层并不会加,也就是这个参数只有在 num_layers 参数大于 1 的时候才有意义。默认为 0。

bidirectional:若是为 True,则表示 RNN 网络为双向结构,默认为 False。

介绍完具体的参数,咱们就能够很简单的直接构建一个 RNN 的网络了,这里必须的两个参数是 input_size,hidden_size,其他的参数都有默认值,并且也符合通常常见的须要。

2.3 用 sin 曲线来预测 cos 曲线

接下来咱们利用上面的知识来实现一个例子,对应的输入是如图的 sin 函数,label 则是 cos 函数。咱们用输入来拟合这个输出。

咱们先看一下这个数据应该如何展现:

steps = np.linspace(0, np.pi*2, 100, dtype=np.float32)x_np = np.sin(steps)y_np = np.cos(steps)

经过 numpy 咱们能够构建出来想要的 sin 和 cos 曲线,这里的 x_np 做为数据,y_np 做为 label。就能够进行咱们的网络构建了。

根据前面学习的各个参数的做用,咱们就能够构建以下的网络结构:

class RNN(nn.Module): def __init__(self): super(RNN, self).__init__() self.rnn = nn.RNN( input_size=1, hidden_size=32, num_layers=1, batch_first=True, )        self.out = nn.Linear(321)
def forward(self, x, h_state): r_out, h_state = self.rnn(x, h_state) outs = [] for time_step in range(r_out.size(1)): outs.append(self.out(r_out[:, time_step, :])) return torch.stack(outs, dim=1), h_state

咱们介绍一下这里的几个关键点,首先参数上比较简单,输入就是一个横坐标,因此 input_size 是 1;将其 embedding 到一个 32 维的空间上,因此 hidden_size 选择了 32,这个值固然能够选择其它的;一层的网络,因此 num_layers 设置为 1,其实默认值也是 1,这一步能够省略;最后关注一下 batch_first 为 True,是由于咱们选择将输入的维度设置为(batch,time_step,input_size)。

值得注意的是 forward 函数中,咱们构建了一个 outs,而后一直往进 append 数据,具体的缘由是什么呢?

当 RNN 在处理数据的时候,每次的输入须要传进去一个 sequence,其中每一个 entry 能够看作一个词,如前面理论部分的介绍,每一个词向量的长度就是 input_size。可是每一个 sequence 的长度呢?其实对应了 time_step,也就是每次输入多少个“词向量”。

因此在 forward 的函数中,第二个维度 time_step 就是序列的长度,在这个例子中就是每次给网络给多少个点的数据。这个 for 循环就是从 r_out 的第二个维度中选择,将每一个 time_step 上的数据的映射结果拿出来,而后经过 out() 函数,也就是 full connected layer 进行处理。最后 append 的就是这个处理结果。

最后咱们把训练过程展示出来看一看:

蓝色线条展现了模型的拟合过程,能够看到最终逐渐拟合到了目标的 cos 曲线上(红色线条)。

3. LSTM,以及实现一个分类器

前面咱们介绍了原始的 RNN,众所周知的一些 RNN 的缺陷,咱们就不赘述了(梯度爆炸,梯度消失等等)。那么 LSTM 成为了一个在序列化数据中很是受欢迎的选项。这一部分,咱们先介绍一下简单的 LSTM 的理论基础,而后主要是 pytorch 中对于 LSTM 提供的接口,最后经过一个分类器的例子,来完成一个分类器。

3.1 简单介绍 LSTM

和前面 RNN 同样,咱们先甩出来一张你们确定见过的图:

从图中能够看到,不一样于 RNN,LSTM 提出了三个门(gate)的概念:input gate,forget gate,output gate。其实能够这样来理解,input gate 决定了对输入的数据作哪些处理,forget gate 决定了哪些知识被过滤掉,无需再继续传递,而 output gate 决定了哪些知识须要传递到下一个时间序列。

pytorch 中 LSTM 源码的 doc 中给出了如下公式:

 咱们本篇文章不是专门介绍 LSTM 的文章,因此这里就不展开细讲。可是能够简略的帮你们回顾一下几个式子的总体流程。

i_t 是处理 input 的 input gate,外面一个 sigmoid 函数处理,其中的输入是当前输入 x_t 和前一个时间状态的输出 h_(t-1)。全部的 b 都是偏置项。

f_t 则是 forget gate 的操做,一样对当前输入 x_t 和前一个状态的输出 h_(t-1) 进行处理。

g_t 也是 input gate 中的操做,一样的输入,只是外面换成了 tanh 函数。

o_t 是前面图中 output gate 中左下角的操做,操做方式和前面 i_t,以及 f_t 同样。

c_t 则是输出之一,对 forget gate 的输出,input gate 的输出进行相加,而后做为当前时间序列的一个隐状态向下传递。

h_t 一样是输出之一,对 前面的 c_t 作一个 tanh 操做,而后和前面获得的 o_t 进行相乘,h_t 既向下一个状态传递,也做为当前状态的输出。

3.2 LSTM 在 pytorch 中的参数要求

简单介绍完上面的数学内容,咱们只须要知道 LSTM 内部进行了这些运算,具体的运算细节已经无需去自行设计,pytorch 已经给咱们封装好了。

那么咱们须要给它传入哪些输入,设置哪些参数呢?

class torch.nn.LSTM(*args, **kwargs): input_sizex的特征维度 hidden_size:隐藏层的特征维度 num_layerslstm隐层的层数,默认为1 biasFalsebihbih=0和bhhbhh=0. 默认为True batch_first:True则输入输出的数据格式为 (batch, seq, feature) dropout:除最后一层,每一层的输出都进行dropout,默认为: 0 bidirectional:True则为双向lstm默认为False

看得出来,和前面的 RNN 参数很是类似,只少了一个 nonlinearty 参数。这是由于 LSTM 无需设置具体的非线性函数,从上面的公式中能够看到,具体每一步的操做,都已经有了清晰的定义。

因此这里的参数的意义就简略注释了,详细的意义和 RNN 同样。

3.3 在 mnist 数据集上实现一个分类器

虽说起 LSTM 你们确定以为是在 nlp 的数据集上比较常见,不过在图片分类中,它一样也可使用。由于只是实现一个例子,因此咱们就选择前面屡次使用,比较熟悉的 mnist 进行验证吧。

咱们知道 mnist 数据集是 28*28 的手写数字,并且由于是黑白照片,因此不像彩色图片同样是三通道,只有一个通道。

这里对于数据的理解,咱们进行一下简单的介绍:对于每一张图片,咱们看做一条数据,就像 nlp 中的一个句子同样。将照片的每一行看作一个向量,对应一个句子中的词向量,因此很显然,图片的行数就句子的长度。因此对这个 28*28 的照片,就是一个由 28 个向量组成的序列,且每一个向量的长度都是 28。在 nlp 领域中,就是一个有 28 个单词的句子,且每一个单词的词向量长度都为 28.

因此咱们能够直接构建 LSTM 的网络结构以下:

class RNN(nn.Module): def __init__(self): super(RNN, self).__init__()
self.rnn = nn.LSTM( input_size=28, hidden_size=64, num_layers=1, batch_first=True, ) self.out = nn.Linear(64, 10)
def forward(self, x): r_out, (h_n, h_c) = self.rnn(x, None) out = self.out(r_out[:, -1, :]) return out

网络的结构方面须要介绍的很少,将一个 28 维的向量映射到 64 维的空间上,网络层数为 1,batch 在数据的第一个维度上。

对 forward 这里简单介绍一下,LSTM 的输出是由两部分组成,第一部分是 r_out,第二部分是 (h_n,h_c)。

对于 r_out 而言,就是最后一个状态的隐藏层的神经元的输出,换句话说,就是上面图片中各个 h_t 连起来的样子,若是是一个翻译的 LSTM,那就是翻译后的句子。

对于第二部分的两个参数而言,分别是最后一个状态的隐含层的隐状态,也就是上面图片中的两条向右一直传递的线,在最后一个状态的结果。

因此咱们须要输出的是 out = r_out[:, -1, :],若是不能理解是什么的话,咱们先给出来 r_out 的 shape:(batch,seq_len,input_size)。如今是否是就明白了,这个 out 就是最后一个向量传进去 LSTM 后,获得的输出结果。也就是咱们把图片的全部行都穿进去之后,LSTM 给出的分类结果。

接下来是对结果进行验证的部分,咱们就不贴出来这部分代码了,意义不是很明显,感兴趣的朋友能够看文章最后,会给出本文的所有代码连接。

下面是咱们对训练结果进行的测试,测试的效果天然不如 CNN 优秀,可是也到了 90% 以上了。

4. 总结

今天的文章主要是介绍了 RNN 和 LSTM 在 pytorch 中的实现,前面也提到 pytorch 还提供了 GRU 的接口,感兴趣的朋友能够自行看看 doc,实现一个分类器或者回归器来进行接口验证。

这篇文章中,你们若是仔细阅读,代码认真的敲一遍,基本上对接口的各个部分也就熟悉了,因此拿起键盘尝试一下吧~

PS:给出几个连接供你们参考,一个是知乎上面一篇对于 LSTM 的解读,一个是莫烦小哥的视频教程,本文的代码示例也都是参考莫烦老师的 pytorch 教程,结合本身的理解进行整理的。

【1】https://zhuanlan.zhihu.com/p/139617364

【2】https://www.bilibili.com/video/BV1Vx411j7kT?p=23

PS2:本文的 RNN 和 LSTM 两部分的代码完整连接以下:

RNN:https://github.com/TrWestdoor/pytorch-practice/blob/master/rnn_regression.py

LSTM:<https://github.com/TrWestdoor/pytorch-practice/blob/master/rnn_classify.py>


本文分享自微信公众号 - 机器学习与推荐系统(ml-recsys)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索