漫谈 Attention Mechanism[注意力机制]

Attention 的核心思想就是从关注全部到关注重点

看了这个 up 主的一些介绍【传送门

1. 注意力机制的由来

如下面这张图,我们观察一张图片或者一段文字的时候,并不是对每个部分都付出了同样的注意力,而是有所侧重的,如下面这张图中,婴儿的脸,文字的标题这些是我们更关注的。但是对于 CNN, RNN, 以及 LSTM 等模型很难决定什么是重要的,什么不重要,于是产生了注意力机制。

2. 注意力机制的实现过程

那么注意力机制是怎么在神经网络中实现的呢?

首先我要观察一张图,我可能会习惯性的有所侧重的去看。我潜意识中会有一个Q(查询对象)表示我更关心的内容,而准备观察的图是K(被查询对象)。

看一段评论区的解释:

Q是要搜索的词, 例如 “苹果”这个词在 ,“我要吃苹果” 这句话中与每个词的相似度求解。 Q = 苹果, K1 = 我 k2=要 k3=吃 k4=苹果。 Q 分别与K1-4 进行相似度的比较得到一个score 分数,如果这个score 越高说明相似度越高。 然后通过softmax 归一化,就是相似度的百分比。 最后在和 V 相乘 求和。 得到注意力矩阵。

这里需要明白一个背景,就能记住 q k v 分别是干什么的了。 这里注意力机制的本质是提取特征,也就是说一个词在一个句子中的特征。 用一个简单的例子来说,就是你在你公司里面属于什么地位。 比如一个公司有三个人, 你, 小红,小强。 就要算出它们之间都是什么关系, 说白了关系好的之间的分数就高,最终用小数表示。 例如你和你自己的关系是 0.7, 你和小红的关系是0.2,你和小强的关系是0.1. 分值大说明关系好。 而且 三个关系相加刚好是1 也就是100%。 那么这个之间的关系是如何计算出来的呢 ?就需要对每个人设置三个值, 就是 k q v。 k 是用来被别人查的,q 是用来查别人的, v 是用来算分数的。 这三个严格来说都是矩阵向量,最开始的时候是随机生成的,通过不断迭代训练计算出来(涉及到正向传递,反向传递,损失函数,求w b ,这里不展开)。 回到你的问题,v 是用来算分的,也就是你和其他人之间的关系到底是多少得分,就是通过v 算出来的。 它是在 k 和q 两两计算计算得到的结果进行归一化的结果之后,再进行乘积求和得到的。
简单一句话,v 就是用来算分的, 算关系的,算你和其他人的关系。 算这个词和整句话中其他词的关系。

通过计算 F(Q, K) (点积运算)得到了 Q 和 K 的一个相似度 s,相似度越高,代表这部分信息我将更关注。然后将 s 用 softmax 归一化成一组比例 a。然后用这个代表了重要程度的比例 a 和原始数据 V 进行加乘操作,最终得到了附加有观察侧重信息的数据 V’(Attention Value),用这个 V‘ 替代原来的 V。

下面内容参考【传送门

知道了 Attention 的大致原理,下面来看一下比较详细的执行过程:

Embedding 就是用一个数值向量“表示”一个对象(Object)的方法。 “实体对象”可以是image、word等,“数值化表示”就是一个编码向量。 例如对“颜色“这种实体对象用(R,G,B)这样一个三元素向量编码。 embedding还可以理解成将离散目标投影到连续空间中的某个点上。

  1. 首先获得输入,即用数值向量表示输入的对象,一串文本或图像。如下图,输入 X 是 800 个 200 维的向量,输出 Y 是 800 个 100 维的向量。

  2. 然后利用不同的权重矩阵通过线性层得到 Q、K、V。注意这里 attention 的 Q、K、V 的来源没有强制要求,如果是 Self-Attention 的话,需要 Q、K、V 同源,即三个权重矩阵与相同的输入 XX 通过线性层得到 Q、K、V。

    计算过程如下:

    Q=WqXK=WkXV=WvXA=KTQA=softmax(A)Y=VAQ = W_qX \\ K = W_kX \\ V = W_vX \\ A = K^TQ \\ A' = softmax(A) \\ Y = VA'

    不同的词向量使用相同的 Wq,Wk,WvW_q, W_k, W_v 获得 Q, K, V。

  3. 然后通过点积运算分别得到不同词向量之间的相关性分数。

  4. 然后做一个 softmax 将分数转化为概率,然后对概率进行归一化得到重要程度的比例

  5. 然后利用上一步得到的比例,对各个向量 viv^i 计算加权和 y1y^1 通过下图可以看出,如果某个输入向量对 x1x^1 更重要,那么其对应的 α\alpha 的值就会越大,其对应的 v2v^2 就得到了更多的关注 attention。

