【NLP】第五章:注意力机制Attention

内容有点长,应该是史上最详细、最有说服力 的一篇文章。因为几乎我能想到的细节都写了。后面attention的计算流程,我使用的例子的计算结果,和调用pytorch中的attention模块的计算结果是一致的,所以是最有说服力的

如果你是小白,最好从头看,前面写的都是原理。如果你有点基础,就从文章中间开始看,后半部分是注意力模块的计算过程的详细讲解和分析。而且分析思路是独树一帜的,和网上很多类似文章是不一样的。

五、注意力机制Attention

在讲Transformer之前,我先把注意力机制Attention单独拿出来先讲一下。

一是因为attention是Transformer中非常关键的技术,也是transformer之所以区别其他模型的关键之处。但是transformer本身的架构就非常复杂了,到真正开讲transformer时才讲attention,transformer的篇幅就会又臭又长,所以这里我单独把attention先提出来讲。

二是因为attention从2016年被transformer发扬光大后,就一直支撑着NLP领域的发展,现在也已经成为了整个大语言模型领域的根基,就连现在火热的大模型微调技术也和注意力机制相关,比如LoRA。所以要学transformer就必须要先学自注意力机制,要学大语言模型以及大模型微调也必须要学习自注意力机制。

三是因为当下注意力机制不仅与Transformer和大模型有关,还和各种各样的深度学习架构进行联用,形成深度学习的融合。比如和循环神经网络(RNN)、卷积神经网络(CNN)的结合。比如它还可以作为特征提取器,去完成在各个环节上的深度学习融合。所以现在注意力机制已经成为深度学习从业者必备的关键技能了。所以有必要单独开一个章节来说透什么是注意力机制、自注意力机制、多头自注意力机制等相关概念。

(一)注意力机制的发展历程

其实注意力机制最早不是诞生在NLP领域的,是在CV领域。很明显嘛,我们看一张图像时,是一眼就看到图片中的重点信息,而不是所有的像素都看。所以注意力机制最早的研究是在CV领域。

早期关于注意力的探索的代表作是1998年的《A model of saliency-based visual attention for rapid scene analysis》论文,论文作者提出了一种视觉注意力系统:将多尺度的图像特征组合成单一的显著性图,利用动态神经网络按显著性顺序 选择重点区域。

此后2014年,谷歌DeepMind发表的《Recurrent models of visual attention》使注意力机制受到广泛关注,该论文首次在RNN模型上应用注意力机制进行图像分类。

2015年,深度学习三巨头之一Yoshua Bengio等人在《Show, attend and tell: Neural image caption generation with visual attention》中提出了两种基于注意力机制的图像描述生成模型,即使用基本反向传播训练的soft attention方法和使用强化学习训练的hard attention方法。

而将注意力机制首次应用到NLP领域的是,2015年Yoshua Bengio等人发表的《Neural machine translation by jointly learning to align and translate》论文,论文实现了同步的对齐和翻译,解决了以往神经机器翻译(NMT)领域使用encoder-decoder架构的一个潜在问题:将信息都压缩在固定长度的向量,无法对应长句子。

然而真正把注意力机制推向风口浪尖的是,2017年谷歌机器翻译团队发表的《Attention is all you need 》这篇论文,论文提出了一个全新的架构Transformer 模型架构。这个架构目前被公认是继承MLP、CNN、RNN后的第四大基础模型结构。而Transformer架构又是完全基于自注意力机制 。也就是说注意力机制是transform与其他架构最显著区别的核心关键。此后又基于Transformer的BERT、GPT等架构直接点燃了当前全球狂热的AIGC应用。

于是不管是NLPer还是CVer都又纷纷回头深入研究注意力机制,出现了许多基于注意力机制的研究改进和跨界融合。比如通道注意力、空间注意力等也在CV领域风生水起,在多个计算机视觉任务上取得了顶尖性能,甚至一度挑战了CNN在CV领域的霸主地位。比如著名的非卷积架构ViT(Vision Transformer)就是将transformer应用到视觉领域的典型代表。

所以,目前注意力机制已经逐渐成为深度学习领域的重要组成部分,被广泛应用于自然语言处理、计算机视觉、语音识别等多个领域,以提高模型对重要信息的关注和处理能力。

也所以,以后当你再看到像什么软注意力、硬注意力、空域注意力、通道注意力等这些概念,你脑子里立马就知道这些是处理图像的。而NLP领域的注意力机制主要就是自注意力机制、交叉注意力机制、多头注意力机制等几个概念,本篇讲NLP领域中的注意力机制,不扯远,在深度学习领域很难为讲某个具体概念而讲,因为每个概念背后都有大量的背景知识,当你有太多的逻辑断点时,就很难讲清楚了。

