你好,游客 登录
背景:
阅读新闻

【TensorFlow 谷歌神经机器翻译】从零开始打造属于你的翻译系统

[日期:2017-07-15] 来源:新智元  作者: [字体: ]

【新智元导读】谷歌今天公布了一个用 TensorFlow 构建神经机器翻译(NMT)系统的教程,全面解释 seq2seq 模型,并演示如何从零开始构建 NMT 翻译模型。这个教程从 NMT 的背景知识讲起,详细讲解如何构建并训练一个 NMT 模型,并提供代码,绝对有用。

机器翻译——自动在两种语言之间进行翻译的任务——是机器学习中最活跃的研究领域之一。在多种机器翻译方法中,序列到序列(“seq2seq”)模型最近取得了巨大的成功,并已经成为大多数商业翻译系统的事实上的标准,例如谷歌翻译。这是由于 seq2seq 模型能够利用深度神经网络捕捉句子意义。但是,虽然 seq2seq 模型(例如 OpenNMT 或 tf-seq2seq)有大量的资料,但是缺少可以同时教知识和构建高质量翻译系统的技能的教程。

谷歌今天公布了一个用 TensorFlow 构建神经机器翻译(NMT)系统的教程,全面解释 seq2seq 模型,并演示如何从零开始构建 NMT 翻译模型。这个教程从 NMT 的背景知识讲起,并提供构建一个 NMT 系统的代码细节。接着,教程讲解注意力机制(attention mechanism),这是让 NMT 能够处理长句子的关键。最后,教程提供如何复制谷歌的 NMT 系统(GNMT)中的关键功能,在多个 GPU 上进行训练的详细信息。

这一教程还包括详细的基准测试结果,使用者可以自行复制。谷歌的模型提供了强大的开源基准,性能与 GNMT 的结果相当,在流行的 WMT'14 英语 - 德语翻译任务上实现了 BLEU 得分 24.4 的性能。

教程还包括其他基准测试结果(英语 - 越南语,德语 - 英语)。

此外,这个教程还提供了完全动态的 seq2seq API(与 TensorFlow 1.2 一起发布),旨在使构建 seq2seq 模型更加简洁:

  • 使用tf.contrib.data中新的输入管道轻松读取和预处理动态大小的输入序列。

  • 使用padded batching和sequence length bucketing来提高训练和推理速度。

  • 使用流行的架构和训练schedule训练seq2seq模型,包括几种类型的attention和scheduled sampling。

  • 使用in-graph beam search在seq2seq模型中执行推理。

  • 为多GPU设置优化seq2seq模型。

希望这一教程有助于研究界创造更多新的NMT模型并进行实验。完整教程的GitHub地址:https://github.com/tensorflow/nmt,本文提供主要内容的翻译介绍。

神经机器翻译(seq2seq)教程

作者:Thang Luong, Eugene Brevdo, Rui Zhao

目录

  • 导言

  • 基础

    神经机器翻译背景知识

安装教程

训练——如何构建你的第一个NMT系统

嵌入

编码器

解码器

损失

梯度计算和优化

实践——让我们开始训练一个NMT模型

推理——如何生成翻译

  • 中级教程

注意力机制的背景知识

Attention Wrapper API

实践——构建一个以注意力为基础的NMT模型

  • 提示与技巧

构建训练,评估和推理图

数据输入管道

更好的NMT模型的其他细节

双向RNN

束搜索(Beam Search)

超参数

多GPU训练

  • 基准

IWSLT英语 - 越南语

WMT德语 - 英语

WMT英语 - 德语(完全比较)

  • 其他资源

  • 致谢

  • 参考文献

导言

序列到序列(seq2seq)模型(Sutskever et al.,2014,Cho et al.,2014)在机器翻译、语音识别、文本概况等各种任务中取得了巨大的成功。本教程提供了对 seq2seq 模型的全面解释,并演示了如何从头开始构建一个具有竞争力的 seq2seq 模型。我们专注于神经机器翻译(NMT)任务,这是第一个大获成功的 seq2seq 模型的测试平台。教程中包含的代码是轻便,高质量,生产就绪,并结合了最新的研究观点的。我们通过以下方式实现这一目标:

  • 使用最新的解码器/注意力包装 API,TensorFlow 1.2 数据迭代器

  • 结合我们在构建循环模型和 seq2seq 模型方面的专长

  • 提供构建最好的 NMT 模型以及复制谷歌的 NMT(GNMT)系统的提示和技巧。

