tensorflow实现seq2seq:完整部分

完整源码地址:https://github.com/colin0000007/seq2seq

源码中我将实现封装为了2个类,一个BasicSeq2SeqModel,一个AttentionSeq2SeqModel。前者包含了bi-rnn,beam search等特效,后者只是多了attention。下面详细讲讲BasicSeq2SeqModel。

1.训练数据

seq2seq数据分为源序列,目标序列。

这里的测试数据源序列是字母序列,目标序列是字母序列的倒序。

例如:

source:
    ozi
    dzrkwam
    cuemir
    krmwaes
target:
    izo
    mawkrzd
    rimeuc
    seawmrk

训练好模型后,输入一个字母序列,期望得到这个序列的倒序。

2.输入和数据

source:就是源序列

target:就是目标序列

比如seq2seq的机器翻译中,source就是被翻译的句子。

Target就是翻译到的句子。

第一步首先定义输入的tensor。包括:

source的tensor,shape=[batchsize,src_max_len]

target的tensor,shape=[batchsize,max_len]

这里有一个问题是,最终的标签(y)是什么?实际上就是target

序列生成的过程。

比如:

Source: abcd

Target:dcba

如果统计后知道src_max_len = 5,tgt_max_len=6

需要对tgt_max_len+1,因为target_x和target_y中分别包含了<s>和</s>

对于不足长度的样本补充<pad>

其次还需在target中添加<s></s>表示序列的开始和结尾。

那么最后的数据应该是这样:

source:

a b c d <pad>

target_x:

<s> d c b a <pad>

target_y:

d c b a </s> <pad>

也就是用<s>加上source上下文向量预测a

用a加上source 上下文向量预测b

生成时遇到</s>或者达到最大生成序列长度就结束。

一般都需要对source和target做embeding。至于是否需要单独为target_x和target_y声明2个tensor完全取决你自己。因为decoder最终使用的embedding后的tensor,此时对原来的target tensor处理一下就可以作为target_y。另外在实现时使用的tf.contrib.seq2seq.sequence_loss,并不要求把target_y变成one-hot向量。

对应代码部分:

#定义encoder的输入tensor,加入了词嵌入
def input_tensor():
    #这里设置位None,意思是不指定batch_size用到多少就是多少
    source_batch = tf.placeholder(tf.int32,[None,None],name="source_batch")
    #对source做词嵌入,词向量矩阵的shape为[source的词库大小,嵌入维度]
    #嵌入矩阵中每一行就是一个词向量
    source_embedding = tf.get_variable(shape=[source_vocab_size,source_embedding_size],name='source_embedding')
    #使用embedding_lookup从embedding矩阵中查询词向量从而将X的每一个单词的index转换一个词向量
    embedded_source_batch = tf.nn.embedding_lookup(source_embedding,source_batch)
    #最后返回的embedded_X.shape = [batch_size,time_step,embedding_size]
    #target类似
    target_batch_x = tf.placeholder(tf.int32,[None,None])
    target_batch_y = tf.placeholder(tf.int32,[None,None])
    target_embedding = tf.get_variable(shape=[target_vocab_size,source_embedding_size],name='target_embedding')
    embedded_target_batch_x = tf.nn.embedding_lookup(target_embedding,target_batch_x)
    source_batch_seq_len = tf.placeholder(tf.int32,[None],name="source_batch_seq_len")
    target_batch_seq_len = tf.placeholder(tf.int32,[None],name="target_batch_seq_len")
    #保存当前batch的最长序列值,mask的时候需要用到
    tgt_batch_max_len = tf.placeholder(tf.int32,[],name="target_batch_max_len")
    return embedded_source_batch,embedded_target_batch_x,source_batch,target_batch_x,target_batch_y,source_batch_seq_len,target_batch_seq_len,target_embedding,tgt_batch_max_len

3.encoder

encoder比较简单,就是堆叠几层rnn cell就行了,关于encoder我以前一直有个错误的想法,以为需要单独训练encoder,实际上并不是,encoder是和decoder连接到一起的,做梯度下降时参数一并更新。

#参照tensorflow/nmt的官方教程
def build_encoder(embedded_source_batch,source_batch_seq_len):
    #定义rnn cell的获取
    def get_rnn_cell():
        return tf.nn.rnn_cell.LSTMCell(num_units=rnn_num_units)
    #定义encoder的rnn_layer
    rnn_layer = tf.nn.rnn_cell.MultiRNNCell([get_rnn_cell() for _ in range(rnn_cell_size)])
    #将rnn沿时间序列展开
    #   encoder_outputs: 返回了每一个序列节点的output,shape为[batch_size,max_time, num_units]
    #   encoder_state: 返回了每个cell最后一个序列节点的state输出,shape为cell个数*[batch_size, num_units]
    #   sequence_length:传入一个list保存每个样本的序列的真实长度,教程中说这样做有助于提高效率
    encoder_outputs, encoder_state = tf.nn.dynamic_rnn(
    rnn_layer, embedded_source_batch,
    sequence_length=source_batch_seq_len, time_major=False,dtype=tf.float32)
    return encoder_outputs, encoder_state

4.decoder

decoder要麻烦一些,首选定义rnn cell,还需要定义projection layer,映射层。它的作用是将decoder的rnn输出映射为大小target词库大小,这样可以做分类。训练用到trainingHelper,测试阶段可以使用GreedyEmbeddingHelper或者beamSearch来解码。inference定义的decoder需要用到训练定义的decoder,这样可以共享rnn参数,projection layer的参数。具体看代码:

