首页 > 其他分享 >Transformer多头自注意力及掩码机制详解

Transformer多头自注意力及掩码机制详解

时间:2024-07-21 19:00:39浏览次数:11  
标签:Transformer self mask len 详解 掩码 model 注意力 size

系列文章目录


文章目录

前言

在本文中我们重点介绍Transformer中的掩码机制及多头自注意力模块的原理以及代码实现。

一、self-attention

1. 注意力机制

 人在处理信息时,会将注意力放到需要关注的信息上,深度学习中的注意力机制便是源自于此,其允许模型在处理信息时能够聚焦于输入数据的特定部分,从而提高模型的性能和泛化能力。
 接下来我们举一个现实生活中的例子。例如我们现在很饿,然后面前有一碗热气腾腾的面条,一本书还有一个水杯,需要我们去选择面前的一个东西,我们很有可能去选择那碗热气腾腾的面条。在这个例子中,我们很饿想吃东西,这是大脑发给我们的自主性提示,引导我们去选择能填报肚子的东西。而热气腾腾的面条,他的外部特征(散发着热气)客观性代表着他是含有热量的食物,这是非自主性提示。我们可以通过眼睛看到面条,书,还有杯子这些东西,这称为感官输入。我们以自主性提示作为引导,然后以面条、书还有水杯这些物体的外部特征作为辅助信息(非自主性提示),最后结合这两种提示,最后选择面条去填饱肚子。
 在注意力机制的背景下,自主性提示叫做查询,非自住性查询称为,而感官输入称为。给定任何查询,我们通过与键去做注意力汇聚(结合自主性提示和非自主性提示),最终选择合适的感官输入(值)。
在这里插入图片描述
 在深度学习中,注意力机制就是给定一些键值对,给定一个查询,通过将查询与键送到注意力评分函数中,最终得到对于不同值的权重,然后将这些值做加权和,最终得到关于这个查询的输出。
在这里插入图片描述
 用数学语言描述,就是假设有一个查询 q q q和 m m m个键值对( k 1 k_1 k1​, v 1 v_1 v1​) ( k 2 k_2 k2​, v 2 v_2 v2​) …( k m k_m km​, v m v_m vm​),注意力汇聚函数 f f f就被表示成值的加权和:

f ( q , ( k 1 , v 1 ) , . . . , ( k m , v m ) ) = ∑ i = 1 m α ( q , k i ) v i f(q,(k_1,v_1),...,(k_m,v_m)) = \sum_{i=1}^m\alpha(q,k_i)v_i f(q,(k1​,v1​),...,(km​,vm​))=∑i=1m​α(q,ki​)vi​

 其中查询 q q q(向量)和键 k i k_i ki​(向量)通过注意力评分函数 α ( q , k i ) \alpha(q,k_i) α(q,ki​)求出的值再做softmax操作最后得到 v i v_i vi​的注意力权重(标量)。

注意力评分函数分为加性注意力和缩放点积注意力。

加性注意力

α ( q , k ) = w v T tanh ⁡ ( W q q + W k k ) \alpha(q,k) = w_v^T\tanh(W_qq+W_kk) α(q,k)=wvT​tanh(Wq​q+Wk​k)

 其中, W q W_q Wq​、 W k W_k Wk​和 W v W_v Wv​为可以学习的参数,将查询和键分别通过各自的多层感知机后相加,使用 tanh ⁡ \tanh tanh作为激活函数,接着输出再通过一个多层感知机得到最终输出。
缩放点击注意力
 点积操作要求查询和键具有相同的长度。假设两个向量的点积的均值维0,方差维 d d d,且无论向量长度如何,点积的方差再不考虑向量长度的情况下仍然为1,再将点积除以 d \sqrt{d} d ​进行缩放,数学公式为:

α ( q , k ) = q T k / d \alpha(q,k) = q^Tk/\sqrt{d} α(q,k)=qTk/d

2. 自注意力机制

 在上文介绍的注意力机制中,查询和键是不同的。当查询、键和值来自同一组输入时,每个查询会关注所有的键值对并生成一个注意力输出,由于查询、键值对来自同一组输入,因此被称为自注意力。
 自注意力机制(Self-Attention)是Transformer模型中的核心组成部分之一,它允许模型在处理序列数据时,能够捕捉序列内部的长距离依赖关系。自注意力机制的核心思想是让模型在处理序列的每个元素时,考虑该元素与序列中其他所有元素的关系。具体来说,它通过计算元素之间的相似度,来决定它们之间的权重,从而实现信息的聚合。在Transfoemer中的注意力评分函数使用的是缩放点积注意力。
 接下来我们举一下例子来说明在transfomer中的自注意力机制的计算过程。
