pytorch训练lstm_Pytorch-RNNBASE-LSTM python+c源码理解

news/2024/7/7 1:34:54

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

  1. Packed_Sequence问题

94ec25bcc0f4f0ec97a1b045259e76cd.png

根据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图吧,说什么都不如这一张图说的清楚,基于这个图进行解释。

9ea8d22864fcdc58c330992a4371817d.png
LSTM

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

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

cb85c392d8ca0655bfccd7a05e240102.png

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

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

9972dc6c397c857ac20009e8fe22b890.png

可以看见在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的维度要和隐状态要匹配。

4c64c95e39b72ced77bd517f229a29f2.png

我们先进入每一层,然后判断该层是否是双向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的工作原理一样,只不过可以更简单的并行化。

0937e8ca72a0b6b9d69fd8ea6ecfae36.png

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

equation?tex=1%2Fsqrt%28hidden%5C_size%29 进行初始值。

a2f0a49f154b47cc8cece115d4bc95a9.png

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

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

b930bfc07f2159c58480be6ce57c7b01.png

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

32117be778561fd584ee0cf7270e43ec.png

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

871cd56c161f5fa4f6c9bd839e84869d.png

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

69296f9f056bb33b62e470f6deb8e281.png

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

8eabb59c253d39dea8b3103d0732ea95.png

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

ebd6f72e904adaab3d2bfa75eb301d32.png

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

0745f0dfb6af10cc44b00396ab4e0d56.png

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

bb1e5fb3f8b89b03914c33560391c297.png

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

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

参考:
【1】

Understanding LSTM Networks​colah.github.io
1c2eab3ea7d3ca904ecc5e8c5af9d7b2.png

【2】

pytorch对可变长度序列的处理 - 深度学习1 - 博客园​www.cnblogs.com
69abfb48fda1696967555a2c98ebe2da.png

http://www.niftyadmin.cn/n/2642652.html

相关文章

骁龙820A USB音频分享方案

USB虚拟双声卡的基本思路就是依据USB驱动中的复合描述符,根据驱动的配置,在驱动中用声音子系统创建出了两个声卡。 USB 驱动中的复合描述符 如 USB 规范所述,每个 USB 设备都会提供一组分层描述符来定义其功能。在顶层,每个设备具…

gitbook 入门教程之前置知识

markdown 基本知识 markdown 是一种简化的 html 语法,相比于 txt 无格式文本更强大. 你可以用专门的软件去编辑 markdown 文件,就像需要使用软件编辑 txt 文件一样,当然也可以什么软件也不用,甚至直接在记事本或命令行书写,只不过这样的缺点就是无法实时预览输出效果,安全依赖…

基于高通骁龙820A的自动驾驶技术实现

自动驾驶已经成为汽车发展的必然趋势,针对这个问题,人们对自动驾驶系统的组成理解为三大系统组成,传感器、汽车的算法思考如何处理、汽车的控制系统去执行操作。但是仅仅依靠这三大系统是不够的,自动驾驶和未来的智慧交通还需要一…

类成员函数的重载、覆盖和隐藏区别 (C++)

这是本人第一次写博客,主要是想记录自己的学习过程、心得体会,一是可以方便以后回顾相关知识,二是可以与大家相互学习交流。 关于C中类成员函数的重载、覆盖和隐藏区别,第一次看到这个问题是在准备找工作的时候在牛客网中&#xf…

傅里叶变换音频可视化_超动感音乐可视化:WebAudio与Shader的震撼结合!

Web Audio API 提供了在 Web 上控制音频的一个非常有效通用的系统,允许开发者来自选音频源,对音频添加特效,使音频可视化,添加空间效果 (如平移),等等。Web Audio API 使用户可以在音频上下文(AudioContext)中进行音频…

骁龙820A与数字座舱平台

骁龙820A处理器能够通过软件更新的方式实现升级来支持信息娱乐系统营造沉浸式的车载体验,帮助汽车通过升级获得最新的差异化特性,如音乐与视频流传输、3D导航、支持多个高分辨率屏幕,以及支持智能优先选择与环境切换的出色GPU性能。该处理器的…

github 进阶说明

目录github 进阶说明前言三个目录树重置 git reset增加路径的reset检出 checkout带路径的checkout仓库数据对象其他资料github 进阶说明 前言 我们可以什么都不管,照搬命令来完成我们大部分git工作,但是如果想要进一步,就要深入理解git的实…

jdbc连接mysql8.0.21_Elasticsearch 架构解析与最佳实践.pdf ; 如何实现 MySQL 的 Binlog 近实时同步...

来源:https://t.zsxq.com/q3nQRrr实现 MySQL 的 Binlog 近实时同步,这么做就对了!近段时间,业务系统架构基本完备,数据层面的建设比较薄弱,因为笔者目前工作重心在于搭建一个小型的数据平台。优先级比较高的一个任务就…