Tensorflow 自动文摘: 基于Seq2Seq+Attention模型的Textsum模型

Github下载完整代码

https://github.com/rockingdingo/deepnlp/tree/master/deepnlp/textsum


简介

这篇文章中我们将基于Tensorflow的Seq2Seq+Attention模型,介绍如何训练一个中文的自动生成新闻标题的模型。自动总结(Automatic Summarization)类型的模型一直是研究热点。 直接抽出重要的句子的抽取式方法较为简单,有如textrank之类的算法,而生成式(重新生成新句子)较为复杂,效果也不尽如人意。目前比较流行的Seq2Seq模型,由 Sutskever等人提出,基于一个Encoder-Decoder的结构将source句子先Encode成一个固定维度d的向量,然后通过Decoder部分一个字符一个字符生成Target句子。添加入了Attention注意力分配机制后,使得Decoder在生成新的Target Sequence时,能得到之前Encoder编码阶段每个字符的隐藏层的信息向量Hidden State,使得生成新序列的准确度提高。

数据准备和预处理

我们选择公开的“搜狐新闻数据(SogouCS)”的语料,包含2012年6月—7月期间的新闻数据,超过1M的语料数据,包含新闻标题和正文的信息。数据集可以从搜狗lab下载。 http://www.sogou.com/labs/resource/cs.php

数据的预处理阶段极为重要,因为在Encoder编码阶段处理那些信息,直接影响到整个模型的效果。我们主要对下列信息进行替换和处理:

  • 特殊字符:去除特殊字符,如:“「,」,¥,…”;
  • 括号内的内容:如表情符,【嘻嘻】,【哈哈】
  • 日期:替换日期标签为TAG_DATE,如:***年*月*日,****年*月,等等
  • 超链接URL:替换为标签TAG_URL;
  • 删除全角的英文:替换为标签TAG_NAME_EN;
  • 替换数字:TAG_NUMBER;

在对文本进行了预处理后,准备训练语料: 我们的Source序列,是新闻的正文,待预测的Target序列是新闻的标题。 我们截取正文的分词个数到MAX_LENGTH_ENC=120个词,是为了训练的效果正文部分不宜过长。标题部分截取到MIN_LENGTH_ENC = 30,即生成标题不超过30个词。

在data_util.py类中,生成训练数据时做了下列事情:

  • create_vocabulary()方法创建词典;
  • data_to_token_ids()方法把训练数据(content-train.txt)转化为对应的词ID的表示;
两个文件的格式:
[python]  view plain  copy
  1. # 数据1 正文 content-train.txt  
  2. 世间 本 没有 歧视 TAG_NAME_EN 歧视 源自于 人 的 内心 活动 TAG_NAME_EN “ 以爱 之 名 ” TAG_DATE 中国 艾滋病 反歧视 主题 创意 大赛 开幕 TAG_NAME_EN 让 “ 爱 ” 在 高校 流动 。 TAG_NAME_EN 详细 TAG_NAME_EN   
  3. 济慈 之 家 小朋友 感受 爱心 椅子  TAG_DATE TAG_NAME_EN 思源 焦点 公益 基金 向 盲童 孤儿院 “ 济慈 之 家 ” 提供 了 首 笔 物资 捐赠 。 这 笔 价值 近 万 元 的 物资 为 曲 美 家具 向 思源 · 焦点 公益 基金 提供 的 儿童 休闲椅 TAG_NAME_EN 将 用于 济慈 之 家 的 小孩子们 日常 使用 。   
  4. ...  
[python]  view plain  copy
  1. # 数据2 标题 title-train.txt  
  2. 艾滋病 反歧视 创意 大赛   
  3. 思源 焦点 公益 基金 联手 曲 美 家具 共 献 爱心   
  4. ...  

训练模型

[python]  view plain  copy
  1. #代码1  
  2. python headline.py  

预测
      运行predict.py, 交互地输入分好词的文本, 得到textsum的结果
[python]  view plain  copy
  1. #代码2-1  
  2. python predict.py  
  3.    
  4. # 输入和输出  
  5. #> 中央 气象台 TAG_DATE TAG_NUMBER 时 继续 发布 暴雨 蓝色 预警 TAG_NAME_EN 预计 TAG_DATE TAG_NUMBER 时至 TAG_DATE TAG_NUMBER 时 TAG_NAME_EN 内蒙古 东北部 、 山西 中 北部 、 河北 中部 和 东北部 、 京津 地区 、 辽宁 西南部 、 吉林 中部 、 黑龙江 中部 偏南 等 地 的 部分 地区 有 大雨 或 暴雨 。  
  6. #current bucket id0  
  7. #中央 气象台 发布 暴雨 蓝色 预警  
  8. #>  

我们尝试输入下列分好词的新闻正文,一些挑选过的自动生成的中文标题如下:

ID 新闻正文 新闻标题 textsum自动生成标题
469 中央 气象台 TAG_DATE TAG_NUMBER 时 继续 发布 暴雨 蓝色 预警 TAG_NAME_EN 预计 TAG_DATE TAG_NUMBER 时至 TAG_DATE TAG_NUMBER 时 TAG_NAME_EN 内蒙古 东北部 、 山西 中 北部 、 河北 中部 和 东北部 、 京津 地区 、 辽宁 西南部 、 吉林 中部 、 黑龙江 中部 偏南 等 地 的 部分 地区 有 大雨 或 暴雨 。 中央 气象台 继续 发布 暴雨 预警 北京 等 地 有 大雨 中央 气象台 发布 暴雨 蓝色 预警
552 美国 科罗拉多州 山林 大火 持续 肆虐 TAG_NAME_EN 当地 时间 TAG_DATE 横扫 州 内 第二 大 城市 科罗拉多斯 普林斯 一 处 居民区 TAG_NAME_EN 迫使 超过 TAG_NUMBER TAG_NAME_EN TAG_NUMBER 万 人 紧急 撤离 。 美国 正 值 山火 多发 季 TAG_NAME_EN 现有 TAG_NUMBER 场 山火 处于 活跃 状态 。 山火 横扫 美 西部 TAG_NUMBER 州 奥 巴马 将 赴 灾区 视察 联邦 调查局 介入 查 原因 美国 科罗拉多州 山火 致 TAG_NUMBER 人 死亡
917 埃及 选举 委员会 昨天 宣布 TAG_NAME_EN 穆斯林 兄弟会 下属 自由 与 正义党 主席 穆尔西 获得 TAG_NUMBER TAG_NAME_EN TAG_NUMBER TAG_NAME_EN 的 选票 TAG_NAME_EN 以 微弱 优势 击败 前 总理 沙 菲克 赢得 选举 TAG_NAME_EN 成为 新任 埃及 总统 。 媒体 称 其 理念 获 下层 民众 支持 。 埃及 大选 昨晚 结束 新 总统 穆尔西 被 认为 具有 改革 魄力 埃及 总统 选举 结果
920 上 周 TAG_NAME_EN 广东 华兴 银行 在 央行 宣布 降息 和 调整 存贷款 波幅 的 第二 天 TAG_NAME_EN 立即 宣布 首 套 房贷 利率 最低 执行 七 折 优惠 。 一 石 激起 千层 浪 TAG_NAME_EN 随之 而 起 的 “ 房贷 七 折 利率 重 出 江湖 ” 和 “ 房地产 调控 松绑 ” 的 谣言 四起 。 房贷 “ 七 折 利率 ” 真相 调查 TAG_NAME_EN 符合 条件 的 几乎 为零 银监会 否认 房贷 房贷 利率

预测并计算ROUGE评估

运行predict.py, 同时调用eval.py 中的 evaluate(X, Y, method = "rouge_n", n = 2) 方法计算ROUGE分


[python]  view plain  copy
  1. #代码2-2 linux shell  
  2. folder_path=`pwd`  
  3. input_dir=${folder_path}/news/test/content-test.txt  
  4. reference_dir=${folder_path}/news/test/title-test.txt  
  5. summary_dir=${folder_path}/news/test/summary.txt  
  6.    
  7. python predict.py $input_dir $reference_dir $summary_dir  
  8.    
  9. # 输出:  
  10. # 中央 气象台 发布 暴雨 蓝色 预警  
  11. # Evaludated Rouge-2 score is 0.1818  
  12. # ...  


下面我们将具体介绍tensorflow的seq2seq模型如何实现,首先先简单回顾模型的结构。

Seq2Seq+Attention模型回顾

Seq2Seq模型有效地建模了基于输入序列,预测未知输出序列的问题。模型有两部分构成,一个编码阶段的”Encoder”和一个解码阶段的”Decoder”。如下图的简单结构所示,Encoder的RNN每次输入一个字符代表的embedding向量,如依次输入A,B,C, 及终止标志,将输入序列编码成一个固定长度的向量;之后解码阶段的RNN会一个一个字符地解码, 如预测为X, 之后在训练阶段会强制将前一步解码的输出作为下一步解码的输入,如X会作为下一步预测Y时的输入。


图1 Seq2Seq model

定义输入序列  ,由Tx个固定长度为d的向量构成; 输出序列为  ,由Ty个固定长度为d的向量构成; 定义输入序Encoder阶段的RNN隐藏层为 hj, Decoder阶段的RNN隐藏层为 Si

Attention注意力分配机制

LSTM模型虽然具有记忆性,但是当Encoder阶段输入序列过长时,解码阶段的LSTM也无法很好地针对最早的输入序列解码。基于此,Attention注意力分配的机制被提出,就是为了解决这个问题。在Decoder阶段每一步解码,都能够有一个输入,对输入序列所有隐藏层的信息h_1,h_2,…h_Tx进行加权求和。打个比方就是每次在预测下一个词时都会把所有输入序列的隐藏层信息都看一遍,决定预测当前词时和输入序列的那些词最相关。

Attention机制代表了在解码Decoder阶段,每次都会输入一个Context上下文的向量Ci, 隐藏层的新状态Si根据上一步的状态Si-1, Yi, Ci 三者的一个非线性函数得出。


Context向量在解码的每一步都会重新计算,根据一个MLP模型计算出输出序列i对每个输入序列j的隐含层的对应权重aij,并对所有隐含层加权平均。文章中说的Alignment Model就是代表这种把输入序列位置j和输出序列位置i建立关系的模型。


