文章目录
- 背景
- 什么是Attention
- Attention权重的计算方法
- 1. 多层感知机法
- 2. Bilinear方法
- 3. Dot Product
- 4. Scaled Dot Product
- Scaled dot-prodect Attention的源码实现
背景
要了解深度学习中的Attention,就不得不先谈Encoder-Decoder框架(sequence to sequence模型),因为Attention的最常见用途就是解决Encoder-Decoder中的信息丢失问题(注:Attention可以看作一种通用的思想,本身并不依赖于特定框架)。
下图是NLP领域里常用的Encoder-Decoder框架的示意图。
可以把上图看作是由一个句子生成另外一个句子的通用处理模型。对于句子对(Source,Target),我们的目标是给定输入句子Source,期待通过Encoder-Decoder框架来生成目标句子Target。Source和Target可以是同一种语言,也可以是两种不同的语言。
Encoder的作用是学习并提取Source句子里的信息,生成一个代表Source的hidden向量,Decoder则是根据接收到的hidden向量生成对应的Target。可见hidden是连接Encoder和Decoder的桥梁,而由于其的大小有限,所能承载的Source句子的信息就有限,如果遇到Source过长的情况,就会出现hidden无法有效表示Source句子的情况,此时的Decoder也就无法正确理解Source,更无法准确生成Target。
此时,Attention的作用便凸显了出来。
什么是Attention
如上图,由于一个hidden无法涵盖所有的Source句子信息,故将句子中每个字对应的hidden信息都输入到Attention中,再将Attention作为Decoder的输入,这样就可以防止Source句子信息的丢失。
而此时又遇到另一个问题,Source句子中的每个字(词)对于Decoder当前时刻要生成的字(词)的影响力不同,我们不能简单的把所有hidden都传入Attention,而是要告诉Decoder哪个hidden对其当前的生成任务更重要(即影响力更大),故需要对所有hidden做weighted sum,如下图:
注:其中的S(i-1)是Decoder生成的上一个字,也会作为Attention输入,因其对同样影响当前的生成任务。
weight即权重,weighted sum即对所有的hidden分配不同的权重后求和,对Decoder当前要生成的字(词)影响力大的hidden,权重大,反之,则权重小。
那么,如何计算每个字在句子中的权重大小呢?
Attention权重的计算方法
设Q(query)、K(key)分别为代表两个字的向量。
1. 多层感知机法
主要是先将query和key进行拼接,然后接一个激活函数为tanh的全连接层,再与一个网络定义的权重矩阵做乘积。这种方法对于大规模的数据特别有效。
2. Bilinear方法
通过一个权重矩阵直接建立q和k的关系映射,比较直接,且计算速度较快。
3. Dot Product
这个方法更直接,连权重矩阵都省了,直接建立q和k的关系映射,优点是计算速度更快了,且不需要参数,降低了模型的复杂度。但是需要q和k的维度要相同。
4. Scaled Dot Product
上面的点积方法有一个问题,就是随着向量维度的增加,最后得到的权重也会增加,为了提升计算效率,防止数据上溢,对其进行scaling,即图中除以的根号下k的维度。后续的Transformer模型中self-attention也是采用了该计算方法。
Scaled dot-prodect Attention的源码实现
Scaled dot-prodect Attention定义如下:
可以理解为:将Source中的构成元素想象成是由一系列的(Key,Value)数据对构成,此时给定Target中的某个元素Query,通过计算Query和各个Key的相似性或者相关性,得到每个Key对应Value的权重系数,然后对Value进行加权求和,即得到了最终的Attention数值。
计算过程图示如下:
源码如下:
def DotProductAttention(query, key, value, mask, scale=True):
"""Dot product self-attention.
Args:
query (numpy.ndarray): 代表q的向量,shape为(L_q by d)
key (numpy.ndarray): 代表k的向量,shape为(L_k by d)
value (numpy.ndarray): 代表v的向量,shape为(L_k by d) ,注L_v = L_k
mask (numpy.ndarray): causal attention标志位,用于判断attention的计算类型
scale (bool): 是否scale,即是否除以根号下词维度(q的维度和k的维度相同)
Returns:
numpy.ndarray: 代表Self-attention的矩阵,shape为(L_q by d)
"""
# 是否除以维度
if scale:
depth = query.shape[-1]
else:
depth = 1
# 计算q和k的点乘
dots = np.matmul(query, np.swapaxes(key, -1, -2)) / np.sqrt(depth)
# 对于causal attention,加上mask
if mask is not None:
dots = np.where(mask, dots, np.full_like(dots, -1e9))
# 对点乘做softmax计算
logsumexp = scipy.special.logsumexp(dots, axis=-1, keepdims=True)
dots = np.exp(dots - logsumexp)
# 计算attention
attention = np.matmul(dots, value)
return attention
def dot_product_self_attention(q, k, v, scale=True):
mask_size = q.shape[-2]
mask = np.tril(np.ones((1, mask_size, mask_size), dtype=np.bool_), k=0)
return DotProductAttention(q, k, v, mask, scale=scale)
dot_product_self_attention(q, k, v)