Seq2Seq模型

前言: 此文翻译自TensorFlow tutorial: Sequence-to-Sequence Models 本文的尽量在做到意思正确的情况下,做到不尬翻。但第一次尝试翻译,另加上英语水平有限,难免有所出入,欢迎指正。

PS: 本人跑的实验是根据翻译的项目修改成的简易中文Chatbot

Sequence-to-Sequence模型

之前在RNN Tutorial讨论过(如果你还没有阅读,请先前往阅读之),循环神经网络(Recurrent Neural Networks, RNNs)可以进行语言的建模。这就产生了一个有趣的问题:我们是否可以在已经产生的词汇生成一些有意义的反馈呢?举个栗子,是否可以训练一个神经网络,用于英法翻译?而这已被证明是可行的。

这份tutorial将会展示如何使用端到端的方法构建并训练这种网络。从TensorFLow main repoTensorFlow models repo把github的项目拷贝(clone)下来,然后你就按照以下方式运行你的翻译程序啦:

cd models/tutorials/rnn/translate
python translate.py --data_dir [你的数据所在目录]

过程中将会从WMT’15 Website下载英法翻译数据用于训练。数据大约有20GB,下载和准备可能需要一些时间,因此阅读这个tutorial和程序运行可以并行进行。 这个tutorial参考如下文件:

文件

文件说明

tensorflow/tensorflow/python/ops/seq2seq.py

构建seq2seq模型所需要的库

models/tutorials/rnn/translate/seq2seq_model.py

神经机器翻译seq2seq模型

tensorflow/tensorflow/python/ops/data_utils.py

用于准备翻译数据的Helper函数

models/tutorials/rnn/translate/translate.py

用于训练和解码

Sequence-to-sequence基础

一个初始的seq2seq模型(Cho et al.(pdf)),包括两个循环神经网络:一个处理输入的编码器和一个生成输出的解码器。基本结构如下图所示:

图片中的每一个方框代表RNNs中的一个单元,最常见的是GRU单元或者LSTM单元(关于这些单元的解释请看RNN Tutorial)。编码器和解码器可以共享权重或者更加常见的使用一组不同的参数。对于多层的单元也已经成功应用在了seq2seq模型当中,如用于翻译(Sutskever et al., 2014(pdf))

在以上描述的基础模型中,每一个输入(译者注:所有的输入更加合适?)都被编码成一个固定的状态向量,而这个状态向量是传如解码器的唯一参数。为了让编码器更加直接地对输入进行编码,就引入了注意力机制(attention mechanism)(Bahdanau et al., 2014(pdf))。这里我们并不会介绍注意力机制的细节(详见论文);只说一点:注意力机制可以使解码器在每一个解码的步骤都可以查看输入。在解码器当中使用了注意力机制的多层LSTM单元的seq2seq网络看起来是这样滴:

译者注:个人认为还是上述论文的图可能更好理解一点

TensorFlow seq2seq的库

如前所述,有许多不同的seq2seq模型。每一个seq2seq模型都可以使用不同的RNN单元,但是它们都接收编码器的输入和解码器的输入。这就产生了TensorFLow seq2seq库中的一个接口(tensorflow/tensorflow/python/ops/seq2seq.py)。最基础带编码器-解码器的seq2seq模型可以按照以下方式使用:

outputs, states = basic_rnn_seq2seq(encoder_inputs, decoder_inputs, cell)

在上面的函数中,“encoder_inputs”代表输入到编码器的一系列的张量,即第一张图中的A, B, C。同理,“decoder_inputs”代表了输入到解码器的张量,即图一中的GO, W, X, Y, Z。

参数“cell”是tf.contrib.rnn.RNNCell类中的一个实例,其决定模型内部将使用哪一种RNN单元。你可以使用诸如GRU单元或者LSTM单元这类已经存在的单元,也可以手动实现。还有,tf.contrib.rnn提供了提供了封装器去构造多层单元,在输入和输入添加dropout或者做其它的一些变化。例子详见RNN Tutorial

在许多的seq2seq模型的应用当中,解码器在时刻t的输出会成为解码器t+1时刻的输入。在测试阶段,当解码一个序列的时候,解码器就是以这种方法构建的。而在训练阶段,常见的做法是:即使是之间已经有错的情况下,在每一个时刻都提供正确的输入。seq2seq.py中的函数通过使用feed_previous参数都可以实现这两种模型。第二个栗子,来看看embedding RNN模型的用法:

outputs, states = embedding_rnn_seq2seq(
    encoder_inputs, decoder_inputs, cell,
    num_encoder_symbols, num_decoder_symbols,
    embedding_size, output_projection=None,
    feed_previous=False)

