Github-jcjohnson/torch-rnn代码详解

zoerywzhou@gmail.com

http://www.cnblogs.com/swje/

作者:Zhouwan

 2016-3-18

声明


1)本文仅供学术交流,非商用。所以每一部分具体的参考资料并没有详细对应。如果某部分不小心侵犯了大家的利益,还望海涵,并联系博主删除。

2)本人才疏学浅,整理总结的时候难免出错,还望各位前辈不吝指正,谢谢。

请联系:zoerywzhou@gmail.com 或13813017783@163.com

 

本研究课题系本人本科毕业论文,具体学习计划见http://www.cnblogs.com/swje/p/5068069.html

后面会实时更新,希望能与大家相互交流,共同进步!
 
继karpathy的char-rnn之后,最近在看jcjohnson写的torch-rnn,作为梳理和总结,发表此篇文章记录一下。 

源文件及参考文献如下


torch-rnn代码@Github:https://github.com/jcjohnson/torch-rnn

学习体会


1、torch-rnn 提供了一个高性能、可再用的RNN和LSTM模块,使用这些模块对字符级别的语言建模和char-rnn是类似的。RNN和LSTM模块仅仅依赖于torch和nn,所以可以很容易地整合到现有的项目中。相比于char-rnn,torch-rnn的速度快了1.9倍,并且节约了七倍的内存。

 
2、实验运行环境:需要安装Python 2.7 和HDF5 库的头文件;预处理脚本使用Python 2.7编写的;实验需要在torch7平台运行,并用LUA语言编程;为了提供CUDA和OpenCL支持以进行GPU加速,需要安装相应的LUA安装包。
 
3、要想训练一个模型并用它来生成新的文本,需要遵循以下三个步骤:

  • 数据预处理:在训练前,需要用脚本scripts/preprocess.py对数据进行预处理,这将会生成一个包含数据的预处理版本的HDF5 文件和 JSON 文件。 比如生成了my_data.h5 和 my_data.json两个文件。
  • 训练模型:预处理之后,需要用脚本train.lua 来训练模型。这一步是最慢的。

可以运行下面的代码来训练:

  1. th train.lua -input_h5 my_data.h5 -input_json my_data.json

以上代码将会读取存储在my_data.h5 和 my_data.json两个文件中的数据,运行一段时间后,将会生成检查点文件checkpoint,文件命名类似cv/checkpoint_1000.t7。

你可以通过参数设置改变RNN模型类型、隐藏层大小和 RNN层数,选择使用CUDA在GPU模式下运行或在CPU模式下运行,也可以选择是否使用OpenCL,还有其他参数设置,参考这里

  • 从模型中抽样:训练完一个模型之后,你可以通过使用脚本sample.lua从文本中抽样来生成新的文本。运行以下代码:th sample.lua -checkpoint cv/checkpoint_10000.t7 -length 2000,将会从前一步载入训练好的检查点集 ,从中抽取2000个字符,并将结果打印到控制台上。

你可以通过参数设置,选择使用CUDA在GPU模式下运行或者在CPU模式下运行,也可以选择是否使用OpenCL,还有其他参数设置,参考这里

4、为了用基准问题测试torch-rnn和char-rnn,我们对莎士比亚散文集训练LSTM语言模型,RNN层数和RNN大小分别设置为1、2、3层和64、128、256和512,并将minibatch大小设为50,序列长度设为50,dropout设为0。对于同一大小RNN模型的两次实验中,在前100次训练迭代过程中,我们记录下向前和向后传播的时间和GPU的内存使用情况,并使用这些测量值来计算平均时间和内存使用情况。所有的基准测试程序数值都是在一个配置有 Intel i7-4790k CPU, 32 GB 主存和带有一个 Titan X GPU的机器上运行的。

从实验结果出可以看出,torch-rnn在任何模型大小下都比char-rnn运行速度快,小模型的加速比更大一些。对于有128个隐藏单元的单层LSMT来说,加速了1.9倍;对于较大的模型,我们达到大约1.4倍的加速。

节约GPU内存方面来看,torch-rnn在所有的模型大小上都胜过char-rnn,但是,对于较大一些的模型节约内存更多一些,例如:对于有512个隐藏单元的模型来说,torch-rnn比char-rnn少用了七倍内存。

一句话总结:torch-rnn相比于char-rnn,更节约内存,模型越大越节约;且训练时间(前向传播&反向传播的时间)更短,模型越小加速越快!

