1 什么是注意力机制
当我们观看一张图片时,会直接关注最关心的内容,例如会只看到兔子,而忽略兔子嘴里叼的草和蓝天白云。注意力机制就是你模仿人脑自动捕捉最重要信息的能力,使得模型更好的学习关键内容。
注意力机制适合位置信息和时序信息,和CNN和RNN结合使用是典型的方法,如上图中人的眼睛和耳朵。
下面从encoder-decoder架构角度解释注意力机制:
上图显示了传统encoder-decoder模型的计算方式,encoder输出全部的语义信息c,然后作为输入,decoder计算由c推出的目标信息。decoder模型将注意力放在全部语义信息中,这有时是不合适的。所以引入注意力机制。
下图显示了添加注意力机制后的encoder-decoder模型,输入x会返回注意力权重c,每个c_i记录了要预测的单词y_i在原信息x中的重要性。那么,理解了注意力机制后,就是考虑如何计算c,这也是最不容易理解的部分。
2 注意力机制计算方式
下图就明确的表示了注意力权重的计算方式。
要计算的是某个单词在原句子中和哪些单词相关度高,即计算目标单词的注意力概率分布,例如X=(我是中国人,我爱中国),要翻译成y=(i am chinese, i love china),china对应的概率分布可能是(0.1, 0.001, 0.5, 0.5, 0.2, 0.01, 0.2, 0.5, 0.5),再经过归一化后和原句子做加权求和。
下图中,Query表示要查询的目标单词的位置,暂时这么理解,不同模型Query取值不同;key是输入的原句子;a表示每个目标单词的注意力概率分布。
下面以自编码器为例,详细解释了注意力值的计算方式,包括数据流的维度,需要点耐心!
RNN-based encoder-decoder framework,目标是预测P(y_1, y_2, ..., y_i | x_1, x_2, ..., x_i),x.shape=(b, sentence_x_len, word_x_len),y.shape=(b, sentence_y_len, word_y_len)。训练阶段结构图如下所示。
encoder:
单词序列经过embedding层得到分布式向量表达,由RNN计算得出序列的特征,送到decoder翻译。
h_t = f(W@x_t, h_t-1) 单词序列对应的状态,最后一个h记录了全部状态,shape(b, sentence_x_len, units_encoder);
c = q({h_i}) q为计算概率分布的函数,上下文向量,最简单的是最后一个h,可以是点积、网络...,shape(b, units_encoder);
decoder:
在获得h、上下文向量c和已经预测出的输出y_t-1的条件下,预测下个输出y_t,即条件概率分布。
P(y) = g(y_t | {y_1, y_2, ..., y_t-1}, c, h) g为计算函数,一般为全连接层加softmax,y-i(b, units_decoder)是由RNN得出,即图中的W。
到此非注意力自编码器介绍完毕。下面以RNN-based encoder-decoder with attention framework为模型,解释注意力概率分布的计算过程:
不同之处就在于c的计算方式,不是简单的取最后一个h,而是计算不同目标单词在原句中的重要性概率分布,即注意力概率分布attention vector(b, units_y)。
encoder:
单词序列经过BiLSTM提取特征后全部送到Attention模块中。
inputs:x(b, sentence_len),embedding后为:(b, sentence_len, word_len)
outputs: y(), h(b, sentence_len, units_encoder)
decoder:
输入为经过添加注意力之后的上下文向量和上一时刻的隐藏状态,再输出为目标单词。
inputs:y_t-1(b, sentence_len), context(b, units_encoder)
outputs: s(b, units_decoder)
attention:
inputs:[s_t-1, h]
outputs: [context_t]
计算公式:context_t = h * softmax(f(s_t-1, h))
3 attention调用
3.1 Attention()
Attention模块参数:
use_scale,取值True或者False,它表示是否对计算出来的得分(score)进行权重缩放,如果是True,进行权重缩放,如果False,不进行缩放,默认值是False。通常也使用默认,因为它只是在最后计算出score是乘一个scale。
inputs:
query_seq_decoding(batch_size, Tq, dim),
value_seq_encoding(batch_size, Tv, dim),
key(batch_size, Tv, dim),可选的,如果没有给定,则默认key=value
outputs:
[batch_size, Tq, dim]
计算过程:
可知Attention是非常简单的,没有参数要训练,但是可以自定义分数scores的计算方式,如神经网络。
encoder_out = tf.random.normal([50, 80, 30])
decoder_out = tf.random.normal([50, 1, 30])
attention = layers.Attention()
y = attention([decoder_out, encoder_out])
print(y) # (50, 1, 30)
3.2 自定义attention
attention模块中分数计算部分可以有不同方式,下面实现一种矩阵参数的。
TensorFlow2.0中自定义层,要点如下:
1、如果需要使用到其他Layer结构或者Sequential结构,需要在init()函数里赋值;2、在build()里面根据输入shape构建权重参数, 每个参数需要赋值name,如果参数没有name,当训练到第2个epoch时会报错:AttributeError: ‘NoneType’ object has no attribute ‘replace’;3、在call()里写计算逻辑。
class AttentionConcat(layers.Layer):
def __init__(self, bias=True):
"""
scores = query @ W @ key^T => shape(b, Tq, Tv)
W = (dim, dim)
result = scores @ value + bias
# Input shape
[query(batch_size, Tq, dim),
value(batch_size, Tv, dim),
key(batch_size, Tv, dim)]
# Output shape
(batch_size, Tq, dim)
:param bias: shape(b, Tq, dim)
"""
super(AttentionConcat, self).__init__()
self.bias = bias
def build(self, input_shape):
"""
:param input_shape:
:return:
"""
self.batch = input_shape[0][0]
self.Tq = input_shape[0][1]
self.Tv = input_shape[1][1]
self.dim = input_shape[0][-1]
self.W = self.add_weight(
name='{}_W'.format(self.name),
shape=(self.batch, self.dim, self.dim),
initializer='one',
trainable=True
)
if self.bias:
self.b = self.add_weight(
# name='{}_b'.format(self.name),
shape=(self.batch, self.Tq, self.dim),
initializer='zero',
trainable=True
)
else:
self.b = None
def call(self, inputs, **kwargs):
# (
scores = inputs[0] @ self.W @ tf.transpose(inputs[2], perm=[0, 2, 1])
scores_flat = tf.reshape(scores, shape=[-1, self.Tq * self.Tv])
attention_flat = tf.nn.softmax(scores_flat)
attention = tf.reshape(attention_flat, shape=[-1, self.Tq, self.Tv])
# (N, step, d) (N, step, 1) ====> (N, step, d)
result = attention @ inputs[1]
if self.bias:
result += self.bias
return result