我们认为,最重要的是提供可以让人轻松复制的基准。因此,我们提供了完整的实验结果,并在以下公开数据集对模型进行了预训练:

  • 小规模:IWSLT Evaluation Campaign 提供的 TED 演讲(133K句子对)的英语 - 越南语平行语料库。

  • 大规模:WMT Evaluation Campaign 提供的德语 - 英语平行语料库(4.5M句子对)。

我们首先提供构建 NMT 的 seq2seq 模型的一些基本知识,说明如何构建和训练一个 NMT 模型。第二部分将详细介绍构建一个有竞争力的 NMT 模式的注意力机制。最后,我们将提供一些提示和技巧,以构建最佳性能的 NMT 模型(包括训练速度和翻译质量),例如 TensorFlow 的最佳实践(batching, bucketing),bidirectional RNN 和 beam search。

基础

神经机器翻译的背景知识

回到过去,传统的基于短语的翻译系统是通过将源语言的句子分解成多个部分,然后逐个短语地进行翻译。这导致机器翻译的结果与人类翻译的结果很不同。人类是通读整个源句子,理解它的含义,然后进行翻译。神经机器翻译(NMT)模拟了这样的过程!

图1:编码器-解码器架构,NMT的一个通用方法的示例。编码器将源句子转换成一个“meaning”向量,这个向量通过解码器传递,产生翻译结果。

具体来说,NMT 系统首先使用编码器读取源语句来构建“meaning”向量,即表示句子意义的一个数字序列; 然后,解码器处理句子向量以输出翻译结果,如图1所示。这一架构同城被称为编码器-解码器架构(encoder-decoder architecture)。以这种方式,NMT 解决了传统的基于短语的方法中翻译局部性的问题:它可以捕获语言的远距离依赖性,例如性一致, 句法结构,等等,并产生更流畅的翻译,如谷歌的神经机器翻译系统所演示的。

NMT 模型的具体结构有所不同。序列数据的一般选择是大多数NMT模型使用的循环神经网络(RNN)。通常,RNN用于编码器和解码器。但是,RNN模型在以下方面不同:(a)方向性——单向或双向; (b)深度——单层或多层; 和(c)类型——通常是普通RNN,长短期记忆(LSTM)或循环门单位(Gated Recurrent Unit, GRU)。有兴趣的读者可以在这篇博客文章了解有关RNN和LSTM的更多信息:http://colah.github.io/posts/2015-08-Understanding-LSTMs/

在本教程中,我们将一个单向的深度多层RNN作为示例,并将LSTM作为一个循环单元。图2是这样一个模型的例子。在这个示例中,我们构建一个模型来将源句子“I am a student”翻译成一个目标句子“Je suisétudiant”。在高层水平上,NMT模型由两个循环神经网络组成:编码器RNN简单地处理输入的源词汇,不进行任何预测; 另一方面,解码器RNN在预测下一个单词的同时处理目标句子。

