热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

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

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

在图像领域,最流行的 building block 大多以卷积网络为主。上一篇我们介绍了转载|使用PaddleFluid和TensorFlow实现图像分类网络SE_ResNeXt。卷积网络本质上依然是一个前馈网络,在神经网络基本单元中循环神经网络是建模序列问题最有力的工具, 有着非常重要的价值。自然语言天生是一个序列,在自然语言处理领域(Nature Language Processing,NLP)中,许多经典模型都基于循环神经网络单元。可以说自然语言处理领域是 RNN 的天下。

这一篇以 NLP 领域的 RNN 语言模型(RNN Language Model,RNN LM)为实验任务,对比如何使用 PaddleFluid 和 TensorFlow 两个平台实现序列模型。 这一篇中我们会看到 PaddleFluid 和 TensorFlow 在处理序列输入时有着较大的差异:PaddleFluid 默认支持非填充的 RNN 单元,在如何组织 mini-batch 数据提供序列输入上也简化很多。

如何使用代码

本篇文章配套有完整可运行的代码, 请从随时从 github [1] 上获取最新代码。代码包括以下几个文件:

注意:在运行模型训练之前,请首先进入 data 文件夹,在终端运行 sh download.sh 下载训练数据。 

在终端运行以下命令便可以使用默认结构和默认参数运行 PaddleFluid 训练 RNN LM。

python rnnlm_fluid.py

在终端运行以下命令便可以使用默认结构和默认参数运行 TensorFlow 训练 RNN LM。

python rnnlm_tensorflow.py背景介绍

one-hot和词向量表示法 

计算机如何表示语言是处理 NLP 任务的首要问题。这里介绍将会使用到的 one-hot 和词向量表示法。 

one-hot 表示方法:一个编码单元表示一个个体,也就是一个词。于是,一个词被表示成一个长度为字典大小的实数向量,每个维度对应字典里的一个词,除了该词对应维度上的值是 1,其余维度都是 0。 

词向量表示法:与 one-hot 表示相对的是 distributed representation ,也就是常说的词向量:用一个更低维度的实向量表示词语,向量的每个维度在实数域 RR 取值。 

在自然语言处理任务中,一套好的词向量能够提供丰富的领域知识,可以通过预训练获取,或者与最终任务端到端学习而来。 

循环神经网络 

循环神经网络(Recurrent Neural Network)是一种对序列数据建模的重要单元,模拟了离散时间(这里我们只考虑离散时间)动态系统的状态演化。“循环” 两字刻画了模型的核心:上一时刻的输出作为下一个时刻的输入,始终留在系统中如下面的图 1 所示,这种循环反馈能够形成复杂的历史。自然语言是一个天生的序列输入,RNN 恰好有能力去刻画词汇与词汇之间的前后关联关系,因此,在自然语言处理任务中占有重要的地位。

▲ 图1. 最简单的RNN单元

RNN 形成“循环反馈” 的过程是一个函数不断复合的过程,可以等价为一个层数等于输入序列长度的前馈神经网络,如果输入序列有 100 个时间步,相当于一个 100 层的前馈网络,梯度消失和梯度爆炸的问题对 RNN 尤为严峻。

直觉上大于 1 的数连乘越乘越大,极端时会引起梯度爆炸;小于 1 的数连乘越乘越小,极端时会引起梯度消失。梯度消失也会令在循环神经网络中,后面时间步的信息总是会”压过”前面时间步。如果 t 时刻隐层状态依赖于 t 之前所有时刻,梯度需要通过所有的中间隐层逐时间步回传,这会形成如图 2 所示的一个很深的求导链。

▲ 图2. t时刻依赖t时刻之前所有时刻

在许多实际问题中时间步之间相互依赖的链条并没有那么长,t 时刻也许仅仅依赖于它之前有限的若干时刻。很自然会联想到:如果模型能够自适应地学习出一些如图 3 所示的信息传播捷径来缩短梯度的传播路径,是不是可以一定程度减梯度消失和梯度爆炸呢?答案是肯定的,这也就是 LSTM 和 GRU 这类带有 “门控”思想的神经网络单元。