aij 即可以理解为Decoder解码输出序列的第i步,对输入序列第j步分配的注意力权重。



eij为一个简单的MLP模型激活的输出;aij的计算是对eij做softmax归一化后的结果。


图2 Translate Seq2Seq alignment model
Soft Attention和Hard Attention区别

Soft Attention通常是指以上我们描述的这种全连接(如MLP计算Attention 权重),对每一层都可以计算梯度和后向传播的模型;不同于Soft attention那样每一步都对输入序列的所有隐藏层hj(j=1….Tx) 计算权重再加权平均的方法,Hard Attention是一种随机过程,每次以一定概率抽样,以一定概率选择某一个隐藏层 hj*,在估计梯度时也采用蒙特卡罗抽样Monte Carlo sampling的方法。



图3 Soft Attention 模型




图4 Hard Attention 模型

模型实现

我们对Tensorflow基本教程里的translate英语法语翻译例子里的seq2seq_model.py类稍加修改,就能够符合我们textsum例子使用,另外我们还会分析针对英文的textsum教程中构建双向Bi-LSTM的Encoder-Decoder的例子。

1.Seq2Seq模型文件: seq2Seq_model.py

单向LSTM的Encoder-Decoder结构

教程中的例子很长,但是将实例代码分解来看不是那么复杂,下面将分三段来介绍官方tutorial里的如何构建seq2seq模型。

定义基本单元: 多层LSTM cell

[python]  view plain  copy
  1. #代码3-1  
  2. # Create the internal multi-layer cell for our RNN.  
  3. single_cell = tf.nn.rnn_cell.GRUCell(size) # default use GRU  
  4. if use_lstm:  
  5.   single_cell = tf.nn.rnn_cell.BasicLSTMCell(size, state_is_tuple=True)  
  6. cell = single_cell  
  7. if num_layers > 1:  
  8.   cell = tf.nn.rnn_cell.MultiRNNCell([single_cell] * num_layers, state_is_tuple=True)  

定义前向过程的 seq2seq_f 函数,利用tf.nn.seq2seq.embedding_attention_seq2seq 返回output

[python]  view plain  copy
  1. # 代码3-2  
  2. # The seq2seq function: we use embedding for the input and attention.  
  3. def seq2seq_f(encoder_inputs, decoder_inputs, do_decode):  
  4.   return tf.nn.seq2seq.embedding_attention_seq2seq(  
  5.       encoder_inputs,  
  6.       decoder_inputs,  
  7.       cell,  
  8.       num_encoder_symbols=source_vocab_size,  
  9.       num_decoder_symbols=target_vocab_size,  
  10.       embedding_size=size,  
  11.       output_projection=output_projection,  
  12.       feed_previous=do_decode,  
  13.       dtype=tf.float32)  