在这里插入图片描述

 从图中可以看出,当我们输入一个句子’我爱吃梨‘时,首先将其编码为4x512维,然后其会分别通过三个全连接网络进行映射得到Q,K,V(图中省略了经过全连接层的过程),接着Q乘以K的转置得到4x4的权重图,权重图的每一行会做softmax操作最终得到4x4的注意力权重图,在这个注意力权重图中,第1行的4个值分别代表了句子中的’我’字与’我‘,’爱‘,’吃‘,’梨‘这4个字的关联程度,其与自身的关联程度最大(标注红色),其他几行同理。接着注意力权重图与V相乘得到最终结果。在输出的最终结果中,其第一行的每一个元素都是由其他位置的词向量的对应维度进行加权和得到的。(换句话说,每一行的值是由所有行按照注意力权重加权得到的,即每一行都或多或少的包含了其他行的信息。)
因此,总的来说,Transformers中的自注意力机制使得模型能够同时考虑输入序列中的所有位置,允许模型根据输入序列中的不同位置之间的关系,对每个位置进行加权处理,从而捕捉全局上下文信息。

3. 代码实现

import torch
import torch.nn as nn
import numpy as np
class self_attention(nn.Module):
    def __init__(self):
        super(self_attention,self).__init__()

    def forward(self,q,k,v,att_mask=None):
        # q:[batch_size,n_heads,len_q,d_k]
        # k:[batch_size,n_heads,len_k,d_k]
        # v:[batch_size,n_heads,len_v,d_k]
        # attn_mask:[batch_size,n_heads,len_q,len_k]
        d_k = q.size(-1)
        scores = q @ k.transpose(-1,-2)/np.sqrt(d_k)
        if att_mask is not None:
            scores.masked_fill_(att_mask,-1e9)
        attn = nn.Softmax(dim=-1)(scores)
        return attn @ v
        
q = torch.randn((2,5,6,6))
att = self_attention()
print(att(q,q,q).shape)

在这里插入图片描述
 在代码中,输入维度为[2,5,6,6],输出维度仍然为[2,5,6,6],因此transform中输入在经过注意力汇聚后输出维度不变。

二、掩码机制

1. 原理介绍

 掩码机制是Transformer中非常重要的一个部分,在模型结构图中的三个地方有用到掩码机制,如下图所示。Transformer中的掩码分为两种,分别是填充mask和因果mask。在下图中,1和2所在为位置为填充mask,3所在的位置为因果mask。
在这里插入图片描述

填充mask: 我们在给transformer输入句子时通常是一次性输入好几个句子(batch),每个句子的长度不相同,为了transformer能够更好的一次性处理这些长度不同的句子,我们通常要对句子进行填充,使这些句子的长度相同(比如在句子末尾填充0)。但是在计算注意力的时候,这些填充的部分不应该参与计算,所以我们要在注意力权重进行softmax之前要把填充部分进行mask(就是把填充部分变成负无穷),使填充部分的注意力权重在经过softmax后无限接近0。
因果mask: 在解码器训练的过程中,不能让模型知道未来时间步的信息,否则就相当于告诉了模型的最终答案是什么。例如我们给解码器输入’I like eating pears’,当我们在计算’like’这个词与与其他词的注意力权重时,因为解码器是一个单词一个单词预测的,在预测’like‘这个词时是不应该知道后面的单词,所以与’like‘后面的单词不应该产生关联性。

 下面两幅图说明了对注意力权重进行mask的过程。在注意力权重图中,黑色部分表示其权重值应为负无穷(softmax之前),红色部分表示权重值最大(肯定是与自身的关联性最大)。
填充mask
在这里插入图片描述

因果mask

在这里插入图片描述

2. 代码实现

填充mask

def get_pad_mask(seq_q,seq_k):
    # seq_q:[batch_size,len_q]
    # seq_k:[batch_size,len_k]
    len_q,len_k = seq_q.size(1),seq_k.size(1)
    pad_att_mask = seq_k.data.eq(0).unsqueeze(1) #[batch_size,1,len_k]
    return pad_att_mask.repeat(1,len_q,1) #[batch_size,len_q,len_k]

q = torch.Tensor([[1,2,3,0,0],[3,7,6,5,0]])
k = torch.Tensor([[3,2,5,0],[1,5,6,0]])
mask = get_pad_mask(q,k)
print(mask.shape)
print(mask)

在这里插入图片描述
 通过上述代码,输入的 q q q维度为[2,5], k k k的维度为[2,4],即此时输入还没有进行embedding编码。然后我们根据上图可知,最后q乘k的注意力权重维度为[2,5,4]。(假设没有多头注意力机制,如果有多头注意力机制的话在加一个维度即可)所以我们通过上述代码产生了一个与注意力权重图维度相同的mask,其中为True的变量代表此位置为填充的,需要把此位置处的权重变为负无穷。
