LSTM实现详解

前言

在很长一段时间里,我一直忙于寻找一个实现LSTM网络的好教程。它们似乎很复杂,而且在此之前我从来没有使用它们做过任何东西。在互联网上快速搜索并没有什么帮助,因为我找到的都是一些幻灯片。

幸运地是,我参加了Kaggle EEG 竞赛,而且我认为使用LSTM很有意思,最后还理解了它的工作原理。这篇文章基于我的解决方案,使用的是Andrej Karpathy的char-rnn代码,这也是我强烈推荐给大家的。

RNN误区

我感觉有一件很重要的事情一直未被大家充分强调过(而且这也是我为什么不能使用RNN做我想做的事情的主要原因)。RNN和前馈神经网络并没有很大不同。最容易实现RNN的一种方法就是像前馈神经网络使用部分输入到隐含层,以及一些来自隐含层的输出。在网络中没有任何神奇的内部状态。它作为输入的一部分。

RNN的整体结构与前馈网络的结构非常相似

LSTM回顾

本节内容将仅覆盖LSTM的正式定义。有很多其它的好博文,都详细地描述了你该如何设想并思考这些等式。

LSTM有多种变换形式,但我们只讲解一个简单的。一个Cell由三个Gate(input、forget、output)和一个cell单元组成。Gate使用一个sigmoid激活函数,而input和cell state通常会使用tanh来转换。LSTM 的cell可以使用下列的等式来定义:

Gates:

输入变换:

状态更新:

使用图片描述类似下图:

由于门控机制,Cell可以在工作时保持一段时间的信息,并在训练时保持内部梯度不受不利变化的干扰。Vanilla LSTM 没有forget gate,并在更新期间添加无变化的cell状态(它可以看作是一个恒定的权值为1的递归链接),通常被称为一个Constant Error Carousel(CEC)。这样命名是因为它解决了在RNN训练时一个严重的梯度消失和梯度爆炸问题,从而使得学习长期关系成为可能。

建立你自己的LSTM层

这篇教程的代码使用的是Torch7。如果你不了解它也不必担心。我会详细解释的,所以你可以使用你喜欢的框架来实现相同的算法。

该网络将作为nngraph.gModule模块来实现,基本上表示我们定义的一个由标准nn模块组成的神经网络计算图。我们需要以下几层:

  • nn.Identity() - 传递输入(用来存放输入数据)
  • nn.Dropout(p) - 标准的dropout模块(以1-p的概率丢弃一部分隐层单元)
  • nn.Linear(in, out) - 从in维到out维的一个仿射变换
  • nn.Narrow(dim, start, len) - 在第dim方向上选择一个子向量,下标从start开始,长度为len
  • nn.Sigmoid() - 应用sigmoid智能元素
  • nn.Tanh() - 应用tanh智能元素
  • nn.CMulTable() - 输出张量(tensor)的乘积
  • nn.CAddTable() - 输出张量的总和

输入

首先,让我们来定义输入形式。在lua中类似数组的对象称为表,这个网络将接受一个类似下面的这个张量表。

local inputs = {} table.insert(inputs, nn.Identity()()) -- network input table.insert(inputs, nn.Identity()()) -- c at time t-1 table.insert(inputs, nn.Identity()()) -- h at time t-1 local input = inputs[1] local prev_c = inputs[2] local prev_h = inputs[3]

Identity模块只将我们提供给网络的输入复制到图中。

计算gate值

为了加快我们的实现,我们会同时运用整个LSTM层转换。

locali2h=nn.Linear(input_size,4*rnn_size)(input)-- input to hiddenlocalh2h=nn.Linear(rnn_size,4*rnn_size)(prev_h)-- hidden to hiddenlocalpreactivations=nn.CAddTable()({i2h,h2h})-- i2h + h2h

如果你不熟悉nngraph,你也许会觉得奇怪,在上一小节我们建立的inputs属于nn.Module,这里怎么已经用图节点调用一次了。事实上发生的是,第二次调用把nn.Module转换为nngraph.gModule,并且参数指定了该节点在图中的父节点。

preactivations输出一个向量,该向量由输入和前隐藏状态的一个线性变换生成。这些都是原始值,用来计算gate 激活函数和cell输出。这个向量被分为四个部分,每一部分的大小为rnn_size。第一部分将用于in gates,第二部分用于forget gate,第三部分用于out gate,而最后一个作为cell input(因此各个gate的下标和cell数量i的输入为{i, rnn_size+i, 2⋅rnn_size+i, 3⋅rnn_size+i})。

接下来,我们必须运用非线性,但是尽管所有的gate使用的都是sigmoid,我们仍使用tanh对输入进行预激活处理。正因为这个,我们将会使用两个nn.Narrow模块,这会选择预激活向量中合适的部分。

-- gates localpre_sigmoid_chunk=nn.Narrow(2,1,3*rnn_size)(preactivations) localall_gates=nn.Sigmoid()(pre_sigmoid_chunk) -- input localin_chunk=nn.Narrow(2,3*rnn_size+1,rnn_size)(preactivations) localin_transform=nn.Tanh()(in_chunk)

在非线性操作之后,我们需要增加更多的nn.Narrow,然后我们就完成了gates。