(二)从序列->样本->词向量->embedding->attention

1、从序列说起

回到NLP领域,NLP的本质就是个序列问题 。比如我给你说三句话"我爱中国"、"爱我中国"、"我中国爱"这三句话。如果让模型看每个词,那就是每个词自身的含义,比如我字就仅仅表示自己的意思;爱字表示喜欢的意思;中国表示中华人民共和国的意思。

但我们人类都知道其实这三句话的语义大不同。其中"我中国爱"是一个病句,不知道想表达什么;"我爱中国"和"爱我中国"表示的基本意思是一致。而且把"我爱中国"和"爱我中国"这两句话放到人类的知识域中,人类按照经验,还能区分出"我爱中国"和"爱我中国"这两句话中的细微差别,比如二者可能表达的场景不一样,甚至还能继续推断出,比如爱得热烈、或者推断出这个人的国籍有可能是中国,甚至还可以推断出这个人的三观行为、场景、心态等等,这些都是这三个词有序排列 后代表的语义 。我们想让模型理解的不仅是每个词本身的含义,还想让模型理解这些词按照某种特定顺序排列起来后,其背后可能隐含的语义。

所以要使模型像人一样理解文字,它不仅要理解每个词本身的含义,它还得理解词与词之间位置代表的含义

2、再次重申一下"样本"这个概念

比如在时间序列数据中,一个时间点对应的一条数据就是一个样本,样本和样本之间的顺序依时间排列,不能缺失也不能乱序,比如股票历史价格数据、气温数据等。这些数据变化的本质更多的是隐藏在样本和样本的时序排序中,而不是单个样本自身特征中。
比如文本数据,一个字、或者一个词就是一条样本。所以样本和样本之间的顺序也是不能乱序,不能缺失的。样本与样本之间位置关系就是语义的表达。一旦样本之间的顺序打乱,那文本的语义就天翻地覆。
再比如语音信号、视频数据等都是序列数据。其样本和样本之间也是不能缺失不能乱序。

所以要使模型像人一样理解文字表示的语义,它不仅要理解每个词本身的含义,它还得理解样本与样本之间的联系

也所以序列问题的本质就是如何理解样本与样本之间的关系 。之前我们学的机器学习算法、FNN、CNN时,这些算法一般都是学习样本的特征和标签之间的关系 。但是在序列数据中,特征和标签之间的规律都隐藏在样本和样本之间的联系中 ,这就让之前的那些算法无法挖掘其中的规律。所以这也是我们要专门创造处理序列数据算法 (Sequence Models)的根本原因。

也所以序列模型(Sequence Models) 的根本诉求就是要建立样本和样本之间的关联,并借助这种关联提炼出对序列数据的理解。或者说序列模型的根本任务就是找出样本和样本之间的关联,建立起样本与样本之间的根本联系,模型才能够对序列数据实现分析、理解和预测。

3、在NLP中,一条样本对应一个词,一个词被映射成一个词向量

让模型理解语义,而模型只认识数字,所以首先要把文本转化为数字:

战略方向是:把所有样本都用向量来表示。战术实操是:先分词->把每个词映射成词向量

关于分词 ,英文文本一般是按单词以及后缀进行分的,中文文本一般都是按字或词来分的。分词其实是个big topic,避免失焦这里不展开讨论。我们先按英文语料、空格分词,继续讲解。

输入的英文文本,被分完词后,每个单词就是一个token ,然后将这些tokens关联到一个称为嵌入向量的高维向量空间 ,这个过程我们称为词嵌入embedding。

4、怎样选择高维空间?又是如何嵌入的?

你有两条道路:

(1)从0开始,自己重新造轮子:

此时你手头上拿到的文本语料就是你的文本数据。你可以这样操作:先把你所有的文本数据分完词后->然后去重->构建成一个字典->给这个字典进行标签编码->然后再用pytorch框架中的embedding类,把标签编码映射到字典对应的高维空间。流程如下:

上图就是embedding编码技术,在NLP中又叫词嵌入技巧word embedding。embedding编码技术对于深度学习非常重要。网上有文章这样评价embeding编码技术:"它能把万物嵌入万物,是沟通两个世界的桥梁,是打破次元壁的虫洞!" 还说用数学语言来说就是:"它是单射且同构的"。我去,说得这么玄乎,主打一个让你看不懂,怀疑人生。其实从上图看,embedding仅仅就是一种映射方式。将字典中的词汇从一个标量的数字表示形式,映射为高维向量的数字表示。而且这种映射过程是通过矩阵乘的操作完成的(上图右边有计算过程),也就是说可以通过线性层来完成,或者说其实embedding层就是一个线性层,这个线性层的参数矩阵就是上图的字典矩阵B,而且在最开始是随机生成的