更多信息请参阅Luong(2016)的教程(https://github.com/lmthang/thesis),本教程正是基于这个教程的扩充。

图2:神经机器翻译——将源句子“I am a student”翻译成目标句子“Je suisétudiant”,这是一个深度循环架构的例子。这里,“<s>”表示解码处理的开始,“</ s>”提示解码器停止。

安装教程

要安装本教程,你需要在系统上安装TensorFlow。本教程要求最新版本的TensorFlow(version 1.2.1)。要安装TensorFlow,请按照官方的安装说明进行操作(https://www.tensorflow.org/install)。

安装好TensorFlow之后,您可以通过运行下面的代码下载本教程的源代码:

git clone https://github.com/tensorflow/nmt/

训练——如何构建你的第一个NMT系统

我们先用一些具体的代码片段看看构建一个NMT模型的核心,详细解释一下图2。我们后面会提供数据准备和完整代码。这部分涉及model.py文件。

在底层,编码器RNN和解码器RNN作为输入接收以下内容:首先是源句子(source sentence),然后是一个边界标记“<s>”,提示从编码模式到解码模式的切换,最后是目标句子(target sentence)。对于训练过程,我们将为系统提供以下张量,它们是time-major的格式,包含单词索引:

encoder_inputs [max_encoder_time, batch_size]: 源输入单词

decoder_inputs [max_decoder_time, batch_size]: 目标输入单词

decoder_outputs [max_decoder_time, batch_size]: 目标输出单词,即 decoder_inputs左移动一个时间步长,同时在右边附一个句末标记。

为了提高效率,我们一次训练多个句子(batch_size)。测试过程略有不同,我们会在后面讨论。

嵌入

给定词类属性,模型必须先查找源和目标嵌入以检索相应的词汇表示。为了使嵌入层工作,首先要为每种语言选择一个词汇表。通常,选择词汇大小V,并且只有最常用的V词汇被视为唯一的。其他所有词汇都转换成一个“unknown”字符(token),并且都得到相同的嵌入。通常在训练期间学习嵌入的权重,每种语言一套。

同样,我们可以构建 embedding_decoder 和 decode_emb_inp。请注意,可以选择使用预训练的单词表示(例如 word2vec 或 Glove vector)来初始化嵌入权重。一般来说,给定大量训练数据,我们可以从头开始学习这些嵌入。

编码器

一旦被检索到,那么嵌入词汇就作为输入被喂入主网络中,该主网络由两个多层RNN组成——用于源语言的编码器和用于目标语言的解码器。这两个RNN原则上可以共享相同的权重; 但是,在实践中,我们经常使用两种不同的RNN参数(这些模型在拟合大型训练数据集时做得更好)。编码器RNN使用零向量作为起始状态,构建如下:

请注意,句子具有不同的长度以避免计算上的浪费,我们通过source_seqence_length 告诉 dynamic_rnn 确切的源句子长度。由于我们的输入是 time major 的,因此设置 time_major = True。 在这里,我们只构建一个单层LSTM,encoder_cell。在后面的部分将介绍如何构建多层 LSTM,添加 dropout,以及使用 attention。

解码器

解码器也需要访问源信息,一个简单的方法就是用编码器的最后一个隐藏状态(encode_state)来初始化解码器。 在图2中,我们将源代码“student”的隐藏状态传递到解码器端。

这里,代码的核心部分是 BasicDecoder ,接收 decode_cell(类似于encoder_cell)的 decoder,一个 helper,以及作为输出的前一个 encoder_state。通过分开 decoders 和 helpers,我们可以重复利用不同的代码库,例如,可以用 reedyEmbeddingHelper 替代 TrainingHelper 进行 greedy decoding。更多信息请查看 helper.py。

最后,我们还没提到 projection_layer,它是一个密集矩阵(dense matrix),用于将顶部的隐藏状态转换为维度V的对数向量(logit vectors)。这个过程在图2的顶部说明了。

损失

有了上面的 logits,现在可以计算训练损失:

这里,target_weights 是与 decode_outputs 大小相同的0-1矩阵,它将目标序列长度之外的位置填充为值为0。

重要注意事项:我们用 batch_size 来分割损失,所以我们的超参数对 batch_size是“不变的”。有的人将损失以 batch_size * num_time_steps 进行分割,这可以减少短句子的翻译错误。更巧妙的是,我们的超参数(应用于前面的方法)不能用于后面的方法。例如,如果两种方法都使用学习律为1.0的SGD,那么后一种方法有效利用更小的学习率,即1 / num_time_steps。

梯度计算和优化

我们现在已经定义NMT模型的前向传播。计算反向传播只需要几行代码:

训练RNN的重要步骤之一是梯度剪切(gradient clipping)。这里,我们按照global norm警醒剪切。最大值max_gradient_norm通常设置为5或1。最后一步是选择优化器。Adam优化器是常见的选择。也需要选择学习率(learning rate)。learning_rate的值通常在0.0001到0.001之间; 也可以设置为随着训练的进行,学习率降低。

在我们自己的实验中,我们使用标准SGD(tf.train.GradientDescentOptimizer)以及可降低的学习率设置,从而产生更好的性能。具体见benchmark部分。

实践——训练一个NMT模型

让我们开始训练第一个NMT模型,将越南语翻译成英语!代码的入口点是 nmt.py

我们将使用一个小型的TED 演讲(133K训练样本)的平行语料库来进行这个实践。我们在这里使用的所有数据可以在下面网址找到:https://nlp.stanford.edu/projects/nmt/。我们将使用tst2012作为dev数据集,tst2013作为测试数据集。

运行以下命令下载训练NMT模型的数据:nmt/s/download_iwslt15.sh /tmp/nmt_data

运行以下命令开始训练:

上面的命令训练一个具有128-dim的隐藏单元和12个epoch的嵌入的2层LSTM seq2seq模型。我们使用的dropout值为0.2(保持或然率为0.8)。如果不出现error,随着训练的困惑度值(perplexity value)降低,应该可以看到类似下面的logs:

详细信息请参阅train.py。

我们可以在训练期间启动Tensorboard来查看模型的概要:

tensorboard --port 22222 --logdir /tmp/nmt_model/

以上是从英语翻译成越南语的训练,通过下面的代码可以简单地变成从越南语翻译成英语:

--src=en --tgt=vi

推理——如何生成翻译

在训练NMT模型时(以及已经训练完时),你可以得到之前模型没见过的源句子的翻译。这个过程称为推理(inference)。训练和推理(测试)之间有明确的区别:在推理时,我们只能访问源句子,即encoder_inputs。执行解码有很多种方法。解码方法包括greedy解码,采样解码和束搜索(beam-search)解码。这里,我们将讨论贪心解码策略。

它的想法是很简单的,如图3:

  • 我们仍然以与训练期间相同的方式对源句子进行编码,以获得encoder_state,并使用该encoder_state来初始化解码器。

  • 一旦解码器接收到开始符号“<s”(参见代码中的tgt_sos_id),就开始进行解码(转换)处理。

  • 对于解码器侧的每个时间步长,我们将RNN的输出视为一组logits。我们选择最有可能的单词,即与最大logit值相关联的id作为输出的单词(这就是“greedy”行为)。例如在图3中,在第一个解码步骤中,单词“moi”具有最高的翻译概率。然后,我们将这个词作为输入提供给下一个时间步长。

  • 这个过程继续进行,直到生成句尾标记“</ s>”作为输出符号(在我们的代码中是tgt_eos_id)。

图3:Greedy解码——训练好的NMT模型使用greedy搜索生成源句子“Je suisétudiant”的翻译。

令推理与训练不同的是步骤3。推理使用模型预测的单词,而不是总是正确的目标单词作为输入。以下是实现greedy解码的代码。它与解码器的训练代码非常相似。

在这里,我们使用GreedyEmbeddingHelper而不是TrainingHelper。由于我们预先不知道目标序列长度,所以使用maximum_iterations来限制翻译长度。 一个启发是解码最多两倍的源句子长度。

训练好一个模型后,现在可以创建一个推理文件并翻译一些句子:

注意,上述命令也可以在模型正在训练时运行,只要存在一个训练的检查点。 详细请参阅inference.py。

进阶版:注意力机制

说完了最基本的 seq2seq 模型后,下面是进阶版!

注意力机制:背景

为了建立最先进的神经机器翻译系统,我们将需要更多的“特殊材料”:注意力机制,这是 Bahdanau 等人于 2015 年首次引入,然后由 Luong 等人在同年完善的。注意力机制的关键在于通过在翻译过程中,对相关来源内容进行“注意”,建立目标与来源之间的直接连接。注意力机制的一个很好的副产品,是源和目标句子之间的对齐矩阵(如图 4 所示)。

图4:注意力机制可视化:源和目标句子之间的比对的例子。图像来自论文 Bahdanau et al.,2015。

在简单的 seq2seq 模型中,开始解码时,我们将最后的源状态从编码器传递到解码器。这对比较短和中等长度的句子效果很好;然而,对于长句子,单个固定大小的隐藏状态就成了信息瓶颈。注意力机制并不是丢掉在源 RNN 中计算的所有隐藏状态,而是让解码器将它们视为源信息的动态存储器。通过这样做,注意力机制改善了较长句子的翻译质量。如今,注意力机制成为神经机器翻译的首选,而且也成功应用于许多其他任务(包括图说生成,语音识别和文本摘要)。

我们现在介绍注意力机制的一个实例,这个实例是 Luong 等人在 2015 年论文中提出的,已被用于 OpenNMT 开放源码工具包等多个最先进的系统,TF seq2seq API 教程中也使用了这个例子。

图5:注意力机制:Luong 等人 2015 年所述的基于注意力的 NMT 系统的例子。这里详细介绍了注意力计算的第一步。为了清楚起见,没有将图 2 中的嵌入和投射层绘制出来。

如图 5 所示,注意力计算在每个解码器时间步长都有发生,包括以下阶段:

  1. 比较当前目标隐藏状态与所有源状态,获得注意力权重“attention weight”(可以如图 4 所示);

  2. 基于注意力权重,计算上下文矢量(context vector),作为源状态的加权平均值;

  3. 将上下文矢量与当前目标隐藏状态相结合,产生最终的注意力向量“attention vector”;

  4. 注意力向量作为输入,被传递到下一个时间步。

注意力机制中最关键的是什么?

根据 score 函数和 loss 函数的不同,存在很多不同的注意力变体。但在实践中,我们发现只有特定的一些选择很重要。首先是注意力的基本形式,也即目标和源之间的直接关系。 其次是将注意力向下馈送到下一个时间步长,这是告知网络过去的注意力做了什么决定(Luong 等人,2015)。最后,score 函数的选择往往会导致性能表现不同。

AttentionWrapper API

在部署 AttentionWrapper 时,我们借鉴了 Weston 等人 2015 年在 memory network 方面的一些术语。与可读写的 memory 不同,本教程中介绍的注意力机制是只读存储器。具体来说,源的一组隐藏状态被作为“记忆”(memory)。在每个时间步长中,使用当前目标隐藏状态作为“query”来决定要读取 memory 的哪个部分。通常,query 需要与对应于各个内存插槽的 key 进行比较。在我们的介绍中,恰好将源隐藏状态作为“key”。你可以受到记忆网络术语的启发,得出其他形式的注意力!

由于有了 attention wrapper,用 attention 扩展普通 seq2seq 代码就十分简单了。这部分参考文件 attention_model.py

首先,我们需要定义注意机制,例如(Luong等人,2015):

在以前的 Encoder 部分中,encoder_outputs 是顶层所有源隐藏状态的集合,其形状为 [max_time,batch_size,num_units](因为我们将 dynamic_rnn 与 time_major 设置为 True)。对于注意力机制,我们需要确保传递的“记忆”是批处理的,所以需要转置 attention_states。 将 source_sequence_length 传递给注意力机制,以确保注意力权重正确归一化(仅在 non-padding 位置上发生)。

定义了注意力机制后,使用 AttentionWrapper 解码单元格:

代码的其余部分与 Decoder 那节是一样的!

实践:构建基于注意力的 NMT 模型

为了实现注意力,我们需要使用 luong,scaled_luong,bahdanau 或 normed_bahdanau 中的一个,作为训练期间的注意力 flag 的值。这个 flag 指定了我们将要使用的注意力机制。 我们还需要为注意力模型创建一个新的目录,这样才不会重复使用以前训练过的基本 NMT 模型。

运行以下指令开始训练:

在训练完成后,使用同样的推理指令 model_dir 做推理:

玩转 NMT:窍门和技巧

构建训练图、评估图和推理图

在 TensorFlow 中构建机器学习模型时,最好建立 3 个独立的图:

  • 首先是训练图,其中:

  1. 批次、bucket 和可能的子样本从一组文件/外部输入输入;

  2. 包括前向和后向 op;

  3. 构建优化器,并添加训练 op。

  • 其次是评估图,其中:

  1. 批次和 bucket 从一组文件/外部输入数据;

  2. 包括 1 个训练前向 op 和不用于训练的其他评估 op

  • 最后是推理图,其中:

  1. 可能不批量输入数据;

  2. 不会对输入数据进行子采样;

  3. 从占位符读取输入数据

  4. 包括模型前向 op 的一个子集,也可能含有用于存储 session.run 调用之间状态的其他特殊输入/输出。

构建单独的图有几个好处:

  • 推理图通常与其他两个不同,因此需要分开构建;

  • 这样评估图也更简单,因为没有了额外的反向 op;

  • 可以为每个图分别实现数据馈送;

  • 各种重用都更加简单。例如,在评估图中,不需要用 reuse = True 重新打开可变范围,因为训练模型已经创建了这些变量。不需要到处使用 reuse=;

  • 在分布式训练中,训练、评估和推断分开用不同的机器做很正常。反正都需要各自建图。因此,分开建图也有助于你构建分布式训练系统。

主要的问题是,在只有单机的情况下,如何在 3 个图中共享变量 Variables。这可以通过为每个图使用单独的 session 来解决。训练 session 定期保存检查点,评估和推理 session 定期从检查点恢复参数。

下面的例子显示了两种方法的主要区别。

1. 统一建图:一个图里 3 个模型

2. 分别建图:3 个 session 共享变量 

注意,后一种方法很容易就能转换为分布式版本。

另一个区别在于,我们使用了有状态的迭代器对象,而不是使用 feed_dicts 来在每个 session.run 调用中提供数据。这些迭代器使输入管道在单机和分布式设置中都容易得多。

其他技巧:双向 RNN

编码器的双向性通常会带来更好的性能(但由于使用了更多层,速度会有一些降低)。在这里,我们给出一个简单的例子,说明如何用单个双向层构建编码器:

其他技巧:Beam Search

虽然贪婪解码得出的翻译质量不错,但是 beam search 解码器可以进一步提高性能。Beam search 在翻译时总是将一小部分顶级候选词留在身边,从而在搜索空间更好地探索所有可能的翻译。 Beam 的大小称为“宽度”width;大小为 10 的宽度基本就够了。以下是 Beam search 的示例:

其他技巧:超参数

有些超参数能带来性能的进一步提升。以下是根据我们的经验列出的一些超参数:

  • 优化函数:虽然在“不太熟悉”的架构里,Adam 能带来不错的结果,但如果你能训练 SGD,SGD 通常会更好;

  • 注意力:Bahadnau 风格的注意力需要解码器双向性才好用;Luong 风格的注意力在不同设置下都挺好。在这份教程中,我们推荐两个变体: scaled_luong & normed bahdanau

其他技巧:多 GPU 训练

训练一个 NMT 模型需要好几天。将不同的 RNN 层放在不用的 GPU 上能提升训练速度。以下为一个例子:

你可能会发现,随着 GPU 数量的增长,基于注意力的 NMT 模型训练速度提升非常有限。这是因为标准注意力架构在每个时间步长使用顶层(最后一层)的输出做为 query 注意力。这意味着每一次解码都需要等前面的步骤完全结束了才行。因此,无法在多台 GPU 上并行解码 RNN。

谷歌提出的 GNMT 注意力架构使用底层(第一层)输出作为 query 注意力。因此,前一步刚刚结束就能实行注意力计算。我们实现了 GNMTAttentionMultiCell 中的架构,这是 tf.contrib.rnn.MultiRNNCell 的一个子类。 以下是使用 GNMTAttentionMultiCell 创建解码器单元的示例:

最后的基准部分请参考原文。

原文:https://github.com/tensorflow/nmt

收藏 推荐 打印 | 录入:admin | 阅读:
相关新闻      
本文评论   查看全部评论 (0)
表情: 表情 姓名: 字数
点评:
       
评论声明
  • 尊重网上道德,遵守中华人民共和国的各项有关法律法规
  • 承担一切因您的行为而直接或间接导致的民事或刑事法律责任
  • 本站管理人员有权保留或删除其管辖留言中的任意内容
  • 本站有权在网站内转载或引用您的评论
  • 参与本评论即表明您已经阅读并接受上述条款