训练encoder:

def build_decoder(encoder_outputs, encoder_state,embedded_target_batch_x,target_batch_seq_len,target_embedding):
    def get_rnn_cell():
        return tf.nn.rnn_cell.LSTMCell(num_units=rnn_num_units)
    rnn_layer = tf.nn.rnn_cell.MultiRNNCell([get_rnn_cell() for _ in range(rnn_cell_size)])
    #decoder:需要helper,rnn cell,helper被分离开可以使用不同的解码策略,比如预测时beam search,贪婪算法
    #这里projection_layer就是一个全连接层,encoder_state连接到encoder_embede后的向量维度不能和target词汇数量一致所以需要映射层
    #这里使用tensorflow.python.keras.layers.core.Dense一直报错
    #换成tf.layers.Dense后解决 但是我在另一份能够运行的seq2seq代码中发现使用上面的Dense并不会报错
    projection_layer = tf.layers.Dense(units=target_vocab_size,kernel_initializer = tf.truncated_normal_initializer(mean = 0.0, stddev=0.1))
    training_helper = tf.contrib.seq2seq.TrainingHelper(
        embedded_target_batch_x, target_batch_seq_len, time_major=False)
    decoder = tf.contrib.seq2seq.BasicDecoder(
        rnn_layer, training_helper, encoder_state,
        output_layer=projection_layer)
    #将序列展开
    decoder_output,_,_ = tf.contrib.seq2seq.dynamic_decode(decoder,output_time_major=False,maximum_iterations=target_max_len)
    #decoder_output的shape:[batch_size,序列长度,target_vocab_size]
 
    return decoder_output,rnn_layer,projection_layer

inference阶段的decoder:

#推理阶段,也就是预测阶段
def inference(target_embedding,encoder_state,decoder_rnn_layer,decoder_projection_layer):
    inuput_batch = tf.placeholder(dtype=tf.int32, shape=[1],name="input_batch")
    start_tokens = tf.tile(tf.constant(value=start_token_id, dtype=tf.int32,shape=[1]), multiples = inuput_batch, name="start_tokens_2")
    predicting_helper = tf.contrib.seq2seq.GreedyEmbeddingHelper(target_embedding,start_tokens,end_token_id)
    predicting_decoder = tf.contrib.seq2seq.BasicDecoder(
        decoder_rnn_layer, predicting_helper, encoder_state,
        output_layer=decoder_projection_layer)
    predicting_decoder_output, _ ,_= tf.contrib.seq2seq.dynamic_decode(predicting_decoder,output_time_major=False,maximum_iterations=target_max_len)
    tf.identity(predicting_decoder_output.sample_id, name='predictions2')
    

4.loss构建,训练计算图构建等

直接贴代码,更多细节问题参考前几篇说细节的文章。

def seq2seq_model(embedded_source_batch,source_batch_seq_len,embedded_target_batch_x,target_batch_seq_len,target_embedding):
    encoder_outputs, encoder_state = build_encoder(embedded_source_batch, source_batch_seq_len)
    decoder_output,rnn_layer,projection_layer = build_decoder(encoder_outputs, encoder_state, embedded_target_batch_x, target_batch_seq_len, target_embedding)
    inference(target_embedding, encoder_state, rnn_layer, projection_layer)
    print("encoder_outputs:",encoder_outputs)
    print("encoder_state:",encoder_state)
    print("decoder_output:",decoder_output)
    return decoder_output


def build_graph():
    #构造计算图
    train_graph = tf.Graph()
    with train_graph.as_default():
        #1.tensor声明
        embedded_source_batch,embedded_target_batch_x,source_batch,target_batch_x,target_batch_y,source_batch_seq_len,target_batch_seq_len,target_embedding,tgt_batch_max_len = input_tensor()
        #2.构造seq2seq产生的output tensor
        decoder_output = seq2seq_model(embedded_source_batch, source_batch_seq_len, embedded_target_batch_x, target_batch_seq_len,target_embedding)
        #1和2这个步骤必须在同一个graph下声明
        #对这2个decoder的输出获取不同的tensor并且取名字
        training_logits = tf.identity(decoder_output.rnn_output, 'logits')
        print("training_logits.shape:",training_logits.shape)
        #尝试是否能成功
        #mask的作用是:计算loss时忽略pad的部分,这部分的loss不需要算,提升性能,
        masks = tf.sequence_mask(target_batch_seq_len, tgt_batch_max_len, dtype=tf.float32, name='masks')
        with tf.name_scope("optimization"):
            #loss
            #这里尝试使用下tensorflow-nmt官方教程中的tf.nn.sparse_softmax_cross_entropy_with_logits
            #不测试了,不能使用mask
            loss = tf.contrib.seq2seq.sequence_loss(
                training_logits,
                target_batch_y,
                masks)
            optimizer = tf.train.AdamOptimizer(lr)
            # Gradient Clipping
            gradients = optimizer.compute_gradients(loss)
            capped_gradients = [(tf.clip_by_value(grad, -5., 5.), var) for grad, var in gradients if grad is not None]
            train_op = optimizer.apply_gradients(capped_gradients)
    return train_graph,loss,train_op,source_batch,target_batch_x,target_batch_y,source_batch_seq_len,target_batch_seq_len,tgt_batch_max_len

这篇本来打算连着前面几个一起写完的,一期末就忙忘了,很多东西也有点不熟悉了,写得很水。

  • 0
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值