模块分析


1、VanillaRNN:

  1. rnn = nn.VanillaRNN(D, H)

VanillaRNN 是 torch nn.Module的一个子类,用双曲正切函数实施一个vanilla 递归神经网络。它将一个D维输入向量的序列转换为一个H维隐藏状态向量的序列;在长度为T的序列、大小为N的minibatch的模型上运行,在每一次前向传播中,序列长度和minibatch的大小可以改变。

暂且忽略minibatch,vanilla RNN使用下面的递归关系式 从前一个隐藏状态 h[t - 1](of shape (H,)) 和当前的输入向量 x[t](of shape (D,)) 来计算下一个隐藏层的状态向量 h[t]:

  1. h[t] = tanh(Wh h[t- ] + Wx x[t] + b)

其中, Wx是一个连接输入层和隐藏层的矩阵,Wh是一个连接隐藏层和隐藏层的矩阵,b 是一个偏项。其中权重Wx 和Wh存储在大小为(D + H, H)的张量rnn.weight 中,而偏差项 b 存储在为H维的张量 rnn.bias中。

你可以用两种不同的方式来使用 VanillaRNN 实例:

  1. h = rnn:forward({h0, x})
  2. grad_h0, grad_x = unpack(rnn:backward({h0, x}, grad_h))
  3.  
  4. h = rnn:forward(x)
  5. grad_x = rnn:backward(x, grad_h)

h0 是隐藏层初始状态,大小为(N,H), x 是输入向量序列,大小为(N, T, D)。在每个时间步长,输出 h 是隐藏状态的序列, 大小是 (N, T, H)。在一些应用中,比如图像字幕,隐藏层的初始状态可能是一些其他网络计算出来的输出结果。

默认情况下,如果前向传播中没有提供 h0 ,那么隐藏层的初始状态就设为0。这种方法可能对情感分析等应用有帮助,这类应用往往需要用一个RNN处理很多独立的序列。

如果没有提供h0,且实例变量rnn.remember_states的值被设置为真,那么首次调用rnn:forward时将会将隐藏层的初始状态设为0;在随后调用rnn:forward时,从先前的调用得到的最后一个隐藏状态将被作为下一个隐藏层的初始状态。这种方法常用在语言模型中,我们想用很长的序列(可能无限长)训练网络,并且使用沿时间截断反向传播的方法(truncated back-propagation through time)计算梯度。通过调用rnn:resetStates()使模型忘记它的隐藏状态,然后下次调用rnn:forward时会将h0的值初始化为0。

这种方法在unit test for VanillaRNN.lua中有所运用。

作为一个实现,我们直接执行 :backward 以同时计算关于输入的梯度并积累关于权重的梯度,因为这两个操作涉及很多相同的计算。我们将 :updateGradInput 和:accGradparameters重写进调用 :backward中,然后直接调用:backward而不是先调用:updateGradInput 再调用:accGradparameters,这样就避免了计算两次同样的事情。

文件VanillaRNN.lua是独立的,除了torch 和 nn,对其他模块没有依赖性。

  1. 2LSTM
  1. lstm = nn.LSTM(D, H)

LSTM( Long Short-Term Memory的简称)是一个别致的递归神经网络类型,比vanilla RNNs常用的多。类似于上面提到的vanilla RNNs的性质,LSTM是一个执行LSTM的torch nn.Module 的子类。它将一个D维输入向量的序列转换为一个H维隐藏状态向量的序列;在长度为T的序列、大小为N的minibatch的模型上运行,在每一次前向传播中,序列长度和minibatch的大小可以改变。

LSTM和vanilla RNN的不同之处在于,在每一个时间步长上它都跟踪隐藏状态和cell 状态。暂且忽略minibatch,vanilla RNN使用下面的递归关系式 从前一个隐藏状态 h[t - 1](of shape (H,)) 、前一个cell状态 c[t-1] 和当前的输入向量 x[t](of shape (D,)) 来计算下一个隐藏状态向量 h[t]和cell状态向量 c[t]:

  1. ai[t] = Wxi x[t] + Whi h[t - ] + bi # Matrix / vector multiplication
  2. af[t] = Wxf x[t] + Whf h[t - ] + bf # Matrix / vector multiplication
  3. ao[t] = Wxo x[t] + Who h[t - ] + bo # Matrix / vector multiplication
  4. ag[t] = Wxg x[t] + Whg h[t - ] + bg # Matrix / vector multiplication
  5.  
  6. i[t] = sigmoid(ai[t]) # Input gate
  7. f[t] = sigmoid(af[t]) # Forget gate
  8. o[t] = sigmoid(ao[t]) # Output gate
  9. g[t] = tanh(ag[t]) # Proposed update
  10.  
  11. c[t] = f[t] * c[t - ] + i[t] * g[t] # Elementwise multiplication of vectors
  12. h[t] = o[t] * tanh(c[t]) # Elementwise multiplication of vectors