在embedding_rnn_seq2seq 的模型当中,所有的输入(包括encoder_inputs 和decoder_inputs)都代表了整数个离散值张量。它们将会被嵌入成稠密的表示(关于embeddings的更多细节请见:Vectors Representations Tutoral)。但是为了构建这样的embeddings我们需要指定会出现的离散符号的最大值:在编码器一端的num_encoder_symbols和解码器一端的 num_decoder_symbols

在上面的例子中,我们将feed_previous 设置为False。这意味着解码器将会使用所提供的decoder_inputs 张量。如果我们将decoder_inputs 设置为True的话,解码器仅仅会使用decoder_inputs 的第一个元素作为输入。所有其它的张量都会被忽略,取而代之的是decoder的前一个输出将会被使用。我们的翻译模型就是使用这种方式,但是在训练阶段,也可以使用这种方式来使得模型对于自己犯的错误更加的鲁棒,和Bengio et al., 2015(pdf)类似。

在上面的例子中使用的一个更加重要的参数是output_projection。如果没有指定,embedding 模型的输出的形状为batch_size x num_decoder_symbols ,其代表了每一个生成符号的logits。当训练模型的输出词典很大的时候,也即num_decoder_symbols 很大的时候,那么存储如此大的张量是不可行的。相反,若转换成更小的输出张量就更好,稍后将会使用output_projection将输出做投影。这使得我们的seq2seq模型可以使用采样的softmax损失(Jean et al., 2014(pdf)) 除了basic_rnn_seq2seq 和embedding_rnn_seq2seq 之外,在seq2seq.py中还有一些seq2seq的模型;去那里看看吧。它们的接口都很相似,所以这里并不会详细介绍。在接下来的内容中,我们使用 embedding_attention_seq2seq 来实现翻译模型。

神经翻译模型

虽然seq2seq模型的核心是由tensorflow/tensorflow/python/ops/seq2seq.py 里面的函数构造的,但是在models/tutorials/rnn/translate/seq2seq_model.py中的有一些技巧还是值得分享的(ps:现在的tensorflow好像没有这个函数了,我是把这个文件直接放到项目里面了)。

Sampled softmax 和 output projection

对于前者,上面已经提到过,我们在处理输出词汇表巨大的时候会使用sampled softmax。为了可以正常解码,我们需要同时使用输出映射。这个两者都在seq2seq_model.py中的以下代码实现:

关于sampled softmax的更多内容,传送门:sampled softmax

if num_samples > 0 and num_samples < self.target_vocab_size:
  w_t = tf.get_variable("proj_w", [self.target_vocab_size, size], dtype=dtype)
  w = tf.transpose(w_t)
  b = tf.get_variable("proj_b", [self.target_vocab_size], dtype=dtype)
  output_projection = (w, b)

  def sampled_loss(labels, inputs):
    labels = tf.reshape(labels, [-1, 1])
    # We need to compute the sampled_softmax_loss using 32bit floats to
    # avoid numerical instabilities.
    local_w_t = tf.cast(w_t, tf.float32)
    local_b = tf.cast(b, tf.float32)
    local_inputs = tf.cast(inputs, tf.float32)
    return tf.cast(
        tf.nn.sampled_softmax_loss(
            weights=local_w_t,
            biases=local_b,
            labels=labels,
            inputs=local_inputs,
            num_sampled=num_samples,
            num_classes=self.target_vocab_size),
        dtype)

首先,需要注意的是我们仅仅会在采样的数量(默认为512个)比目标输出词表更小的时候才会使用sampled softmax。对于那些输出词表小于512的情况,也许使用标准的softmax损失会更好。

其次,我们构建一个输出映射。它是一个包含了权重矩阵和偏置向量的元组对。如果使用的话,rnn的单元将会返回形状为batch_size x size的向量,而不是batch_size x target_vocab_size. 为了覆盖logits,我们需要乘上权重矩阵并且加上偏置,如seq2seq_model.py文件中124-126行所示:

if output_projection is not None:
  for b in xrange(len(buckets)):
    self.outputs[b] = [tf.matmul(output, output_projection[0]) +
                       output_projection[1] for ...]

装桶(Bucketing)和填充(padding)

