最难读的Theano代码

这份LSTM代码的作者,感觉和前面Tutorial代码作者不是同一个人。对于Theano、Python的手法使用得非常娴熟。

尤其是在两重并行设计上:

①LSTM各个门之间并行

②Mini-batch让多个句子并行

同时,在训练、预处理上使用了诸多技巧,相比之前的Tutorial,更接近一个完整的框架,所以导致代码阅读十分困难。

本文旨在梳理这份LSTM代码的脉络。

数据集:IMDB Large Movie Review Dataset

来源

该数据集是来自Stanford的一个爬虫数据集。

对IMDB每部电影的评论页面的每条评论进行爬虫,分为正面/负面两类情感标签。

相比于朴素贝叶斯用于垃圾邮件分类,显然,分析一段文字的情感难度比较大。

因为语义在各个词之间连锁着,有些喜欢玩梗的负面讽刺语义需要一个强力的Represention Extractor。

该数据集同时也在CS224D:Deep Learning for NLP    [Leture4]中演示,用于体现Pre-Training过后的词向量威力。

数据读取

原始数据集被Bengio组封装过,链接 http://www.iro.umontreal.ca/~lisa/deep/data/imdb.pkl

cPickle封装的格式如下:

train_set[0]  ---->  一个包含所有句子的二重列表,列表的每个元素也为一个列表,内容为:

[词索引1,词索引2,.....,词索引n],构成一个句子。熟悉文本数据的应该很清楚。

词索引之前建立了一个词库,实际使用的时候如果要对照索引,获取真实的词,则需要词库:~Link~

train_set[0][n]指的是第n个句子。

————————————————————————————————————————————

train_set[1]  ---->  一个一重列表,每个元素为每个句子的情感标签,0/1。

test_set格式相同。

————————————————————————————————————————————

本Tutorial使用的一些简单处理包括:maxLen句子词数剪枝、词库越界词剪枝、句子词数排序(不知道啥作用)

数据变形与预处理

这份代码的经典之处在于,让多个句子并行训练,构成一个mini-batch。

在RNN章节中,每次训练只是一个句子,所以输入就是一个向量,但mini-batch之后就是一个矩阵。

这个矩阵最大不同在于,xy轴是倒置的,文字方向在竖式伸展。

这么做的原因是由于theano.tensor.scan函数的工作机制。

scan函数的一旦sequence不为空,就进入序列循环模式,设sequence=[x],则

①Step1:取x[0]作为循环函数第一参数

②Step2:取x[1]作为循环函数第一参数

........

③StepN: 取x[N]作为循环函数第一参数

对应语言模型的序列学习算法,每个Step就相当于取一个句子的一个词。

竖式伸展,每次scan取的一排词,称为examples,数量等于batch_size。

横向batch并行,纵向序列时序伸展,这是mini-batch和序列学习的共同作用结果。

——————————————————————————————————————————

每个句子长度是不同的,为了便于并行矩阵计算,必须选定最长句子,最大化矩阵。句子词较少的,用0填充。

而在实际LSTM计算中,这个0填充则成了麻烦,因为你不能让标记为0的Padding参与递归网络计算,需要剔除。

这时候就需要Mask矩阵。来看LSTM.py中的实际代码:

$c = m\_[:, None] * c + (1. - m\_)[:, None] * c\_$

由于mask矩阵也在sequence中,所以m_被降成了1D,通过Numpy的第二维None扩充,可以和c([batch_size,dim_proj])

进行点乘(不是矩阵乘法)。

Mask矩阵的作用就是对于Padding,通过递推,直接滚到到上一个非0的状态,而不引入Padding。

源码解析

def get_minibatches_idx(n, minibatch_size, shuffle=False)

这部分设计对数据(句子)Shuffle随机排列。首先获取数据集所有句子数n,

