〇、笔者前言

  • 2025年开始,准备学习大模型相关的东西,所以从基础知识transformer开始补起。文章思路如下:
    • 以论文顺序讲解
    • 参考我学习的视频的部分简写了,详情见参考文献(链接)
    • 在必要的部分穿插了源码讲解

一、介绍 & 背景

  • 目的:transformer是2017年由Google提出的,用于序列转导任务的模型(例如文本生成,翻译),以解决之前的基于RNN模型做此任务的问题(输入输出层必须等长、遗忘、只能串行运行)
  • 借鉴的研究
    • FFN(前馈神经网络 Feedforward Neural Network)
      • 优点:可学习参数多
      • 缺点:① 无法建模序列顺序;② 处理不同长度的输入输出效果差(因为输入输出层维度固定)
    • RNN(循环神经网络 Recurrent Neural Network)
      • 原始RNN
        • 优点:① 能建模序列顺序(因为逐个输入);② 支持不定长输入
        • 缺点:① 遗忘;② 输入输出必须等长
      • 编码器-解码器结构
        • 结构:把输出中间结果(编码器)、最终结果(解码器)的模型拆开
      • 注意力机制
        • 结构:给解码器赋权值
        • 优点:解决遗忘问题
  • 核心创新点
    • 并行计算
    • 只用自注意力机制、FFN、其他小组件
  • 推荐一起学:【文献1】的视频前半部分 + PPT + 笔记(有丰富的用于解释的原理图)

二、模型结构

1.结构

1.1 论文部分

  • 整体结构:编码器(左半部分),解码器(右半部分)

  • 小部件

    • Input Embedding 词嵌入:自然语言根据词表,分词并投影成向量
    • Positional Encoding 位置编码:正余弦函数
    • Attention 注意力(上下文编码)
      • 自注意力:Q、K、V来源于同一向量,仅投影矩阵W不同
      • 交叉注意力:Q来自编码器,K、V来自解码器
      • 掩码注意力:用于解码器,因为要隐藏此处及此处之后的词(即预测未来)
      • 多头注意力:每个头关注的信息不一样,学完后拼起来再过线性层(~8个低清晰度结果,映射而非仅拼接,才能得1个高精度结果)
    • Add & Norm 残差连接和归一化
    • Linear:将向量映射到词表中,获取每个词的得分
    • Softmax:将得分映射成概率
    • Feed forward:FNN
      哈哈哈哈在这里插入图片描述
  • 注意力公式

    • d k \sqrt {d_k} dk 的原因:缩放。分析某行和某列相乘得到矩阵中的某个元素 q ⋅ k q · k qk = ( q 1 ⋅ k 1 ) (q_{1} · k_{1}) (q1k1) + ( q 2 ⋅ k 2 ) (q_{2} · k_{2}) (q2k2) + … + ( q d ⋅ k d ) (q_{d} · k_{d}) (qdkd) ,共 d k \sqrt {d_k} dk 项加和,其中 V a r ( q i ⋅ k i ) = 1 Var(q_{i} · k_{i})=1 Var(qiki)=1,因此加和的方差为 d k {d_k} dk,标准差为 d k \sqrt {d_k} dk
      在这里插入图片描述
  • 笔者私货:是之后很多transformer的改进版本的思路源头。比如FNN→MoE,MHA、Lora→MLA

1.2 代码部分

  • 先看整体流程
def forward(self, src_seq, trg_seq):
    # 1. 编码器处理输入序列
    src_mask = get_pad_mask(src_seq, self.src_pad_idx)
    enc_output, *_ = self.encoder(src_seq, src_mask)
    
    # 2. 解码器生成输出序列  
    trg_mask = get_pad_mask(trg_seq, self.trg_pad_idx) & get_subsequent_mask(trg_seq)
    dec_output, *_ = self.decoder(trg_seq, trg_mask, enc_output, src_mask)
    
    # 3. 线性层得到最终输出
    seq_logit = self.trg_word_prj(dec_output)
    return seq_logit.view(-1, seq_logit.size(2))
  • 再看每个组件(组件内的子函数,如需详细了解,再查找即可)
class Encoder(nn.Module):
    def forward(self, src_seq, src_mask):
        # 词嵌入 + 位置编码
        enc_output = self.src_word_emb(src_seq)  # 论文中的"Input Embedding"
        enc_output = self.dropout(self.position_enc(enc_output))  # "Positional Encoding"
   
        # 经过多个编码器层
        for enc_layer in self.layer_stack:  # 论文中的Nx重复
            enc_output, enc_slf_attn = enc_layer(enc_output, slf_attn_mask=src_mask)
        
        return enc_output