localin_gate=nn.Narrow(2,1,rnn_size)(all_gates) localforget_gate=nn.Narrow(2,rnn_size+1,rnn_size)(all_gates) localout_gate=nn.Narrow(2,2*rnn_size+1,rnn_size)(all_gates)

Cell和hidden state

有了计算好的gate值,接下来我们可以计算当前的Cell状态了。所有的这些需要的是两个nn.CMulTable模块(一个用于,一个用于),并且nn.CAddTable用于把它们加到当前的cell状态上。

-- previous cell state contribution localc_forget=nn.CMulTable()({forget_gate,prev_c}) -- input contribution localc_input=nn.CMulTable()({in_gate,in_transform}) -- next cell state localnext_c=nn.CAddTable()({ c_forget, c_input })

最后,是时候来实现hidden 状态计算了。这是最简单的部分,因为它仅仅是把tanh应用到当前的cell 状态(nn.Tanh)并乘上output gate(nn.CMulTable)。

localc_transform=nn.Tanh()(next_c) localnext_h=nn.CMulTable()({out_gate,c_transform})

定义模块

现在,如果你想要导出整张图作为一个独立的模块,你可以使用下列代码把它封装起来:

-- module outputs outputs={} table.insert(outputs,next_c) table.insert(outputs,next_h) -- packs the graph into a convenient module with standard API (:forward(), :backward()) returnnn.gModule(inputs,outputs)

实例

LSTM layer实现可以在这里获得。你也可以这样使用它:

th> LSTM= require 'LSTM.lua' [0.0224s] th> layer= LSTM.create(3, 2) [0.0019s] th> layer:forward({torch.randn(1,3), torch.randn(1,2), torch.randn(1,2)}) { 1 : DoubleTensor - size: 1x2 2 : DoubleTensor - size: 1x2} } [0.0005s]

为了制作一个多层LSTM网络,你可以在for循环中请求后续层,用上一层的next_h作为下一层的输入。你可以查看这个例子。

训练

最后,如果你感兴趣,请留个评论吧,我会试着扩展这篇文章!

结束语

确实是这样!当你理解怎样处理隐藏层的时候,实现任何RNN都会很容易。仅仅把一个常规MLP层放到顶部,然后连接多个层并且把它和最后一层的隐藏层相连,你就完成了。

如果你有兴趣的话,下面还有几篇关于RNN的好论文:

  • Visualizing and Understanding Recurrent Networks
  • An Empirical Exploration of Recurrent Network Architectures
  • Recurrent Neural Network Regularization
  • Sequence to Sequence Learning with Neural Networks

原文链接:LSTM implementation explained(编译/刘帝伟 审校/赵屹华、朱正贵、李子健 责编/周建丁)

译者简介: 刘帝伟,中南大学软件学院在读研究生,关注机器学习、数据挖掘及生物信息领域。


原文发布于微信公众号 - 人工智能头条(AI_Thinker)

原文发表时间:2015-09-14

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏机器之心

学界 | ImageNet 2017目标定位冠军论文:双路径网络

选自arXiv 作者:Yunpeng Chen等 机器之心编译 参与:蒋思源、Smith 最后一届 ImageNet 挑战赛刚刚落下帷幕,而新加坡国立大学参与的...

2797
来自专栏IT派

教程 | 用TensorFlow Estimator实现文本分类

本文选自介绍 TensorFlow 的 Datasets 和 Estimators 模块系列博文的第四部分。读者无需阅读所有之前的内容,如果想重温某些概念,可以...

1473
来自专栏DHUtoBUAA

Ray-AABB交叉检测算法

  最近在解决三维问题时,需要判断线段是否与立方体交叉,这个问题可以引申为:射线是否穿过立方体AABB。   在3D游戏开发中碰撞检测普遍采用的算法是轴对齐矩...

5267
来自专栏PaddlePaddle

转载|使用PaddleFluid和TensorFlow训练RNN语言模型

在图像领域,最流行的 building block 大多以卷积网络为主。上一篇我们介绍了转载|使用PaddleFluid和TensorFlow实现图像分类网络S...

1403
来自专栏机器之心

教程 | 用TensorFlow Estimator实现文本分类

6484
来自专栏PPV课数据科学社区

百度校园招聘数据挖掘工程师面试题集锦(2013)

一、简答题(30分) 1、简述数据库操作的步骤(10分) 步骤:建立数据库连接、打开数据库连接、建立数据库命令、运行数据库命令、保存数据库命令、关闭数据库连接。...

3815
来自专栏大数据文摘

有这5小段代码在手,轻松实现数据可视化(Python+Matplotlib)

2616
来自专栏大数据挖掘DT机器学习

数字识别,从KNN,LR,SVM,RF到深度学习

@蜡笔小轩V 原文:http://blog.csdn.net/Dinosoft/article/details/50734539 之前看了很多入门的资料,如果...

5005
来自专栏机器学习算法与Python学习

长文 | LSTM和循环神经网络基础教程(PDF下载)

目录: 前言 前馈网络回顾 循环网络 时间反向传播BPTT 梯度消失与梯度爆炸 长短期记忆单元(LSTM) 多时间尺度和远距离依赖 门控循环单元GRU LSTM...

50410
来自专栏大数据文摘

利用 Scikit Learn的Python数据预处理实战指南

4486

扫码关注云+社区