seq2seq的简单实现---实现输入单词的反序任务

seq2seq 模型常用于实现翻译,对话等生成任务。实际上是建立输入和输出的映射关系,翻译对话等任务的实现比价复杂,为了帮助我们更快的理解seq2seq,我们这里实现一个简单的反序任务。本文章重点关注seq2seq模型的代码实现,主要参考了这篇文章。本文的代码在Pycharm上实现,不同与原文使用Jupiter,在代码上有一些改动,并增加了详细的注释。本文的代码后面会放到我的Github上。
好了,下面开始。

任务目标

输入一个单词,输出实现反序。例如:

输入:hello
输出:olleh

数据集

TXT文本。
源数据source_data,每一行是一个单词。
目标数据target_data,每一行与源数据对应,是其的反序。

source_data:             target_data:
bsaqq                        abqqs
npy                             npy
lbwuj                           bjluw

数据处理

数据处理部分主要分为下面几部:
1.字符转整数,建立字符到整数的映射表,和整数到字符的映射表
2.添加特殊字符
3.pad到相同长度
4.对每个字母进行embedding

数据处理这部分代码在项目的data_process.py文件中,需要的同学可以去看一下。

另外,还有一点对数据处理的部分在训练时调用,主要是为decoder训练的输入数据服务,需要添加<go>和删除最后一个<eos>标志符或者<pad>。对数据处理的详细解释可以参考这一篇博客。代码在下面:

def process_decoder_input(data, vocab_to_int, batch_size):
    '''
    先移除最后一个字符,再在前面补充<GO>
    '''
    # cut掉最后一个字符
    ending = tf.strided_slice(data, [0, 0], [batch_size, -1], [1, 1])
    decoder_input = tf.concat([tf.fill([batch_size, 1], vocab_to_int['<GO>']), ending], 1)

    return decoder_input
模型构建

对于模型的构建使用的是tensorflow提供的API接口,使用非常方便,如果对接口不是很熟悉的话可以看一看我的这篇博客,里面详细介绍了搭建encoder和decoder用到的接口的使用。

encoder部分

我们首先将输入通过embedding映射成词向量的形式,这里是将每一个字母映射成为了一个15维的向量。当然将26个字母每个映射成一个这么长的想来那个在实际中没有什么意义,这个项目也只是为了我们更好的额理解seq2seq,大家明白就好。然后我们创建想要的LSTM单元。最后将这些参数放入 tf.nn.dynamic_rnn中进行编码。

def get_encoder_layer(input_data, rnn_size, num_layers,
                      source_sequence_length, source_vocab_size,
                      encoding_embedding_size):
    '''
    构造Encoder层

    参数说明:
    - input_data: 输入tensor  [None, None]
    - rnn_size: rnn隐层结点数量  50
    - num_layers: 堆叠的rnn cell数量, RNN层数  2
    - source_sequence_length: 源数据的序列长度
    - source_vocab_size: 源数据的词典大小
    - encoding_embedding_size: embedding的大小 15
    '''
    # Encoder embedding
    encoder_embed_input = tf.contrib.layers.embed_sequence(input_data, source_vocab_size, encoding_embedding_size)

    # RNN cell
    def get_lstm_cell(rnn_size):
        lstm_cell = tf.contrib.rnn.LSTMCell(rnn_size, initializer=tf.random_uniform_initializer(-0.1, 0.1, seed=2))
        return lstm_cell

    # 堆叠的 rnn cell
    cell = tf.contrib.rnn.MultiRNNCell([get_lstm_cell(rnn_size) for _ in range(num_layers)])
    # 动态RNN 允许每个batch内有相同padding长度,不同batch间可以有不同的batch长度
    encoder_output, encoder_state = tf.nn.dynamic_rnn(cell, encoder_embed_input,
                                                      sequence_length=source_sequence_length, dtype=tf.float32)

    return encoder_output, encoder_state
decoder部分

decoder的构建需要两个部分,分别用于训练和预测。在训练时我们将target数据输入decoder中进行训练,上一时刻的输出不放入下一时刻的输入。再预测阶段,我们没有target,所以将上一时刻的输出作为下一时刻的输入放入decoder中。预测阶段的过程如下图所示:
在这里插入图片描述