(2)站在别人的肩膀上,拿别人已经训练好的词向量为己用:

你可以使用别人已经训练过的高维空间。比如如果别人已经训练过一个理解侦探小说的模型,它就会有一个已经训练好的映射关系(就类似上图中的字典矩阵B),此时你自己的任务是训练另外一本侦探小说,你就可以直接使用人家的这个已经训练好了的字典矩阵。我这样说也只是一种同理类比,就是想表达,其实现在市面上很多分词工具给我们提供的功能就是这个道理。比如Jieba分词、HanLP、THULAC\FuanNLP\LTP\SNLP:

THULAC是清华大学体提供的分词工具包。

FuanNLP是复旦大学的。据说复旦的最好用。

LTP是哈工大的。

SNLP,Sanford NLP for Chinese,是斯坦福大学开发的NLP工具,提供分词、词性标注、命名实体识别等功能,都是基于深度学习的分词方法。

我们可以直接使用它们的,就不用重复造轮子,资源浪费了。而且还能站在一个优秀的起点开始训练

(3)为什么说"而且还能站在一个优秀的起点开始训练"?

因为你拿别人的词向量,都是别人已经训练好的词向量,也就是有一定语义表示 了的词向量。比如表示苹果梨葡萄的词向量彼此距离要近一点,表示猫狗老虎的词向量彼此近一些。而你自己造词典->embedding的词向量还是一个完全没有语义 的词向量。因为embeding层初始化的句子就是一个随机矩阵啊!所以一开始你的词向量就是一堆随机的向量。

但是,上面我又说embedding层其实就是一个线性层是因为,我们在模型训练过程中,在损失函数的牵引下,embedding层的参数是会随着损失降低的方向进行迭代的 。所以只有当我们的模型也训练完毕,此时我们的词向量才是有语义的词向量。这也是embedding层的精髓所在。

但是,训练过模型的同学都知道,模型在一个优秀的起点 开始训练,训练过程会比较丝滑,损失曲线会以一个优美漂亮的弧度下降最后水平。如果你的训练起点很糟糕,那训练过程损失曲线就是大幅上下震荡,面目可憎,一脸不可思议,而且很可能训练很久都无法收敛。而且训练模型也是有技巧的,部分理论可以参考【深度学习】第六章:模型效果评估与优化_模型评估与优化-CSDN博客 这篇文章。

所以即使我们自己搭建embedding层进行语义学习,也建议大家在一个优秀的起点上开始学习,所以也建议大家要站在别人的肩上继续前进。

5、那么词向量到底是如何携带语义的?

前面说过,embedding让词向量有语义表示是在损失函数牵引下,逐渐迭代出来的语义。我们看看词向量是如何逐渐开始携带语义的:

在所有可能的嵌入向量构成的高维空间中,方向可以对应语义 。就是不断地迭代过程中,不停地扭转各个词向量的方向,直到很多方向能表示它的语义。比如上图中的黄色箭头,就可以表示男性和女性这两种语义。在一个词向量空间中,像这种方向肯定是有很多,所以可能会携带很多的语义。

或者我们从低维角度看,从字典维度看,也就是上图在三维空间中携带语义的词向量,坍塌到一维字典中,那nephew、man、uncle、"男性"这几个词的编码都比较接近,而同时niece、woman、aunt、"女性"这几个之间的编码更接近。你可以理解成聚类吧,就是之前所有的词向量都是随机的,迭代完毕后的词向量开始按照语义聚类了,物以类聚了。

上图训练完毕后携带语义的词向量,我们称为查找表(lookup table)。也就是迭代收敛后的embedding层的参数矩阵。

6、从记忆->循环网络->attention,让模型有语义推断的能力

上面的查找表是没有上下文参照的查找表。就是embedding层的表示的语义,也仅仅是每个单词自身的语义。也就是让苹果梨葡萄的词向量彼此靠近一点,让猫狗老虎的词向量彼此近一点。但是这种语义仅仅限于词语义本身。如果给模型一个"苹果公司"或者"我的小猫咪"这种文本,那模型就推理不出啥意思了,它只知道苹果是水果,为什么这里变成了一个公司的名字?!而小猫咪也只是一个小动物,怎么会变成爱人之间的称呼了?!

遇到这样的文本,模型对"苹果"和"小猫咪"的语义理解就得从上下文的联系中来推理了。比如苹果后面跟了一个公司,那苹果大概率就是一个公司的名称,而非水果;如果小猫咪前后都是两个爱人之间在对话,那大概率就是一个昵称而非小动物了。而这种语义理解都是建立在对上下文的理解。也就是需要从上下文中来推理。而这是embedding层做不到的事情,因为embedding层对应的是没有上下文参照的查找表。