▲ 图3. 自适应地形成一些信息传播的“捷径”

关于 LSTM 更详细的介绍请参考文献 [2],这里不再赘述,只需了解 LSTM/GUR 这些门控循环神经网络单元提出的动机即可。

RNN LM 

语言模型是 NLP 领域的基础任务之一。语言模型是计算一个序列的概率,判断一个序列是否属于一个语言的模型,描述了这样一个条件概率,其中是输入序列中的 T 个词语,用 one-hot 表示法表示。

言模型顾名思义是建模一种语言的模型,这一过程如图 4 所示:

▲ 图4. RNN语言模型

RNN LM的工作流程如下: 

1. 给定一段 one-hot 表示的输入序列 {x1,x2,...,xT},将它们嵌入到实向量空间,得到词向量表示 :{ω1,ω2,...,ωt}。 

2. 以词向量序列为输入,使用 RNN 模型(可以选择LSTM或者GRU),计算输入序列到 t 时刻的编码 ht。 

3. softmax 层以 ht 为输入,预测下一个最可能的词的概率。 

4. ,根据计算误差信号。

PTB数据集介绍

至此,介绍完 RNN LM 模型的原理和基本结构,下面准备开始分别使用 PaddleFluid 和 TensorFlow 来构建我们的 训练任务。这里首先介绍这一篇我们使用 Mikolov 与处理过的 PTB 数据,这是语言模型任务中使用最为广泛的公开数据之一。 PTB 数据集包含 10000 个不同的词语(包含句子结束符  ,以及表示 低频词的特殊符号  )。 

通过运行 data 目录下的 download.sh 下载数据,我们将使用其中的 ptb.train.txt 文件进行训练,文件中一行是一句话,文本中的低频词已经全部被替换为  预处理时我们会在 每一行的末尾附加上句子结束符  。

程序结构

这一节我们首先整体总结一下使用 PaddleFluid 平台和 TensorFlow 运行自己的神经网络模型都有哪些事情需要完成。

PaddleFluid 

1. 调用 PaddleFluid API 描述神经网络模型。PaddleFluid 中一个神经网络训练任务被称之为一段 Fluid Program 。 

2. 定义 Fluid Program 执行设备: place 。常见的有 fluid.CUDAPlace(0) 和fluid.CPUPlace() 

place = fluid.CUDAPlace(0) if conf.use_gpu else fluid.CPUPlace()

注:PaddleFluid 支持混合设备运行,一些运算(operator)没有特定设备实现,或者为了提高全局资源利用率,可以为他们指定不同的计算设备。 

3. 创建 PaddleFluid 执行器(Executor),需要为执行器指定运行设备。

exe = fluid.Executor(place)

让执行器执行 fluid.default_startup_program() ,初始化神经网络中的可学习参数,完成必要的初始化工作。 

5. 定义 DataFeeder,编写 data reader,只需要关注如何返回一条训练/测试数据。 

6. 进入训练的双层循环(外层在 epoch 上循环,内层在 mini-batch 上循环),直到训练结束。

TensorFlow 

1. 调用 TensorFlow API 描述神经网络模型。 TensorFlow 中一个神经网络模型是一个 Computation Graph。 

2. 创建TensorFlow Session用来执行计算图。

sess = tf.Session()

3. 调用 sess.run(tf.global_variables_initializer()) 初始化神经网络中的可学习参数。

4. 编写返回每个 mini-batch 数据的数据读取脚本。

5. 进入训练的双层循环(外层在 epoch 上循环,内层在 mini-batch 上循环),直到训练结束。

如果不显示地指定使用何种设备进行训练,TensorFlow 会对机器硬件进行检测(是否有 GPU), 选择能够尽可能利用机器硬件资源的方式运行。

从以上的总结中可以看到,PaddleFluid 程序和 TensorFlow 程序的整体结构非常相似,使用经验可以非常容易的迁移。

构建网络结构及运行训练

加载训练数据