def decoding_layer(target_letter_to_int, decoding_embedding_size, num_layers, rnn_size,
                   target_sequence_length, max_target_sequence_length, encoder_state, decoder_input):
    '''
    构造Decoder层

    参数:
    - target_letter_to_int: target数据的映射表
    - decoding_embedding_size: embed向量大小
    - num_layers: 堆叠的RNN单元数量 2
    - rnn_size: RNN单元的隐层结点数量 50
    - target_sequence_length: target数据序列长度
    - max_target_sequence_length: target数据序列最大长度
    - encoder_state: encoder端编码的状态向量
    - decoder_input: decoder端输入
    '''
    # 1. Embedding
    target_vocab_size = len(target_letter_to_int)
    # 这里为甚么没有与encoder用相同的embedding,
    decoder_embeddings = tf.Variable(tf.random_uniform([target_vocab_size, decoding_embedding_size]))
    decoder_embed_input = tf.nn.embedding_lookup(decoder_embeddings, decoder_input)

    # 2. 构造Decoder中的RNN单元
    def get_decoder_cell(rnn_size):
        decoder_cell = tf.contrib.rnn.LSTMCell(rnn_size,
                                               initializer=tf.random_uniform_initializer(-0.1, 0.1, seed=2))
        return decoder_cell

    cell = tf.contrib.rnn.MultiRNNCell([get_decoder_cell(rnn_size) for _ in range(num_layers)])

    # 3. Output全连接层  输出的维度是target_vocab_size
    output_layer = Dense(target_vocab_size,
                         kernel_initializer=tf.truncated_normal_initializer(mean=0.0, stddev=0.1))

    # 4. Training decoder
    with tf.variable_scope("decode"):
        # 得到help对象
        # A helper for use during training. Only reads inputs.
        # Returned sample_ids are the argmax of the RNN output logits.
        # Decoder端用来训练的函数。这个函数不会把t-1阶段的输出作为t阶段的输入,而是把target中的真实值直接输入给RNN
        training_helper = tf.contrib.seq2seq.TrainingHelper(inputs=decoder_embed_input,
                                                            sequence_length=target_sequence_length,
                                                            time_major=False)
        # 构造decoder
        training_decoder = tf.contrib.seq2seq.BasicDecoder(cell=cell,
                                                           helper=training_helper,
                                                           initial_state=encoder_state,
                                                           output_layer=output_layer)
        training_decoder_output, _, _ = tf.contrib.seq2seq.dynamic_decode(training_decoder,
                                                                       impute_finished=True,
                                                                       maximum_iterations=max_target_sequence_length)

    # 5. Predicting decoder
    # 与training共享参数
    with tf.variable_scope("decode", reuse=True):
        # 创建一个常量tensor并复制为batch_size的大小
        start_tokens = tf.tile(tf.constant([target_letter_to_int['<GO>']], dtype=tf.int32), [batch_size],
                               name='start_tokens')
        # GreedyEmbeddingHelper和TrainingHelper的区别在于它会把t-1下的输出进行embedding后再输入给RNN
        # 注意:input 是 embeddings 而不是 decoder_embed_input
        predicting_helper = tf.contrib.seq2seq.GreedyEmbeddingHelper(decoder_embeddings,
                                                                     start_tokens,
                                                                     target_letter_to_int['<EOS>'])
        predicting_decoder = tf.contrib.seq2seq.BasicDecoder(cell,
                                                             predicting_helper,
                                                             encoder_state,
                                                             output_layer)
        predicting_decoder_output, _, _ = tf.contrib.seq2seq.dynamic_decode(predicting_decoder,
                                                                         impute_finished=True,
                                                                         maximum_iterations=max_target_sequence_length)

    return training_decoder_output, predicting_decoder_output
最终模型

建立好encoder和decoder后,我们就可以将一个完整的seq2seq模型搭建起来了。

def seq2seq_model(input_data, targets, lr, target_sequence_length,
                  max_target_sequence_length, source_sequence_length,
                  source_vocab_size, target_vocab_size,
                  encoder_embedding_size, decoder_embedding_size,
                  rnn_size, num_layers):
    # 获取encoder的状态输出
    _, encoder_state = get_encoder_layer(input_data,
                                         rnn_size,
                                         num_layers,
                                         source_sequence_length,
                                         source_vocab_size,
                                         encoding_embedding_size)

    # 预处理后的decoder输入
    decoder_input = process_decoder_input(targets, data_process.target_letter_to_int, batch_size)

    # 将状态向量与输入传递给decoder
    training_decoder_output, predicting_decoder_output = decoding_layer(data_process.target_letter_to_int,
                                                                        decoding_embedding_size,  # 15
                                                                        num_layers,  # 2
                                                                        rnn_size,  # 50
                                                                        target_sequence_length,
                                                                        max_target_sequence_length,
                                                                        encoder_state,
                                                                        decoder_input)

    return training_decoder_output, predicting_decoder_output
模型的训练

模型的训练我们使用mask减去padding的影响

masks = tf.sequence_mask(target_sequence_length, max_target_sequence_length, dtype=tf.float32, name='masks')

with tf.name_scope("optimization"):
     # Loss function 对序列logits计算加权交叉熵
     # training_logits是输出层的结果,targets是目标值,
     # masks是我们使用tf.sequence_mask计算的结果,在这里作为权重,也就是说我们在计算交叉熵时不会把<PAD>计算进去
     cost = tf.contrib.seq2seq.sequence_loss(
         training_logits,   # 训练输出
         targets,           # 正确值
         masks)
	# Optimizer
	optimizer = tf.train.AdamOptimizer(lr)
	
	# Gradient Clipping
	gradients = optimizer.compute_gradients(cost)
	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)
训练结果
原始输入: common

Source
  Word 编号:    [5, 7, 13, 13, 7, 21, 0]
  Input Words: c o m m o n <PAD>

Target
  Word 编号:       [21, 5, 13, 13, 7, 7]
  Response Words: n c m m o o
原始输入: result

Source
  Word 编号:    [7, 11, 13, 29, 22, 9, 0]
  Input Words: r e s u l t <PAD>

Target
  Word 编号:       [11, 29, 9, 22, 13, 7]
  Response Words: e u t l s r
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值