Featured image of post Transformer模型结构

Transformer模型结构

内容介绍

先简要概述构建和训练一个基于 Transformer 的自然语言处理模型的步骤,对其的详细描述和基于pytorch的代码实现例子及解析见后文。

1,分词,得到和词语对应的词表,参数:词表长度,vocab_size;还需要包含BOS(begin of sentence),EOS(end of sentence),UNK (Unknown) 未知词标记。

2,构建transformer架构。这个架构依靠注意力层以捕捉到上下文的语义关联,利用注意力层和全连接层的堆叠可以构造编码器/解码器。

参数包含:d_model(模型给每个词分配的向量的长度),num_heads(多头注意力的头数),num_layers=6,(编码器/解码器的堆叠层数),context_length(最大序列长度,超出此长度会被截断),等。

transformer结构,加上位置信息的词嵌入层,注意力层+全连接层的反复堆叠,layer norm和残差连接。具体结构可见https://www.runoob.com/pytorch/transformer-model.html。

这个网站也会给出简单的实现transfomer具体结构的代码。

3,经过模型训练,训练每个词对应的向量和模型参数。

参数包括,batch size,一次性放入多少个数据对/多少个样本。一般所有数据张量的第一个维度都是batchsize,bsz.

另外还有填充padding的概念,统一用填充标记PAD (Padding),将不同长度的序列统一到相同长度seq_length(当前 Batch 中最长的那个序列的长度),形成规则的张量以方便计算。如果Batch 中的最大序列长度 超过 max_seq_length就会被截断。


注意力层的核心原理:

注意力层是transformer架构的核心,是模型 理解上下文语意的关键。

embedding词嵌入层,会给注意力层一个输入,(batch_size, seq_length, d_model)(需要加上位置嵌入向量)。d_model是模型给每个词分配的向量的长度,batchsize是模型同时处理的序列数目,seq_length是每个序列被填充后的长度。

注意力机制可以看成对每个词的向量,用这句话的所有词向量进行线性表示,线性叠加系数=注意力得分=点积运算+softmax归一化。对此我见过的最简单形象的表示是b站的费曼学徒冬瓜老师的视频,短短几分钟就让我彻底理解架构(可能还需要一些简单的矩阵运算的前置知识)

视频地址:https://www.bilibili.com/video/BV1eoh5zbEfj/?spm_id_from=333.1387.collection.video_card.click&vd_source=37d5a29914e4a5da6d4f88fb9163024d

这里不赘述,复制粘贴冬瓜老师视频的图片:

(Q:query, 查询,“我想要什么”。K,key,被查询,“我能提供什么”。点积运算可以看成Q K的点积,alpha是点积运算再softmax归一化的结果。 这里X对应于V,value,输出,“我的实际内容是什么”)

注意力机制示意图

注意力权重矩阵

这样可以得到注意力权重矩阵,对于GPT需要掩盖这个词之后的序列,softmax归一化之前,把后面序列的相似度点积结果变成-Inf

掩码机制示意图

掩码操作细节

掩盖这个词之后的序列的操作,具体是用掩码实现的。实际的掩码来源有两个,一个是把同一batch都填充到最长序列长度时带来的(只看原本有内容的部分,其余部分填充掩码;对batch内不同序列都是不同的),另外一个就是这里的no peek/因果机制导致,不能偷看这个词之后的序列。这两个掩码的融合就是进行 逻辑与 (Logical AND) 运算.


注意力层,保留了之前结构和查询 被查询 输入的三者的分工,但通过引入Wq,Wk,Wv矩阵和对矩阵参数的训练强化了注意力捕捉的能力。

设Z是输入给注意力层的序列嵌入(batch_size, seq_length, d_model)。Wq Wk矩阵维度是(d_model,dim1),$Q=Z W_q , K=Z W_k$。由于Q K需要点积,其维度必须一致。$V=Z W_v$,Wv矩阵维度是(d_model,dim2),得到的V维度是(batch_size, seq_length,dim2)。 通常情况下,dim1 和 dim2 被选择为相等。

如果有多个头,需要拼接得到V_all=(batch_size, seq_length,dim2×N_heads)。

之后需要混合多个头的贡献,还需要乘以矩阵Wo, Output=V_all × W_o,dim(Wo)=(dim2×N_heads,d_model)。这里有 dim2 * N_heads = d_model,即$\mathbf{W}_o$ 作用后得到的最终输出向量的维度,与该注意力层的输入向量维度 必须一致,因为之后需要残差连接,然后层归一化。


全连接层

注意力层后面会跟两个全连接层nn.Linear,构成transformer层。

首先投影到高维,例如4×输出维度(4×d_model),以获得高维的复杂的特征表示,然后激活函数例如RELU变得非线性。之后必须投影回原来维度,因为也会残差连接,以保证了信息和梯度能够顺畅地流过许多层而不衰减。同时,保持输入输出维度相同,可以让所有 Transformer 层共享一个统一的输入/输出接口维度,使得整个架构具有模块化和可扩展性。

transformer层可以多次叠加,组成encoder或decoder层。这两个层的结构是很像的。


transformer的信息流动

文本序列经过分词,可以在嵌入层用张量表示;此时也加上位置嵌入positional encoding(pe)对应的张量。有些是采用可以训练的参数表示的,有些干脆写死,就用序列位置和sin cos函数的组合直接给定,也不需要参与优化(通过 self.register_buffer(‘pe’, pe_tensor) 存储,register_buffer() 自动设置 requires_grad=False)。

张量流过多个transformer层,最后输出结果。具体结构可见https://www.runoob.com/pytorch/transformer-model.html

以GPT为例,训练和评估时候的输出方式有所不同。训练时候,由于是对整个序列都计算注意力矩阵,可以一次性得到整个序列中任何位置的下一个位置上模型预测的词向量的概率分布,将这个和实际位置的词元对比,可以计算出loss。之后根据loss梯度求导,反向计算所有有关参数(所有nn.parameter默认requires_grad=True)的更新:loss = criterion(…)#计算loss, loss.backward() # 反向传播,optimizer.step() #更新所有可训练参数。

对于评估时候,或者说推理时候,训练已结束,冻结参数,让其根据输入的prompt连续生成,直到输出EOS或者达到最大输出长度为止。模型需要反复地将自己上一步输出的词(输出词向量的概率分布之后,具体是输出哪个词,可以用不同的采样序列,例如Top-k/Top-p 采样)也作为输入,对这个新的词语也计算注意力矩阵。 在每一步生成时, 不需要重新计算 Prompt 部分 及其之前已生成部分的 $\mathbf{K}$ 和 $\mathbf{V}$ 矩阵。这些值可以被缓存(Key/Value 缓存,KV Caching),后续步骤只需计算 新增 Token 的 $\mathbf{Q}, \mathbf{K}, \mathbf{V}$,这大大提高了推理速度,但没有改变生成必须是自回归的本质。