桶Bucket的机制: 应用Bucket机制,核心的思想是把输入序列的句子按照长度的相似程度分到不同的固定长度的Bucket里面,长度不够的都添加PAD字符。之所以有Bucket的原因是工程效率:”RNN在数学上是可以处理任意长度的数据的。我们在TensorFlow中使用bucket的原因主要是为了工程实现的效率” (摘自知乎JQY的回答https://www.zhihu.com/question/42057513)


[python]  view plain  copy
  1. # 代码3-3  
  2. # Training outputs and losses.  
  3. if forward_only:  
  4.   self.outputs, self.losses = tf.nn.seq2seq.model_with_buckets(  
  5.       self.encoder_inputs, self.decoder_inputs, targets,  
  6.       self.target_weights, buckets, lambda x, y: seq2seq_f(x, y, True),  
  7.       softmax_loss_function=softmax_loss_function)  
  8.   # If we use output projection, we need to project outputs for decoding.  
  9.   if output_projection is not None:  
  10.     for b in xrange(len(buckets)):  
  11.       self.outputs[b] = [  
  12.           tf.matmul(output, output_projection[0]) + output_projection[1]  
  13.           for output in self.outputs[b]  
  14.       ]  
  15. else:  
  16.   self.outputs, self.losses = tf.nn.seq2seq.model_with_buckets(  
  17.       self.encoder_inputs, self.decoder_inputs, targets,  
  18.       self.target_weights, buckets, lambda x, y: seq2seq_f(x, y, False),  
  19.       softmax_loss_function=softmax_loss_function)  

tf.nn.seq2seq.model_with_buckets()结果返回output序列;’ buckets = [(120, 30), (200, 35), (300, 40), (400, 40), (500, 40)]

https://github.com/tensorflow/tensorflow/blob/64edd34ce69b4a8033af5d217cb8894105297d8a/tensorflow/contrib/legacy_seq2seq/python/ops/seq2seq.py
双向LSTM的Encoder-Decoder结构

Encoder阶段自己定义了Bi-LSTM结构

[python]  view plain  copy
  1. # 代码4-1  
  2. # URL: https://github.com/tensorflow/models/tree/master/textsum  
  3. # Encoder: Multi-Layer LSTM, Output: encoder_outputs  
  4.   for layer_i in xrange(hps.enc_layers):  
  5.     with tf.variable_scope('encoder%d'%layer_i), tf.device(  
  6.         self._next_device()):  
  7.       cell_fw = tf.nn.rnn_cell.LSTMCell(  
  8.           hps.num_hidden,  
  9.           initializer=tf.random_uniform_initializer(-0.10.1, seed=123),  
  10.           state_is_tuple=False)  
  11.       cell_bw = tf.nn.rnn_cell.LSTMCell(  
  12.           hps.num_hidden,  
  13.           initializer=tf.random_uniform_initializer(-0.10.1, seed=113),  
  14.           state_is_tuple=False)  
  15.       (emb_encoder_inputs, fw_state, _) = tf.nn.bidirectional_rnn(  
  16.           cell_fw, cell_bw, emb_encoder_inputs, dtype=tf.float32,  
  17.           sequence_length=article_lens)  
  18.   encoder_outputs = emb_encoder_inputs  
  19.    
  20.   with tf.variable_scope('output_projection'):  
  21.     w = tf.get_variable(  
  22.         'w', [hps.num_hidden, vsize], dtype=tf.float32,  
  23.         initializer=tf.truncated_normal_initializer(stddev=1e-4))  
  24.     w_t = tf.transpose(w)  
  25.     v = tf.get_variable(  
  26.         'v', [vsize], dtype=tf.float32,  
  27.         initializer=tf.truncated_normal_initializer(stddev=1e-4))  
  28. Decoder阶段,利用内置的tf.nn.seq2seq.attention_decoder()函数返回output  

[python]  view plain  copy
  1. # 代码4-2  
  2.   with tf.variable_scope('decoder'), tf.device(self._next_device()):  
  3.     # When decoding, use model output from the previous step  
  4.     # for the next step.  
  5.     loop_function = None  
  6.     if hps.mode == 'decode':  
  7.       loop_function = _extract_argmax_and_embed(  
  8.           embedding, (w, v), update_embedding=False)  
  9.    
  10.     cell = tf.nn.rnn_cell.LSTMCell(  
  11.         hps.num_hidden,  
  12.         initializer=tf.random_uniform_initializer(-0.10.1, seed=113),  
  13.         state_is_tuple=False)  
  14.    
  15.     encoder_outputs = [tf.reshape(x, [hps.batch_size, 12*hps.num_hidden])  
  16.                        for x in encoder_outputs]  
  17.     self._enc_top_states = tf.concat(1, encoder_outputs)  
  18.     self._dec_in_state = fw_state  
  19.     # During decoding, follow up _dec_in_state are fed from beam_search.  
  20.     # dec_out_state are stored by beam_search for next step feeding.  
  21.     initial_state_attention = (hps.mode == 'decode')  
  22.     decoder_outputs, self._dec_out_state = tf.nn.seq2seq.attention_decoder(  
  23.         emb_decoder_inputs, self._dec_in_state, self._enc_top_states,  
  24.         cell, num_heads=1, loop_function=loop_function,  
  25.         initial_state_attention=initial_state_attention)  

Attention注意力矩阵的可视化

Attention 注意力分配机制的权重矩阵[Aij]可以反映在Decoder阶段第i个输出字符对Encoder阶段的第j个字符的注意力分配的权重aij。 我们可以通过绘制Heatmap来可视化seq2seq模型中Decoder的Y对Encoder的X每个字符的权重。

获取attention_mask的值

attention_mask 即为我们感兴趣的注意力权重分配的tensor,我们首先来看tensorflow的源码seq2seq.py这个ops的实现, 容易发现,计算attention_mask 变量a的代码出现在 attention_decoder()函数内的attention()函数体下, a = nn_ops.softmax(s) 这句。 我们把该变量添加到return语句中的返回值,同时也修改所有调用了attention_decoder()的上层的函数,为了最终能够在主函数中将attn_mask这个变量抽取出。 具体需要修改的脚本参考textsum项目下的seq2seq_attn.py这个文件。 之后我们在主函数中利用attn_out = session.run(self.attn_masks[bucket_id], input_feed) ,对变量进行session.run() 就可以获得当前这个样本的attention矩阵的值。

[python]  view plain  copy
  1. # 代码5-1  
  2. # URL: https://github.com/rockingdingo/deepnlp/blob/master/deepnlp/textsum/seq2seq_attn.py  
  3.    
  4. def attention_decoder():  
  5.     ## some code  
  6.        
  7.     def attention(query):  
  8.       """Put attention masks on hidden using hidden_features and query."""  
  9.       ds = []  
  10.       if nest.is_sequence(query):  
  11.         query_list = nest.flatten(query)  
  12.         for q in query_list:  
  13.           ndims = q.get_shape().ndims  
  14.           if ndims:  
  15.             assert ndims == 2  
  16.         query = array_ops.concat_v2(query_list, 1)  
  17.       for a in xrange(num_heads):  
  18.         with variable_scope.variable_scope("Attention_%d" % a):  
  19.           y = linear(query, attention_vec_size, True)  
  20.           y = array_ops.reshape(y, [-111, attention_vec_size])  
  21.           # Attention mask is a softmax of v^T * tanh(...).  
  22.           s = math_ops.reduce_sum(v[a] * math_ops.tanh(hidden_features[a] + y),  
  23.                                   [23])  
  24.              
  25.           # Tensor a 即为我们需要抽取的attention_mask  
  26.           a = nn_ops.softmax(s)  
  27.              
  28.           d = math_ops.reduce_sum(  
  29.               array_ops.reshape(a, [-1, attn_length, 11]) * hidden, [12])  
  30.           ds.append(array_ops.reshape(d, [-1, attn_size]))  
  31.       return ds, a  

利用matplotlib可视化

我们利用matplotlib包中绘制heatmap的函数,可以简单地将上一步抽取出的attn_matrix可视化。在eval.py模块中我们整合了一个eval.plot_attention(data, X_label=None, Y_label=None) 函数来简单绘制attention权重矩阵。 运行 predict_attn.py 脚本,输入分好词的待分析新闻文本,然后自动生成的jpg图片就保存在./img目录下。


[python]  view plain  copy
  1. # 代码5-2  
  2. # 输入文本, 查看Attention的heatmap:  
  3. # > 中央 气象台 TAG_DATE TAG_NUMBER 时 继续 发布 暴雨 蓝色 预警 TAG_NAME_EN 预计 TAG_DATE TAG_NUMBER 时至 TAG_DATE TAG_NUMBER 时 TAG_NAME_EN 内蒙古 东北部 、 山西 中 北部 、 河北 中部 和 东北部 、 京津 地区 、 辽宁 西南部 、 吉林 中部 、 黑龙江 中部 偏南 等 地 的 部分 地区 有 大雨 或 暴雨 。  
  4.    
  5. python predict_attn.py  





图5 Attention注意力权重分配的Heatmap

可视化部分代码参考项目中的 predict_attn.py seq2seq_attn.py seq2seq_model_attn.py 这三个文件。

预测阶段Decode策略: 贪心算法和Beam Search
贪心算法   Beam Search

拓展阅读

对tensorflow 中的attention encoder-decoder模型调试分析

复制代码
  1 #-*-coding:utf8-*-
  2 
  3 __author = "buyizhiyou"
  4 __date = "2017-11-21"
  5 
  6 
  7 import random, time, os, decoder
  8 from PIL import Image
  9 import numpy as np
 10 import tensorflow as tf
 11 import pdb
 12 import decoder
 13 import random
 14 
 15 '''
 16 在汉字ocr项目中,利用基于attention的encoder-decoder(seq2seq)模型进行端对端的训练
 17 单步调试,追踪tensorflow 对 attention-seq2seq模型的实现方式
 18 python 中seq2seq.py的接口:tf.nn.seq2seq.embedding_attention_seq2seq()
 19 把用到的部分取出来单独调试
 20 '''
 21 
 22 batch_size = 16
 23 dec_seq_len = 8#图片对应的汉字数8
 24 enc_lstm_dim = 256
 25 dec_lstm_dim = 512
 26 vocab_size = 1002
 27 embedding_size = 100
 28 lr = 0.01
 29 global_step = tf.Variable(0)
 30 
 31 cnn = tf.truncated_normal([16,10,35,64],mean=0,stddev=1.0,dtype=tf.float32)#模拟初始化一个cnn提取特征后的图片
 32 #(batch_size,height,width,channels)(16, 10, 35, 64)
 33 true_labels = []
 34 #随即生成batch中图片对应的序列,无需embedding
 35 for i in range(batch_size):
 36     seq_label = []
 37     for j in range(dec_seq_len):
 38         seq_label.append(random.randint(0,1000))
 39     true_labels.append(seq_label)
 40 
 41 
 42 #编码
 43 def encoder(inp):#inp:shape=(16, 35, 64)
 44     #pdb.set_trace()
 45     enc_init_shape = [batch_size, enc_lstm_dim]#[16,256]
 46     with tf.variable_scope('encoder_rnn'):
 47         with tf.variable_scope('forward'):
 48             lstm_cell_fw = tf.nn.rnn_cell.LSTMCell(enc_lstm_dim)
 49             init_fw = tf.nn.rnn_cell.LSTMStateTuple(\
 50                                 tf.get_variable("enc_fw_c", enc_init_shape),\
 51                                 tf.get_variable("enc_fw_h", enc_init_shape)
 52                                 )
 53         with tf.variable_scope('backward'):
 54             lstm_cell_bw = tf.nn.rnn_cell.LSTMCell(enc_lstm_dim)
 55             init_bw = tf.nn.rnn_cell.LSTMStateTuple(\
 56                                 tf.get_variable("enc_bw_c", enc_init_shape),\
 57                                 tf.get_variable("enc_bw_h", enc_init_shape)
 58                                 )
 59         output, _ = tf.nn.bidirectional_dynamic_rnn(lstm_cell_fw, \
 60                                                     lstm_cell_bw, \
 61                                                     inp, \
 62                                                     sequence_length = tf.fill([batch_size],\
 63                                                     tf.shape(inp)[1]), #(35,35,35...,35,35,35)
 64                                                     initial_state_fw = init_fw, \
 65                                                     initial_state_bw = init_bw \
 66                                                     )#shape=(16, 35, 256)
 67     return tf.concat(2,output)##shape=(16, 35, 512)
 68 
 69 encoder = tf.make_template('fun', encoder)
 70 # shape is (batch size, rows, columns, features)
 71 # swap axes so rows are first. map splits tensor on first axis, so encoder will be applied to tensors
 72 # of shape (batch_size,time_steps,feat_size)
 73 rows_first = tf.transpose(cnn,[1,0,2,3])#shape=(10, 16, 35, 64)
 74 res = tf.map_fn(encoder, rows_first, dtype=tf.float32)#shape=(10, 16, 35, 512)
 75 encoder_output = tf.transpose(res,[1,0,2,3])#shape=(16, 10, 35, 512)
 76 
 77 dec_lstm_cell = tf.nn.rnn_cell.LSTMCell(dec_lstm_dim)
 78 dec_init_shape = [batch_size, dec_lstm_dim]
 79 dec_init_state = tf.nn.rnn_cell.LSTMStateTuple( tf.truncated_normal(dec_init_shape),\
 80                                                 tf.truncated_normal(dec_init_shape) )
 81 
 82 init_words = np.zeros([batch_size,1,vocab_size])#(16, 1, 1002)
 83 
 84 
 85 #pdb.set_trace()
 86 (output,state) = decoder.embedding_attention_decoder(dec_init_state,#[16, 512]第一个解码cell的state=[c,h]
 87                                                     tf.reshape(encoder_output,[batch_size, -1,2*enc_lstm_dim]),
 88                                                     #encoder输出reshape为 attention states作为attention模块的输入 shape=(16,350,512)
 89                                                     dec_lstm_cell,#lstm单元,作为解码层
 90                                                     vocab_size,#1002
 91                                                     dec_seq_len,#8
 92                                                     batch_size,#16
 93                                                     embedding_size,#100
 94                                                     feed_previous=True)#dec_seq_len = num_words = time_steps
 95 pdb.set_trace()
 96 cross_entropy = tf.reduce_mean(tf.nn.sparse_softmax_cross_entropy_with_logits(output,true_labels))
 97 learning_rate = tf.train.exponential_decay(lr, global_step, 50, 0.9)
 98 train_step = tf.train.AdamOptimizer(learning_rate).minimize(cross_entropy,global_step=global_step)
 99 correct_prediction = tf.equal(tf.to_int32(tf.argmax( output, 2)), true_labels)
100                                                
复制代码

decode.py

复制代码
  1 #-*-coding:utf8-*-
  2 
  3 
  4 """
  5 截取自tensorflow seq2seq.py 文件
  6 """
  7 import numpy as np
  8 import tensorflow as tf
  9 import pdb
 10 from tensorflow.python import shape
 11 from tensorflow.python.framework import dtypes
 12 from tensorflow.python.framework import ops
 13 from tensorflow.python.ops import array_ops
 14 from tensorflow.python.ops import control_flow_ops
 15 from tensorflow.python.ops import embedding_ops
 16 from tensorflow.python.ops import math_ops
 17 from tensorflow.python.ops import nn_ops
 18 from tensorflow.python.ops import rnn
 19 from tensorflow.python.ops import rnn_cell
 20 from tensorflow.python.ops import variable_scope
 21 from tensorflow.python.util import nest
 22 
 23 linear = rnn_cell._linear    # pylint: disable=protected-access
 24 
 25 def attention_decoder(initial_state,#(16, 512)
 26                       attention_states,#shape=(16, 350, 512)
 27                       cell,
 28                       vocab_size,#1002
 29                       time_steps,#num_words,8
 30                       batch_size,#16
 31                       output_size=None,#512
 32                       loop_function=None,
 33                       dtype=None,
 34                       scope=None):
 35     pdb.set_trace()
 36     if attention_states.get_shape()[2].value is None:#tf 张量 get_shape()方法获取size
 37         raise ValueError("Shape[2] of attention_states must be known: %s"
 38                                          % attention_states.get_shape())
 39     if output_size is None:
 40         output_size = cell.output_size#512
 41 
 42     with variable_scope.variable_scope(scope or "attention_decoder", dtype=dtype) as scope:
 43         dtype = scope.dtype
 44 
 45         attn_length = attention_states.get_shape()[1].value #350
 46         if attn_length is None:
 47             attn_length = shape(attention_states)[1]
 48         attn_size = attention_states.get_shape()[2].value#512
 49 
 50         # To calculate W1 * h_t we use a 1-by-1 convolution, need to reshape before.
 51         hidden = array_ops.reshape(attention_states, [-1, attn_length, 1, attn_size])#shape=(16, 350, 1, 512) 
 52         attention_vec_size = attn_size    # Size of query vectors for attention.   512
 53         k = variable_scope.get_variable("AttnW",[1, 1, attn_size, attention_vec_size])#shape=(1,1,512,512)
 54         hidden_features = nn_ops.conv2d(hidden, k, [1, 1, 1, 1], "SAME")#(16 ,350, 1, 512) w_1*h_j
 55         v = variable_scope.get_variable("AttnV", [attention_vec_size])
 56 
 57 
 58         def attention(query):
 59             #LSTMStateTuple(c= shape=(16, 512) dtype=float32>, h=< shape=16, 512) dtype=float32>)
 60             """Put attention masks on hidden using hidden_features and query."""
 61             if nest.is_sequence(query):    # If the query is a tuple, flatten it.
 62                 query_list = nest.flatten(query) #[c,h],第一个随即初始化,以后调用之前计算的
 63                 for q in query_list:    # Check that ndims == 2 if specified.
 64                     ndims = q.get_shape().ndims
 65                     if ndims:
 66                         assert ndims == 2
 67                 query = array_ops.concat(1, query_list)# shape=(16, 1024)
 68             with variable_scope.variable_scope("Attention_0"):
 69                 y = linear(query, attention_vec_size, True)# shape=(16, 512) w_2*s_t
 70                 y = array_ops.reshape(y, [-1, 1, 1, attention_vec_size]) # shape=(16, 1, 1, 512)
 71                 s = math_ops.reduce_sum(
 72                         v * math_ops.tanh(hidden_features + y), [2, 3])  #!!!!!!!!!!!公式(3)shape=(16, 350)
 73                 a = nn_ops.softmax(s)#  公式(2)shape=(16, 350)
 74                 # Now calculate the attention-weighted vector d.
 75                 d = math_ops.reduce_sum(
 76                         array_ops.reshape(a, [-1, attn_length, 1, 1]) * hidden,#公式(1)
 77                         [1, 2])#shape=(16, 512) 
 78                 ds = array_ops.reshape(d, [-1, attn_size])#shape=(16, 512) #!!!!!!!!!!!!以上是attention model中三个关键公式的实现
 79             return ds
 80         #pdb.set_trace()
 81         prev = array_ops.zeros([batch_size,output_size])# shape=(16, 512) cell层第一个cell启动计算所需输入,
 82                                                         #随机初始化,以后的cell调用之前的计算结果
 83         batch_attn_size = array_ops.pack([batch_size, attn_size]) #(2,?)
 84         attn = array_ops.zeros(batch_attn_size, dtype=dtype)#shape=(16, 512)
 85         attn.set_shape([None, attn_size])#(16,512)
 86 
 87         def cond(time_step, prev_o_t, prev_softmax_input, state_c, state_h, outputs2):
 88             return time_step < time_steps
 89 
 90         def body(time_step, prev_o_t, prev_softmax_input, state_c, state_h, outputs2):#prev_o_t=prev:shape=(16,512) 
 91                                                 #outputs:shape=(16, ?, 1002) prev_softmax_input=init_word:shape=(16, 1002)
 92             state = tf.nn.rnn_cell.LSTMStateTuple(state_c,state_h)#第一次随机初始状态,之后调用之前的
 93             pdb.set_trace()
 94             with variable_scope.variable_scope("loop_function", reuse=True):
 95                 inp = loop_function(prev_softmax_input, time_step)#shape=(16,100) inp用来做什么 作为每个cell单元从下而
 96                 #来的输入??而prev_o_t则为从左而来的输入??而且Inp和上一个cell单元的softmax_input(最终进softmax之前的cell输出)有关(prev_softmax_input)
 97 
 98             input_size = inp.get_shape().with_rank(2)[1]#100
 99             if input_size.value is None:
100                 raise ValueError("Could not infer input size from input: %s" % inp.name)
101             x = tf.concat(1,[inp,prev_o_t])#shape=(16, 612)  这个地方inp ,prev_o_t = loop_function(softmax_output),output
102             # Run the RNN.
103             cell_output, state = cell(x, state)#decoder层512个lstm单元 cell_output:shape=(16, 512) state:shape=(16, 512)
104             # Run the attention mechanism.
105             attn = attention(state)#shape=(16, 512) attenion模块的输出,C_i
106 
107             with variable_scope.variable_scope("AttnOutputProjection"):
108                 output = math_ops.tanh(linear([cell_output, attn], output_size, False))#shape=(16, 512) y_i = f(C_i,S_i)
109                 with variable_scope.variable_scope("FinalSoftmax"):
110                     softmax_input = linear(output,vocab_size,False)#shape=(16, 1002) #decoder层后加一层softmax??作为softmax_input
111 
112             new_outputs = tf.concat(1, [outputs2,tf.expand_dims(softmax_input,1)])#shape=(16, ?, 1002)[,...y_t-1,y_t,...]
113             return (time_step + tf.constant(1, dtype=tf.int32),\
114                             output, softmax_input, state.c, state.h, new_outputs)#既是输出,又是下一轮的输入
115 
116         time_step = tf.constant(0, dtype=tf.int32)
117         shape_invariants = [time_step.get_shape(),\
118                             prev.get_shape(),\
119                             tf.TensorShape([batch_size, vocab_size]),\
120                             tf.TensorShape([batch_size,512]),\
121                             tf.TensorShape([batch_size,512]),\
122                             tf.TensorShape([batch_size, None, vocab_size])]
123 
124 # START keyword is 0
125         init_word = np.zeros([batch_size, vocab_size])#shape=(16,1002)
126 
127         loop_vars = [time_step,\
128                      prev,\
129                      tf.constant(init_word, dtype=tf.float32),\
130                      initial_state.c,initial_state.h,\
131                      tf.zeros([batch_size,1,vocab_size])] 
136 
137         outputs = tf.while_loop(cond, body, loop_vars, shape_invariants)##shape=(16, ?, 1002)
138         '''
139         loop_vars = [...]
140         while cond(*loop_vars):
141             loop_vars = body(*loop_vars)   
142         '''
143 
144     return outputs[-1][:,1:], tf.nn.rnn_cell.LSTMStateTuple(outputs[-3],outputs[-2])
145 
146 def embedding_attention_decoder(initial_state,#shape=(16, 512)
147                                 attention_states,# shape=(16, 350, 512)
148                                 cell,#定义的lstm单元
149                                 num_symbols,#1002
150                                 time_steps,
151                                 batch_size,#16
152                                 embedding_size,#100
153                                 output_size=None,#512
154                                 output_projection=None,
155                                 feed_previous=False,#True
156                                 update_embedding_for_previous=True,
157                                 dtype=None,
158                                 scope=None):
159     if output_size is None:
160         output_size = cell.output_size#512
161     if output_projection is not None:
162         proj_biases = ops.convert_to_tensor(output_projection[1], dtype=dtype)
163         proj_biases.get_shape().assert_is_compatible_with([num_symbols])
164 
165     with variable_scope.variable_scope(scope or "embedding_attention_decoder", dtype=dtype) as scope:
166         embedding = variable_scope.get_variable("embedding",[num_symbols, embedding_size])
167         loop_function = tf.nn.seq2seq._extract_argmax_and_embed(embedding, 
168                           output_projection,update_embedding_for_previous) if feed_previous else None
169                         #(16,1002)==>(16,100)找argmax,然后embedding
170         return attention_decoder(
171                 initial_state,
172                 attention_states,
173                 cell,
174                 num_symbols,#1002
175                 time_steps,#8
176                 batch_size,
177                 output_size=output_size,#512
178                 loop_function=loop_function)
复制代码

 

关于embedding接口:

测试如下:

复制代码
 1 #-*-coding:utf8-*-
 2 
 3 __author = "buyizhiyou"
 4 __date = "2017-11-21"
 5 
 6 import tensorflow as tf
 7 import numpy as np
 8 
 9 '''
10 测试embedding接口
11 '''
12 embedding = tf.Variable(np.identity(5,dtype=np.int32))
13 inputs = tf.placeholder(dtype=tf.int32,shape=[None])
14 input_embedding = tf.nn.embedding_lookup(embedding,inputs)
15 
16 with tf.Session() as sess:
17     sess.run(tf.global_variables_initializer())
18     print(sess.run(embedding))
19 '''
20 [[1 0 0 0 0]
21  [0 1 0 0 0]
22  [0 0 1 0 0]
23  [0 0 0 1 0]
24  [0 0 0 0 1]]
25 '''
26     print(sess.run(input_embedding,feed_dict={inputs:[1,2,3,0,3,2,1]}))
27 '''
28 [[0 1 0 0 0]
29  [0 0 1 0 0]
30  [0 0 0 1 0]
31  [1 0 0 0 0]
32  [0 0 0 1 0]
33  [0 0 1 0 0]
34  [0 1 0 0 0]]
35 '''
复制代码

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
seq2seq是一种用于序列到序列(sequence-to-sequence)任务的模型架构,其中包括一个编码器(encoder)和一个解码器(decoder)。编码器将输入序列编码为一个固定长度的向量,解码器将该向量解码为目标序列。 在使用LSTM进行seq2seq任务时,编码器和解码器都是由LSTM层组成。编码器的LSTM层将输入序列逐步处理,并在最后一个时间步输出一个上下文向量(context vector),该向量包含了输入序列的信息。解码器的LSTM层接收上下文向量作为初始状态,并生成输出序列。 具体实现时,可以使用TensorFlow库来构建这样的模型。以下是一个简单的实现示例: 首先,导入所需的库: ```python import tensorflow as tf from tensorflow.keras.layers import LSTM, Dense from tensorflow.keras.models import Model ``` 定义编码器的输入和LSTM层: ```python encoder_inputs = tf.keras.Input(shape=(input_sequence_length,)) encoder_lstm = LSTM(units=hidden_units, return_state=True) ``` 通过LSTM层处理输入序列,获取编码器的输出和状态: ```python encoder_outputs, state_h, state_c = encoder_lstm(encoder_inputs) encoder_states = [state_h, state_c] ``` 定义解码器的输入和LSTM层: ```python decoder_inputs = tf.keras.Input(shape=(target_sequence_length,)) decoder_lstm = LSTM(units=hidden_units, return_sequences=True, return_state=True) ``` 通过LSTM层处理解码器的输入序列和编码器的状态: ```python decoder_outputs, _, _ = decoder_lstm(decoder_inputs, initial_state=encoder_states) ``` 添加一个全连接层作为解码器的输出层: ```python decoder_dense = Dense(target_vocab_size, activation='softmax') outputs = decoder_dense(decoder_outputs) ``` 构建模型: ```python model = Model([encoder_inputs, decoder_inputs], outputs) ``` 编译模型并训练: ```python model.compile(optimizer=tf.keras.optimizers.Adam(), loss='sparse_categorical_crossentropy') model.fit([encoder_input_data, decoder_input_data], decoder_target_data, batch_size=batch_size, epochs=epochs) ``` 以上是一个简单的seq2seq模型实现示例,你可以根据具体的任务需求进行进一步的调整和改进。
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值