transformer组成模块介绍——自注意力机制

目录


前言

transformer笔记,用于学习或回顾transformer中的注意力机制,主要学习其中的公式与代码,浅显易懂。


自注意力机制

自注意力机制其实从属于注意力机制,那么注意力机制是什么呢?

注意力机制是一种用于捕捉输入序列不同位置之间相关性的技术 。其核心思想是通过为每个单词分配不同的权重 ,突出与当前任务关联性较强的信息。

通俗理解:依赖于给定任务,机器能够分清输入的重点内容更好完成任务。如翻译。

自注意力从属于注意力,根据其开头增加的一个自字 ,我们可以大概明白该机制更加依赖于输入的序列本身。

通俗理解就是能够结合上下文理解当前语境

例:

  1. 小明摔倒了,他感觉痛并大哭。
  2. 小明摔倒了,因为他感觉痛并且他年纪还小所以他大哭。

传统的神经网路如RNN按照单词的顺序分析,难以捕捉长句子中词语间关系 。可能在第一句能分析痛和哭的关系,而在第二个句子会受到年纪还小影响无法分析痛和哭。

通过自注意力机制,会计算痛与其他单词间的相关性,即使在第二个句子中痛和哭的距离较远,注意力机制依旧可以通过权重调整捕捉到痛和哭的关系。

注意力公式介绍

在注意力机制中,有这三个非常重要的向量
Q:查询是主观意识的特征向量,外生给定
K:键是物体的突出特征信息向量,外生给定
V:值则是代表物体本身的特征向量(输入一个句子机器无法直接理解,所以需要词向)

注意力机制涉及到了两个公式

  1. 相关性计算 F ( Q , K ) F(Q,K) F(Q,K):
    • 点积: S i m i l a r i t y ( Q , K ) = Q ∗ K Similarity(Q, K) = Q * K Similarity(Q,K)=Q∗K,通过点积可以提取出语义信息的方向相关性,计算起来简单。
    • 余弦相似度: S i m i l a r i t y ( Q , K ) = Q K ∣ Q ∣ ∣ K ∣ Similarity(Q, K) = \frac{Q K}{|Q| |K|} Similarity(Q,K)=∣Q∣∣K∣QK,与点积的不同在于除以了二者的模长,相当于两个方向向量的相关性提取,更加专注语义信息的方向相关性
  2. 类softmax:
    先将注意力得分根据维度缩放再进行softmax归一化 ,维度缩放让输出更加平滑
    S i m i = S i d i Sim_{i} = \frac{S_{i}}{\sqrt{d_{i}}} Simi=di Si
    a i = S o f t m a x ( S i m i ) = e S i m i Σ j e S i m j a_{i} = Softmax(Sim_{i}) = \frac{e^{Sim_{i}}}{\Sigma_{j} e^{Sim_{j}}} ai=Softmax(Simi)=ΣjeSimjeSimi

自注意力公式介绍

从属于注意力的自注意力自然少不了QKV,但其QK与注意力机制的QK不同

从上图注意力机制计算流程,我们知道计算权重的计算过程X并没有参与

而对于自注意力,因为其专注于自,所以Q与K在计算的时候X也参与了进去。
Q = X W Q Q = X W_{Q} Q=XWQ( W Q W_{Q} WQ是权重矩阵,会对X进行线性变换,其参数会随着神经网络的优化而不断更新),K也有权重矩阵 W K W_{K} WK

下面这张图是一个自注意力机制图

刚接触可能会比较懵,我们加上矩阵大小变换来讲可能能帮助理解

公式过程中矩阵的大小变换(括号内为该矩阵大小)

