从头实现一个深度学习对话系统--tensorflow Seq-to-Seq API介绍和源码分析

上一篇文章中我们已经分析了各种seq2seq模型,从理论的角度上对他们有了一定的了解和认识,那么接下来我们就结合tensorflow代码来看一下这些模型在tf中是如何实现的,相信有了对代码的深层次理解,会在我们之后构建对话系统模型的时候有很大的帮助。

tensorflow版本升级之后把之前的tf.nn.seq2seq的代码迁移到了tf.contrib.legacy_seq2seq下面,其实这部分API估计以后也会被遗弃,因为已经开发出了新的API放在tf.contrib.seq2seq下面,更加灵活,但是目前在网上找到的代码和仿真实现基本上用的还是legacy_seq2seq下面的代码,所以我们先来分析一下这部分的函数功能及源码实现。本次我们会介绍下面几个函数,这部分代码的定义都可以在python/ops/seq2seq.py文件中找到。

首先看一下这个文件的组成,主要包含下面几个函数:

可以看到按照调用关系和功能不同可以分成下面的结构:

  • model_with_buckets
    • seq2seq函数
      • basic_rnn_seq2seq
        • rnn_decoder
      • tied_rnn_seq2seq
      • embedding_tied_rnn_seq2seq
      • embedding_rnn_seq2seq
        • embedding_rnn_decoder
      • embedding_attention_seq2seq
        • embedding_attention_decoder
        • attention_decoder
          • attention
      • one2many_rnn_seq2seq
    • loss函数
      • sequence_loss_by_example
      • sequence_loss

在这里,我会主要介绍一下功能最完备的几个函数,足以让我们实现一个基于seq_to_seq模型的对话系统。就让我们按照函数的调用关系来进行一一介绍吧:

model_with_buckets()函数

首先来说最高层的函数model_with_buckets(),定义如下所示:

    def model_with_buckets(encoder_inputs,
                           decoder_inputs,
                           targets,
                           weights,
                           buckets,
                           seq2seq,
                           softmax_loss_function=None,
                           per_example_loss=False,
                           name=None):

首先来说一下这个函数,目的是为了减少计算量和加快模型计算速度,然后由于这部分代码比较古老,你会发现有些地方还在使用static_rnn()这种函数,其实新版的tf中引入dynamic_rnn之后就不需要这么做了。但是呢,我们还是来分析一下,其实思路很简单,就是将输入长度分成不同的间隔,这样数据的在填充时只需要填充到相应的bucket长度即可,不需要都填充到最大长度。比如buckets取[(5,10), (10,20),(20,30)…](每个bucket的第一个数字表示source填充的长度,第二个数字表示target填充的长度,eg:‘我爱你’–>‘I love you’,应该会被分配到第一个bucket中,然后‘我爱你’会被pad成长度为5的序列,‘I love you’会被pad成长度为10的序列。其实就是每个bucket表示一个模型的参数配置),这样对每个bucket都构造一个模型,然后训练时取相应长度的序列进行,而这些模型将会共享参数。其实这一部分可以参考现在的dynamic_rnn来进行理解,dynamic_rnn是对每个batch的数据将其pad至本batch中长度最大的样本,而bucket则是在数据预处理环节先对数据长度进行聚类操作。明白了其原理之后我们再看一下该函数的参数和内部实现:

    encoder_inputs: encoder的输入,一个tensor的列表。列表中每一项都是encoder时的一个词(batch)。
    decoder_inputs: decoder的输入,同上
    targets:        目标值,与decoder_input只相差一个<EOS>符号,int32型
    weights:        目标序列长度值的mask标志,如果是padding则weight=0,否则weight=1
    buckets:        就是定义的bucket值,是一个列表:[(5,10), (10,20),(20,30)...]
    seq2seq:        定义好的seq2seq模型,可以使用后面介绍的embedding_attention_seq2seq,embedding_rnn_seq2seq,basic_rnn_seq2seq等
    softmax_loss_function: 计算误差的函数,(labels, logits),默认为sparse_softmax_cross_entropy_with_logits
    per_example_loss: 如果为真,则调用sequence_loss_by_example,返回一个列表,其每个元素就是一个样本的loss值。如果为假,则调用sequence_loss函数,对一个batch的样本只返回一个求和的loss值,具体见后面的分析
    name: Optional name for this operation, defaults to "model_with_buckets".

内部代码这里不会全部贴上来,捡关键的说一下:

    #保存每个bucket对应的loss和output   
    losses = []
    outputs = []
    with ops.name_scope(name, "model_with_buckets", all_inputs):
    #对每个bucket都要选择数据进行构建模型
    for j, bucket in enumerate(buckets):
      #buckets之间的参数要进行复用
      with variable_scope.variable_scope(variable_scope.get_variable_scope(), reuse=True if j > 0 else None):

        #调用seq2seq进行解码得到输出,这里需要注意的是,encoder_inputs和decoder_inputs是定义好的placeholder,
        #都是长度为序列最大长度的列表(也就是最大的那个buckets的长度),按上面的例子,这两个placeholder分别是长度为20和30的列表。
        #在构建模型时,对于每个bucket,只取其对应的长度个placeholder即可,如对于(5,10)这个bucket,就取前5/10个placeholder进行构建模型
        bucket_outputs, _ = seq2seq(encoder_inputs[:bucket[0]], decoder_inputs[:bucket[1]])
        outputs.append(bucket_outputs)
        #如果指定per_example_loss则调用sequence_loss_by_example,losses添加的是一个batch_size大小的列表
        if per_example_loss:
          losses.append(
              sequence_loss_by_example(
                  outputs[-1],
                  targets[:bucket[1]],
                  weights[:bucket[1]],
                  softmax_loss_function=softmax_loss_function))
        #否则调用sequence_loss,对上面的结果进行求和,losses添加的是一个值
        else:
          losses.append(
              sequence_loss(
                  outputs[-
  • 9
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值