说明:按照道理来说,生成的mask最后一行也该为True,但是仔细想一想,最后一行代表查询Q中的填充词与句子中其他词的关联性,填充词最后能否被正确预测对于最终的结果都没有影响,因此mask的最后一行是否为True对于最终的训练结果来说影响不大。

因果mask

def get_causal_mask(seq):
    # seq:[batch_size,tgt_len]
    att_shape = [seq.size(0),seq.size(1),seq.size(1)] #[batch_size,tgt_len,tgt_len]
    causal_mask = np.tril(np.ones(att_shape),k=0) # 上三角矩阵
    causal_mask = torch.from_numpy(causl_mask).byte()
    return causal_mask #[batch_size,tgt_len,tgt_len]

q = torch.Tensor([[1,2,3,0,0],[3,7,6,5,0]])
mask = get_causal_mask(q)
print(mask.shape)
print(mask)

在这里插入图片描述
 在上述代码中,np.tril用于生成上三角矩阵。解码器输入句子的维度为[2,5],首先输入句子会进行自注意力计算,然后生成[2,5,5]的注意力权重图,因为我们通过上述代码生成了一个与注意力权重图相同维度的上三角矩阵,上三角矩阵为0的地方代表注意力权重图此位置的权重变成负无穷。

三、多头注意力模块

1. 原理介绍

 多头注意力指的是用独立学习得到的h组不同的线性投影来变换查询、键和值,然后将这 h h h组变换后的查询、键和值并行地进行注意力权重计算,最后将这 h h h组地输出拼接到一起,然后通过另一个可以学习地线性投影进行变换,产生最终输出。
 在论文中讲到,将模型分为多个头,形成多个子空间,可以让模型去关注不同方面的信息,最后再将各个方面的信息综合起来。多头的注意力有助于网络捕捉到更丰富的特征和信息,进行多次 attention 综合的结果可以能够起到增强模型的作用,类似于卷积神经网络中的多个卷积核。
在这里插入图片描述
 在实际实现过程中,为了避免计算代价和参数代价地大幅增长,通常是在输入的 d m o d e l d_{model} dmodel​维进行切割,把他分成多个 n u m _ h e a d num\_head num_head个头,以此起到并行计算的作用。例如,如果输入的维度是 [ b a t c h s i z e , s e q _ l e n , d _ m o d e l ] [batchsize,seq\_len,d\_model] [batchsize,seq_len,d_model],首先其通过线性层,输出维度为 [ b a t c h s i z e , s e q _ l e n , d _ m o d e l ] [batchsize,seq\_len,d\_model] [batchsize,seq_len,d_model],如果想分成 n u m _ h e a d num\_head num_head个头,则线性层的输出维度的维度被分割成 [ b a t c h s i z e , s e q _ l e n , n u m _ h e a d , d _ m o d e l / n u m _ h e a d ] [batchsize,seq\_len,num\_head,d\_model/num\_head] [batchsize,seq_len,num_head,d_model/num_head],然后将这 n u m _ h e a d num\_head num_head个头分别进行注意力汇集,最后拼接成 [ b a t c h s i z e , s e q _ l e n , d _ m o d e l ] [batchsize,seq\_len,d\_model] [batchsize,seq_len,d_model]维。

2. 代码实现

import torch
import torch.nn as nn
import numpy as np

class MutiheadAttention(nn.Module):
    def __init__(self,d_model,n_head):
        super(MutiheadAttention,self).__init__()
        self.d_model = d_model
        self.n_head = n_head
        self.w_q = nn.Linear(d_model,d_model)
        self.w_k = nn.Linear(d_model,d_model)
        self.w_v = nn.Linear(d_model,d_model)
        self.w_concat = nn.Linear(d_model,d_model)
        self.att = self_attention()

    def forward(self,q,k,v,att_mask=None):
    	# q:[batch_size,len_q,d_model]
    	# k:[batch_size,len_k,d_model]
    	# v:[batch_size,len_k,d_model]
        batch_size,len_q,len_k,len_v = q.size(0),q.size(1),k.size(1),v.size(1)
        n_dim = int(self.d_model / self.n_head)
        q,k,v = self.w_q(q),self.w_k(k),self.w_v(v)
        q = q.view(batch_size,len_q,self.n_head,n_dim).permute(0,2,1,3)
        k = k.view(batch_size,len_k,self.n_head,n_dim).permute(0,2,1,3)
        v = v.view(batch_size,len_v,self.n_head,n_dim).permute(0,2,1,3)
        context = self.att(q,k,v,att_mask) #[batch_size,n_head,len_q,n_dim]
        context = context.permute(0,2,1,3).contiguous().view(batch_size,len_q,self.d_model)
        return self.w_concat(context)
    