要理解上下文就得有记忆 ,就是对上下文见过的词有记忆,才能凭记忆去推理"苹果"和"小猫咪"的真正语义。如何推理呢?简单,在词向量空间中,把本来是代表某种水果的"苹果"词向量,拉到代表公式语义的方向即可。此时的"苹果"词向量就表示公司了呀。"小猫咪"词向量同理喽。那又如何拉呢?用矩阵乘啊,向量在空域中变换不就是通过矩阵变换的嘛。那如何找到那个合适的矩阵,就正好和"苹果"词向量一乘,就把"苹果"拉到公司的语义了呢?这个合适的矩阵其实就是前后文的依赖关系。聚焦最近十几年的时间跨度,NLP领域推出的RNN、LSTM、encoder-decoder架构、注意力机制等,面临的核心问题都是:如何理解文本前后文的依赖关系。

面对这个问题,在注意力机制之前一般使用的是Bag-of-words的方法,但这种方法不能很好的解决语义理解问题。所以后来提出的RNN一族(RNN、GRU、LSTM),就是让样本在时间维度上循环,在时间维度上"记住"样本和样本之间的关系。这种方法虽然一定程度上解决了样本间的依赖问题,但是模型不好训练。因为RNN模块太简单,训练时是同一套参数的n次方,训练很久都无法收敛。一句话,模型不好用!于是人们开始在空间维度上进行改进,就是让模型复杂一点,于是就诞生了基于RNN或者CNN的encoder-decoder架构,但是面对复杂场景时依旧无能为力。一是计算时是时序依赖的,无法并行处理,效率太低呀,只能应付一些小型文本数据;二是无法有效捕捉长距离依赖问题,就是样本和样本之间的距离过长时,二者之间的关系就非常微弱了,无法捕捉长距离样本之间的关系。

直到谷歌的《Attention is all you need》论文发表,注意力机制重新进入NLPers眼前。注意力机制也是解决序列中样本与样本之间依赖问题的,但是它完全不同于RNN一族那样,在时序空间下一个样本一个样本的看。注意力机制是一次性看完整个sequence中的所有样本,就是用空间换时间。我要一下看完所有的样本,这样所有的样本我都看过了。然后再计算所有样本之于其他样本之间的重要性,得到一个相关性矩阵(你也可以理解为就是一个权重矩阵,就是一个上下文信息重要性的矩阵),然后根据这个相关性矩阵扭转输入的各个词向量,直到各个新向量都能根据上下文信息指向最恰当的语义方向。

7、attention模块是如何扭动样本的?

这种就是embedding层词嵌入后的效果,embedding层训练完毕的词向量只有一个,但是很多单词都是含有多种语义的。而具体是哪个意思是得参照上下文的。mole在第一个句子中就表示是一种动物,到第二个句子中就是单位的意思了,到第三个句子中就是脸上的痣的意思。所以这是查找表无法解决的问题。也就是"没有考虑样本和样本之间关系"的问题。也是一个"如何考虑样本和样本之间关系"的问题。

attention模块是通过,生成样本与样本之间的注意力分数矩阵 attention_score(你可以理解为重要性矩阵、权重矩阵都可以),用这个矩阵*原来的词向量,生成新的词向量,让新的词向量指向新的语义方向:

可见,当数据流进入attention模块后,attention模块可以看到所有的上下文信息。attention模块就根据上下文信息生成一个注意力分数矩阵,注意力分数矩阵*输入的每个词向量。此时你可以把注意力分数矩阵理解为权重矩阵,权重矩阵乘以每个词向量后生成的新向量,新向量中就携带了大量的上下文样本的信息,也就是根据上下文含义,拉动旧向量转动到新向量的方向。而新向量方向表示的语义就是通过上下文推导出来的、携带上下文信息、变得更加准确、丰富的词向量了。

或者这样说吧:mole这个词有三种语义,但是mole对应的词向量在某一个时刻只能有一个向量表示。attention可以根据上下文,让mole在不同语境下,表示成不同的向量,也就是不同的语义。比如mole本来的向量是[1,2,3,4],大体上能表示脸上的痣的意思。当序列"One mole of carbon dioxide"流经attention模块时,attention模块计算了这句话中的所有词向量之间的相关性,生成一个各个词之间重要性的权重矩阵,就用这个权重矩阵拉动mole的词向量从[1,2,3,4]变成[6,7,8,9]了,而[6,7,8,9]的方向正是表示单位语义的方向。

