欢迎光临散文网 会员登陆 & 注册

自注意力机制详解

2022-09-20 10:19 作者:wwbs  | 我要投稿

1.1 注意力

先来看注意力机制,它的思想来自于网络搜索:有一个q(query),相当于你在搜索框里打的东西;有一个v(value),相当于网络上各种各样的内容;有一个k(key),它代表value所对应的关键词,和value是一一对应的关系。

过程是这样的:你在搜索框里打进某个q,系统就应该将q和各种各样的k进行比对,看看相似度如何:相似度高的,搜索结果中该k对应的v就排在前面(也即,对应的v的注意力权重就高),反之同理。

(eg:q是“发烧了怎么办”,有个v是“xx退烧药”,其对应的k是“发烧、退烧…”,将q和k一对比,发现相似度高,所以搜索结果中会把“xx退烧药”放在前面。)

上面的过程很符合直觉,但是有个关键的技术性细节没有讲到:到底如何计算q和k的相似度呢?主要有两个方法:

  1. 简单粗暴的方法:把q和k丢进某个神经网络,让网络给你算一个相似度出来;

  2. 数学方法:对q和k做点积。接下来展开讲一下这个方法:

我们知道,无论什么样的数据,到了神经网络里面无非就是向量(矩阵)的形式。所以这里,我们可以将q,k,v都视为向量:

对q和两个k分别做点积(这里就隐含了一个要求,q和k的长度必须相等),得到两个注意力权重:


然后,用得到的注意力权重对v求加权:


可以发现,结果是一个向量,其长度=v的长度。

当然,在实操中q,k,v的个数不可能就一两个,因此接下来要做一下维度的扩展。首先,把k,v都增加到m个:


自然而然地,k,v就可以各自合并成矩阵K,V:


d_k表示key的长度,d_v表示value的长度

接下来还是重复之前的步骤,先将q和K相乘(和刚才一样,K要先转置):


我们得到了一个长度为m的向量a_1,其中每一个元素分别代表q对每一个(k,v)对的注意力权重。

接下来将向量a_1与V相乘:


得到的结果和刚才一样,还是一个长度为d_v的向量。

现在,把q增加到n个,那么最终的结果就会变成一个n\times d_v的矩阵。其中每一行是以Q中对应行作为q,对(k,v)做注意力计算得来的。

1.2 自注意力

以上,就是所谓的“注意力机制”。而自注意力机制呢,其实就是在注意力机制的基础上,加入了一个前提条件:q,k,v都是一个东西。

我们用一个NLP的例子来理解自注意力机制:

输入是一个句子长度(在这里是4)\times词典长度的矩阵,它代表“state of the art”这个句子:


我们将它复制三份,每一份分别对应着Q,K,V。然后和刚才一样,对Q和K做矩阵乘法:


得到一个4\times4的矩阵A,其中的元素,比如(3,2)代表的是第三个词“the”与第二个词“of”的相似度。

然后将A和V相乘:


结果是一个和输入同样形状的矩阵。但是,在输入中,每一行的意义很简单,就是代表了句子中的每一个词——而在这里,每一行的意义是:以句子中原来的那个词为前提,考虑这个词和句子中其他词的相关性,根据相关性计算出的所有词的信息的加权。

比如说,第一行是以“state”为前提,那么“state”肯定和自己相关性最大,其次也许是”art“,然后是”of“,”the“,那么第一行中的信息最主要就来自于state,还包含了一定”art“的信息,”of“,”the“的信息可能就没多少了。

(各个矩阵现实意义的理解非常重要,之后的mask操作会用到这块的知识。)

(做任何东西都不能只关注技术细节,更要关注现实意义。经济学是这样,深度学习也是如此。)

1.3 多头自注意力

自注意力的思想还是非常符合直觉的:从人类的角度来看,对于一个包含很多词的句子,我们在考虑每一个词时,不是光考虑这个词本身,更会考虑这个词和其他词的关系。

至于多头自注意力呢…呃,说实话,就没有这么优美了,并不是很好解释,所以我们直接进入技术细节。

(”多头“和”自“其实并不一定要挂钩,它们其实是两个不相干的概念,但这里为了方便就放到一起讲了)

首先,我们要决定”头“的数量,比如说8。

”头“是什么意思呢?你可以理解为,每有一个头,就做一次自注意力。

聪明的你肯定一下就会发现不对:我们刚才谈到的自注意力机制中,是没有任何可学习参数的。换言之,在输入序列给定的情况下,你无论做多少次自注意力,结果都只有一个。

因此在多头自注意力中,并不是直接把输入序列拿来做自注意力,而是对输入序列做3n次线性投影(n为头的数量),得到n组的(q,k,v)之后,再对各组做自注意力。

”线性投影“,看起来就不像人话,在实现中其实就是经过某个不带bias的线性层后得到的东西:

 linear = nn.Linear(INPUT_SIZE, OUTPUT_SIZE, bias=False)
 x = torch.ones([10, INPUT_SIZE])
 y = linear(x)
 # y就是x进行一次线性投影后的结果,其形状是(10, OUTPUT_SIZE)

既然是线性层,那就有可学习参数;有可学习参数,得到的各组(q,k,v)就不一样,进而做自注意力的结果就不一样。

1.4 带掩码的自注意力

Transformer做预测的流程如下(假设是一个机器翻译的任务):

  1. 把待翻译的句子喂给encoder,得到某个输出结果x'

  2. x'<SOS>喂给decoder,得到翻译结果的第一个词y_1

  3. x'y_1喂给decoder,得到翻译结果的第二个词y_2

  4. …重复以上步骤,直到decoder输出<EOS>为止。

我们可以发现,这个流程和基于RNN的Seq2Seq差不多,都是串行的,也即每一次预测必须以上一次预测的结果为根据,只能一个词一个词地输出结果,没办法一次性输出整个结果句子。

但Transformer的一个特点,它训练的过程是并行的,也即我们会一次性把整个(真实的)翻译结果都输入decoder,然后让它一次性输出整个(预测的)翻译结果。

我们还是拿刚才自注意里面的图来讲解:

(decoder的输入按理应该有<BOS>这个token,这里为了方便就不加了)


行代表的是注意力的”根据“,列代表的是注意力的”对象“。比如说,元素(1,2)就是”state“对”of“的注意力——等等,这里是不是有问题?

我们的模型最终是用来做预测的,不是光训练着玩的,而预测是根据前一个词预测后一个词,比如说,我们知道了第一个词是”state“,我们是在知道且仅知道它的情况下,去预测下一个词”of“。但是上面矩阵里面的元素(1,2)是”state“对”of“的注意力——这下就变成:我们在知道”of“的情况下去预测”of“,训练的意义就没了。

所以我们需要对这个矩阵做一个”掩码“操作,使得模型在预测某个词的时候,只能看到它之前的词。具体来说,就是把下面阴影部分的地方给”删掉“:


具体是如何删掉呢?就是把这些地方的值给替换成负无穷大(或者某一个很大的负数),这样在对这个矩阵做softmax操作后,这些地方的值就会变成0。(softmax这一环刚才没有提到,因为它只能算一个技术细节,对理解自注意力机制本身并不是很关键)


自注意力机制详解的评论 (共 条)

分享到微博请遵守国家法律