PaddleFluid 

定义 输入data layers

PaddleFluid 模型通过 fluid.layers.data 来接收输入数据。图像分类网络以图片以及图片对应的类别标签作为网络的输入:

word = fluid.layers.data(name="current_word", shape=[1], dtype="int64", lod_level=1)
lbl = fluid.layers.data(name="next_word", shape=[1], dtype="int64", lod_level=1)

1. 定义 data layer 的核心是指定输入 Tensor 的形状( shape )和类型。 

2. RNN LM 使用 one-hot 作为输入,一个词用一个和字典大小相同的向量表示,每一个位置对应了字典中的 一个词语。one-hot 向量仅有一个维度为 1, 其余全部为 0。因此为了节约存储空间,通常都直接用一个整型数表示给出词语在字典中的 id,而不是真的创建一个和词典同样大小的向量 ,因此在上面定义的 data layer 中 word 和 lbl 的形状都是 1,类型是 int64 。 

3. 需要特别说明的是,实际上 word 和 lbl 是两个 [batch_size x 1] 的向量,这里的 batch size 是指一个 mini-batch 中序列中的总词数。对序列学习任务, mini-batch 中每个序列长度 总是在发生变化,因此实际的 batch_size 只有在运行时才可以确定。 batch size 总是一个输入 Tensor 的第 0 维,在 PaddleFluid 中指定 data layer 的 shape 时,不需要指定 batch size的大小,也不需要考虑占位。框架会自动补充占位符,并且在运行时 设置正确的维度信息。因此,上面的两个 data layer 的 shape 都只需要设置第二个维度,也就是 1。

LoD Tensor和Non-Padding的序列输入 

与前两篇文章中的任务相比,在上面的代码片段中定义 data layer 时,出现了一个新的lod_level 字段,并设置为 1。这里就要介绍在 Fluid 系统中表示序列输入的一个重要概念 LoDTensor。 

那么,什么是 LoD(Level-of-Detail) Tensor 呢? 

1. Tensor 是 nn-dimensional arry 的推广,LoDTensor 是在 Tensor 基础上附加了序列信息。 

2. Fluid 中输入、输出,网络中的可学习参数全部统一使用 LoDTensor(n-dimension array)表示,对非序列数据,LoD 信息为空。一个 mini-batch 输入数据是一个 LoDTensor。 

3. 在 Fluid 中,RNN 处理变长序列无需 padding,得益于 LoDTensor表示。 

4. 可以简单将 LoD 理解为:std::vector

下图是 LoDTensor 示意图(图片来自 Paddle 官方文档):

▲ 图5. LoD Tensor示意图

LoD 信息是附着在一个 Tensor 的第 0 维(也就是 batch size 对应的维度),来对一个 batch 中的数据进一步进行划分,表示了一个序列在整个 batch 中的起始位置。 

LoD 信息可以嵌套,形成嵌套序列。例如,NLP 领域中的段落是一种天然的嵌套序列,段落是句子的序列,句子是词语的序列。 

LoD 中的 level 就表示了序列信息的嵌套:

  • 图 (a) 的 LoD 信息 [0, 5, 8, 10, 14] :这个 batch 中共含有 4 条序列。

  • 图 (b) 的 LoD 信息 [[0, 5, 8, 10, 14] /*level=1*/, [0, 2, 3, 5, 7, 8, 10, 13, 14] /*level=2*/] :这个 batch 中含有嵌套的双层序列。

有了 LoDTensor 这样的数据表示方式,用户不需要对输入序列进行填充,框架会自动完成 RNN 的并行计算处理。

如何构造序列输入信息 

明白了 LoD Tensor 的概念之后,另一个重要的问题是应该如何构造序列输入。在 PaddleFluid 中,通过 DataFeeder 模块来为网络中的 data layer 提供数据,调用方式如下面的代码所示:

train_reader = paddle.batch(paddle.reader.shuffle(train_data, buf_size=51200),batch_size=conf.batch_size)place = fluid.CUDAPlace(0) if conf.use_gpu else fluid.CPUPlace()
feeder = fluid.DataFeeder(feed_list=[word, lbl], place=place)

观察以上代码,需要用户完成的仅有:编写一个实现读取一条数据的 python 函数: train_data。 train_data 的代码非常简单,我们再来看一下它的具体实现 [3]:

def train_data(data_dir="data"):data_path = os.path.join(data_dir, "ptb.train.txt")_, word_to_id = build_vocab(data_path)with open(data_path, "r") as ftrain:for line in ftrain:words = line.strip().split()word_ids = [word_to_id[w] for w in words]yield word_ids[0:-1], word_ids[1:]

在上面的代码中: 

1. train_data 是一个 python generator ,函数名字可以任意指定,无需固定。 

2. train_data 打开原始数据数据文件,读取一行(一行既是一条数据),返回一个 python list,这个 python list 既是序列中所有时间步。具体的数据组织方式如下表所示(其中,f 代表一个浮点数,i 代表一个整数):

3. paddle.batch() 接口用来构造 mini-batch 输入,会调用 train_data 将数据读入一个 pool 中,对 pool 中的数据进行 shuffle,然后依次返回每个 mini-batch 的数据。

TensorFlow 

TensorFlow 中使用占位符 placeholder 接收 训练数据,可以认为其概念等价于 PaddleFluid 中的 data layer。同样的,我们定义了如下两个 placeholder 用于接收当前词与下一个词语:

def placeholders(self):self._inputs = tf.placeholder(tf.int32,[None, self.max_sequence_length])self._targets = tf.placeholder(tf.int32, [None, self.vocab_size])

1. placeholder 只存储一个 mini-batch 的输入数据。与 PaddleFluid 中相同, _inputs 这里接收的是 one-hot 输入,也就是该词语在词典中的 index,one-hot 表示 会进一步通过此词向量层的作用转化为实值的词向量表示。

2. 需要注意的是,TensorFlow 模型中网络输入数据需要进行填充,保证一个 mini-batch 中序列长度 相等。也就是一个 mini-batch 中的数据长度都是 max_seq_length ,这一点与 PaddleFluid 非常不同。 

通常做法 是对不等长序列进行填充,在这一篇示例中我们使用一种简化的做法,每条训练样本都按照 max_sequence_length 来切割,保证一个 mini-batch 中的序列是等长的。 

于是, _input 的 shape=[batch_size, max_sequence_length] 。 max_sequence_length即为 RNN 可以展开长度。

构建网络结构 

PaddleFluid RNN LM 

这里主要关注最核心的 LSTM 单元如何定义:

def __rnn(self, input):for i in range(self.num_layers):hidden = fluid.layers.fc(size=self.hidden_dim * 4,bias_attr=fluid.ParamAttr(initializer=NormalInitializer(loc=0.0, scale=1.0)),input=hidden if i else input)lstm = fluid.layers.dynamic_lstm(input=hidden,size=self.hidden_dim * 4,candidate_activation="tanh",gate_activation="sigmoid",cell_activation="sigmoid",bias_attr=fluid.ParamAttr(initializer=NormalInitializer(loc=0.0, scale=1.0)),is_reverse=False)return lstm

PaddleFluid 中的所有 RNN 单元(RNN/LSTM/GRU)都支持非填充序列作为输入,框架会自动完成不等长序列的并行处理。当需要堆叠多个 LSTM 作为输入时,只需利用 Python 的 for 循环语句,让一个 LSTM 的输出成为下一个 LSTM 的输入即可。在上面的代码片段中有一点需要特别注意:PaddleFluid 中的 LSTM 单元是由 fluid.layers.fc+ fluid.layers.dynamic_lstm共同构成的。

▲ 图6. LSTM计算公式

图 6 是 LSTM 计算公式,图中用红色圈起来的计算是每一时刻输入矩阵流入三个门和 memory cell 的之前的映射。PaddleFluid 将这个四个矩阵运算合并为一个大矩阵一次性计算完毕,fluid.layers.dynamic_lstm 不包含这部分运算。因此: 