因为one这个词向量很可能就和单位的语义方向就很近,而且carbon和dioxide是二氧化碳嘛,所以这两个词就也可能和单位方向的语义比较近。而attention模块算的就是一个权重矩阵,假如它算出来的是mole'=0.1mole+0.5ONE+0.2carbon+0.2dioxide,这计算出来的mole'可不就和它原来的语义就相差很远了,就好像就被扭转到表示单位的那个语义方向了。

同理,当"American shrew mole"这个序列流经attention模块时,attention模块就会根据这句话各个词之间的相关性,生成这句话的注意力分数矩阵,这个矩阵拉动着mole从[1,2,3,4]变成了[11,12,13,14],而[11,12,13,14]向量的指向正是各种小动物的聚集区。

一个训练的非常好的注意力模块,它能计算出需要给初始的泛型 嵌入加个什么向量,才能把它移动到上下文对应的具体方向上。我们再看一个例子:

比如"Tower"这个词就是一个泛型词,就是它的语义是很宽泛的,因为它是一个种类嘛。所以这个词在词向量空间中的方向也是很宽泛的,比如它可以和很多高大物体的名称的方向差不多(上左图)。

但是如果给Tower字前加一个Eiffel,那就具体表示埃菲尔铁塔了。所以此时我们就需要一个机制能更新这个向量,让"Tower"更准确地指向"埃菲尔铁塔"的方向,而且新向量也许还会和巴黎、法国、钢铁制品等词的方向也很近(上中图),就是也相关,就是语义方向大体一致。

但是如果我们再加上一个Miniature一词,那么向量就得进一步更新,就不能和高大的事物相近了,就得和小物件种类相近(上右图)。

所以注意力机制attention是逐步调整这些词嵌入,移动成一个新向量,这个新向量不单单是编码单个词,还融入了更丰富的上下文含义 。所有词向量流经许多层的注意力模块后,新嵌入的词向量的信息就加入了上下文信息,新的词向量就比原来的语义更加准确了。熟悉CV的同学,看到这里是不是会联想到,卷积网络cnn中的卷积核组,疯狂提取图片特征啊,是不是特别类似!卷积核组本身就是各种不同的特征组,比如有点、斑、线、圈等不同的特征,当原图出现角点时,那有角点的特征模板就被激活;当原图出现圈时,有圈的特征模板就被激活。这里的词向量也时类似的道理。就是attention模块中包含有大量的转换方向,当怎么转动损失函数减少最多时,那个方向的矩阵就被激活,拉动词向量往最能代表它的含义上转动。

感兴趣的可以参考【深度视觉】第十七章:卷积网络的可视化_卷积网络可视化-CSDN博客【深度视觉】第三章:卷积网络诞生前:卷积、边缘、纹理、图像分类等_深度学习寻找分界线-CSDN博客

所以,attention模块一是可以精细化一个词的含义,让每个样本表示的语义更加准确;二是让每个样本都携带了上下文样本的信息,并且上下文信息还可以长距离传递,因为它是一次看完序列中的所有样本,才生成的注意力分数矩阵:

(三)自注意力机制的计算流程

上面啰啰嗦嗦一大堆的扭转词向量 ,也许真的是太抽象,无法理解。那本部分讲解数学计算流程,搭配着理解,你就会豁然开朗。

因为《Attention is all you need》论文中提出的是自注意力机制 (Self-Attention),所以这里我们先讲Self-Attention。论文中的Self-Attention的最核心的公式是:

公式很简单,其实也就是很简单。但市面上的相关博文都讲的极其晦涩,各种专业词汇、各种流程图,让你云里雾里,主打一个眼花缭乱、脑子浆糊。所以我打算反其道而行之。既然从前往后理解很费劲,那就倒着来,先看结论,再一步步反推过程,就会一步步豁然开朗了。

所以这里我先用pytorch给大家展示一下上面attention公式的计算过程,然后搭配着计算过程讲解这个公式:

上图就是pytorch框架中的注意力模块的类。我们造了一句话,这句话中有3个词,每个词有5个特征。

然后我们实例化一个attention对象,参数embed_dim表示词向量的长度,所以我们这个例子就得等于5;num_heads是MultiheadAttention的head的数量,num_heads=1表示只有一个attention模块,就是单头注意力。

因为我们求的是自注意力 分数,所以给实例化对象atten传入的query\key\value三个参数都是data,就是求这个3个词之间的注意力分数,或者说就是求这输入的数据中的三个样本之间的注意力。

pytorch的计算结果包含了2个对象,一个是数据data流经注意力模块后的输出结果output。另一个是3个样本之间的权重矩阵,这个权重矩阵其实就是这三个样本之间的注意力分数。

下面我们手动复现一下pytorch的计算流程:

上图是我们手算的流程,结果和pytorch的结果一模一样,说明我们算得过程没错。现在我们结合这个计算过程再讲一遍attention的原理。

1、输入->Q、K、V

假设我们的输入文本是"never give up"->然后对这句话按空格分词->编码成词向量->生成Q、K、V矩阵:

Transformer论文中将这个Attention公式描述为:Scaled Dot-Product Attention。其中,Q、K和V分别叫Query、Key和Value。一看名称就很懵,其实你先不用纠结名称,其实这三个矩阵一开始都是随机生成的。

(1)上图的wq、wk、wv就是随机生成的 ,仅仅是对序列中的所有样本进行了三次重复的线性变换而已。就是attention不是直接在词向量空间寻转样本与样本之间的关系,而是先将所有样本映射到另外一个空间,再开始学习样本与样本之间关系的

(2)参数矩阵wq、wk、wv是在模型训练过程中不断迭代的。也是随着迭代次数的增加、随着损失函数逐渐减小,矩阵wq、wk、wv才逐渐具备查询、索引、内容这个功能的。

(3)上图中,Q矩阵中的词向量我们称为新词向量。那每个新词向量中的每个特征都是旧词向量(data)中所有特征的线性组合而得到的。这里不牵扯样本和样本之间关系啊!没有样本和样本之间关系!never和give和up这三个样本都是独立的,互不干涉的。因为就是一个全连接的线性层嘛,认真学过DNN的同学都知道,它只是混合每个样本自己内部各个特征,样本和样本之间是互不干涉的。

同理K矩阵和V矩阵。所以这个过程中,每个特征进行了3种不同的线性组合。也就是每个词向量的特征混合了它自己的其他特征。也表示每个词的词嵌入向量从随机数开始,是可以到逐渐迭代到接近其自身语义的。

2、计算QKt

这一步是理解注意力机制的核心,我们从各个角度来拆解这个过程:

(1)Q矩阵和K矩阵的来源是一样的,都是源数据data经过架构相同、但参数不同的线性层变换得到的,因为是两套全连接架构嘛。所以Q矩阵来源于never、give、up的词向量矩阵,K矩阵也是来源于never、give、up的词向量矩阵。所以此时Q*Kt的计算结果矩阵(上图A),就是序列(never, give, up)中样本与样本之间的关系矩阵

(2)有的地方给A矩阵也叫问答矩阵,因为A是由Q和K相乘得来的。Q的全称是Query,查询的意思。K的全称是Key,索引的意思。你可以把查询理解为问,把索引理解为答,就不难理解为啥叫问答矩阵了。

(3)因为Q和K都来自同一份数据:序列(never, give, up)中,所以又叫自注意力机制 (Self-Attention)也被称为内部注意力机制(Intra-Attention)。以后我们学机器翻译时Q和K的来源是不同的,那个才是我们平时老说的注意力机制,也叫交叉注意力机制。那时你才需要认认真真的想想谁当Q谁当K?为啥这个输入当Q另外一个输入当K?这里先不扯远,后面会单独开一个小标题讲这个话题。

(4)看A中的红色框,就是对角线上的数字,这些数字你可以看成样本自己和自己之间的关系。你也可以理解为在序列never give up中,要理解这句话,每个单词自身含义占的比例。这是当然了,你理解这句话的前提是你每个单词都单独认识它啊,每个单词都不认识,就别谈理解句子了。这是一个锚点。也就是我前面说的要使用人家已经训练好的词嵌入,也就是站在一个优秀的起点上。

(5)对比A中黄框和绿框中的两个数字。假如黄框中的数字表示give和never的关系,那绿框中的数字就表示never和give的关系!我和你的关系好,不代表你和我的关系就也好。所以A也是一个不对称矩阵。这很合理,也很容易理解。give后面跟up的概率和up前面出现give的概率是不一样的。give依赖up,up未必也依赖give呀。所以give之于up的重要性和up之于give的重要性是不一样的。

(6)对矩阵A进行解读:

当Q说我是never,我想查询一下我和谁最相关?K是索引,K就把它里面的所有词都给never匹配一个"关注度",包括never本身。就是A矩阵的第一行三个数字。第一个数字就表示never本身的含义之于never语义的重要性。第二个数字就是give样本之于never的重要性。第三个数字就是up样本之于never的重要性。

如果Q说我是give,我想查询一下我和谁最相关?K就把它里面的所有词都给give匹配一个"关注度",就是A矩阵的第二行的三个数字。

如果Q说我是up谁和我最相关?K就把它里面的所有词都给up匹配一个"关注度",就是A矩阵的第三行的三个数字。

所以,Q矩阵是问,K矩阵是罗列出所有可能答案的"关注度"。所以A矩阵按行看,就是Q中某一个向量对K中的所有向量的"关注度"。

