tensorflow-seq2seq-Attention

初学tensorflow, 发现网上坑太多了,各种版本的滞后,一下代码这样写,一下代码那样写~~很多都是老的书写方式! 走了许许多多的弯路,所以想整理一下最新的版本来梳理一下,也可以自己未来回顾的时候有个参考!

注: 现在最新版本是 tensorflow1.9 的版本, 我自己安装的是 1.8的! 所以本文代码肯定适用1.8版本!  1.9应该也适用~~并没有多少改动这方面的代码

tensorflow是什么么,基础的概念我就不重复了,一点都不懂的请去看基础教程自己学习! 本文主要以seq2seq 做NLP方面的讲解, 如果你也跟我一样是从  普通神经网络--卷积神经网络--循环神经网络的步骤走过来的话, 一些基本概念应该也都能明白

做初级的 NLP 主要分为以下几个步骤:

1. sample 文本数据的处理
2. seq2seq encoder
3. seq2seq decoder
4. train model
5. prediciting
 

本文跳过文本处理的代码阶段,主要讲 encoder decoder的构建方面, 文本方面的处理会稍微带过一下

一、文本处理

首先你需要有一些sample的数据,可能是英文、中文等等各种文本,里面也可能有各种符号等等

比如聊天机器人的文本, 第一行是问,第二行是答, 第三行是问,第四行是答 这样依此类推下去, 你要做的就是讲这个文本进行处理,无论使用 python的基本语法还是用一些python工具模块 nltk 等来处理文本,最终要讲文本处理成  input_source , output_target这样两个大类,  input_source 就是所有问的语句 一行代表一句问.   output_target就是所有答的语句 也是一行一句答. 类似于基本的神经网络里  X,  Y 这样的数据! 当然分好后,你还要对input_source output_target做一些处理 比如取掉一些没用的特殊字符等,就是要保留纯英文(中午)等,还有记得去除每个行尾巴的 '\n'符号

这时候 你经过处理的到了纯净的 input_source, output_target  然后你就要用这两个内容去做 word_to_int   int_to_word这两个方面的转换了 source 和target都要做这方面的转换  然后你的到 input_source_int    output_target_int 类似这样的 纯数字的东西~~ 也是一样的 一行就代表一个sample 一个target 这样的数据就可以 用到 encoder decoder里面了!

文本处理方面不是本文重点,只是讲了大概要做的处理 最终就是讲文本变成 [1,32,4123,4121,53,56,78......] 类似这样的东西 因为每句话的长度大小都不一定,以及一些其他问题 所以这时候就会有 <PAD>, <UNK>,<EOS>,<GO>这些特殊的符号也要加入进来 当然这些特殊的符号也是转换成特定的数字加入到 input_source_int    output_target_int里面来! 详细的操作代码方面,我下回有空了整理一下 专门开一篇文章来介绍如何处理.

二、seq2seq encoder

seq2seq 的原理本文就不介绍了,相信有太多的文章会很详细的介绍,然后画一大堆图以及数学公式来告诉你如何实现, 那些数学公式基本上数学差的人是看不懂的,看懂了也记得不~~~ 所以本文主要从代码方面介绍, 详细的理论方面请google~~~

seq2seq 分为两大部分, 一个是encoder部分 另一个是decoder部分,我们先讲encoder部分:

def get_encoder_layer(input_data, source_vocab_size,encoding_embedding_size,rnn_size,source_sequence_length, num_layers):
    encoder_embed_input = tf.contrib.layers.embed_sequence(input_data, source_vocab_size, encoding_embedding_size)

    def get_signle_cell(rnn_size):
        cell = tf.contrib.rnn.LSTMCell(rnn_size)
        return cell

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

    encoder_output,encoder_state = tf.nn.dynamic_rnn(rnn_cell,encoder_embed_input,source_sequence_length, dtype=tf.float32)

    return encoder_output, encoder_state

上面这部分代码就是encoder层的全部代码了~~ 各个参数当然是之前定义好的 我这里讲解一下

input_data  就是通过batch的 input_source_int
encoding_embedding_size  是embedding层的size 至于什么是embedding 请google
source_vocab_size 是 source的词汇量的大小, 记住 是处理过的 没有重复的source词汇量大小 不是input_source_int的大小哦
source_sequence_length 输入一行的长度,并不固定,所以source_sequence_length 会用tf.placeholder来定义
num_layers 隐藏层的层数
rnn_size 一个隐藏层里的神经但愿 unit cell

以上这些参数都可以自己定义大小,我在刚学习的时候非常不了解为什么这个是2啊 那个是128之类的~~  不过一般都是2的次方或者倍数!

embed_sequence() 也可以用 embedding_nslookup 等函数来替换, 有兴趣的朋友可以自己尝试一下,参考decoder_layer 的代码

以上就是seq2seq的encoder层了, 不过这只是 basic encoder 就是最基本的encoder 常用于处理 你的source_sequence_length 在20以下的, 因为20以上的效果就不好了, 说的通俗一点就是 你把机器当作人, 一句话,短的你很快就背下来了,如果太长你可能背到后面就忘记前面说的是什么了! 详细的理论请google! 所以如果你要处理长的语句怎么办呢???   当然有办法,那就是在encoder层用
bidirectional_dynamic_rnn() 来替代dynamic_rnn()  这两个函数详细代码的差别就不说了,可以看tensorflow的官方文档!

直接上例子:

def get_encoder_layer(input_data, rnn_size, num_layers,
                   source_sequence_length, source_vocab_size,
                   encoding_embedding_size):
    # 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

    cell_fw = get_lstm_cell(rnn_size)
    cell_bw = get_lstm_cell(rnn_size)
    (encoder_output, encoder_state) = tf.nn.bidirectional_dynamic_rnn(cell_fw, cell_bw, encoder_embed_input,sequence_length=source_sequence_length,dtype=tf.float32)

    return encoder_output, encoder_state

大家看代码也大致明白了, 所谓双向就是 fw 然后还有个bw~~ 无论是fw还是bw 都是一个lstm_cell 这里主要说一下 return值, 跟dynamic_rnn的区别就是  双向的return encoder_output, encoder_state 也都是一个tupple 所以有时候也写成以下的方式:

((encoder_output_fw,encoder_output_bw), (encoder_state_fw, encoder_state_bw)) = tf.nn.bidirectional_dynamic_rnn(cell_fw, cell_bw, encoder_embed_input,sequence_length=source_sequence_length,dtype=tf.float32)                                                        

以上就是 basic seq2seq encoder 以及 加了 bidirectional_dynamic_rnn的 seq2seq encoder 的代码! 大家可以看出来 encoder还是比较简单的.  大部分操作还是在decoder上!

三、seq2seq decoder

decoder需要用到 encoder layer的return值! 不多说,先上代码,同样的我们先上basic decoder的代码

def decoding_layer(output_target_int, decoding_embedding_size, num_layers, rnn_size, target_sequence_length, max_target_sequence_length, encoder_state, decoder_input):
    target_vocab_size = len(output_target_int)
    decoder_embedding = tf.Variable(tf.random_uniform([target_vocab_size, decoding_embedding_size]))
    decoder_embed_input = tf.nn.embedding_lookup(decoder_embedding, decoder_input)

    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)])

    output_layer = tf.layers.Dense(target_vocab_size, kernel_initializer=tf.truncated_normal_initializer(mean=0.0, stddev=0.1))

    with tf.variable_scope("decode"):
        training_helper = tf.contrib.seq2seq.TrainingHelper(inputs=decoder_embed_input, sequence_length=target_sequence_length, time_major=False)
        training_decoder = tf.contrib.seq2seq.BasicDecoder(cell, training_helper, encoder_state, output_layer)
        train_decoder_output,_,_ = tf.contrib.seq2seq.dynamic_decode(decoder=training_decoder, output_time_major=False,
                                                                       impute_finished=True, maximum_iterations=max_target_sequence_length)

    
    return train_decoder_output

get_decoder_cell 跟encoder的get_signle_cell 功能一样 都是的到一个定义好的 lstm_cell.
decoder可没有所谓的双向, 他就fw方向,  前半部分都跟encoder一样 通过MultiRNNCell 后的到 一个多层的cell  !
然后再做Dense 根据target_vocab_size的大小进行展开. ok到这里后 就要引入decoder的Helper类了, 我刚开始学习的时候一直以为这个是个帮助类,Helper嘛,所以一直没想明白这个类干嘛用~~ 基本的概念你们可以看官方文档,总之你在用decoder的时候要定义一个Helper类 helper类分三种
1. TrainingHelper
2. GreedyEmbeddingHelper
3. CustomHelper

具体的区分就是 TrainingHelper 主要是在你train model的时候用, GreedyEmbeddingHelper 主要是预测的时候使用, CustomHelper就比较难,你要自己定义三个函数来作为它的参数,这里就不讲了,感兴趣的朋友可以google! 因为我们现实train 所以用的是TrainingHelper类  定义好后, 那么使用BasicDecoder()的参数都齐全了 先BasicDecoder后 再Dynamic_decode() 这里要重点提一下 Dynamic_decode 函数的return  他返回的是 三个值, (final_outputs, final_state, final_sequence_lengths) 所以你需要

