01 什么是Attention

我们以招聘网站的简历匹配度为例,帮助你轻松理解Attention。

Attention

想必各位社畜都用过各大招聘网站投过简历,在这里把公司 HR 招聘过程比做一个注意力计算过程,HR 拿到了甲乙丙丁 4 人的简历,4 人都有自己的擅长,甲擅长 IT,乙擅长跑步,丙擅长游泳,丁擅长演讲,IT、跑步、游泳和演讲就是 key,将这些 key 在每人身上拉平,并赋予能力值,例如甲擅长 IT,乙不擅长 IT,则甲IT的能力值为 0.9,乙IT的能力值为 0.1,这些能力值就是 value。

HR 要找一个 IT 能力强的人,HR 的需求就是 query,query 与 key 求点积相似度(再除以根下 dk,mask、softmax、dropout),再与能力值 value 矩阵计算,最终就是公司得到的这 4 人的 JD 匹配值。

Multi-Head-Attention

如果公司招聘的 JD 不仅要求 IT 能力强,而且要求擅长演讲,即为 2 头 Attention。

02 Attention计算方式

注意力机制的计算方式有很多,在这里拿最常用的举例,公式如下:

先对公式有个印象:假设 querykeyvalue 是维度相同的 3 个向量,用 querykey 的转置进行点积,然后除以维度 dk,这一步可以先理解为归一化,然后进行 sofemax 激活函数,最后去乘以 value

下面用代码来实现这个过程:

defattention(query,key,value):
d_k=query.size(-1)
scores=torch.matmul(query,key.transpose(-2,-1))/math.sqrt(d_k)
returntorch.matmul(p_attn,value)

如何获得 query、key、value

假设我们现在要对这句话计算注意力:I have a dream,这句话有 4 个 token,假设每个 token 用一个 512 维度的向量表示,同时这一个批次只输入这一句话,那么 ‘I have a dream’ 可以用一个形状为 (1,4,512)的矩阵 A 来表示,注意矩阵 A,后面要用到这个矩阵,(1,4,512)这三个参数的含义分别是批次句子数量、句子长度、词嵌入维度。

那么此时如果让 query = key = value = A,直接带入到上面的 attention 函数,进行注意力计算,由于注意力机制是我们模型的一部分,我们要训练模型,要学习注意力机制的参数,而现在 query = key = value = A 是一个固定矩阵,这肯定是不行的,那么怎么解决呢?我们让 A 并行经过 3 个线性层,得到 3 个线性变换后的矩阵,这 3 个结果分别表示 query、key、value,然后再代入到 attention 函数进行注意力计算。假设这 3 个线性层都是 512 * 512,那么变换后结果的形状都还是(1,4,512)。

03 实现多头注意力Multi-head-attention

上述 attention 函数实现的是单头注意力计算,关于多头的宏观理解,可以参考文章开头的举例,那么多头到底是怎么实现的呢?

接着上面 I have a dream 的例子,矩阵 A 并行经过 3 个线性层后,得到 query、key、value,形状都是(1,4,512),假设多头的头数为 8,那么将三者的形状从 (1,4,512)变化到(1,4,8,64),这一步是将最后一维 512,拆分成 8 * 64,也就是将词嵌入维度平均划分成 8 份。还有一个细节,就是将头数 8 与句子长度 4 进行一个维度调换,变换成(1,8,4,64),然后送入到前面的 attention 函数计算注意力,注意力计算结果 B 的形状为 (1,8,4,64),然后进行形状的反向操作,头数 8 和句子长度 4 调换,再将 8 * 64 恢复到 512,最终结果的形状还是(1,4,512),这就完成了多头注意力计算。

下面是代码实现:

defclones(module,N):
returnnn.ModuleList([copy.deepcopy(module)for_inrange(N)])


classMutiHeadedAttention(nn.Module):
def__init__(self,head,embedding_dim):
super(MutiHeadedAttention,self).__init__()
self.d_k=embedding_dim//head
self.head=head
self.linears=clones(nn.Linear(embedding_dim,embedding_dim),4)

defforward(self,query,key,value):
batch_size=query.size(0)
query,key,value=[model(x).view(batch_size,-1,self.head,self.d_k).transpose(1,2)
formodel,xinzip(self.linears,(query,key,value))]
x=attention(query,key,value)
x=x.transpose(1,2).contiguous().view(batch_size,-1,self.head*self.d_k)
returnself.linears[-1](x)

04 什么是自注意力 self-Attention

self-Attention 就是 query = key = value,上面讲的I have a dream的例子实现的就是多头自注意力,在 Transformer 中,连接 Encoder 和 Decoder 的就不是自注意力,query 来自 Decoder,key 和 value 来自 Encoder,也就是 query != key = value。

05 延伸阅读

query,key,value这种命名方式来自于搜索领域,假设我们有一个 key-value 形式的数据集,比如举例说我们知乎的文章,key 就是文章的标题,value 是文章的内容,那这个搜索系统就是希望,当我们输入一个 query 的时候,能够唯一返回一篇最我们最想要的文章。那在 Attention 中其实是对这个 task 做了一些退化处理,我们优化并不是返回一篇文章,而是返回所有的文章 value,并且使用 key 和 query 计算出来的相关权重,来找到一篇得分最高的文章,并给出一堆文章的排序,摆在眼前任你挑选。

点击卡片关注+星标,第一时间获取干货~

本文由 mdnice 多平台发布