前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【TensorFlow 谷歌神经机器翻译】从零开始打造属于你的翻译系统

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

作者头像
新智元
发布2018-03-27 16:47:02
2.1K0
发布2018-03-27 16:47:02
举报
文章被收录于专栏:新智元新智元

【新智元导读】谷歌今天公布了一个用 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/scripts/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

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2017-07-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 新智元 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
机器翻译
机器翻译(Tencent Machine Translation,TMT)结合了神经机器翻译和统计机器翻译的优点,从大规模双语语料库自动学习翻译知识,实现从源语言文本到目标语言文本的自动翻译,目前可支持十余种语言的互译。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档