论文《Neural Machine Translation by Jointly Learning to Align Translate》

背景

这篇论文是第一个在NLP中使用attention机制的工作。翻译任务是典型的seq2seq问题。那么,什么是seq2seq问题?简单的说就是,根据输入序列X,生成一个输出序列Y,序列的长度不固定。当输入序列X和输出序列Y是不同的语言时,就是机器翻译;当输入序列X是问题,输出序列Y是答案时,就是问答系统或者对话系统。根据输入和输出序列的特征,seq2seq主要应用在机器翻译、会话建模、文本摘要等。

解决seq2seq问题的基本框架是encoder-decoder模型,即编码-解码模型。编码就是将输入序列X转化成一个固定长度的向量;解码就是将之前生成的固定向量转换成输出序列Y。encoder和decoder可以看作两个自动编码器,使用常用的模型RNN,LSTM,GRU进行编码。本文encoder使用BiRNN,隐藏层的输出包含了输入序列中的词以及前后一些词的信息,decoder输入时加入attention。下面首先接受encoder-decoder模型,重点介绍如何在encoder-decoder模型上加入attention。

encoder-decoder模型

基本的seq2seq模型,包括三个部分,即Encoder、Decoder以及连接两者的中间状态向量State Vector,Encoder通过学习输入,将其编码成一个固定大小的状态向量C(也叫上下文向量),继而将C传给Decoder,Decoder再通过对状态向量C的学习来进行输出。如下图:


Encoder-Decoder

得到C的方式很多,最简单的方法是把Encoder的最后一个隐状态赋值给C,也可以对最后一个隐状态做一个变换得到C,还可以对所有的隐状态做变换,如下图:


得到固定大小的状态向量C

拿到c之后,就用另一个RNN网络对其进行解码,这部分RNN网络被称为Decoder。具体做法就是将c当做之前的初始状态h0输入到Decoder中:


将C作为decoder的初始状态

根据给定的语义向量C和之前已经生成的输出序列y_1,y_2,..,y_{t-1}来预测下一个输出的单词y_t:
y_t=argmaxP(y_t)=\prod_{t=0}^{T}p(y_t|\{y_1,y_2,..,y_{t-1}\},C)
也可以写作:
y_t=g(\{y_1,y_2,..,y_{t-1} \},C)
在RNN中,上式可以简化成:
y_t=g(y_{t-1},s_t,C)
其中s_t是RNN中的隐藏层,C是语义向量,y_{t-1}是上个时间段的输出,反过来作为这个时间段的输入。g则可以是一个非线性的多层的神经网络,产生词典中各个词语属于y_t的概率。

还有一种做法将c作为每一步的输入:

将C作为每一步的输入

encoder-decoder模型的优点是:输入和输出序列可以长度不同。

encoder-decoder模型的局限性

最大的局限性就在于编码和解码之间的唯一联系就是一个固定长度的语义向量C。也就是说,编码器要将整个序列的信息压缩进一个固定长度的向量中去。

这样做有两个弊端,一是语义向量无法完全表示整个序列的信息,还有就是先输入的内容携带的信息会被后输入的信息稀释掉,或者说,被覆盖了。输入序列越长,这个现象就越严重。这就使得在解码的时候一开始就没有获得输入序列足够的信息, 那么解码的准确度自然也就要打个折扣了。

由于基础Seq2Seq的种种缺陷,随后引入了Attention的概念以及Bi-directional encoder layer等。

attention

为了解决这个问题,作者提出了Attention模型,或者说注意力模型。简单的说,这种模型在产生输出的时候,还会产生一个“注意力范围”表示接下来输出的时候要重点关注输入序列中的哪些部分,然后根据关注的区域来产生下一个输出,如此往复。模型的大概示意图如下所示:


seq2seq_attention

相比于之前的encoder-decoder模型,attention模型最大的区别就在于它不在要求编码器将所有输入信息都编码进一个固定长度的向量之中。相反,此时编码器需要将输入编码成一个向量的序列,而在解码的时候,每一步都会选择性的从向量序列中挑选一个子集进行进一步处理。这样,在产生每一个输出的时候,都能够做到充分利用输入序列携带的信息。而且这种方法在翻译任务中取得了非常不错的成果。
在这篇文章中,作者提出了一个用于翻译任务的结构。解码部分使用了attention模型,而在编码部分,则使用了BiRNN(bidirectional RNN,双向RNN)。

解码

