pytorch dropout_Pytorch-RNNBASE-LSTM python+c源码理解

实验室要做一个语义相似度判别的项目,分给了我这个本科菜鸡,目前准备使用LSTM做一个Baseline来评价其它的方法,但是卡在了pytorch的LSTM模块使用上,一是感觉这个模块的抽象程度太高,完全封装了所有内部结构的情况下使得使用体验并不是很好,同时在pack_sequence的时候也遇到了一些理解问题,因此用这篇文章记录整个过程。

  1. Packed_Sequence问题

pytorch dropout_Pytorch-RNNBASE-LSTM python+c源码理解_第1张图片

根据pack之后的结果,是按照竖向方法存储,这使得我对packsequence输入的顺序正确性产生了理解问题,经过查询官方文档和内部实现后发现,这样的操作是使得并行化更为方便,同时也要更深刻的理解, RNN是按照time_step的方法来输入序列的,这样的排序使得处于同一个time_step的在内存空间上相互比邻,比如在第一个step,rnn知道每个句子的第一个step是[ I,I,THIS,NO,YES],第二个step是[love,love,is,way],而如果是按照序列存储[I,love,Mom,',s,cooking]则一个输入里每一个time_step都有,而非是竖存储方式的每一个step都是一列,同时每一个step都是一列,这使得GPU的并行化更加容易。

2.内部结构

祭出这一张出名的LSTM图吧,说什么都不如这一张图说的清楚,基于这个图进行解释。

pytorch dropout_Pytorch-RNNBASE-LSTM python+c源码理解_第2张图片
LSTM

要注意几点:这是时间步上的展开,并不是3个LSTM单元,而是一个,输出的隐状态向量会直接循环输入进输入进,所以这三个是一个展开,而不是3个LSTM

好下面开始解读LSTM的源码:

c96e82dcbb51a8b3c7c371b776af2f39.png

可以见到LSTM继承了RNNBase这个父类。

先看它的初始化,也挺长的:

pytorch dropout_Pytorch-RNNBASE-LSTM python+c源码理解_第3张图片

可以看见在RNNBase里还传入了一个mode参数,这个mode在图底部判断了使用的模型名字,参数赋值就不说了,直接进入条件语句里看,首先判断了dropout是不是一个正常值,同时在我们只有一个LSTM的时候并不让我们dropout,根据解释是,dropout是在所有recurrent layer之后的,也就是只有当我们的隐层状态作为第二个LSTM的输入的时候才允许使用dropout,我个人这样理解,既然第一层是明确的处理时序信息,那我们就不应该在训练时使用dropout,因为这样会降低时序的处理能力,因为drop掉一部分权重,会让我们在第一层就损失时序信息,而当我们输出隐层之后,由于隐层是不明确的时序信息,可以在训练时dropout。随后判断我们使用的是什么模型,当我们输入LSTM的时候,门的维度是隐向量维度的四倍,因为我们在 LSTM里,我们一共有4个gate,分别是input_gate,控制了我有多少要输入进去,而是forget_gate决定了LSTM里cell有多少要遗忘,然后是output_gate决定了我有多少要传入下一个状态,还有就是cell_gate决定有多少要来更新这个cell,这四个gate控制了整个LSTM的读写过程,而每个gate的维度要和隐状态要匹配。

pytorch dropout_Pytorch-RNNBASE-LSTM python+c源码理解_第4张图片

我们先进入每一层,然后判断该层是否是双向LSTM,如果是的话,input的size将会乘以2,同时明确在第一层时以input_size输入。

在这里,我们把所有层要训练的参数都添加进pytorch的Parameter中,当torch的parameter被在一个nn.Module下的神经网络启用时,它将自定把用Parameter包装的向量作为神经网络中需要训练的参数,次数我们有了每层的权值和偏置,然后包装成一个元组作为layer_params,即layer_params包含了该层的所有可训练参数,如果有反响的话还会添加suffix后缀,随后将所有可训练参数都通过_all_weights.append添加进去。下面有个flatten函数,是用于cuda的,不作解读,理解cpu版本,cuda的工作原理一样,只不过可以更简单的并行化。

baf950e8c12af2581828f07ff7d302c7.png

在这个函数中,我们对parameters中的所有在

进行初始值。

pytorch dropout_Pytorch-RNNBASE-LSTM python+c源码理解_第5张图片

这个函数检查了要前向传输的自变量,当batch_size不为空时,这以为着我们的sequence已经是pack之后的了,当这个sequence是pad之后的我们输入的维度便期待是2维,这正好和pack后的sequence对准,如果此处理解不了的话可以先看一下pack_sequence的函数功能。

当输入维度不是期待维度时提出错误,然后在确定pack之后得到mini_batch的size,否则则以input的size来作为mini_batch。然后判断是否是双向LST,如果是,则期待的隐状态维度要变化,然后check隐状态的size,不对则报错。

pytorch dropout_Pytorch-RNNBASE-LSTM python+c源码理解_第6张图片

在这里定义了前向的传播,也是最终的一部分,首先还是判断sequence是否是pack之后的,如果是pack了的,则获得data和batch大小。同时默认hx为空,此处是如果我们没有默认的隐向量输入时,hx会启用,并初始化为0,随后通过查询_rnn_impls来知道我们要启用的前向函数。

pytorch dropout_Pytorch-RNNBASE-LSTM python+c源码理解_第7张图片

当模型是lstm时,则启动_VF.lstm,进行前向传播,而_VF.lstm是c++代码,在pytorch上找到了相关源码,贴c++代码。

pytorch dropout_Pytorch-RNNBASE-LSTM python+c源码理解_第8张图片

可见,在c++里的这个_lstm_impl还是没有告诉我们具体的计算过程,让我们再看看这个函数做了什么,首先,它将每个层的隐状态向量和细胞状态分层化给每一个layer,注意我们看到result是以_rnn_imp的调用出现的,再往下看,得到了result之后,我们又把每一层的隐状态向量和细胞状态组合起来,做成一个tuple返回,所以在这里我们发现,所有的隐藏状态并不是放在一个大矩阵里不断更新的,而是不断的切分成一层一层的,计算之后,再合在一起返回,既可以理解为分层后可以更快的并行同时将隐藏向量分层计算也比较符合LSTM的特性。

pytorch dropout_Pytorch-RNNBASE-LSTM python+c源码理解_第9张图片

然后我们在追到result所调用的_rnn_impl了,这个应该已经是所有RNN网络都要调用的一个模块了,体现了LSTM只不过是RNN的一种形式。然而我们看到,这里面还是没有告诉我们是怎么计算的。在这里,首先是保存了一堆参数,然后直接判断是否是,如果是,则构双向层,然后调用apply_layer_stack得到双层结果,如果不是也调用这个也会返回结果,那么我们继续找apply_layer_stack这个函数做了什么。

pytorch dropout_Pytorch-RNNBASE-LSTM python+c源码理解_第10张图片

在这里,我们看到还是没有告诉我们是怎么计算的,但是我们先看看,首先检查了的层数是否一致,然后复制给一些参数,随后我们看到每一个层的layer_output都是调用layer得到的,layer的参数是上一层layer的input,隐状态还有权重,然后我们看到,这里有一个RNN的递归实现,每一个的最后隐状态都会push__back进layer中,实现隐状态进入下一时刻的LSTM,而下一层的layer_input则是等于上一层的layer_output的输出,同时如果有dropout则会调用dropout,又由于在const里定义了Layer&layer,在c++中相当于给Layer取了一个别名layer,所以我们去看Layer.

caa01e30c35fecd13fe0968450cd80d8.png

但是经过一番查询后,还是没有找到定义计算的过程,浏览了一整遍的代码,在传入的cell_parameters中,有LSTM,而在构建这个结构体时就已经进行了计算。

pytorch dropout_Pytorch-RNNBASE-LSTM python+c源码理解_第11张图片

直到这里,终于发现了计算过程和整体的定义,在这里有一个cuda版本的我们不作阅读,首先我们就看见了,它将集合起来的gate分成了4个gate,这和python中的操作不谋而合。然后就是根据门来更新细胞的状态和隐向量,随后组成一个tuple返回,再细查这个cell_params,它包括了所有的RNN带有cell的结构,LSTM和GRU这两个带有cell的RNN

517cbce5020227f6db100b8a9102901d.png

回到result这条语句上,这里的rnn_impl就是输入了LSTMCell的结构体进行了运算.

目前先到这里,由于个人C语言阅读能力不是很高,有错误或者遗漏或者误读都有可能,如果有错误还望不吝赐教。

参考:
【1】

Understanding LSTM Networks​colah.github.io
pytorch dropout_Pytorch-RNNBASE-LSTM python+c源码理解_第12张图片

【2】

pytorch对可变长度序列的处理 - 深度学习1 - 博客园​www.cnblogs.com
pytorch dropout_Pytorch-RNNBASE-LSTM python+c源码理解_第13张图片

你可能感兴趣的:(pytorch,dropout,pytorch,lstmcell)