资讯动态

95页PPT拆解Transformer:从注意力公式到GPT、ViT完整链路

发布时间:2026/10/17 19:00:46 来源:尧图企业网站定制
简介这份PPT面向深度学习入门与进阶学习者系统讲解Transformer从论文原理到NLP、CV两大领域演进的完整脉络帮助读者建立对注意力机制与主流大模型的整体认知。内容共95页涵盖Transformer背景、整体架构、输入部分、编码器、解码器、输出部分及其他细节并延伸至GPT-1与BERT、GPT-2、GPT-3、InstructGPT与ChatGPT以及ViT、CLIP与DALL·E-1、Diffusion Model与DALL·E-2等代表性工作兼顾机器翻译、分类、分割、检测等任务场景。资源包为1个pptx文件约17.48MB结构清晰、图文并茂适合课堂讲授或自学翻阅。目前已有2515人学习下载可作为理解Transformer核心机制、梳理大模型发展路线与跨模态应用的参考材料。1. 95 页 PPT 拆解 Transformer从注意力公式到 GPT、ViT 的完整链路如果你正在找一份能把 Transformer 从公式推导到 NLP、CV 两条落地路线讲透的课件这份 95 页的 PPT 值得放进你的学习清单。它没有停留在“Attention is all you need”这句口号上而是把输入嵌入、位置编码、多头注意力、编码器堆叠、解码器掩码、输出层这些模块逐个拆开再顺着 GPT-1 到 ChatGPT、ViT 到 Diffusion Model 的脉络往下讲。适合刚学完深度学习基础、想系统啃 Transformer 的算法工程师也适合需要一份现成课件做内部分享的从业者。我翻完之后最大的感受是它把很多教程里一笔带过的“为什么除以根号 dk”“位置编码为什么用 sin/cos”都给了推导和数值例子这对排查模型不收敛、位置信息丢失这类玄学问题很有帮助。2. Transformer 输入部分词嵌入与位置编码的工程细节2.1 从“我爱你”到 (10, 512) 张量嵌入层到底做了什么PPT 里用中文“我爱你”翻译成英文“I love you”作为贯穿例子这个选法很聪明因为句子短、词汇少每一步的张量形状都能手算验证。假设中文词汇表大小是 20000那么“我”对应 37“爱”对应 4201“你”对应 197输入变成一个长度为 10 的数组不足的位置补 0。这一步在工程上对应的是 tokenizer 的 vocab 映射常见做法是用 BPE 或 WordPiece 先切子词再查表转 ID。接下来进入嵌入层。PPT 里明确说嵌入层就是一个单层 MLP把每个 token ID 映射成 512 维向量。这里有个容易翻车的点很多人以为嵌入层是“查表”所以不需要初始化实际上查表操作等价于一个 one-hot 乘以权重矩阵权重依然要初始化。我一般会用正态分布初始化std 取 0.02 左右太大容易在深层堆叠时梯度爆炸太小则不同 token 的向量区分度不够。import torch import torch.nn as nn class TokenEmbedding(nn.Module): def __init__(self, vocab_size, d_model): super().__init__() # 等价于 one-hot 查表但计算上更高效 self.embedding nn.Embedding(vocab_size, d_model) # 常见初始化std0.02避免深层堆叠时方差累积 nn.init.normal_(self.embedding.weight, mean0.0, std0.02) def forward(self, x): # x: (batch, seq_len) 的 token ID return self.embedding(x) # 输出 (batch, seq_len, d_model)这段代码里vocab_size对应 PPT 里的 20000d_model对应 512。nn.Embedding的 forward 接收的是整数张量输出浮点向量。注意padding_idx参数如果设了 0那么填充位置的梯度会被屏蔽这在变长序列训练时能省掉不少无效计算。2.2 位置编码为什么 sin/cos 能表达绝对位置PPT 里用黄色高亮标出了位置编码的加法操作并给出公式偶数维度用 sin(pos/10000^(2i/d_model))奇数维度用 cos。它强调这是“绝对位置信息”但没展开的是这种编码方式能让模型通过线性变换学到相对位置关系。具体来说位置 posk 的编码可以表示为 pos 编码的线性函数这让模型在推理时即使遇到训练没见过的长度也能外推一部分。工程上位置编码有两种实现方式一种是像 PPT 里那样预计算好一个 (max_len, d_model) 的矩阵训练时直接切片相加另一种是把位置编码设为可学习参数。前者不增加参数量后者更灵活但需要更多数据。我一般会在数据量小于百万级时用预计算 sin/cos大于百万级时尝试可学习位置编码但后者在长文本外推时容易崩。import math def positional_encoding(max_len, d_model): pe torch.zeros(max_len, d_model) position torch.arange(0, max_len, dtypetorch.float).unsqueeze(1) div_term torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model)) pe[:, 0::2] torch.sin(position * div_term) pe[:, 1::2] torch.cos(position * div_term) return pe # (max_len, d_model)div_term那行是数值稳定写法等价于 1/10000^(2i/d_model)但避免了直接算幂次时的溢出。pe[:, 0::2]和pe[:, 1::2]分别填偶数和奇数维度。实际使用时把pe[:seq_len]加到嵌入输出上即可。注意如果用了 padding填充位置也会被加上位置编码但后续注意力掩码会把这些位置屏蔽掉所以不影响结果。3. 编码器与多头注意力从 q/k/v 到梯度稳定的完整推导3.1 单头注意力q、k、v 的物理含义与计算流程PPT 用“The animal didnt cross the street because it was too tired”这个经典例子解释自注意力模型要知道“it”指代“animal”就需要让“it”的查询向量 q 和“animal”的键向量 k 做点积得到一个高分从而在加权求和时给“animal”的值向量 v 更大权重。这个解释很直观但落到代码上关键是把 q、k、v 的维度搞清楚。假设输入 x 的形状是 (10, 512)经过三个线性层分别得到 q、k、v每个的形状是 (10, 64)。这里的 64 是单头维度512/8648 是头数。PPT 里明确写了“除以根号 dk 的原因为了梯度稳定”。具体来说q 和 k 的点积方差会随 dk 增大而增大如果不缩放softmax 的输入会落在饱和区梯度接近 0。除以根号 64 等于 8正好把方差拉回 1 附近。import torch.nn.functional as F def scaled_dot_product_attention(q, k, v, maskNone): # q: (batch, heads, seq_len, d_k) d_k q.size(-1) scores torch.matmul(q, k.transpose(-2, -1)) / math.sqrt(d_k) if mask is not None: scores scores.masked_fill(mask 0, -1e9) attn F.softmax(scores, dim-1) return torch.matmul(attn, v), attnmask在解码器里是下三角矩阵防止当前位置看到未来 token。-1e9是常见做法比-inf更安全避免 NaN。返回的attn可以拿来做可视化检查模型是否真的把注意力放到了正确位置。3.2 多头注意力为什么要拆成 8 个头PPT 里把多头注意力画成多个单头并行最后拼接再线性变换。很多人会问既然单头也能算注意力为什么还要多头我的理解是多头相当于让模型在多个子空间里分别学习不同的关注模式。比如一个头可能关注语法依赖另一个头关注语义相似度。如果只用一个头这些模式会互相干扰平均下来反而学不好。工程上多头注意力的实现通常是把 d_model 拆成 heads × d_k然后通过 reshape 和 transpose 把 heads 维度提出来并行计算后再合并。这样比循环每个头快得多。注意d_model必须能被heads整除否则 reshape 会报错。我见过有人设 d_model512、heads7结果调试了半天才发现是整除问题。class MultiHeadAttention(nn.Module): def __init__(self, d_model, heads): super().__init__() assert d_model % heads 0 self.d_k d_model // heads self.heads heads 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_o nn.Linear(d_model, d_model) def forward(self, q, k, v, maskNone): batch q.size(0) # 线性变换后拆头(batch, seq_len, d_model) - (batch, heads, seq_len, d_k) q self.w_q(q).view(batch, -1, self.heads, self.d_k).transpose(1, 2) k self.w_k(k).view(batch, -1, self.heads, self.d_k).transpose(1, 2) v self.w_v(v).view(batch, -1, self.heads, self.d_k).transpose(1, 2) out, _ scaled_dot_product_attention(q, k, v, mask) # 合并头(batch, heads, seq_len, d_k) - (batch, seq_len, d_model) out out.transpose(1, 2).contiguous().view(batch, -1, self.heads * self.d_k) return self.w_o(out)transpose(1, 2)把 heads 维度换到 seq_len 前面这样scaled_dot_product_attention里的矩阵乘法会自动对每个头并行。contiguous()是因为 transpose 后内存不连续view 会报错。这个坑我踩过不止一次血泪经验是只要看到 transpose 后面跟 view先加 contiguous 再说。3.3 前馈网络与残差连接编码器模块的完整堆叠PPT 里编码器模块的结构是多头自注意力 → 残差 LayerNorm → 前馈网络 → 残差 LayerNorm。前馈网络通常是两个线性层中间夹一个 ReLU隐藏层维度是 d_model 的 4 倍。这个 4 倍不是随便定的原论文里试过 1 倍、2 倍、4 倍4 倍在翻译任务上最好。但到了 CV 或大语言模型这个比例会变比如 ViT 用 4 倍GPT-3 用 4 倍有些高效变体会用 2 倍甚至 1 倍来省显存。残差连接的作用是缓解深层网络的梯度消失。LayerNorm 放在残差之后还是之前有两种流派Post-LN 和 Pre-LN。原论文用 Post-LN但后来大家发现 Pre-LN 训练更稳定不需要 warmup 也能收敛。PPT 里没有展开这个细节但如果你要复现建议先用 Pre-LN等模型稳定后再试 Post-LN。class EncoderLayer(nn.Module): def __init__(self, d_model, heads, d_ff2048, dropout0.1): super().__init__() self.attn MultiHeadAttention(d_model, heads) self.ffn nn.Sequential( nn.Linear(d_model, d_ff), nn.ReLU(), nn.Linear(d_ff, d_model) ) self.norm1 nn.LayerNorm(d_model) self.norm2 nn.LayerNorm(d_model) self.dropout nn.Dropout(dropout) def forward(self, x, maskNone): # Pre-LN先归一化再进子层 attn_out self.attn(self.norm1(x), self.norm1(x), self.norm1(x), mask) x x self.dropout(attn_out) ffn_out self.ffn(self.norm2(x)) x x self.dropout(ffn_out) return xd_ff2048对应 d_model512 的 4 倍。dropout0.1是常见默认值数据量小的时候可以调到 0.2 或 0.3。注意 Pre-LN 里norm1(x)被用了三次实际实现时可以只算一次存下来省点计算。4. 解码器与输出层掩码注意力和自回归生成的实现4.1 解码器模块三个子层与两种注意力PPT 里解码器模块比编码器多了一个子层编码器-解码器注意力。这个子层的 q 来自解码器上一层的输出k 和 v 来自编码器的最终输出。这样做的目的是让解码器在生成每个词时能动态地从编码器输出的源序列里提取相关信息。比如翻译“我爱你”时生成“love”的时候模型会重点关注编码器里“爱”对应的位置。解码器的自注意力层需要加掩码防止当前位置看到未来 token。这个掩码是一个上三角为 0、下三角为 1 的矩阵形状是 (seq_len, seq_len)。实际实现时通常用torch.tril生成下三角矩阵再扩展成 (batch, heads, seq_len, seq_len) 的形状。def causal_mask(seq_len): # 下三角为 1上三角为 0 mask torch.tril(torch.ones(seq_len, seq_len)).bool() return mask # (seq_len, seq_len)在scaled_dot_product_attention里mask 0的位置会被填成-1e9softmax 后权重接近 0。注意编码器-解码器注意力不需要因果掩码因为编码器输出是完整的源序列解码器可以看全部。4.2 输出层从 512 维向量到词表概率解码器最后一层的输出形状是 (batch, seq_len, 512)经过一个线性层映射到词表大小比如 20000得到 (batch, seq_len, 20000) 的 logits再经过 softmax 得到每个位置的概率分布。训练时用交叉熵损失推理时用贪心搜索或束搜索。PPT 里没有展开束搜索的细节但工程上束搜索的beam_size是个关键参数。设 1 就是贪心设 5 到 10 通常能提升生成质量但计算量线性增长。我一般会在验证集上试 3、5、8 三个值选 BLEU 或 ROUGE 最高的。class Transformer(nn.Module): def __init__(self, vocab_size, d_model512, heads8, num_layers6): super().__init__() self.embedding TokenEmbedding(vocab_size, d_model) self.pos_enc positional_encoding(5000, d_model) self.encoder_layers nn.ModuleList([EncoderLayer(d_model, heads) for _ in range(num_layers)]) self.decoder_layers nn.ModuleList([DecoderLayer(d_model, heads) for _ in range(num_layers)]) self.fc_out nn.Linear(d_model, vocab_size) def forward(self, src, tgt, src_maskNone, tgt_maskNone): src_emb self.embedding(src) self.pos_enc[:src.size(1)].to(src.device) tgt_emb self.embedding(tgt) self.pos_enc[:tgt.size(1)].to(tgt.device) for layer in self.encoder_layers: src_emb layer(src_emb, src_mask) for layer in self.decoder_layers: tgt_emb layer(tgt_emb, src_emb, tgt_mask, src_mask) return self.fc_out(tgt_emb) # (batch, tgt_len, vocab_size)num_layers6是原论文的默认值但实际任务里 3 到 12 层都常见。层数越多参数量越大训练越慢但表达能力越强。我一般会从 3 层开始调等 pipeline 跑通了再加深。5. 从 NLP 到 CVGPT、ViT、Diffusion 的演进路线与避坑5.1 GPT 系列与 BERT预训练范式的两条路PPT 后半部分把 GPT-1、GPT-2、GPT-3、InstructGPT、ChatGPT 串成一条线核心讲的是“预训练 微调”到“预训练 提示”的转变。GPT 系列用解码器架构做自回归语言建模BERT 用编码器架构做掩码语言建模。两者的区别在于GPT 只能看左边适合生成BERT 能看两边适合理解。工程上GPT 的注意力掩码是下三角BERT 没有掩码。如果你要复现 BERT注意[CLS]和[SEP]的位置编码要正确否则下游任务会掉点。我见过有人把[CLS]放在句尾结果分类任务准确率直接掉 10 个点。5.2 ViT 与 DiffusionTransformer 在 CV 领域的落地ViT 的做法是把图像切成 16×16 的 patch每个 patch 展平后经过线性层映射成 token再加上位置编码送进标准 Transformer 编码器。PPT 里提到 CLIP 和 DALL-E前者用对比学习对齐图像和文本后者用 Transformer 做图像生成。Diffusion Model 则是另一条路线用 U-Net 做去噪但 DALL-E-2 把 U-Net 换成了 Transformer。ViT 的坑在于它需要大量数据预训练否则不如 CNN。如果你只有几万张图直接训 ViT 大概率过拟合。常见做法是用预训练权重微调或者用 DeiT 的蒸馏方法让小模型也能训。5.3 避坑与常见问题排查现象训练 loss 不降一直卡在 10 左右。原因学习率太大或 warmup 不够。Transformer 对学习率敏感原论文用了 4000 步 warmup。 解决加 warmup峰值学习率设 1e-4 到 3e-4用 Adam 的 beta20.98。现象推理时生成的句子重复。原因解码器掩码写错或者束搜索的重复惩罚没设。 解决检查causal_mask是否下三角束搜索加repetition_penalty1.2。现象显存溢出batch 只能设 1。原因注意力矩阵是 O(seq_len^2) 的长序列直接爆。 解决用梯度累积模拟大 batch或者换 FlashAttention 这类高效实现。现象位置编码加错了维度。原因pe[:seq_len]的形状是 (seq_len, d_model)但嵌入输出是 (batch, seq_len, d_model)广播时可能对不上。 解决用pe[:seq_len].unsqueeze(0)显式加 batch 维度。现象多头注意力 reshape 报错。原因d_model 不能被 heads 整除或者 transpose 后没加 contiguous。 解决检查整除关系transpose 后加.contiguous()。6. 用这份 PPT 做内部分享一个可复用的讲解节奏如果你打算拿这份 95 页 PPT 做内部分享我建议不要按页码从头讲到尾而是按“输入 → 编码器 → 解码器 → 输出 → 演进”五段来切。每段控制在 15 到 20 分钟中间留 5 分钟提问。第一段讲输入部分时重点演示“我爱你”到 (10, 512) 的张量变化让听众手算一遍位置编码的前几个值这样后面讲注意力时他们才有体感。第二段讲编码器时我会把 q、k、v 的计算过程用 Excel 或 NumPy 现场跑一遍特别是除以根号 dk 前后的 softmax 分布对比。这个对比很直观不缩放时 softmax 输出接近 one-hot梯度几乎为 0缩放后分布更平滑梯度能传回去。第三段讲解码器时重点演示掩码矩阵的生成和可视化让听众看到上三角被屏蔽的效果。第四段讲输出层时可以现场跑一个贪心搜索和束搜索的对比用同一个输入看生成结果差异。第五段讲 GPT、ViT、Diffusion 时不要展开太多细节而是画一条时间线标出每个节点的关键创新。最后留一个动手环节让听众用 PyTorch 写一个单头注意力输入随机张量验证输出形状和 softmax 权重之和为 1。我自己的习惯是每次讲 Transformer 之前都会把这份 PPT 里的公式和代码对照着重跑一遍确保没有版本兼容问题。从那以后我每次做技术分享都强制走一遍“手算 代码验证 可视化”三步避免纸上谈兵。希望这份拆解能帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