输入层到隐藏层的矩阵 WxiWxfWxo和 Wxg 以及隐藏层到隐藏层的矩阵 WhiWhfWho和 Whg都被存储在大小为(D + H, 4 * H)的单个张量lstm.weight 中,偏差向量 bibfbo和 bg 被存储在大小为(4 * H,)的单个张量 lstm.bias 中。

你可以用三种不同的方式来使用  LSTM 实例:

  1. h = lstm:forward({c0, h0, x})
  2. grad_c0, grad_h0, grad_x = unpack(lstm:backward({c0, h0, x}, grad_h))
  3.  
  4. h = lstm:forward({h0, x})
  5. grad_h0, grad_x = unpack(lstm:backward({h0, x}, grad_h))
  6.  
  7. h = lstm:forward(x)
  8. grad_x = lstm:backward(x, grad_h)

在所有情况下,c0 是初始cell 状态,大小为(N,H), h0 是初始隐藏状态,大小为(N,H),x 是输入向量序列,大小为(N, T, D), h 是输出隐藏状态的序列, 大小是 (N, T, H)

如果没有提供初始cell状态或者初始隐藏状态,那么它们的初始状态就默认设为0。

如果没有提供初始 cell 状态或者初始隐藏状态,且实例变量 lstm.remember_states 的值被设置为真,那么首次调用 lstm:forward 时将会将隐藏层的初始状态和cell 状态设为0;在随后调用lstm:forward 中,将会把隐藏状态和cell状态的初始值设为前一次调用时得到的最终的隐藏层和cell的状态,这和 VanillaRNN 很类似。你可以通过调用lstm:resetStates() 重置它们的cell 状态和隐藏状态,然后下次调用 lstm:forward时会将初始隐藏状态和cell 状态设为 0。

这种方法在unit test for LSTM.lua中有所运用。

作为一个实现,我们直接执行 :backward 以同时计算关于输入的梯度并积累关于权重的梯度,因为这两个操作涉及很多相同的计算。我们将 :updateGradInput和 :accGradparameters 重写进调用 :backward 中,然后直接调用:backward而不是先调用:updateGradInput 再调用:accGradparameters,这样就避免了计算两次同样的事情。

文件LSTM.lua 是独立的,除了torch 和 nn,对其他模块没有依赖性。

3、LanguageModel module:

torch-rnn提供了一个LanguageModel module,用于对字符级的语言建模。 

  1. model = nn.LanguageModel(kwargs)
  1.  

LanguageModel 使用以上模块 通过dropout 正则化来实现多层递归神经网络语言模型。因为 LSTM 和 VanillaRNN 是 nn.Module子类,我们可以通过在容器 nn.Sequential 中简单地堆积多个实例来实现多层递归神经网络。

kwargs 是一张表,包含以下关键词:

    • idx_to_token: 一张给定语言模型词汇的表,将整型ids 映射为 字符串tokens
    • model_type: "lstm" 或 "rnn"
    • wordvec_size: 单词向量嵌入的维度
    • rnn_size: RNN的隐藏状态大小
    • num_layers: 使用的RNN层数
    • dropout: 介于0和1之间的数字,指定经过每个RNN层之后的dropout长度