(7)我们再从数学的角度来理解:黄框数字是从Q中的never向量点乘K中的give向量;绿框数字是从Q中的give向量点乘K中的never向量。

向量点乘的几何意义是一个向量在另一个向量方向上的投影,点乘的结果可以衡量两个向量的相似度。当两个向量的方向特别一致,点乘的结果就越大;当两个向量的方向完全不一致,就是垂直喽,那点乘的结果就是0。所以点积越大就表示越相似,越相似就表示语义越接近,那让而且语义越接近是不是就是让二者的"关注度"越高。所以点乘是可以表示"关注度"的。

3、QKt除以根号dk->softmax变换

dk指词向量的长度 ,就是词向量的特征个数。

QKt除以根号dk,论文中叫scale 过程,也就是一个标准化的过程。

对scale再进行softmax 转化,是把样本与样本之间的关系转化成一个类概率的形式。就是attention模块中计算出来的注意力分数矩阵(Attention Score)。这个矩阵就是注意力模块推理、记忆的关键。

也就是用了scale和softmax两步,把QKt这个类似于问答矩阵,给转化成了一个真真正正的、样本与样本之间重要性的、注意力分数矩阵:

从上图看,never的语义和它自己本身的语义有57%的关联,和give有38%的关联,和up有5%的关联。

give的语义和它自己本身的语义有35%的关联,和never有62%的关联,和up有3%的关联。

up的语义和它自己本身的语义有2%的关联,和never有78%的关联,和give有20%的关联。

为什么会有scale操作呢

前面已经说过QKt就是序列中样本与样本之间关系了,那我们接下来的方向就是,尽量把QKt中的数字往权重的方向映射。而最常用的方式就是softmax变换。但是为什么softmax之前又除以了一个根号dk呢?

我们现在这个例子,每个词向量是5个特征。实际项目中词向量一般都是成千上万或者数万个特征的。当词向量中的特征非常多的时候,而且还不稀疏,那么这向量之间点乘的结果的方差就会很大,即结果中的元素差距很大。就是矩阵QKt中数字间的方差比较大。

在transformer原文中描述,假设q和k的元素是相互独立维度为dk的随机变量,它们的均值是0,方差为1。那么q和k的点乘的平均值为0,方差为dk,如果将点乘的结果进行缩放操作,也就是除以dk,就可以有效控制方差从dk回到1 ,也就是有效控制梯度消失问题

如果矩阵QKt中数字间的方差比较大,那进行softmax转化时,softmax是先用一个自然底数e将输入中的元素间差距继续"拉大",然后才归一化到权重。所以如果不除以dk,把方差从dk拉回到1,那对于QKt行中有数量级较大的数,softmax会将几乎全部的概率都分配给这个大数,大数的概率就无限接近1,同时其他相对较小的数的概率就无限接近0。而在softmax后的词向量中,不管是出现0还是出现1,反向传播求导时,softmax这里的梯度都会趋近0,导致梯度消失,无法训练:

4、根据注意力分数矩阵,扭转各个词向量

注意力分数矩阵其实就是一个权重矩阵,用权重矩阵就可以扭转各个词向量到适合上下文的方向了:

上图中从V矩阵到z矩阵就是加入了注意力分数的新的词向量,如果看得不太懂,看下图:

至此我们的新向量never"、give"、up"也学习彼此之间的重要性。这就是注意力机制如何学习样本与样本之间关系的。

5、把扭转后的词向量,通过线性层变换后输出

前面讲计算K、Q、V时,就是把输入的词向量映射到另外一个空间中计算的,计算完毕并扭转完毕后自然还要再映射回原空间:

至此,这就是单头自注意力模块的所有计算流程。

(四)注意力机制、自注意力机制

其实上面讲自注意力机制时,已经给大家科普了什么是注意力机制。自注意力机制 是注意力机制的变体,捕捉的是序列内部样本和样本之间的关系。自注意力机制的Q、K、V是同一个东西,或者三者来源于同一个序列X,三者同源。找序列X中每个样本之于其他样本的重要性,每个样本都只关注对它重要的样本,忽略不重要的样本,就是自注意力。

注意力机制 又叫交叉注意力,就是因为它的Q和K是不同来源的:

就是交叉注意力机制计算的是,来自一个模态的查询和来自另一个模态的键之间的注意力分数。就是让模型关注一个输入中与另外一个输入中最相关的部分。交叉注意力机制是能够联合两种模态提取注意力的。所以在seq2seq任务中,也就是encoder-decoder架构中,Q就来自decoder中的内容,K来自encoder中的内容。自然V也是来自encoder了。所以自注意力机制是重点关注数据A内部的各个细节和重点的。而交叉注意力则是重点关注模态A数据和模态B数据之间相互关注的重点,是从多模态数据中提取注意力分值的。

