前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Seq2Seq模型

Seq2Seq模型

作者头像
用户1148830
发布2018-01-04 10:15:03
1.1K0
发布2018-01-04 10:15:03
举报

前言: 此文翻译自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)下来,然后你就按照以下方式运行你的翻译程序啦:

代码语言:javascript
复制
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模型可以按照以下方式使用:

代码语言:javascript
复制
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模型的用法:

代码语言:javascript
复制
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

代码语言:javascript
复制
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行所示:

代码语言:javascript
复制
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中,我们使用默认的桶如下:

代码语言:javascript
复制
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]

待续。。。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2017年08月03日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Sequence-to-Sequence模型
  • Sequence-to-sequence基础
  • TensorFlow seq2seq的库
  • 神经翻译模型
    • Sampled softmax 和 output projection
      • 装桶(Bucketing)和填充(padding)
      相关产品与服务
      数据库
      云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档