muti_head = MutiheadAttention(512,8)
q = torch.randn((5,5,512))
k = torch.randn((5,3,512))
v = k
print(muti_head(q,k,v).shape)

在这里插入图片描述
 在上述代码中,对于输入的 q , k , v q,k,v q,k,v ([batch_size,seq_len,d_model]维),首先将其分别经过三个全连接网络,然后增加一个维度,变换为[batch_size,n_head,seq_len,d_model/n_head]维,然后进行注意力汇聚(可以理解成输入为(batch_size乘n_head)个句子,然后分别并行进行注意力权重的计算),最后在进行完注意力权重汇聚后,维度重新变换成[batch_size,seq_len,d_model]维,最后在通过一个全连接层进行映射得到最终的结果。同时从代码的输出结果可以看出,在经过多头注意力模块后,输入的维度保持不变。

标签:Transformer,self,mask,len,详解,掩码,model,注意力,size
From: https://blog.csdn.net/m0_64148253/article/details/140424469

相关文章

  • Linux - 网络状态工具ss命令详解
    ss是SocketStatistics的缩写。ss命令可以用来获取socket统计信息,它显示的内容和netstat类似。但ss的优势在于它能够显示更多更详细的有关TCP和连接状态的信息,而且比netstat更快。当服务器的socket连接数量变得非常大时,无论是使用netstat命令还是直接cat/proc......
  • 如何恢复硬盘删除的数据?10个方法,简单实用恢复详解!
    在日常使用电脑的过程中,我们可能会不小心删除一些重要的文件,或者在清理硬盘空间时误删一些需要保留的数据。这时,了解如何恢复硬盘删除的数据就显得尤为重要。本文将为你介绍硬盘删除文件的恢复原理、告诉你哪些情况下文件恢复的可能性较低、详细介绍十个硬盘数据恢复方法。同......
  • C++多线程编程中的锁详解
    在现代软件开发中,多线程编程是提升应用程序性能和响应能力的重要手段。然而,多线程编程也带来了数据竞争和死锁等复杂问题。为了确保线程间的同步和共享数据的一致性,C++标准库提供了多种锁机制。1.std::mutexstd::mutex是最基础的互斥锁,用于保护共享数据,防止多个线程同时访问......
  • 如何在 8 个 GPU 上并行化 Transformer 模型进行机器翻译?
    我正在尝试使用变压器模型以几乎与原始文章相同的方式执行机器翻译。虽然该模型运行得相当好,但它需要更多的计算资源。为了解决这个问题,我在一台具有8个GPU处理器的计算机上运行了该模型,但我缺乏这方面的经验。我尝试对并行化进行必要的调整:transformer=nn.DataParallel......
  • Android中Activity生命周期详解
    目录一典型情况二异常情况2.1系统配置改变2.2系统资源不足kill掉低优先级activityActivity是四大组件之一,也是接触的最多的,一般来说Activity经常是与用户交互的界面。一典型情况先看下google官网,其实已经很清楚了再来个总结onCreate,正在被创建,一次,可以做......
  • IO多路复用-select的使用详解【C语言】
    1.多进程/线程并发和IO多路复用的对比IO多路转接也称为IO多路复用,它是一种网络通信的手段(机制),通过这种方式可以同时监测多个文件描述符并且这个过程是阻塞的,一旦检测到有文件描述符就绪(可以读数据或者可以写数据)程序的阻塞就会被解除,之后就可以基于这些(一个或多个)就绪的文件......
  • transformer model architecture
    transformermodelarchitecturehttps://www.datacamp.com/tutorial/how-transformers-work 动手写https://www.datacamp.com/tutorial/building-a-transformer-with-py-torch Attentionhttps://www.cnblogs.com/jins-note/p/13056604.html人类的视觉注意力从注意力......
  • 从输入URL到页面展示到底发生了什么?--02 握手的故事:三次握手详解
    在这个数字化时代,网络通讯就像人类之间的交流,需要一种方式来确保彼此能够顺利对话。在计算机网络中,TCP三次握手就是这样一种确保双方通信顺畅的机制。今天,我们将通过一个生动有趣的故事来讲解这个重要的过程。引子:约会前的准备想象一下,你要和朋友约个饭,但由于时间久了彼此不太确......
  • Pandas入门,作业详解
    #课程作业2补充——个人笔记#直播链接:第二课堂作业讲解_哔哩哔哩_bilibili配合上个笔记:课程作业2——Pandas入门基本操作-CSDN博客作业用到的数据:链接:https://pan.quark.cn/s/4f673346914c提取码:gix8一.准备工作打开jupyterNoteBook参照Python入门(含python基础语法+......
  • 猫头虎 Python知识点分享:pandas--read_csv()用法详解
    ......