- 编码器-解码器 (Encode-Decode) 结构:将输入序列编码成一个固定长度的向量表示,对于长度较短的输入序列而言,该模型能够学习出对应合理的向量表示。然而,这种模型存在的问题在于:当输入序列非常长时,模型难以学到合理的向量表示。
- seq2seq模型通常由编码器 (encode)- 解码器(decode)架构组成,其中编码器处理输入序列并将信息编码/压缩/概括为固定长度的上下文向量(也称为“思想向量”)。该向量是整个输入序列的良好的压缩特征。然后用该上下文向量初始化解码器,使用该上下文向量开始生成变换后的输出。
- 这种固定长度的上下文矢量设计的一个关键和明显的缺点是系统无法记住更长的序列。一旦处理完整个序列,通常会忘记序列的早期部分。
Attention机制
- 打破了传统编码器-解码器结构在编解码时都依赖于内部一个固定长度向量的限制。
- 在传统的Seq2Seq模型中,我们丢弃编码器的所有中间状态,并仅使用其最终状态(向量)来初始化解码器。该技术适用于较小的序列,但随着序列长度的增加,单个载体成为瓶颈,并且很难将长序列汇总到单个载体中。
- 通过保留LSTM编码器对输入序列的中间输出结果,然后训练一个模型来对这些输入进行选择性的学习并且在模型输出时将输出序列与之进行关联。(将encoder的每一个隐藏状态设定一个权重,根据权重的不同决定decoder输出更侧重于哪一个编码状态。)
attention结合在seq2seq模型中的应用:
约定encoder hidden states:h1,h2,…,hn; 第t时刻decoder hidden state:St;
输出序列中的下一个字取决于解码器的当前状态(解码器也是GRU)以及编码器的隐藏状态。
由于预测第一个字解码器没有任何当前的内部状态,我们将编码器的最后状态(即h5)视为先前的解码器状态。
最后,解码器使用以下两个输入向量来生成序列中的下一个字
a)上下文向量
b)从前一时间步骤生成的输出字。
我们简单地连接这两个向量并将合并的向量馈送到解码器。
对于第一个步骤,由于上一个时间步骤没有输出,我们为此使用一个特殊的标记。
一旦解码器输出令牌,就停止生成过程。
- 由encoder hidden states和decoder hidden state 计算每个encoder状态对应的attention score Et(向量点积)
- 将Et softmax化后得到attention分布
- 将attention分布与encoder hidden state 相乘后相加得到attention vector
- 将attention vector与decoder hidden state 作为输入计算得出输出
Attention机制的本质
将source中的构成元素想像成一系列的(key,value)数据对构成,对于某个给定的Query,我们去计算Query和各个Key之间的相关性,得到每个key对应value的权重系数,然后对value进行加权求和,即最终的attention score。所以从上面分析可以看出,attention机制本质上就是一个加权求和的过程:
点积:
Cosine相似性:
MLP网络:
Attention的各种类型:
Soft Attention VS Hard Attention
- Soft Attention就是平时常用的attention
- Hard Attention是随机的,它会依概率S来采样输入端的隐状态一部分来计算,而不是整个encoder的隐状态。因此它不能直接求导反向传播
Global attention VS local attention
- global attention就是平时所说的attention
- local attention提出是为了解决global attention需要attend所有输入导致的计算复杂问题,在attend时只选择encoder中的一部分来做attention。Local Attention可以认为是介于Soft Attention和Hard Attention之间的一种Attention方式,即把两种方式结合起来。首先定义一个位置对齐向量pt,然后根据pt从source中选定窗口为D的输入做attention
Multi-dimensional Attention
- Hierarchical Attention,层次attention,就是模型框架中包含有多个attention机制,首先是一个word-level的attention获取句子表示,之后是一个sentence-level的attention获取文章表示,最后接一个softmax进行文章的分类。
- Self-Attention,Self-Attention即在朴素Attention机制中满足条件Q=K=V,例如输入一个句子,那么Self-Attention就是句子中的每个词都要和其他词做attention,这样就可以学习句子内部词的依赖关系,捕获句子内部结构。
构造
Keras中没有定义Attention层,因此要自己去定义。Keras中实现一个Layer,要按照下面骨架编写:只需要实现三个方法即可:
- build(input_shape): 这是你定义权重的地方。这个方法必须设 self.built = True,可以通过调用 super([Layer], self).build() 完成。
- call(x): 这里是编写层的功能逻辑的地方。你只需要关注传入 call 的第一个参数:输入张量,除非你希望你的层支持masking。
- compute_output_shape(input_shape): 如果你的层更改了输入张量的形状,你应该在这里定义形状变化的逻辑,这让Keras能够自动推断各层的形状。
import numpy
import keras
from keras import backend as K
from keras import activations
from keras.engine.topology import Layer
from keras.preprocessing import sequence
from keras.models import Sequential
from keras.models import Model
from keras.layers import Input, Dense, Embedding, LSTM, Bidirectional
K.clear_session()
class AttentionLayer(Layer):
def __init__(self, attention_size=None, **kwargs):
self.attention_size = attention_size
super(AttentionLayer, self).__init__(**kwargs)
def get_config(self):
config = super().get_config()
config['attention_size'] = self.attention_size
return config
def build(self, input_shape):
assert len(input_shape) == 3
self.time_steps = input_shape[1]
hidden_size = input_shape[2]
if self.attention_size is None:
self.attention_size = hidden_size
self.W = self.add_weight(name='att_weight', shape=(hidden_size, self.attention_size),
initializer='uniform', trainable=True)
self.b = self.add_weight(name='att_bias', shape=(self.attention_size,),
initializer='uniform', trainable=True)
self.V = self.add_weight(name='att_var', shape=(self.attention_size,),
initializer='uniform', trainable=True)
super(AttentionLayer, self).build(input_shape)
def call(self, inputs):
self.V = K.reshape(self.V, (-1, 1))
H = K.tanh(K.dot(inputs, self.W) + self.b)
score = K.softmax(K.dot(H, self.V), axis=1)
outputs = K.sum(score * inputs, axis=1)
return outputs
def compute_output_shape(self, input_shape):
return input_shape[0], input_shape[2]