对所有句子,按照batch_size,划分出 (n//batch_size)+1个列表。

拼在一起,构成一个二重列表。

返回一个zip(batch索引,batch内容),用于运行。

每份batch是一个列表,包含句子的索引。后续会根据句子的索引,拼出一个小量的x,

进过prepare_data处理过之后,方能使用。

def dropout_layer(state_before, use_noise, trng)

50%的Dropout,开了之后千万不要加Weght_Decay,两种一叠加,惩罚太重了。

Dropout的实现,利用了Tensor.switch(Bool,Ture Operation,False Operation)来动态实现。

传入的use_noise,在训练时置为1,这时候Dropout为动态概率屏蔽。

反之在测试时,置为0,这时候Dropout为平均网络。

具体原理见Hinton关于Dropout的论文:

Dropout: A Simple Way to Prevent Neural Networks from Overfitting

def init_params(options)

这部分主要是初始化全部参数。分为两个部分:

①初始化Embedding参数、Softmax输出层参数

②初始化LSTM参数,在下面的 def param_init_lstm()函数中。

①中参数莫名其妙使用了[0,0.01]的随机值初始化,不知道为什么不用负值。

像Word2Vec的初始化,可能更好些:

$ Init \, \sim \, Rand(\frac{-0.5}{Emb\_Dim},\frac{0.5}{Emb\_Dim}) $

Emb大小为$[VocabSize,Emb\_Dim]$,Softmax参数大小为$[Emb\_Dim,2]$

def param_init_lstm(options, params, prefix='lstm')

这部分用于初始化LSTM参数,W阵、U阵。

LSTM的初始化值很特殊,先用[0,1]随机数生成矩阵,然后对随机矩阵进行SVD奇异值分解。

取正交基矩阵来初始化,即 ortho_weight,原理不明,没有找到相关文献。

————————————————————————————————————————

值得注意的就是numpy.concatenate函数的使用,它锁定了AXIS=1轴(横向,列,第二维)

将Input、Forget、Output三态门与Cell的RNN核的相同操作$Wx+Uh^{'}$合并,并行计算。

如果$x$是$[1000,100]$,即$dim\_proj=100$, 那么$W$大小是$[100,100*4]$。

一次计算,有$Wx$的大小是$[1000,400]$,四个部分在矩阵中被并行计算了。

矩阵计算和FOR循环在串行算法下速度是差不多的,但是在并行算法下,矩阵同时计算比先后串行计算快不少。

def init_tparams(params)

这个函数意义就是一键将Numpy标准的params全部转为Theano.shared标准。

替代大量的Theano.shared(......)

启用tparams作为Model的正式params,而原来的params废弃。

def lstm_layer(tparams, state_below, options, prefix='lstm', mask=None)

LSTM的计算核心。

首先得注意参数state_below,这是个3D矩阵,$[n\_Step,BatchSize,Emb\_Dim]$

在scan函数的Sequence里,每步循环,都会降解第一维n_Step,得到一个Emb矩阵,作为输入$X\_$

计算过程:

①用 $[state\_below] \cdot [lstm\_W]$。

这步很奇妙,这是一个3D矩阵与2D矩阵的乘法,由于每个Step,都需要做$Wx$

所以无须每一个Step做一次$Wx$,而是把所有Step的$Wx$预计算好了,并行量很大。

②进入scan函数过程,每一个Step:

I、将前一时序$h'$与4倍化$U$阵并行计算,并加上4倍化$Wx$的预计算

II、分离计算,按照LSTM结构定义,分别计算$Input Gate$、$Forget Gate$、$Outout Gate$

$\tilde{Cell}$、$Cell$、$h$

③返回rval[0],即h矩阵。注意,scan函数的输出结果会增加1D。

每个Step里,h结果是一个2D矩阵,$[BatchSize,Emb\_Dim]$

而rval[0]是一个3D矩阵,$[n\_Step,BatchSize,Emb\_Dim]$。

后续会对第一维$n\_Step$进行Mean Pooling,之后才能降解成用于Softmax的2D输入。

def sgd|adadelta|rmsprop(lr, tparams, grads, x, mask, y, cost)

这是三个可选梯度更新算法。由于作者想要保持格式一致,所以AdaDelta和RMSProp写的有点啰嗦。

AdaDelta详见我的 自适应学习率调整:AdaDelta

至于Hinton在2014 Winter的公开课提出的RMSProp,没有找到具体的数学推导,不好解释。

应该是由AdaDelta改良过来的,代码很费解。由可视化结果来看,RMSProp在某些特殊情况下,比AdaDelta要稳定。

官方代码里默认用的是AdaDelta,毕竟有Matthew D. Zeiler的论文详细推导与说明。

AdaDelta和RMSProp都是模拟二阶梯度更新,所以可以和Learning Rate这个恶心的超参说Bye~Bye了。值得把玩。

def build_model(tparams, options)

该部分是联合LSTM和Softmax,构成完整Theano.function的重要部分。

①首先是定义几个Tensor量,$x$、$y$、$mask$。

②接着,从tparams['Wemb']中取出Words*Sentences数量的词向量,并且变形为3D矩阵。

x.flatten()的使用非常巧妙,它将词矩阵拆成1D列表,然后按顺序取出词向量,然后再按顺序变形成3D形态。

体现了Python和Numpy的强大之处。

③得到3D的输入state_below之后,配合mask,经过LSTM,得到一个3D的h矩阵proj。

④对3D的h矩阵,各个时序进行Mean Pooling,得到2D矩阵,有点像Dropout的平均网络。

⑤Dropout处理

⑥Softmax、构建prob、pred、cost,都是老面孔了。

特别的是,这里有一个offset,防止prob爆0,造成log溢出。碰到这种情况可能不大。

def pred_probs|pred_error()

用于Cross-Validation计算Print信息。也就是Debug Mode...

def train_lstm()

训练过程。Theano代码块中最冗长、最臃肿的部分。

①options=locals().copy()是python中的一个小trick。

它能将函数中所有参数按爬虫下来,保存为一个词典,方便访问。

②load_data。定义在imdb.py中,获取train、vaild、test三个数据集

让人费解的是,既然这里要对test做shuffle,又何必在load_data里把test排序呢?

③初始化params,并且build_model,返回theano.function的pred、prob、cost。

④WeightDecay。有Dropout之后,必要性不大。

⑤利用cost,得到grad。利用tparams、grad,得到theano.function的update。

这里代码很啰嗦,如cost完全没有必要通过theano.function转化出来,保持Tensor状态是可以带入T.grad

而前面就没有这么写。代码风格和前面的章节截然不同。

至此,准备工作完毕,进入mini-batch执行阶段。

——————————————————————————————————————————————

①首先获取vaild和test的zip化minibatch。

每轮zip返回一个二元组(batch_idx,batch_content_list),idx实际并不用。

list是指当前batch中所有句子的idx。然后,对这些离散的idx,拼出实际的1D$x$。

经过imdb.py下的prepare_data,得到2D的$x$,作为Word Embedding的预备输入。

②进入max_epochs循环阶段:

包括early stopping优化,这部分与之前章节大致相同。

——————————————————————————————————————————————

至此,这份源码算是解析完了。

Theano:LSTM源码解析的更多相关文章

  1. [源码解析] 深度学习流水线并行Gpipe(1)---流水线基本实现

    [源码解析] 深度学习流水线并行Gpipe(1)---流水线基本实现 目录 [源码解析] 深度学习流水线并行Gpipe(1)---流水线基本实现 0x00 摘要 0x01 概述 1.1 什么是GPip ...

  2. [源码解析] 深度学习流水线并行 GPipe(3) ----重计算

    [源码解析] 深度学习流水线并行 GPipe(3) ----重计算 目录 [源码解析] 深度学习流水线并行 GPipe(3) ----重计算 0x00 摘要 0x01 概述 1.1 前文回顾 1.2 ...

  3. [源码解析] 深度学习流水线并行之PopeDream(1)--- Profile阶段

    [源码解析] 深度学习流水线并行之PopeDream(1)--- Profile阶段 目录 [源码解析] 深度学习流水线并行之PopeDream(1)--- Profile阶段 0x00 摘要 0x0 ...

  4. [源码解析] 深度学习流水线并行 PipeDream(2)--- 计算分区

    [源码解析] 深度学习流水线并行 PipeDream(2)--- 计算分区 目录 [源码解析] 深度学习流水线并行 PipeDream(2)--- 计算分区 0x00 摘要 0x01 前言 1.1 P ...

  5. [源码解析] 深度学习流水线并行 PipeDream(3)--- 转换模型

    [源码解析] 深度学习流水线并行 PipeDream(3)--- 转换模型 目录 [源码解析] 深度学习流水线并行 PipeDream(3)--- 转换模型 0x00 摘要 0x01 前言 1.1 改 ...

  6. [源码解析] 深度学习流水线并行 PipeDream(4)--- 运行时引擎

    [源码解析] 深度学习流水线并行 PipeDream(4)--- 运行时引擎 目录 [源码解析] 深度学习流水线并行 PipeDream(4)--- 运行时引擎 0x00 摘要 0x01 前言 1.1 ...

  7. [源码解析] PyTorch 分布式(14) --使用 Distributed Autograd 和 Distributed Optimizer

    [源码解析] PyTorch 分布式(14) --使用 Distributed Autograd 和 Distributed Optimizer 目录 [源码解析] PyTorch 分布式(14) - ...

  8. 【原】Android热更新开源项目Tinker源码解析系列之三:so热更新

    本系列将从以下三个方面对Tinker进行源码解析: Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Android热更新开源项目Tinker源码解析系列之二:资源文件热更新 A ...

  9. 【原】Android热更新开源项目Tinker源码解析系列之一:Dex热更新

    [原]Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Tinker是微信的第一个开源项目,主要用于安卓应用bug的热修复和功能的迭代. Tinker github地址:http ...

随机推荐

  1. Redis学习笔记7--Redis管道(pipeline)

    redis是一个cs模式的tcp server,使用和http类似的请求响应协议.一个client可以通过一个socket连接发起多个请求命令.每个请求命令发出后client通常会阻塞并等待redis ...

  2. [NHibernate]代码生成器的使用

    目录 写在前面 文档与系列文章 代码生成器的使用 总结 写在前面 前面的文章介绍了nhibernate的相关知识,都是自己手敲的代码,有时候显得特别的麻烦,比如你必须编写持久化类,映射文件等等,举得例 ...

  3. 一个很全的VTK实例网址

    https://cmake.org/Wiki/VTK/Examples/Cxx#Visualization

  4. Windows操作技巧 之二(持续更新)

     定时自动关机 shutdown -s -t 3600 shutdown [/i | /l | /s | /r | /g | /a | /p | /h | /e] [/f /m \\computer] ...

  5. jsp动作元素之forward指令

    forward指令用于将页面响应转发到另外的页面.既可以转发到静态的HTML页面,也可以转发到动态的JSP页面,或者转发到容器中的Servlet. forward指令格式如下: <jsp:for ...

  6. POJ2914 (未解决)无向图最小割|Stoer-Wagner算法|模板

    还不是很懂,贴两篇学习的博客: http://www.hankcs.com/program/algorithm/poj-2914-minimum-cut.html http://blog.sina.c ...

  7. 四种浏览器对 clientHeight、offsetHeight、scrollHeight、clientWidth、offsetWidth 和 scrollWidth 的解释差异

    网页可见区域宽:document.body.clientWidth 网页可见区域高:document.body.clientHeight 网页可见区域宽:document.body.offsetWid ...

  8. ASP.NET正则表达式(URL,Email)

    public static bool IsUrl(this string str)    {        if (str.IsNullOrEmpty())            return fal ...

  9. Palindrome Number

    Determine whether an integer is a palindrome. Do this without extra space. public class Solution { p ...

  10. ASP.NET MVC中的两个Action之间值的传递--TempData

    一. ASP.NET MVC中的TempData 在ASP.NET MVC框架的ControllerBase中存在一个叫做TempData的Property,它的类型为TempDataDictiona ...