Github-jcjohnson/torch-rnn代码详解的更多相关文章

  1. Kaggle网站流量预测任务第一名解决方案:从模型到代码详解时序预测

    Kaggle网站流量预测任务第一名解决方案:从模型到代码详解时序预测 2017年12月13日 17:39:11 机器之心V 阅读数:5931   近日,Artur Suilin 等人发布了 Kaggl ...

  2. Github-karpathy/char-rnn代码详解

    Github-karpathy/char-rnn代码详解 zoerywzhou@gmail.com http://www.cnblogs.com/swje/ 作者:Zhouwan  2016-1-10 ...

  3. DeepLearning tutorial(3)MLP多层感知机原理简介+代码详解

    本文介绍多层感知机算法,特别是详细解读其代码实现,基于python theano,代码来自:Multilayer Perceptron,如果你想详细了解多层感知机算法,可以参考:UFLDL教程,或者参 ...

  4. python golang中grpc 使用示例代码详解

    python 1.使用前准备,安装这三个库 pip install grpcio pip install protobuf pip install grpcio_tools 2.建立一个proto文件 ...

  5. BM算法  Boyer-Moore高质量实现代码详解与算法详解

    Boyer-Moore高质量实现代码详解与算法详解 鉴于我见到对算法本身分析非常透彻的文章以及实现的非常精巧的文章,所以就转载了,本文的贡献在于将两者结合起来,方便大家了解代码实现! 算法详解转自:h ...

  6. ASP.NET MVC 5 学习教程:生成的代码详解

    原文 ASP.NET MVC 5 学习教程:生成的代码详解 起飞网 ASP.NET MVC 5 学习教程目录: 添加控制器 添加视图 修改视图和布局页 控制器传递数据给视图 添加模型 创建连接字符串 ...

  7. 代码详解:TensorFlow Core带你探索深度神经网络“黑匣子”

    来源商业新知网,原标题:代码详解:TensorFlow Core带你探索深度神经网络“黑匣子” 想学TensorFlow?先从低阶API开始吧~某种程度而言,它能够帮助我们更好地理解Tensorflo ...

  8. JAVA类与类之间的全部关系简述+代码详解

    本文转自: https://blog.csdn.net/wq6ylg08/article/details/81092056类和类之间关系包括了 is a,has a, use a三种关系(1)is a ...

  9. Java中String的intern方法,javap&cfr.jar反编译,javap反编译后二进制指令代码详解,Java8常量池的位置

    一个例子 public class TestString{ public static void main(String[] args){ String a = "a"; Stri ...

随机推荐

  1. golang安装环境变量配置和beegoan安装

    下载安装 go get github.com/astaxie/beego bee 工具的安装 go get github.com/beego/bee 升级 $ go get -u github.com ...

  2. 小程序发送 request请求失败 提示不在合法域名列表中的解决方法

    可以在小程序开发工具中设置不校验域名.

  3. mybatis-generator和TKmybatis的结合使用

    mybatis-generator可以自动生成mapper和entity文件,mybatis-generator有三种用法:命令行.eclipse插件.maven插件.这里使用的是maven插件方式, ...

  4. pip升级或卸载安装的包的方法

    先 pip list 看看包的具体名字是什么,然后 pip uninstall **包名** ===== 打印出有新版本的包: pip list --outdated --format=freeze ...

  5. linux list.h 移植

    Linux内核中List链表的实现,对于想进阶的程序员来说,无疑是一个很好的学习机会.内核实现了一个功能十分强大的链表,而且是开源的,用在其他需要的地方岂不是很省事. 一.看List实现前,先补充ty ...

  6. javascript高级语法二

    一.BOM对象 1.什么是BOM对象? BOM是浏览器对象模型,核心对象就是window,所有浏览器都支持 window 对象.一个html文档对应一个window对象,主要功能是控制浏览器窗口的, ...

  7. set去重,session,cookie c#与python 对比

    端口,发送请求进行监听,然后处理 session 是存储在服务器端的数据,靠sessionId来验证获取信息,没有大小和类型限制, cookie   是存储在客户端的数据,可以长期使用,有面临被获取的 ...

  8. selenium之坑:点击后页面刷新重新获取刷新前的页面(StaleElementReferenceException:Message:Element not found in the cache...)

    循环点击一列链接,但只能点到第一个,第二个失败,这是为什么,因为第二个已经是新页面,当然找不到之前页面的元素,虽然元素没变,甚至是后退回来,页面都没有变,为什么是新页面,页面长的一样不代表是同一张页面 ...

  9. springboot-数据库

    Spring-data-jpa jpa定义了一系列持久化的标准,比如hibernate就实现了这一标准. Springboot 的jpa就是hibernate的整合. 在pom文件中增加配置: < ...

  10. 在各种Linux发行版上安装Git的教程

    Git是一个流行的开源版本控制系统(VCS),最初是为Linux环境开发的.跟CVS或者SVN这些版本控制系统不同的是,Git的版本控制被认为是“分布式的”,某种意义上,git的本地工作目录可以作为一 ...