PS:该图中简化计算,QK大小相同,但在实际运用如英译中,源语言序列长度(len_k)和目标语言序列长度(len_q)通常是不同的

  1. X: (6, 3)

    代表了1个句子由6个词组成,一共6个词向量 ,每个词向量有3个维度

  2. W q , W k , W v W_{q}, W_{k}, W{v} Wq,Wk,Wv: (4, 6)

    大小原因: Q = W q X Q = W_{q}X Q=WqX,图中 Q Q Q的大小为(4, 3),所以这里的 W q W_{q} Wq大小为(4, 6)

    (矩阵乘法的基本知识 ( 4 , 6 ) ⋅ ( 6 , 3 ) = ( 4 , 3 ) (4, 6) \cdot (6, 3) = (4, 3) (4,6)⋅(6,3)=(4,3))
    K与V的乘法与Q一致

  3. Q , K , V Q,K,V Q,K,V:(4, 3)

  4. a t t e n t i o n = S o f t m a x ( K T ⋅ Q D k ) attention = Softmax(\frac{K^{T} \cdot Q}{\sqrt{D_k}}) attention=Softmax(Dk KT⋅Q):(3, 3)

    大小原因: K T ( 3 , 4 ) ⋅ Q ( 4 , 3 ) = ( 3 , 3 ) K^{T}(3, 4) \cdot Q(4, 3) = (3, 3) KT(3,4)⋅Q(4,3)=(3,3)

    根据Q和K使用点乘计算相关性 ,接着除以词向量维度的根号进行特征缩放 ,最后进行softmax(与注意力机制的公式一样

  5. H = V ⋅ a t t e n t i o n H = V\cdot attention H=V⋅attention: (4, 3)

    大小原因: V ( 4 , 3 ) ⋅ a t t e n t i o n ( 3 , 3 ) = H ( 4 , 3 ) V(4, 3) \cdot attention(3, 3) = H(4, 3) V(4,3)⋅attention(3,3)=H(4,3)
    采用缩放点积作为注意力打分函数

多头自注意力机制

多头自注意力是对单头自注意力的扩展 ,通过并行计算多个自注意力机制 ,能够捕获不同子空间的信息,提高模型的表达能力和性能。其实就是计算多个H并merge在一起,最后需要进行一个线性变换哦转换成单头自注意力的H大小

掩码矩阵

因为自注意力机制本质上会涉及到这个序列的所有内容 ,而在进行输出的时候我们是不希望未来的词组数据泄露 给当前词组的,所以引入了掩码矩阵。该矩阵对目标序列中未来位置的注意力分数设置为负无穷,从而将这些分数的 softmax 结果变为零。其作用体现在 a t t e n t i o n attention attention的计算中
a t t e n t i o n = S o f t m a x ( K T ⋅ Q + M D k ) attention = Softmax(\frac{K^{T} \cdot Q + M}{\sqrt{D_k}}) attention=Softmax(Dk KT⋅Q+M)M为掩码矩阵

这个掩码矩阵如下面的公式所示
M i j = { 0 if i ≥ j − ∞ if i < j M_{ij} = \begin{cases} 0 & \text{if } i \geq j \\ -\infty & \text{if } i < j \end{cases} Mij={0−∞if i≥jif i<j

M = ( 0 − ∞ − ∞ − ∞ 0 0 − ∞ − ∞ 0 0 0 − ∞ 0 0 0 0 ) M = \begin{pmatrix} 0 & -\infty & -\infty & -\infty \\ 0 & 0 & -\infty & -\infty \\ 0 & 0 & 0 & -\infty \\ 0 & 0 & 0 & 0 \end{pmatrix} M= 0000−∞000−∞−∞00−∞−∞−∞0

个人理解:每一行都代表了一个词向量 ,所以当前( i i i)位置的词向量只能看到过去位置( i > = j i >= j i>=j)的词向量的 a t t e n t i o n attention attention ,而不能看到之后词向量 的attention,所以当 i < j i < j i<j时其attention应该为0

自注意力代码

分为

python 复制代码
# 掩码矩阵
# 构造一个和注意力得分一样大小矩阵,说明哪个位置是PAD部分,之后在计算计算softmax之前会把这里置为无穷大;
# 一定需要注意的是这里得到的矩阵形状是batch_size x len_q x len_k,我们是对k中的pad符号进行标识,并没有对q中的做标识
# seq_q 和 seq_k 不一定一致,在交互注意力,q来自解码端,k来自编码端,seq是向量序列哦
def get_attn_pad_mask(seq_q, seq_k):
    batch_size, len_q = seq_q.size()
    batch_size, len_k = seq_k.size()
    # eq(0)函数留下了值为0的坐标
    pad_attn_mask = seq_k.data.eq(0).unsqueeze(1)  # batch_size x 1 x len_k
    return pad_attn_mask.expand(batch_size, len_q, len_k)  # batch_size x len_q x len_k
    
# 计算多头注意力得分
class ScaledDotProductAttention(nn.Module):
    def __init__(self):
        super(ScaledDotProductAttention, self).__init__()

    def forward(self, Q, K, V, attn_mask):
        """
        QKV的大小应该都一样
        :param Q: (batch_size, n_heads, len_q, d_k),代表了每一个多头下的所有词向量的中间矩阵
        :param K: (batch_size, n_heads, len_k, d_k)
        :param V: (batch_size, n_heads, len_v, d_k)
        :param attn_mask: (batch_size, n_heads, len_q, len_k)
        :return:
        """
        ## 输入进来的维度分别是 [batch_size x n_heads x len_q x d_k]  K: [batch_size x n_heads x len_k x d_k]  V: [batch_size x n_heads x len_k x d_v]
        ##首先经过matmul函数得到的scores形状是 : [batch_size x n_heads x len_q x len_k]
        # matmul就是矩阵乘法,其中对于多维向量会先提取出batch(会广播到最大batch),反正要压缩到最后的一个二维矩阵进行相乘
        scores = torch.matmul(Q, # (batch_size, n_head, len_q, d_k)
                              K.transpose(-1, -2)  # (batch_size, n_head, d_k, len_k) 相当于k的转置,矩阵乘法
                              ) / np.sqrt(d_k)  # 除以根号d_k

        ## 下面这个就是用到了我们之前重点讲的attn_mask,把被mask的地方置为无限小,softmax之后基本就是0,对q的单词不起作用
        # attn_mask是上三角矩阵,已经是False和True的了,True的位置会被填values,也就是1e-9
        # score的大小为(batch_size, n_head, len_q, len_k)
        # attn_mask大小为(batch_size, n_head, len_q, len_k)
        scores.masked_fill_(attn_mask, -1e9)  # 将上三角未来信息进行隐藏,越往下代表了这个词在句子中的位置越靠后
        attn = nn.Softmax(dim=-1)(scores)  # 进行激活(batch_size, n_head, len_q, len_k)
        context = torch.matmul(attn, V)  # 最后计算(batch_size, n_head, len_q, len_k) X (batch_size, n_head, len_v, d_k), len_k = len_v
        # context = (batch_size, n_head, len_q, d_k)
        # attn = (batch_size, n_head, len_q, len_k)
        return context, attn

# 多头注意力机制
class MultiHeadAttention(nn.Module):
    def __init__(self):
        super(MultiHeadAttention, self).__init__()
        ## 我们会使用映射linear做一个映射得到参数矩阵W_q, W_k,W_v
        # d_k 代表向量维度一般取64,固定值
        # n_head是多头的数量
        # 单个自注意力机制的组成是由:attention = softmax(Q * K转置 / sqrt(d_k))
        self.W_Q = nn.Linear(d_model, d_k * n_heads)  # 输入一个(loc, d_model)的词向量,将其转换为(loc, d_k * n_head)
        self.W_K = nn.Linear(d_model, d_k * n_heads)  # 同Q,K输出大小与Q一致
        self.W_V = nn.Linear(d_model, d_v * n_heads)
        self.linear = nn.Linear(n_heads * d_v, d_model)  # 将注意力表格再次转换为原来规定的维度,权重矩阵的线性变换
        self.layer_norm = nn.LayerNorm(d_model)  # 进行层标准化,一行代码的事,输入层大小

    def forward(self, Q, K, V, attn_mask):
        """
        
        :param Q: X,这里的Q需要经过Wq线性变换才能变为目标Q
        :param K: X,需要经过Wk线性变换
        :param V: X,需要经过Wv线性变换
        :param attn_mask: 是掩码矩阵,防止未来信息泄露,就是一个上三角矩阵!!!(batch_size, len_q, len_k)
        :return: 下一个待处理的层
        """
        ## 这个多头分为这几个步骤,首先映射分头,然后计算atten_scores,然后计算atten_value;
        ## 输入进来的数据形状: Q: [batch_size x len_q x d_model], K: [batch_size x len_k x d_model], V: [batch_size x len_k x d_model]
        residual, batch_size = Q, Q.size(0)  # 将观测值提取出来,因为Q会经过下面的变换,Batch_size 没有问题
        ## 下面这个就是先映射,后用view分头;一定要注意的是q和k分头之后维度是一致,所以一看这里都是dk
        # 以q_s距离,探讨大小变换
        q_s = (self.W_Q(Q)  # 当前Q(batch_size, len_q, d_k * n_heads)
               .view(batch_size, -1, n_heads, d_k)  # 将d_k和n_heads分开,每一个词向量都有一个n_heads行,d_k列的中间内容(batch_size, len_q, d_k, n_heads)
               .transpose(1, 2))  # 将第二和第三维互换,(batch_size, n_heads, len_q, d_k)多头注意力的每一个头都有词向量len_q行,d_k列
        # q_s: [batch_size x n_heads x len_q x d_k]
        k_s = self.W_K(K).view(batch_size, -1, n_heads, d_k).transpose(1,
                                                                       2)  # k_s: [batch_size x n_heads x len_k x d_k]
        v_s = self.W_V(V).view(batch_size, -1, n_heads, d_v).transpose(1,
                                                                       2)  # v_s: [batch_size x n_heads x len_k x d_v]
        ## [batch_size x n_heads x len_q x len_k],就是把pad信息重复了n个头上

        attn_mask = (attn_mask.unsqueeze(1)  # (batch_size, 1, len_q, len_k)代表了一个注意力机制下的的掩码矩阵,关注的是Q和K,而不是原始的词向量
                     .repeat(1, n_heads, 1, 1))  # repeat就是在某个维度上重复n次
                    # 输出为(batch_size, n_heads, lqn_q, len_k), 代表了每一个自注意力机制的掩码矩阵

        ## 得到的结果有两个:context: [batch_size x n_heads x len_q x d_v], attn: [batch_size x n_heads x len_q x len_k]
        context, attn = ScaledDotProductAttention()(q_s, k_s, v_s, attn_mask)
        context = (context.transpose(1, 2)  # (batch_size, len_q, n_heads, d_v)  相当于每一个词都有对应的d_k,这俩是一样的
                   .contiguous()  # 深拷贝
                   .view(batch_size, -1, n_heads * d_v))  # context(batch_size, len_q, n_head * d_v)降低维度,恢复成最初的V矩阵
        # context: [batch_size x len_q x n_heads * d_v]
        output = self.linear(context)  # 乘上权重矩阵,返回初始的输入大小(batch_size, len_q, d_model), 其中len_q在一开始就判断过,其值大小与loc相同
        # 残差链接:F(x) = H(x) - x,这里的F(x)是残差网络的值,x是真实值,H(x)是预测值,残差是预测值和观测值之间的差距,残差+预测值=真实值
        # 下一层的输入output + residual = (batch_size, loc, d_model)
        # attn = (batch_size, n_head, len_q, len_n)
        return self.layer_norm(output + residual), attn

## 模型参数
d_model = 512  # Embedding Size,代表词嵌入的维度,每个词都会有512维
d_k = d_v = 64  # dimension of K(=Q), V
n_heads = 8  # 多头注意力机制的个数

总结

文章讨论了自注意力机制与多头自注意力机制,并使用掩码矩阵防止未来信息泄露。

注意pytorch代码中,最好标注出每一次输入输出的张量大小变换,更好理解其中的意义

PS:作为新手分享经验,如果有讲得不好的地方或者错误麻烦在评论区指出,我会努力修改学习的Orz

相关推荐
lijianhua_97121 小时前
国内某顶级大学内部用的ai自动生成论文的提示词
人工智能
EDPJ1 小时前
当图像与文本 “各说各话” —— CLIP 中的模态鸿沟与对象偏向
深度学习·计算机视觉
蔡俊锋1 小时前
用AI实现乐高式大型可插拔系统的技术方案
人工智能·ai工程·ai原子能力·ai乐高工程
自然语1 小时前
人工智能之数字生命 认知架构白皮书 第7章
人工智能·架构
大熊背2 小时前
利用ISP离线模式进行分块LSC校正的方法
人工智能·算法·机器学习
eastyuxiao2 小时前
如何在不同的机器上运行多个OpenClaw实例?
人工智能·git·架构·github·php
诸葛务农2 小时前
AGI 主要技术路径及核心技术:归一融合及未来之路5
大数据·人工智能
光影少年2 小时前
AI Agent智能体开发
人工智能·aigc·ai编程
ai生成式引擎优化技术2 小时前
TSPR-WEB-LLM-HIC (TWLH四元结构)AI生成式引擎(GEO)技术白皮书
人工智能
帐篷Li2 小时前
9Router:开源AI路由网关的架构设计与技术实现深度解析
人工智能