注意力机制

基础注意力机制

注意力机制的公式如下所示:

attn(Q,K,V)=softmax(QKTdk)Vattn(Q,K,V) = softmax(\frac{QK^T}{\sqrt{d_k}}) V

其中,各个矩阵的尺寸分别为:

  • Q矩阵,尺寸为{batch_size,q_len,d_k}\{batch\_size,q\_len,d\_k\}

  • K矩阵,尺寸为{batch_size,k_len,d_k}\{batch\_size,k\_len,d\_k\}

  • V矩阵,尺寸为{batch_size,k_len,v_feature}\{batch\_size,k\_len,v\_feature\}
    首先Q和K的转置做矩阵乘法,QKTQK^T的尺寸为{q_len,k_len}\{q\_len,k\_len\},随后除固定值dk\sqrt{d_k}后,计算softmax函数(softmax函数是每行独立做,而不是全矩阵做),最后和矩阵V做乘法,最终的结果尺寸为{q_len,v_feature}\{q\_len,v\_feature\},具体的过程如下图所示:

    Q、K、V矩阵中,每个行矩阵都对应一个Token,QKTQK^T表示了Q和K矩阵每个行矩阵的相关度,经过除定值和softmax函数(功能类似于归一化),并使用相关度和V矩阵相乘,最终得到Y矩阵中的每个行向量。

自注意力机制和多头注意力机制

自注意力机制,就是Q、K、V全部来自同一个矩阵,如下所示:

self_attn(X)=attn(XWQ,XWK,XWV)self\_attn(X) = attn(XW^Q,XW^K,XW^V)

即Q、K、V都来自一个输入X的线性变换,其中WQW^QWKW^KWVW_V均为可训练的参数,尺寸为{embding,feature}\{embding,feature\},其中embding为词嵌入矩阵的长度,而feature则对应注意力机制中的dkd_k,在自注意力机制中,输入的Q、K、V矩阵和输出Y矩阵的尺寸均为{batch_size,q_len,feature}\{batch\_size,q\_len,feature\}
多头注意力机制即构造多套W参数,并行多个自注意力机制,并最后将结果拼接后进行统一线性变换回来,如下所示:

其中,并行了多少套(图中的N)就是多少头注意力机制,图中就是N头注意力机制。

后向Mask

在GPT类的自回归模型中,不能基于后续的知识生成当前的输出,只能基于前序知识生成当前输出,就需要一个Mask矩阵,将后续知识的V矩阵掩蔽。Mask矩阵式一个上三件矩阵,矩阵中为1的部分应当被Mask为0,过程如下所示:

在生成Y0向量时,只能使用V0向量,而生成Y1向量时,可以使用前序的V0和V1向量。

GPT组件

GPT结构如下图所示,其核心组件包括:

  • 解码器层(经典参数为层数为6层,多头为8头)

  • 线性输出层

  • 位置掩码和词嵌入

解码器层DecoderLayer

解码器层结构如下所示,包括一个残差连接的多头自注意力机制(带后向Mask)和残差链接的线性层:

在解码器层中,包括以下的可训练参数:

  • 多头注意力层中,每个“头”用于生成Q、K、V的三个线性层矩阵
  • 线性层中的线性矩阵参数
    这里的前馈网络命名为FFN(Feedforward Network),是一个多Token共享的两层线性连接层,即对于每个Token,分别进行如下操作:

FFN(xi)=activation_function(xi×W0+b0)×W1+b1FFN(x_i) = activation\_function(x^i \times W_0 + b_0) \times W_1 +b_1

整体结构如下所示,注意每个位置的Token均共享一套参数:

线性输出层

线性输出层就是一个不带激活函数的线性层,实现词嵌入的逆过程,将词嵌入转换成每个词的可能性。举个例子,词库中包括10000个词,词嵌入压缩为512维向量,这个线性层就是将512维的词嵌入转换成10000维的每个词的概率向量。

词嵌入和位置编码

词嵌入和位置编码是对输入的预处理,可以认为是如下公式,其中EMBD表示词嵌入过程,PLACE表示位置编码过程,实际的Decoderlayer的输入是词嵌入和位置向量的和:

X=[x0,x1,....]>X=[x0,x1,...],xi=EMBD(xi)+PLACE(i)X = [x_0,x_1,....] -> X' = [x'_0,x'_1,...],x'_i = EMBD(x_i) + PLACE(i)

从正向角度看,词嵌入是一个查表行为,每个词都有一个对应向量。举例而言,一个包括10000个词的词库,词嵌入向量为512维,可以认为这是一个有10000个地址的查找表,每个表项都是一个512个向量。每输入一个词,都通过查表的方式查出对应的向量。且这是一个确定性的过程,输入相同的词,查出的向量时一致的。
在GPT中,位置编码的实现方式和词嵌入相同,都是通过训练生成的(Transform原版中有固定的公式),区别在于位置编码的输入是位置而不是词(Token),比较容易理解的公式表示如下所示:

1
2
3
4
5
6
7
8
9
10
11
# init函数中
self.src_emb = torch.nn.Embedding(vocab_size,d_embedding)
# vocab_size是Token的种类数,d_embbeding是词嵌入向量的维度
self.pos_emb = torch.nn.Embedding(max_seq_len,d_embedding)
# max_seq_len是最大的输入Token向量长度

# forward函数中,原始输入为x
position = torch.arange(len(x)).unsqueeze(-1)
# position 为[[0,1,2,...,max_seq_len-1]]
inputs_embedding = self.src_emb(x) + self.pos_emb(position)
# inputs_embedding为后续Decoderlayer的输入

GPT的前向传播过程

GPT的前向传播是个自回归的过程。首先,Token中除了有含义的词外,还包括了几种特殊的Token:

  • <sos>:表示一个句子的开始
  • <eos>:表示一个句子的结束
  • <pad>:表示没有Token,用于填充不满足长度的情况
    对于任意一个句子,都可以表示为如下所示的情况:

<sos>,token0,token1,...,tokenn1,<eos>,<pad>,<pad>,...<sos>,token_0,token_1,...,token_{n-1},<eos>,<pad>,<pad>,...

基于上述表示,GPT的前向传播过程如下所示,每次生成1个Token,并将这个Token增加到下一次前传过程中,直到碰到<eos>字符或达到最大长度: