赞
踩
经过词嵌入和位置编码后,进入编码器之前,输入的数据维度为:(batch_size, seq_length, embedding_size)之后数据进入编码器堆栈中的第一个 Encoder,与 Query、Key、Value 矩阵相乘。 embedding_size:词嵌入向量的大小。batch_size:批量大小,是一次输入模型中样本的数量。
为方便理解,以下的图示与介绍中将去掉 batch_size 维度,聚焦于剩下的维度:
Query, Key, Value 实际上是三个独立的线性层。每个线性层都有自己独立的权重。输入数据与三个线性层的权重(可看李宏毅的那门课,刚入门有讲解的)分别相乘,产生 Q、K、V。
数据被分割到多个注意头中,以便每个注意头能够独立。 1.“切分”只是逻辑上的分割。对于参数矩阵 Query, Key, Value 而言,并没有物理切分成对应于每个注意力头的独立矩阵, 2.逻辑上,每个注意力头对应于 Query, Key, Value 的独立一部分。各注意力头没有单独的线性层,而是所有的注意力头共用线性层,只是不同的注意力头在独属于各自的逻辑部分上进行操作。
第一步,线性层权重矩阵的切分 这种逻辑分割,是通过将输入数据以及线性层权重,均匀划分到各注意头中来完成的。我们可以通过选择下面的 Query Size大小来实现: 在我们的例子中,这就是为什么 Query_Size=6/2=3。尽管层权重(和输入数据)均为单一矩阵,我们可以认为它是“将每个头的独立层权重 ‘堆叠’在一起"成为一个矩阵。
所以在代码中定义 W_Q、W_K、W_V 矩阵时可这样定义:
self.W_Q = nn.Linear(embedding_size, Query_size * n_heads, bias=False) self.W_K = nn.Linear(embedding_size, Query_size * n_heads, bias=False) self.W_V = nn.Linear(embedding_size, Value_size * n_heads, bias=False)
第二步,重塑 Q、K 和 V 矩阵形状 经由线性层输出得到的 Q、K 和 V 矩阵要经过 Reshape 操作,以产生一个 Head 维度。现在每个 "切片 "对应于代表每个头的一个矩阵。
现在有了分属各头的 Q、K、V 3个矩阵,这些矩阵用来计算注意力分数。为方便理解,只展示单个 head 的计算。使用最后两个维度(seq_length, Query size),跳过前两个维度(batch_size, n_heads)。从本质上讲,可以想象,计算对于每个头和每个批次中的每个样本都是 "重复"进行的。(虽然实际上它们是作为一个单一矩阵操作进行的)。
在 Q 和 K 的转置矩阵做一个矩阵乘法。
2.Padding操作
将掩码值被添加到结果中。在 Encoder Self-attention 中,掩码用于掩盖填充值,这样它们就不会参与到注意分数中。
3.上一步的结果通过除以 Query size 的平方根进行缩放,然后对其应用Softmax。
4.在 Softmax 的输出和 V 矩阵之间进行另一个矩阵乘法。
在 Encoder Self-attention 中,一个注意力头的完整注意力计算如下图所示:
我们现在对每个头都有单独的注意力分数,需要将其合并为一个分数。这个合并操作本质上是与分割操作相反,通过重塑结果矩阵以消除 n_heads 维度来完成的。其步骤如下:
1. 交换头部和序列维度来重塑注意力分数矩阵。换句话说,矩阵的形状从
(batch_size,n_heads,seq_length,Query_size)
变为:
(batch_size,seq_length,n_heads,Query_size)
2. 通过重塑为 (Batch, Sequence,Head * Query size)折叠头部维度。这就有效地将每个头的注意得分向量连接成一个合并的注意得分。
由于 embedding_size=n_heads * query_size ,合并后的分数是(batch_size,seq_length,embedding_size)。在下图中,我们可以看到分数矩阵的完整合并过程:
解码端采用自回归的方式进行输出,也即t时刻的输入依赖于t-1时刻以前的输出,但是为了加快训练速度,使用了teacher forcing 方法,也即将真实标签当作输入。但是在预测阶段,是没有真实标签的,这就导致训练阶段和测试阶段形式不匹配的问题,由此引进了Mask机制。
query 、 key 、 value 的概念其实来源于推荐系统。基本原理是:给定一个 query,计算query 与 key 的相关性,然后根据query 与 key 的相关性去找到最合适的 value。举个例子:在电影推荐中。query 是某个人对电影的喜好信息(比如兴趣点、年龄、性别等)、key 是电影的类型(喜剧、年代等)、value 就是待推荐的电影。在这个例子中,query, key 和 value 的每个属性虽然在不同的空间,其实他们是有一定的潜在关系的,也就是说通过某种变换,可以使得三者的属性在一个相近的空间中。
在 self-attention 的原理中,当前的输入样本 ,通过空间变换变成了一个 query, qi=xi⋅WQqi=xi⋅WQ, 。 类比与推荐系统中的检索项,我们要根据 query 与 key 的相关性去检索所需要的value。 那么 K=X⋅WKK=X⋅WK 为什么是 key 呢?
因为按照推荐系统的流程,我们要去找 query 和key 的相关性,最简单的方法就是进行点积,获得当前样本与关系向量。而在self-attention 的操作中,会进行如下操作 ri=qi⋅KTri=qi⋅KT,这样 每一个元素就可以看做是当前样本 与序列中其他样本之间的关系向量。
获得样本之间的关系后,就顺理成章了,只需要将 riri 归一化后乘以 V 矩阵,便可以得到self-attention 的最终加权输出:
为什么是Q、K、V?既然K和Q差不多(唯一区别是W_k和W_Q权值不同),直接拿K自己点乘就行了,何必再创建一个Q?创建了还要花内存去保存,不断去更新,多麻烦。
为什么Q,K?
先从点乘的物理意义说,两个向量的点乘表示两个向量的相似度。
Q,K,V物理意义上是一样的,都表示同一个句子中不同token组成的矩阵。矩阵中的每一行,是表示一个token的word embedding向量。假设一个句子"Hello, how are you?"长度是6,embedding维度是300,那么Q,K,V都是(6, 300)的矩阵
简单的说,K和Q的点乘是为了计算一个句子中每个token相对于句子中其他token的相似度,这个相似度可以理解为attetnion score,关注度得分。比如说 "Hello, how are you?"这句话,当前token为”Hello"的时候,我们可以知道”Hello“对于” , “, "how", "are", "you", "?"这几个token对应的关注度是多少。有了这个attetnion score,可以知道处理到”Hello“的时候,模型在关注句子中的哪些token。
为什么不Q=K?
和Q的点乘是为了得到一个attention score 矩阵,用来对V进行提纯。K和Q使用了不同的W_k, W_Q来计算,可以理解为是在不同空间上的投影。正因为有了这种不同空间的投影,增加了表达能力,这样计算得到的attention score矩阵的泛化能力更高。这里解释下我理解的泛化能力,因为K和Q使用了不同的W_k, W_Q来计算,得到的也是两个完全不同的矩阵,所以表达能力更强。
但是如果不用Q,直接拿K和K点乘的话,你会发现attention score 矩阵是一个对称矩阵。因为是同样一个矩阵,都投影到了同样一个空间,所以泛化能力很差。这样的矩阵导致对V进行提纯的时候,效果也不会好。
为什么引入V?
虽然有了attention score矩阵,但是这个矩阵是经过各种计算后得到的,已经很难表示原来的句子了。然而V还代表着原来的句子,所以我们拿这个attention score矩阵与V相乘,得到的是一个加权后结果。也就是说,原本V里的各个单词只用word embedding表示,相互之间没什么关系。但是经过与attention score相乘后,V中每个token的向量(即一个单词的word embedding向量),在300维的每个维度上(每一列)上,都会对其他token做出调整(关注度不同)。与V相乘这一步,相当于提纯,让每个单词关注该关注的部分。
参考:https://www.zhihu.com/question/319339652/answer/3315664321
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。