下面看个例子:

如果假设 q2q^2 是 2 维的,输入 x2x^2 是 3 维的,那么就需要训练一个 2*3 维的权重矩阵 WqW_q

分别计算每个输入向量:

然后将向量拼接到一起,就得到了矩阵 Q:

同理可以一次性计算出 Q, K, V 矩阵:

然后计算 q, k 点积得到的相关度分数

这一步同样可以拼成一个矩阵:

同样可以同时计算出每个 q,k 分量的点积

然后计算 V 与 α\alpha 的加权结果

简单来说,整个 Attention 的计算过程如下:

可以看到 attention 无视距离,无论隔多远,用我的 qiq_i 和你的 kjk_j 做内积,就知道我需要放多少注意力在你身上,然后用 softmax(α)softmax(\alpha) 乘上你的 V 就是我从你身上得到的一些信息。

如此就实现了”天涯若比邻,Attention is all you need“,但是 Attention 有问题,它不包含位置信息,对于 x1x^1 来说,x2,x3x^2, x^3 与它的距离是一样的。所以在处理序列任务的 Attention 模型中,需要引入位置编码。

3. 位置编码

对不同位置的输入序列加上不同的位置向量。

transformer 中用的位置向量如下:

需要知道输入向量的位置 pos 和维度 dmodeld_{model}

假设 pos = 2, dmodel=4d_{model}=4 ,即第二个输入向量的维度为 4。要算出一个维度为 4 的位置编码。

第奇数行用第二个公式计算,第偶数行用第一个公式计算,计算结果如下。

于是得到了输入向量的 x2x^2 的位置编码 e2e^2

为什么这样的操作就可以实现位置编码呢?就是因为编码的过程中同时涉及到了输入向量的位次信息 pos,和其维度信息。此后这个位置编码 eie^i 会融合进输入向量 xix^i 中,在之后的 Attention 过程中都会携带着这样的位置信息。

当然,位置编码的方式有很多,上面只是一种。

4. Self-Attention

Self-Attention 就是在 Attention 的基础上多了一条限制,要求 Q, K, V 同源,均来自输入的线性变换。

对比 RNN、LSTM 和 Self-Attention:

RNN 只能串行执行,并且越往后,前面的内容会被逐渐冲淡,所以无法做长序列,一段话达到 50 个字,效果就很差了。

LSTM(Long Short-Term Memory)在 RNN 的基础上改进,增加了各种门,比如遗忘门等,可以选择性遗忘一些之前不重要的信息,而巩固一些后面更可能用到的信息,提升了可处理的序列长度。不过也只有 200 字左右。

而 Self-Attention 相比前两者,具有可以并行处理长序列的优势,没有长序列的时序依赖问题。如下图可以看出句子序列中的每个词都要和其他所有词进行 Attention 处理,所以不存在时序关系。

同时,Self-Attention 能够获得句法特征和语法特征,从而得到表征更完善的词向量。

词法特征:

下图可以看出 Attention 的过程中,making 更倾向于与 more difficult 形成词组;

语法特征:

下图看出,its 和句中的 Law 和 application 的关联性更强,从语法上也是合理的,it 指代 Law,application 补充 its。

Truncated Self-Attention 提出,我们在理解一个东西的时候,可能并不需要理解全部输入,有时候只需要理解一些前后文就足够了。例如仅考虑 a2, a3, a4.

那么对于视觉领域 Self-attention 如何理解呢?

下图右侧可以看出图片可以看成多个向量(vector)组成的,所以同样可以利用 attention 来处理。

如下图,Self-attention 与 CNN 相比,CNN 可以看作简化版的 Self-attention。粗略理解就是,CNN 一次卷积处理,得到的结果只包含了一个 filter 内的信息,但是 Self-attention 可以获取整张图的信息。

5. Multi-Head Self-Attention

多头自注意力机制。

通过 Self-Attention,从输入 X 得到了具有一些额外信息的词向量 Y。Y 相比 X 有提升,通过 Multi-Head Self-Attention 要得到比 Y 提升更大的词向量。

下面是一个博主对词向量做一点解释:

机器学习的本质就是通过某个手段(训练模型),把一个看起来的不合理的东西变得合理。

