2017年,Google 机器翻译团队发表了《Attention is All You Need》这篇论文,犹如一道惊雷,Attention横空出世了,很快Attention就被推上了神坛,搭载着犹如核弹的Transformer,Bert出现了,在NLP的各个任务上屠榜,后来预训练模型遍地开花,在NLP的各个领域大放异彩。让人一度觉得NLP只需要一个Transformer就够了。让他们火的一塌糊涂的正是Attention,今天就来浅谈一下Attention机制和Self-Attention机制
1、什么是注意力?
简单来说,就是人用眼睛观察东西的时候,大脑会有意识或无意识地观察某些地方,这就是人的注意力。在万物皆可AI的情况下,希望计算机能够模仿人类的这一种行为,因此那些天才研究人员就研究,最后弄出了计算机版本的注意力机制,称为Attention机制。
2、为什么要引入注意力机制?
我们知道基本的神经网络CNN和RNN都可以实现对目标特征的提取,那为啥还要大费周章的弄出一个注意力机制来呢?自然可以想象到,肯定是为了解决CNN和RNN存在的缺点问题。
对于RNN神经网络,在对于一些数据量比较少时,我们知道目前的基于LSTM门控机制变体的神经网络可以很好得处理有关时序的问题,但是当数据量很大时,特别是处在目前大数据时代,数据动不动都是十万百万级别的,这时候循环神经网络还不能并行,可想而知,网络效率有多慢。
虽然CNN能够有效解决并行的问题,但是是通过堆叠多层CNN来实现,那层数较多时,又会出现梯度消失等问题,因此,attention机制的出现正是迎合了目前这个大数据时代的需要。
3、注意力机制的计算过程
对于计算机来说,想要表达一个东西的重要程度,最直观的方式就是用数字来表示。如果一个数越大,可以认为它越重要,Attention就是这样的。在一些任务中,我们会通过矩阵的数学运算得到某两个东西之间的值,它反映了这两个东西之间的相关程度,或者说“注意程度”,这个值就是注意力权重。再把这个注意力权重乘上对应的值就得到了某个东西关注另一个东西的程度,这就是注意力分数。计算机就是通过这个分数实现注意力机制的。
详细计算的过程可以参考这位大佬的文章:https://zhuanlan.zhihu.com/p/265108616
其中Q、K、V是attention机制中最为关键,通过以下例子可以理解为:
就是先计算Query和各个Key的相似性或者相关性(这里用点乘),得到每个Key对应Value的权重系数,然后对Value进行加权求和,即得到了最终的Attention数值。
详细来说可以分为三个阶段:
第一个阶段根据Query和Key计算两者的相似性或者相关性,求相似性方法有点乘,权重,余弦相似性等;
第二个阶段对第一阶段的原始分值进行归一化处理;
第三阶段就是根据权重系数对Value进行加权求和
4、自注意机制及其计算过程
自注意力机制是注意力机制的变体,其减少了对外部信息的依赖,更擅长捕捉数据或特征的内部相关性
具体计算过程如下:
1.将输入单词转化成嵌入向量;Embedding一般的维度大小设置为512
2.根据嵌入向量与随机生成的矩阵进行相乘得到q,k,v三个向量,这个随机初始化的矩阵后面会通过反向传播过程中进行更新参数;
3.为每个向量计算一个score:score =q . k ;
4.为了梯度的稳定,Transformer使用了score归一化,即除以 d的开方 ;
5.对score施以softmax激活函数,将分数控制在0-1之内;
6.softmax点乘Value值v,得到加权的每个输入向量的评分v;
7.相加之后得到最终的输出结果z :z= ∑ v。
具体矩阵计算过程:
可能大家会有疑问,这个计算过程不就是一堆向量在进行计算吗?怎么求出谁跟谁的注意分数呢?
本质上是一堆张量在计算,但在计算过程中,注意力机制可以通过对输入数据的不同部分分配不同的权重,从而实现对这些部分的加权关注,权重越大就说明越重要。现在看一下具体代码是如何进行计算的:
class SelfAttention(nn.Module):
def __init__(self, embed_dim):#embed_dim 是嵌入维度,用于指定输入和输出的维度大小
super(SelfAttention, self).__init__()
#于将输入 x 的维度进行变换,将其转换为 embed_dim 维度的向量。
self.query = nn.Linear(embed_dim, embed_dim)
self.key = nn.Linear(embed_dim, embed_dim)
self.value = nn.Linear(embed_dim, embed_dim)
def forward(self, x):
q = self.query(x)
k = self.key(x)
v = self.value(x)
#进行矩阵相乘
attn_weights = torch.matmul(q, k.transpose(1, 2))
#对注意力权重进行 softmax 操作,将其归一化为概率分布。这样可以确保注意力权重的和为 1。
attn_weights = nn.functional.softmax(attn_weights, dim=-1)
#将注意力权重与 v 相乘,得到注意力值 attended_values。
attended_values = torch.matmul(attn_weights, v)
return attended_values
5、注意力机制与自注意机制的区别
在上面我们了解到自注意力机制的计算过程,但是怎么理解自注意力呢?作为目前这么火的模型,它跟注意力机制有什么不同点呢?
我们可以通过在经典的seq2seq机器翻译任务中,根据源句子与目标句子是否相同,分成了Attention和Self-Attention。
(1)对于注意力机制来说,举个例子: “机器学习” -> “machine learning”
翻译“machine”的英文的时候,把注意力放在中文的“机器”身上,而不希望从“学习”身上拿到信息。实际上,这个操作叫作单词对齐,对于传统的RNN模型很难实现这一点,而用注意力机制就可以很好的实现这一点,因为注意力的计算不依赖于上一个单词。除此之外,Attention的相比RNN还大大增加了计算效率,不同距离的单词也可以很快的完成计算,不管被计算的这两个单词距离多远,计算注意力花的时间是一样的,这样就可以实现并行计算了。
(2)对于自注意力机制来说,举个例子:
比如这句话“这只蝴蝶真漂亮,停在花朵上,我很喜欢它”,我们怎么知道这个“它”指的是“蝴蝶”还是“花朵”呢?答案是用自注意力机制计算出这个“它”和其他所有输入词的“分数”,这个“分数”一定程度上决定了其他单词与这个联系。可以理解成越相似的,分就越高。通过计算,发现对于“它”这个字,“蝴蝶”比“花朵”打的分高。所以对于“它”来说,“蝴蝶”更重要,我们可以认为这个“它”指的就是蝴蝶。
通过上面的例子,可以发现整个过程没有涉及翻译的过程,也就没有从“源句子”转化成“目标句子”这一个过程,或者说此时“源句子”=“目标句子”。这种情况就是对输入(也就是”源句子“)自身去计算注意力,称为自注意力。
总结:
在翻译任务中,如果源句子≠目标句子,那么你用目标句子中的词去query源句子中的所有词的key,再做相应运算,这种方式就是Attention;如果你的需求不是翻译,而是对当前这句话中某几个词之间的关系更感兴趣,期望对他们进行计算,这种方式就是Self-Attention。