除了sampled softmax,我们的翻译模型也是用了装桶(bucketing)和填充(padding),这两种方法是用于高效地处理不同长度句子的情况。我们首先来弄清楚是怎么一回事。当我们从英语翻译成法语的时候,假设我们的输入英语的长度为L1,输出法语的长度为L2。因为英语句子是作为encoder_inputs而传入的,法语句子作为decoder_inputs而传入(最开始加了一个GO前缀),原则上对于每一个长度为(L1,L2+1)的语句对,都要创建一个seq2seq的模型。这将导致一个巨大的计算图,而这个图由许多十分相似的子图构成。还有,因为我们只能使用一个特殊的PAD符号来填充每一个句子。对于已经填充的长度,我们只需要一个seq2seq模型。但是对于较短的句子的话,由于我们需要编码和加码很多没有意义的PAD字符,我们的模型将会变得十分低效。

作为折衷,我们使用一定数量的桶(buckets)并且把每一个句子桶填充至桶的长度。在 translate.py中,我们使用默认的桶如下:

buckets = [(5, 10), (10, 15), (20, 25), (40, 50)]

这意味着如果我们的英文句子有3个字符,对应法语的输出有6个字符,那么我们将会把这个句子放入第一个桶,并且将输入和输出分别填充到5和10个字符。如果输入输出的长度分别为8和18,不会用(10,15),而是使用(20,25)的桶,同样滴,输入和输出将会分别填充到20和25个字符。

记住当我们构建解码器的输入的时候,我们在输入数据之前加了一个特殊的 GO 字符。这个操作是在 seq2seq_model.py中的 get_batch()完成的,这个函数也将输入逆序(译者注:即本来从左到右,变成从右到左。对于这里,个人认为可能是因为rnn的性质,后面的序列保留的有效信息会更多而决定的,不一定正确)。在Sutskever et al., 2014中,展示了将输入逆序可以提升神经机器翻译的效果。把这些串起来,假设我们有一个句子”I go.”为输入,输出为“Je vais.” 。符号化成[“I”,”go”,”.”]和[“Je”,”vais”,”.”]分别作为输入和输出。他将会放入(5,10)的桶,然后填充并且将输入逆序之后,编码其的输入为[PAD PAD “.” “go” “I”],解码器的输入为[GO “Je” “vais” “.” EOS PAD PAD PAD PAD PAD]

待续。。。

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

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏AI研习社

教你从零开始检测皮卡丘-CNN目标检测入门教程(下)

本文为大家介绍实验过程中训练、测试过程及结果。算法和数据集参见《从零开始码一个皮卡丘检测器-CNN目标检测入门教程(上)》 训练 Train 损失函数 Lo...

2733
来自专栏Python小屋

Python扩展库scipy.misc中图像转换成pillow图像

众所周知,在数字图像处理领域中有很多基准测试图像,这些图像用来作为科研人员PK自己的算法时的参考,给大家提供一个公平的样本,针对同一个问题进行处理时,可以用这些...

2755
来自专栏机器学习算法全栈工程师

机器学习之——自动求导

作者:叶虎 小编:张欢 随机梯度下降法(SGD)是训练深度学习模型最常用的优化方法。在前期文章中我们讲了梯度是如何计算的,主要采用BP算法,或者说利用链式法则...

3038
来自专栏人工智能LeadAI

GoogLeNet的心路历程(二)

本文介绍关于GoogLeNet的续作,习惯称为inception v2,如下: [v2] Batch Normalization: Accelerating D...

3296
来自专栏量子位

Attention!神经网络中的注意机制到底是什么?

原作:Adam Kosiorek 安妮 编译自 GitHub 量子位 出品 | 公众号 QbitAI 神经网络的注意机制(Attention Mechanism...

3985
来自专栏机器之心

基于注意力机制,机器之心带你理解与训练神经机器翻译系统

3678
来自专栏AI科技评论

大会 | DiracNets:无需跳层连接的ResNet

AI 科技评论按:本文作者 David 9,首发于作者的个人博客,AI 科技评论获其授权转载。 虚拟化技术牺牲硬件开销和性能,换来软件功能的灵活性;深度模型也类...

3216
来自专栏悦思悦读

数据挖掘_R_Python_ML(2): Linear Regression vs SVR

在上一篇“数据挖掘: R, Python,Machine Learning,一起学起来!”中,我们介绍了用R进行线性回归的例子。 这次我们来看看,同样一份简单的...

3389
来自专栏PaddlePaddle

转载|使用PaddleFluid和TensorFlow实现图像分类网络SE_ResNeXt

视觉(vision)、自然语言处理(Nature Language Processing, NLP)、语音(Speech)是深度学习研究的三大方向。三大领域各自...

1173
来自专栏用户2442861的专栏

循环神经网络教程第二部分-用python,numpy,theano实现一个RNN

作者:徐志强 链接:https://zhuanlan.zhihu.com/p/22289383 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,...

782

扫码关注云+社区