非线性变换,其实就是改变空间上的位置坐标。任何一个点都可以在维度空间上找到,通过某些手段,让一个位置不合理的点(初始点),变换到合理的位置。

下图可以看出 Multi-Head Attention 就是由多个 Self-Attention 组成的。

下图是 Multi-Head Self-Attention 的执行过程:

  1. 输入一个序列
  2. 通过 one-hot 等编码方式得到一个词向量
  3. 随机生成 8 组权重矩阵(即 8 heads)
  4. 将输入的同一个 X 矩阵分别与不同的权重矩阵相乘,得到了 8 组,Q, K, V
  5. 产生了 8 个词向量 ZiZ_i,将其拼接到一起,再通过一次线性变换使得最后得到的 Z 和输入 X 的维度相同

整个这个过程,8 头可以视为产生了 8 个表示空间,在不同的空间上获得不同的词向量,最后将多样的信息叠加在一起。所以 Multi-Head Self-Attention 可以取得更好的表现。

代码参考【传送门

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
import torch
import torch.nn as nn

class MultiHeadAttention(nn.Module):
def __init__(self, d_model, num_heads):
super(MultiHeadAttention, self).__init__()
self.num_heads = num_heads
self.d_model = d_model
self.d_k = d_model // num_heads

self.query_linear = nn.Linear(d_model, d_model)
self.key_linear = nn.Linear(d_model, d_model)
self.value_linear = nn.Linear(d_model, d_model)
self.output_linear = nn.Linear(d_model, d_model)

def forward(self, input):
batch_size = input.size(0)
seq_len = input.size(1)

# Linear transformations
query = self.query_linear(input)
key = self.key_linear(input)
value = self.value_linear(input)

# Reshape for multi-head attention
query = query.view(batch_size, seq_len, self.num_heads, self.d_k)
key = key.view(batch_size, seq_len, self.num_heads, self.d_k)
value = value.view(batch_size, seq_len, self.num_heads, self.d_k)

# Transpose dimensions for matrix multiplication
query = query.transpose(1, 2) # [batch_size, num_heads, seq_len, d_k]
key = key.transpose(1, 2) # [batch_size, num_heads, seq_len, d_k]
value = value.transpose(1, 2) # [batch_size, num_heads, seq_len, d_k]

# Scaled dot-product attention
scores = torch.matmul(query, key.transpose(-2, -1)) # [batch_size, num_heads, seq_len, seq_len]
scores = scores / torch.sqrt(torch.tensor(self.d_k, dtype=torch.float32))

attention_weights = nn.Softmax(dim=-1)(scores)
attention_output = torch.matmul(attention_weights, value) # [batch_size, num_heads, seq_len, d_k]

# Concatenate and reshape
attention_output = attention_output.transpose(1, 2).contiguous().view(batch_size, seq_len, self.d_model)

# Linear transformation for final output
attention_output = self.output_linear(attention_output)

return attention_output

# 使用示例
d_model = 256 # 输入维度
num_heads = 8 # 注意力头数

# 创建Multi-Head Attention层
attention = MultiHeadAttention(d_model, num_heads)

# 创建输入张量
batch_size = 4
seq_len = 10
input = torch.randn(batch_size, seq_len, d_model)

# 前向传播
output = attention(input)

print("输入维度:", input.shape)
print("输出维度:", output.shape)

输出结果为:

1
2
输入维度: torch.Size([4, 10, 256])
输出维度: torch.Size([4, 10, 256])

self-attention 的公式为

attention(Q,K,V)=Softmax(QKdk)Vattention(Q,K,V)=Softmax(\frac{QK}{\sqrt{d_k}})V

dkd_k 是词向量/隐藏层的维度

目的:防止输入 softmax 的值过大,导致偏导数趋近于 0。

6. 注意力池化

简单说就是用一个基于注意力的层来取代平均池化层。池化层和 attention 好像还挺像的,都是对输入信息的加权平均进行整合。加入注意力机制之后的池化层,可以明确的显示出不同 patch 所占的比重。表现很好。

暂时就这么多。

参考:

https://www.cnblogs.com/nickchen121/p/15105048.html

attention

https://nlp.seas.harvard.edu/2018/04/03/attention.html

https://zhuanlan.zhihu.com/p/149490072

multi-head attention

https://blog.csdn.net/weixin_44750512/article/details/124250497

注意力池化

https://cloud.tencent.com/developer/article/1946460