解码部分使用了attention模型。我们可以将之前定义的条件概率写作
p(y_i|y_1,y_2,..,y_{i-1},X)=g(y_{i-1},s_i, c_i)
其中s_i表示解码器i时刻的隐藏状态。计算公式:
s_i=f(s_{i-1},y_{i-1},c_i)
注意这里的条件概率与每个目标输出y_i相对应的内容向量c_i有关。而在传统的方式中,只有一个内容向量C。那么这里的内容向量c_i又该怎么算呢?其实c_i是由编码时的隐藏向量序列(h_1,…,h_{T_x})按权重相加得到的。
c_i=\sum_{j=1}^{T_x}\alpha_{ij}h_j
由于编码是双向RNN,因此可以认为h_i中包含输入序列中第i个词以及 前后一些词的信息。将隐藏权重向量按权重相加,表示在生成第i个内容向量对第j个输出的注意力分配是不同的。\alpha_{ij}越高表示第i个输出在第j个输入上分配的注意力越多,在生成第i个输出的时候受第j个输入的影响也就越大。新的问题是\alpha_{ij}如何得到?
\alpha_{ij}由第i-1个输出隐藏状态s_{i-1}和输入中各个隐藏状态(h_1,…,h_{T_x})共同决定的,公式如下:
\alpha_{ij}=\frac {exp(e_{ij})}{\sum_{k=1}^{T_x}exp(e_{ik})}
e_{ij}=a(s_{i-1},h_{j})
也就是说s_{i-1}跟每一个h分别计算一个数值,然后使用softmax得到i时刻的输出在T_x个输入隐藏状态中的注意力分配向量。这个分配向量,也就是计算c_i的权重。
把公式按执行顺序汇总:
e_{ij}=a(s_{i-1},h_{j})
\alpha_{ij}=\frac {exp(e_{ij})}{\sum_{k=1}^{T_x}exp(e_{ik})}
c_i=\sum_{j=1}^{T_x}\alpha_{ij}h_j
s_i=f(s_{i-1},y_{i-1},c_i)
p(y_i|y_1,y_2,..,y_{i-1},X)=g(y_{i-1},s_i, c_i)
attention:

def get_att_score(dec_output, enc_output):  # enc_output [n_step, n_hidden]
    score = tf.squeeze(tf.matmul(enc_output, attn), 0)  # score : [n_hidden]
    dec_output = tf.squeeze(dec_output, [0, 1])  # dec_output : [n_hidden]
    return tf.tensordot(dec_output, score, 1)  # inner product make scalar value

def get_att_weight(dec_output, enc_outputs):
    attn_scores = []  # list of attention scalar : [n_step]
    enc_outputs = tf.transpose(enc_outputs, [1, 0, 2])  # enc_outputs : [n_step, batch_size, n_hidden]
    for i in range(n_step):
        attn_scores.append(get_att_score(dec_output, enc_outputs[i]))

    # Normalize scores to weights in range 0 to 1
    return tf.reshape(tf.nn.softmax(attn_scores), [1, 1, -1])  # [1, 1, n_step]

编码器:

model = []
Attention = []
with tf.variable_scope('decode'):
    dec_cell = tf.nn.rnn_cell.BasicRNNCell(n_hidden)
    dec_cell = tf.nn.rnn_cell.DropoutWrapper(dec_cell, output_keep_prob=0.5)

    inputs = tf.transpose(dec_inputs, [1, 0, 2]) #decoder的输入
    hidden = enc_hidden  #encoder每一层最后一个step的输出,将encoder的最后一个隐状态赋值给hidden
    for i in range(n_step):
        # time_major True mean inputs shape: [max_time, batch_size, ...]
        dec_output, hidden = tf.nn.dynamic_rnn(dec_cell, tf.expand_dims(inputs[i], 1),
                                               initial_state=hidden, dtype=tf.float32, time_major=True)
        #拿到hidden以后,将hidden当作之前的初始状态h0输入到decoser中
        #dec_output 最后一层每个step的输出
        #hidden 每一层最后一个step的输出

        attn_weights = get_att_weight(dec_output, enc_outputs)  # attn_weights : [1, 1, n_step]
        Attention.append(tf.squeeze(attn_weights))

        # matrix-matrix product of matrices [1, 1, n_step] x [1, n_step, n_hidden] = [1, 1, n_hidden]
        context = tf.matmul(attn_weights, enc_outputs)
        dec_output = tf.squeeze(dec_output, 0)  # [1, n_step]
        context = tf.squeeze(context, 1)  # [1, n_hidden]

        model.append(tf.matmul(tf.concat((dec_output, context), 1), out))  # [n_step(i), batch_size(=1), n_class]

编码

编码比较普通,只是传统的单向的RNN中,数据是按顺序输入的,因此第j个隐藏状态\overrightarrow {h_j}只能携带第j个单词本身以及之前的一些信息;而如果逆序输入,则\overleftarrow {h_j}包含第j个单词及之后的一些信息。如果把这两个结合起来,h_j=[\overrightarrow {h_j},\overleftarrow {h_j}]就包含了第j个输入和前后的信息。

with tf.variable_scope('encode'):
    enc_cell = tf.nn.rnn_cell.BasicRNNCell(n_hidden) #n_hidden 隐藏层神经单元的个数
    enc_cell = tf.nn.rnn_cell.DropoutWrapper(enc_cell, output_keep_prob=0.5)
    # enc_outputs : [batch_size(=1), n_step(=decoder_step), n_hidden(=128)] 最后一层每个step的输出
    # enc_hidden : [batch_size(=1), n_hidden(=128)] 每一层最后一个step的输出
    enc_outputs, enc_hidden = tf.nn.dynamic_rnn(enc_cell, enc_inputs, dtype=tf.float32)

实验

注意力矩阵:


matrix of attention

参考:
https://www.jianshu.com/p/1c6b1b0cd202
https://blog.csdn.net/u014595019/article/details/52826423
https://github.com/graykode/nlp-tutorial/blob/master/4-2.Seq2Seq(Attention)/Seq2Seq(Attention)-Tensor.py

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,636评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,890评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,680评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,766评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,665评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,045评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,515评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,182评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,334评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,274评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,319评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,002评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,599评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,675评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,917评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,309评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,885评论 2 341