使用seq2seq API(版本1.1及以上)的Tensorflow序列到序列模型

18
我正在使用TensorFlow v:1.1,并希望使用tf.contrib.seq2seq api实现一个序列到序列的模型。然而,我很难理解如何使用提供的所有函数(BasicDecoder、Dynamic_decode、Helper、Training Helper等)来构建我的模型。
这是我的设置:我想要“翻译”一系列特征向量序列:(batch_size, encoder_max_seq_len, feature_dim),并将其转化为不同长度的序列(batch_size, decoder_max_len, 1)
我已经有了LSTM单元的RNN编码器,并获取了它的最终状态,我想将其作为初始输入馈送给解码器。我已经有了解码器的cell,MultiRNNCell LSM。
你能帮助我使用tf.contrib.seq2seq2的函数和dynamic_decode构建最后一部分吗?如果能提供示例代码或说明,那就太好了。
以下是我的代码:
import tensorflow as tf
from tensorflow.contrib import seq2seq
from tensorflow.contrib import rnn
import math

from data import gen_sum_2b2

class Seq2SeqModel:
def __init__(self,
             in_size,
             out_size,
             embed_size,
             n_symbols,
             cell_type,
             n_units,
             n_layers):
    self.in_size = in_size
    self.out_size = out_size
    self.embed_size = embed_size
    self.n_symbols = n_symbols
    self.cell_type = cell_type
    self.n_units = n_units
    self.n_layers = n_layers

    self.build_graph()

def build_graph(self):
    self.init_placeholders()
    self.init_cells()
    self.encoder()
    self.decoder_train()
    self.loss()
    self.training()

def init_placeholders(self):
    with tf.name_scope('Placeholders'):
        self.encoder_inputs = tf.placeholder(shape=(None, None, self.in_size), 
                                             dtype=tf.float32, name='encoder_inputs')
        self.decoder_targets = tf.placeholder(shape=(None, None),
                                              dtype=tf.int32, name='decoder_targets')
        self.seqs_len = tf.placeholder(dtype=tf.int32)
        self.batch_size = tf.placeholder(tf.int32, name='dynamic_batch_size')
        self.max_len = tf.placeholder(tf.int32, name='dynamic_seq_len')
        decoder_inputs = tf.reshape(self.decoder_targets, shape=(self.batch_size,
                                    self.max_len, self.out_size))
        self.decoder_inputs = tf.cast(decoder_inputs, tf.float32)
        self.eos_step = tf.ones([self.batch_size, 1], dtype=tf.float32, name='EOS')
        self.pad_step = tf.zeros([self.batch_size, 1], dtype=tf.float32, name='PAD')

def RNNCell(self):
    c = self.cell_type(self.n_units, reuse=None)
    c = rnn.MultiRNNCell([self.cell_type(self.n_units) for i in range(self.n_layers)])
    return c

def init_cells(self):
    with tf.variable_scope('RNN_enc_cell'):
        self.encoder_cell = self.RNNCell()  
    with tf.variable_scope('RNN_dec_cell'):
        self.decoder_cell = rnn.OutputProjectionWrapper(self.RNNCell(), self.n_symbols)

def encoder(self):
    with tf.variable_scope('Encoder'):
        self.init_state = self.encoder_cell.zero_state(self.batch_size, tf.float32) 
        _, self.encoder_final_state = tf.nn.dynamic_rnn(self.encoder_cell, self.encoder_inputs,
                                                        initial_state=self.init_state) 

1
@AllenLavoie 我认为问题在于API已经更新到v1.1,但示例代码还没有更新。 - Ned Ruggeri
2
@AllenLavoie 我认为教程代码没有出现任何错误:它只是使用了已被新类替代的旧函数。如果你是新手,很难理解,但是当我完全理解时,也许我可以提出一些建议 :-) - Ned Ruggeri
谢谢大家,我终于找到了如何使用新的seq2seq库。 - JimZer
@AllenLavoie 在传统的seq2seq中,有embedding_attention_seq2seq,它不使用双向编码器。你能指导我实现这个功能吗? 我正在寻找嵌入式双向注意力seq2seq。 - pseudo_teetotaler
@bot28:我建议您提出一个新问题。主要是因为我没有一个好的答案(而其他人可能有),而且这个评论的边距太小,无法容纳这个不存在的答案。 - Allen Lavoie
显示剩余6条评论
1个回答

20

解码层:

由于在训练和推断期间的不同,解码分为两个部分:

特定时间步的解码器输入始终来自上一个时间步的输出。但是在训练期间,输出被固定为实际目标(实际目标被反馈为输入),这已经证明可以提高性能。

这两个问题都使用tf.contrib.seq2seq中的方法来处理。

  1. decoder的主要函数是:seq2seq.dynamic decoder(),它执行动态解码:

    tf.contrib.seq2seq.dynamic_decode(decoder,maximum_iterations)

    这需要一个Decoder实例和maximum_iterations=maximum seq length作为输入。

    1.1 Decoder实例来自:

    seq2seq.BasicDecoder(cell, helper, initial_state,output_layer)

    输入为:cell(RNNCell实例)、helper(helper实例)、initial_state(解码器的初始状态,应该是编码器的输出状态)和output_layer(一个可选的密集层作为输出来进行预测)。

    1.2 RNNCell实例可以是rnn.MultiRNNCell()

    1.3 helper实例在traininginference中不同。在training期间,我们希望将输入馈送到解码器中,而在inference期间,我们希望将解码器的输出传递给下一个时间步中的解码器。

    对于训练:我们使用helper函数:seq2seq.TrainingHelper(inputs, sequence_length),它只读取输入。

    对于推断:我们调用helper函数:seq2seq.GreedyEmbeddingHelper()或seqseq.SampleEmbeddingHelper(),这取决于是否使用输出的argmax()或从分布中进行抽样,并通过嵌入层传递结果以获取下一个输入。

组合:Seq2Seq模型

  1. 从编码器层获取编码器状态,并将其作为“initial_state”传递给解码器。
  2. 使用“seq2seq.dynamic_decoder()”获取“decoder train”和“decoder inference”的输出。在调用这两种方法时,请确保共享权重。(使用“variable_scope”重用权重)
  3. 然后使用损失函数“seq2seq.sequence_loss”训练网络。

这里提供了一个示例代码,可以在这里这里找到。


谢谢你的回答。还有一件事情不太清楚。在训练期间,我们是否真的将“真实”的输出传递给解码器,而不是提供序列的先前输出(就像在真实情况下无法确定序列的任何部分一样)? - George Pligoropoulos
换句话说,我们是使用真实的目标/解码器输入进行训练,然后在未见过解码器输入的数据上进行测试(除了架构中必须的第一个输入);还是让我们的模型在没有提供任何解码器输入的情况下进行训练和测试。前者感觉像作弊,但它可能会产生我现在无法猜测的良好行为。 - George Pligoropoulos
1
这样思考:解码器的输入始终来自先前的输出。因此,解码器始终会被提供一些输入。但在训练过程中,输出被“固定”为实际目标,并且这已被证明可以提高性能。您可以查看来自tensorflow的seq-2-seq教程:https://github.com/google/seq2seq/blob/master/seq2seq/models/basic_seq2seq.py - Vijay Mariappan
感谢@vijaym,关于1.1版本中的output_layer,如果我没记错的话,它将把解码器隐藏层投影到目标上。如何基于output_layer构建序列损失?dynamic_decode只返回隐藏状态,而sequence_loss需要解码器logits,那么如何获取它们呢? - vega

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接