train_decoder_output,_,_ 来获取~~ 很多其他的文章基于老版本包括google官方的例子都是这样 train_decoder_output,_ 来获取数据 所以很多用新版本的人会报错~~然后你查官方手册给你的还是错误的例子!!! 很坑

这里的train_decoder_output 有两个值分别是 rnn_output,  sample_id  你可以直接用train_decoder_output.rnn_output 等方式来调用

以上就将seq2seq 的basic decoder写好了, 其实也很简单对吧,你只要记住各个函数的调用步骤跟方式,以及各个参数的含义就可以了! 当然有basic版本自然就会有进阶版本了! 下面还是直接上例子,给出加了Attention的decoder版本! 至于为什么要加Attention前面我也说过了 详细的理论可以google哈, 本文不讲理论,我觉得你先会用了大致明白了再去回过头看理论的东西,你会更加理解其中的意义!

上代码:

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)])

    attension_menchian = tf.contrib.seq2seq.LuongAttention(rnn_size, encoder_output, source_sequence_length)
    decoder_cell = tf.contrib.seq2seq.AttentionWrapper(cell, attension_menchian, rnn_size)
    decoder_initial_state = decoder_cell.zero_state(batch_size=batch_size, dtype=tf.float32).clone(cell_state=encoder_final_state)
    #decoder_initial_state = decoder_cell.zero_state(batch_size=batch_size, dtype=tf.float32)


    # 3. Output全连接层
    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对象
        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(decoder_cell,
                                                           training_helper,
                                                           decoder_initial_state,
                                                           output_layer)
        training_decoder_output, _, _ = tf.contrib.seq2seq.dynamic_decode(training_decoder,
                                                                       impute_finished=True,
                                                                       maximum_iterations=max_target_sequence_length)

    return training_decoder_output

ok 以上代码我们只讲区别部分, 其实要加入一个基本的Attentiion也简单, 你首先建立一个 AttentionMechanism, 然后再进行AttentionWrapper,并对Wrapper后的cell做初始化, 这里的初始化则采用之前encoder layer的return 里的state进行初始化. 其他的都跟basic decoder的没有区别. 这里 AttentionMechanism 我用的是LuongAttention, 当然你也可以用BahdanauAttention! 性质是一样的! 

注:
这里有点需要注意的是 如果你encoder采用的是basic encoder 则 decoder部分用basic和 attention都没有问题, 用attention明显会比basic的效率高很多, 如果你encoder采用了bidirectional 则 decoder部分用attention的时候要注意  encoder的return要做些处理,比如 encoder_output=tf.concat(encoder_output, 2) 这个函数也是跟老版本的有变化, 老版本2在前面,需要注意一下! encoder_state 也要做处理 不能直接套用!

以上部分就讲decoder讲完了, 那么接下来自然学习过basic nn 以及 CNN的人都知道 要做loss函数以及优化了

四、LossFunction and Optmization

lossfunction有哪几种这里就不说了,可以自己google常用的lossfunction 这里我们可以使用seq2seq的sequence_loss来生成lossfunction

    training_logits = tf.identity(training_decoder_output.rnn_output, 'logits')
    masks = tf.sequence_mask(target_sequence_length, max_target_sequence_length, dtype=tf.float32, name='masks')

    with tf.name_scope("optimization"):

        # Loss function
        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)
        '''
        #simple minimize Optimizer
        train_op = tf.train.AdamOptimizer(lr).minimize(cost)

sequence_loss() 可以帮你是生成cost 还记得前面说过的 rnn_output嘛? 这里用到了,  生成logits 加入到sequence_loss里 targets就是output_target_int 这个变量值. 的到cost函数后  有点点基础的人都知道最简单的办法就是 AdamOptimizer(lr).minimize(cost) 这个就是 梯度下降的优化, 上面被我注释掉的代码跟最下面一行的minimize一样作用 只不过更加有效果而已! 这等同于 Helper里的 CustomHelper类一样 自己分布实现minimize!

好了上面train部分基本的核心讲解基本就结束了. 下面自然就是 什么 with tf.Session() as sess: sess.run() 一大堆东西了,这些就不讲了 跟其他的nn没有什么区别~有兴趣的朋友可以自己完成一下!

注:
以上部分代码来至于 知乎 机器不学习 大家可以去关注一下
https://github.com/justinli909/zhihu/blob/master/basic_seq2seq/Seq2seq_char.ipynb  


这是他的basic seq2seq的完整代码 虽然没有 bidirectional 和attention 不过你可以根据我前面的代码块去自己补全. 如果你单独添加attention 效率会提高很多! 另他的代码版本tensorflow较低所以如果你跑不起来可以参考我的代码来更改!

猜你喜欢

转载自blog.csdn.net/weixin_42724775/article/details/81094429