1. PaddleFluid 中的 LSTM 单元是由 fluid.layers.fc + fluid.layers.dynamic_lstm 。 

2. 假设 LSTM 单元的隐层大小是 128 维, fluid.layers.fc  fluid.layers.dynamic_lstm 的 size 都应该设置为 128 * 4,而不是 128。 

TensorFlow RNN LM 

这里主要关注最核心的 LSTM 单元如何定义:

def rnn(self):def lstm_cell():return tf.contrib.rnn.BasicLSTMCell(self.hidden_dim, state_is_tuple=True)cells = [lstm_cell() for _ in range(self.num_layers)]cell = tf.contrib.rnn.MultiRNNCell(cells, state_is_tuple=True)_inputs = self.input_embedding()_outputs, _ = tf.nn.dynamic_rnn(cell=cell, inputs=_inputs, dtype=tf.float32)last = _outputs[:, -1, :]logits = tf.layers.dense(inputs=last, units=self.vocab_size)prediction = tf.nn.softmax(logits)

 tf.nn.rnn_cell.BasicLSTMCell(n_hidden, state_is_tuple=True) : 是最基本的 LSTM 单元。n_hidden 表示 LSTM 单元隐层大小。 state_is_tuple=True 表示返回的状态用一个元祖表示。 

 tf.contrib.rnn.MultiRNNCell : 用来 wrap 一组序列调用的 RNN 单元的 wrapper。 

 tf.nn.dynamic_rnn : 通过指定 mini-batch 中序列的长度,可以跳过 padding 部分的计算,减少计算量。这一篇的例子中由于我们对输入数据进行了处理,将它们都按照max_sequence_length 切割。 

但是, dynamic_rnn 可以让不同 mini-batch 的 batch size 长度不同,但同一次迭代一个 batch 内部的所有数据长度仍然是固定的。

运行训练 

运行训练任务对两个平台都是常规流程,可以参考上文在程序结构一节介绍的流程,以及代码部分:PaddleFluid vs. TensorFlow,这里不再赘述。

总结

这一篇我们第一次接触 PaddleFluid 和 TensorFlow 平台的序列模型。了解 PaddleFluid 和 TensorFlow 在接受序列输入,序列处理策略上的不同。序列模型是神经网络模型中较为复杂的一类模型结构,可以衍生出非常复杂的模型结构。 

不论是 PaddleFluid 以及 TensorFlow 都实现了多种不同的序列建模单元,如何选择使用这些不同的序列建模单元有很大的学问。到目前为止平台使用的一些其它重要主题:例如多线程多卡,如何利用混合设备计算等我们还尚未涉及。接下来的篇章将会继续深入 PaddleFluid 和 TensorFlow 平台的序列模型处理机制,以及更多重要功能如何在两个平台之间实现。

参考文献

[1]. 本文配套代码

https://github.com/JohnRabbbit/TF2Fluid/tree/master/03_rnnlm

[2]. Understanding LSTM Networks

http://colah.github.io/posts/2015-08-Understanding-LSTMs/

[3]. train_data具体实现

https://github.com/JohnRabbbit/TF2Fluid/blob/master/03_rnnlm/load_data_fluid.py