交叉注意力和自注意力的区别比较好理解,这里我想重点讲一下多头自注意力机制。因为这里还是有几个坑的,有必要从"头"这个维度也把注意力机制也说清楚。

(五)多头自注意力机制

自注意力机制 是一次性看完一个序列中的所有样本,然后通过所有样本生成一个问答矩阵 ,再对问答矩阵进行scale和softmax归一化成注意力分数矩阵 Attention Score。注意力分数矩阵是自注意力模块具有推理、记忆的关键。但是我们可以想象一下,如果是一个整本推理小说那样的长序列的输入呢?假如推理小说有1万个单词,那注意力分数矩阵就是1万x1万的方阵!这就是注意力机制的推理代价,计算量太大!所以人们又研究出了一个多头注意力机制,让这个沉重的计算过程可以并行计算,节省时间成本。

很多讲多头注意力机制的资料都云里雾里,好像一直在说,多头就是多弄几个注意力模块就是多头了。其实不是的,因为一旦你的序列定下来了,比如前面使用的例子,输入的是一整篇推理小说,就在文章的最后一个单词was后面让你预测,这种情况就是输入序列是一定的。那样本和样本之间的关系就是定的,就是注意力分数矩阵就是定的。你不能说一个头生成一个注意力分数,多个头生成多个注意力分数,那每个头的理解不一样,预测就也不一样啊。谁杀人没杀人你不能胡说呀。所以要想有正确答案,那大家都文本序列的理解应该是一致的。比如我说苹果,你脑子里马上浮现的就是苹果水果。如果你理解成苹果公司也可以,因为我也知道苹果公司,我会纠正你的理解方向。但是如果你理解的是埃菲尔铁塔,那我们两个是无法沟通的,我纠正都无从下手。所以多头不是多个注意力模块对序列进行多次解读,也不是多个头对序列分而解读,而是多个头对输入的词向量的特征进行分而解读,而且是解读完毕后再cancat一起,映射回原向量空间 。所以你一定要明白,多头的初衷不是解决模型效果的!而是解决计算效率的!或者说多头的初衷是解决计算效率的,但是在解决计算效率时,它的解决方式一定程度上也带来了,比如说,增强了模型的表达能力和泛化能力这样的效果。

我们还是以pytorch的多头注意力模块的计算流程为标杆,看看人家的计算流程,看看人家是怎么理解的:

下面开始手动计算上右图中的两头注意力模块,看看结果一样不一样:

我想上面计算流程摆出来,其他就不必多说了,都是多余的了。现在总结一下:

1、多头自注意力机制中的QKV是来自全部的序列样本和样本的全部特征

2、之所以叫多头是因为,把QKV在词向量的特征维度空间给分隔了,然后分而治之。分隔几段就是几头,然后每个头就各种计算自己的注意力分数,各自求自己对应的那部分数据的z,然后把所有头的z横向拼接,再用一个线性层输出。

3、pytorch输出的注意力分数矩阵attention_score,是所有头的注意力分数,按照权重,加权求和得到的。

至此结束。

相关推荐
IT古董27 分钟前
【深度学习】常见模型-Transformer模型
人工智能·深度学习·transformer
沐雪架构师1 小时前
AI大模型开发原理篇-2:语言模型雏形之词袋模型
人工智能·语言模型·自然语言处理
摸鱼仙人~2 小时前
Attention Free Transformer (AFT)-2020论文笔记
论文阅读·深度学习·transformer
python算法(魔法师版)2 小时前
深度学习深度解析:从基础到前沿
人工智能·深度学习
小王子10242 小时前
设计模式Python版 组合模式
python·设计模式·组合模式
kakaZhui3 小时前
【llm对话系统】大模型源码分析之 LLaMA 位置编码 RoPE
人工智能·深度学习·chatgpt·aigc·llama
struggle20253 小时前
一个开源 GenBI AI 本地代理(确保本地数据安全),使数据驱动型团队能够与其数据进行互动,生成文本到 SQL、图表、电子表格、报告和 BI
人工智能·深度学习·目标检测·语言模型·自然语言处理·数据挖掘·集成学习
佛州小李哥3 小时前
通过亚马逊云科技Bedrock打造自定义AI智能体Agent(上)
人工智能·科技·ai·语言模型·云计算·aws·亚马逊云科技
Mason Lin4 小时前
2025年1月22日(网络编程 udp)
网络·python·udp
清弦墨客4 小时前
【蓝桥杯】43697.机器人塔
python·蓝桥杯·程序算法