class Decoder(nn.Module):
    def forward(self, trg_seq, trg_mask, enc_output, src_mask):
        # 词嵌入 + 位置编码
        dec_output = self.trg_word_emb(trg_seq)
        dec_output = self.dropout(self.position_enc(dec_output))
        
        # 经过多个解码器层
        for dec_layer in self.layer_stack:  # 论文中的Nx重复
            dec_output, dec_slf_attn, dec_enc_attn = dec_layer(
                dec_output, enc_output, slf_attn_mask=trg_mask, dec_enc_attn_mask=src_mask)
        
        return dec_output
  • 举个例子:翻译"I love you" → “我爱你”
# 假设输入:src_seq = [I, love, you] → 编码为 [1, 2, 3]
# 目标:trg_seq = [<start>, 我, 爱, 你] → 编码为 [0, 4, 5, 6]

# 1. 编码器处理英文
enc_output = encoder([1, 2, 3], src_mask)  # 得到英文的上下文表示

# 2. 解码器逐步生成中文
# 第一步:输入 [0] → 预测 "我"
# 第二步:输入 [0, 4] → 预测 "爱"  
# 第三步:输入 [0, 4, 5] → 预测 "你"
# 第四步:输入 [0, 4, 5, 6] → 预测 <end>

# 掩码确保每一步只能看到之前的词

2. 为什么用 self-attention

  • 笔者私货:
    • 是之后很多transformer的改进版本(比如swin transformer)的思路源头
    • CNN和transfomer结合的模型在训练/推理时,经常在attention处报显存不够
  • 为啥用自注意力,而非RNN、CNN,3个标准:
    • 每层的总计算复杂度(Computational Complexity per Layer)
    • 可并行化的计算量(Amount of Computation that can be Parallelized)
    • 网络中长距离依赖之间的路径长度(Path Length between Long-range Dependencies)
  • 表格
    在这里插入图片描述
    • 参数解释

      参数 含义 例子 备注
      n 序列的长度 一句话有10个词,n=10 由输入数据决定,约10~5000
      d 词向量的维度 每个词用一个512维的向量表示,d=512 由模型设计决定。模型越大则越大,因为会算的更细更精确(比如描述一个人的特征[身高, 体重, 年龄, 性格分数, …]) ;基础模型d = 512,GPT-3的d=12288
      k 卷积核的宽度 卷积核每次看3个词,k=3 ~关注的区间。CNN在区间内为静态,训练好后(推理时)权重(卷积核矩阵)就无法改变
      r 受限注意力的邻域大小 r=10,则序列中第 50 个词,就只关注第 45 到 55 个词 ~关注的区间。attention在区间内为动态,权重由自注意力矩阵实时计算得到。注:投影矩阵W是静态的,属于模型参数;自注意力权重是计算得出的中间结果,”动静结合“
    • 公式推导方式

      层类型 每层计算复杂度 串行操作数 最长路径长度
      自注意力 某序列给其他序列打分,共需 n ⋅ d n·d nd次;所有n个序列都给其他序列打分,故 n 2 ⋅ d n^{2}·d n2d / /
      RNN 第1个块把自己的特征传给第2个块,第2个块输出为h=Wx+h1,即第二个块的计算量为 d 2 d^{2} d2,共需传 n n n次,即 n ⋅ d 2 n·d^{2} nd2 / 最坏的情况:第一个词想影响最后一个词,需要传递的长度即为序列长度n
      CNN 取决于卷积类型 每个局部窗口的计算都是独立的,所以可并行 用队伍传令兵(即卷积核)解释(不许交头接耳),传令兵必须走n步(普通卷积时,或者说n-k步,但 O ( ⋅ ) O(·) O()时不计入 -k)。注:普通卷积 O ( n ) O(n) O(n),扩张卷积 O ( l o g k ( n ) ) O(log_{k}(n)) O(logk(n))
      受限自注意力 某序列给其他序列打分,共需 r ⋅ d r·d rd次;所有n个序列都给其他序列打分,故 r ⋅ n ⋅ d r·n·d rnd 每个局部窗口的计算都是独立的,所以可并行 用队伍交头接耳解释,长度为n的队伍,每个子队伍(不重叠)长度为r,故有n/r个子队伍,从第一个子队伍传到最后一个子队伍需n/r此信息交流。若队伍重叠一半也可,则2n/r,但是算 O ( ⋅ ) O(·) O()时不计入2

三、训练 & 结论

本文重点关注模型设计和结构,故具体的训练、结果章节未分析

参考文献

  1. 某大神的 attention is all you need(B站视频+全套文档)
  2. 跟着李沐学AI之transformer(B站视频)
  3. GitHub的transformer源码(pytorch版)
  4. 详解自注意力
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