推荐阅读
  • 本文提供了PyTorch框架中常用的预训练模型的下载链接及详细使用指南,涵盖ResNet、Inception、DenseNet、AlexNet、VGGNet等六大分类模型。每种模型的预训练参数均经过精心调优,适用于多种计算机视觉任务。文章不仅介绍了模型的下载方式,还详细说明了如何在实际项目中高效地加载和使用这些模型,为开发者提供全面的技术支持。 ... [详细]
  • 在Windows系统中安装TensorFlow GPU版的详细指南与常见问题解决
    在Windows系统中安装TensorFlow GPU版是许多深度学习初学者面临的挑战。本文详细介绍了安装过程中的每一个步骤,并针对常见的问题提供了有效的解决方案。通过本文的指导,读者可以顺利地完成安装并避免常见的陷阱。 ... [详细]
  • 本文详细介绍了在Windows操作系统上使用Python 3.8.5编译支持CUDA 11和cuDNN 8.0.2的TensorFlow 2.3的步骤。文章不仅提供了详细的编译指南,还分享了编译后的文件下载链接,方便用户快速获取所需资源。此外,文中还涵盖了常见的编译问题及其解决方案,确保用户能够顺利进行编译和安装。 ... [详细]
  • TensorFlow基础知识深化讲解
    批标准化批标准化(batchnormalization,BN)是为了克服神经网络层数加深导致难以训练而诞生的。深度神经网络随着深度加深,收 ... [详细]
  • 深入解析C++ Atomic编程中的内存顺序
    在多线程环境中,为了防止多个线程同时修改同一数据导致的竞争条件,通常会使用内核级同步对象,如事件、互斥锁和信号量等。然而,这些方法往往伴随着高昂的上下文切换成本。本文将探讨如何利用C++11中的原子操作和内存顺序来优化多线程编程,减少不必要的开销。 ... [详细]
  • 深入解析 RuntimeClass 及多容器运行时应用
    本文旨在探讨RuntimeClass的起源、功能及其在多容器运行时环境中的实际应用。通过详细的案例分析,帮助读者理解如何在Kubernetes集群中高效管理不同类型的容器运行时。 ... [详细]
  • 本文详细介绍了 Node.js 中 OS 模块的 arch 方法,包括其功能、语法、参数以及返回值,并提供了具体的使用示例。 ... [详细]
  • 本文详细介绍了如何在 Ubuntu 14.04 系统上搭建仅使用 CPU 的 Caffe 深度学习框架,包括环境准备、依赖安装及编译过程。 ... [详细]
  • Beetl是一款先进的Java模板引擎,以其丰富的功能、直观的语法、卓越的性能和易于维护的特点著称。它不仅适用于高响应需求的大型网站,也适合功能复杂的CMS管理系统,提供了一种全新的模板开发体验。 ... [详细]
  • 深入解析经典卷积神经网络及其实现代码
    深入解析经典卷积神经网络及其实现代码 ... [详细]
  • 利用PaddleSharp模块在C#中实现图像文字识别功能测试
    PaddleSharp 是 PaddleInferenceCAPI 的 C# 封装库,适用于 Windows (x64)、NVIDIA GPU 和 Linux (Ubuntu 20.04) 等平台。本文详细介绍了如何使用 PaddleSharp 在 C# 环境中实现图像文字识别功能,并进行了全面的功能测试,验证了其在多种硬件配置下的稳定性和准确性。 ... [详细]
  • 在Windows命令行中,通过Conda工具可以高效地管理和操作虚拟环境。具体步骤包括:1. 列出现有虚拟环境:`conda env list`;2. 创建新虚拟环境:`conda create --name 环境名`;3. 删除虚拟环境:`conda env remove --name 环境名`。这些命令不仅简化了环境管理流程,还提高了开发效率。此外,Conda还支持环境文件导出和导入,方便在不同机器间迁移配置。 ... [详细]
  • Docker安全策略与管理
    本文探讨了Docker的安全挑战、核心安全特性及其管理策略,旨在帮助读者深入理解Docker安全机制,并提供实用的安全管理建议。 ... [详细]
  • 如何在Linux系统中部署TensorFlow的详细指南
    本文详细介绍了在Linux系统中部署TensorFlow的过程。作者基于北京大学曹建教授的MOOC课程进行学习,但由于课程内容较旧,环境配置方面遇到了不少挑战。经过多次尝试,最终成功解决了这些问题,并总结了一套详细的安装指南,帮助初学者快速上手TensorFlow。 ... [详细]
  • 如何在Linux中配置环境变量:命令与脚本详解
    请关注本头条号,每天坚持更新原创干货技术文章。当您在Linux命令行上键入命令时,您实际是在告诉shell运行指定名称的可执行文件。在Linux中,这些可执行程序通常位于系统上的几 ... [详细]
author-avatar
huanghxn
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有