做算法开发这几年,Transformer 几乎是绕不开的一道门槛。不管你是做 NLP、CV,还是最近在折腾大模型微调,最终都会回到同一个问题上:Transformer 内部到底是怎么工作的?我刚开始接触时,也被 QKV、多头注意力、位置编码这些概念绕得头晕,网上的资料要么太偏数学,要么只有代码没有解释。这篇文章便是我自己的学习梳理笔记,用“图解 + 最小代码 + 工程经验”的方式,把 Transformer 讲清楚。文中会从循环神经网络并行化困境讲起,逐步拆解 Self-Attention、Multi-Head Attention、位置编码、Encoder-Decoder 结构,最后给出 PyTorch 手写核心模块和 Hugging Face 实际调用示例。新手可以把这篇文章当作第一份学习地图,有基础的开发者则可以直接跳到代码和排错部分。本文不追求论文级严谨,只追求“讲得明白、能跑起来、能用上”。
1. 为什么最后是 Transformer:从 RNN 到自注意力的演进
1.1 RNN / LSTM 的并行化困境
在 Transformer 出现之前,处理序列数据(如文本、语音、时间序列)的主流模型是 RNN(循环神经网络)和 LSTM(长短期记忆网络)。
RNN 的基本思路是:按时间步顺序读取序列,每一步把当前输入和历史隐藏状态融合,生成新的隐藏状态。
h_t = f(h_{t-1}, x_t)这个结构有两个明显问题:
- 无法并行。第 t 步必须等第 t-1 步算完,文本越长,算得越慢。GPU 虽然有很多计算单元,但 RNN 这种串行模式让 GPU 难以充分发挥算力。
- 长距离依赖衰减。虽然 LSTM 通过门控机制缓解了梯度消失问题,但当序列长度达到几百甚至上千时,早期信息传递到后期仍然会大幅衰减。
1.2 Attention 带来的改变
Attention(注意力)机制最早被引入到 Seq2Seq 模型中,用来解决“编码器把整个源句子压缩成一个向量,信息损失严重”的问题。它的核心思想是:在解码时,不要只依赖一个固定向量,而是动态地查看原始输入的不同位置,并分配不同权重。
这给了我们一个重要启发:如果每个位置都能直接看到其他所有位置,那么长距离依赖问题可以被大幅缓解。2017 年,Google 在论文《Attention Is All You Need》中把这个思想推到极致——直接用 Attention 替代循环结构,这就是 Transformer。它的核心优势是:
- 全局建模能力强。每个 token 都能直接与其他所有 token 交互。
- 并行度高。矩阵运算可以由 GPU 高效并行执行。
- 扩展性好。通过堆叠层数和扩大参数量,模型效果持续提升,这也是后来 BERT、GPT 等一系列大模型的基础架构。
1.3 Transformer 的定位
简单来说,Transformer 是一个基于自注意力机制的序列模型架构。它不再按顺序迭代,而是通过注意力矩阵来捕捉输入内部的关系。这样的设计让它在自然语言处理、图像分类、语音识别、推荐系统等场景中都能适用。
接下来我们进入核心部分:Transformer 的完整架构。
2. Transformer 整体架构图解
2.1 宏观视角:Encoder-Decoder 结构
Transformer 的原始版本采用 Encoder-Decoder 框架。左侧是 Encoder(编码器),右侧是 Decoder(解码器)。
输入序列: [我, 爱, 学习] │ ▼ ┌──────────────────────┐ │ Input Embedding │ 词向量嵌入 └──────────────────────┘ │ ┌──────────────────────┐ │ Positional Encoding│ 位置编码 └──────────────────────┘ │ ▼ ┌──────────────────────┐ │ Encoder Layer ×N │ 多头注意力 + 前馈网络 └──────────────────────┘ │ ▼ ┌──────────────────────┐ │ Encoder 输出 │ 即 KV 的来源 └──────────────────────┘ 输出侧流程类似: 输入序列: [<s>, 我, 爱] │ ▼ ┌──────────────────────┐ │ Output Embedding │ └──────────────────────┘ │ ▼ ┌──────────────────────┐ │ Positional Encoding │ └──────────────────────┘ │ ▼ ┌──────────────────────┐ │ Decoder Layer ×N │ │ Masked Self-Attention│ │ Cross-Attention │ │ Feed Forward │ └──────────────────────┘ │ ▼ ┌──────────────────────┐ │ Linear + Softmax │ → 预测下一个 token └──────────────────────┘在机器翻译、文本摘要、对话生成这类“输入一段文本,输出另一段文本”的任务中,Encoder 负责理解输入文本,Decoder 负责生成输出文本。
但很多人刚开始学习时会有一个困惑:现在流行的 GPT 模型好像只用 Decoder?BERT 好像只用 Encoder?没错,这是 Transformer 的发展分支:
- Encoder-only:适合理解类任务,例如文本分类、命名实体识别、语义相似度。典型代表是 BERT。
- Decoder-only:适合生成类任务,例如对话、续写、代码生成。典型代表是 GPT 系列。
- Encoder-Decoder:适合序列到序列的转换任务,例如翻译、摘要。典型代表是 T5、BART。
2.2 每个子模块的作用
一个标准的 Transformer Block 内部包含以下组件:
| 组件 | 作用 |
|---|---|
| Embedding | 将 token 映射为稠密向量 |
| Positional Encoding | 给模型提供 token 的顺序信息 |
| Multi-Head Self-Attention | 捕捉 token 之间的依赖关系 |
| Add & Norm | 残差连接 + 层归一化,帮助训练稳定 |
| Feed-Forward Network | 每个 token 独立通过的全连接网络,增强非线性表达能力 |
| Masked Multi-Head Attention | Decoder 中防止看到未来 token |
下面我们从最核心的 Self-Attention 开始拆解。
3. 自注意力机制(Self-Attention)详解
3.1 从“查字典”理解 Q、K、V
Self-Attention 中最难理解的就是 Q(Query)、K(Key)、V(Value)这三个向量。其实可以类比一个检索场景:
- 你脑子里有一个问题,这是 Query。
- 你面前有多个知识点,每个知识点有标题,这是 Key。
- 每个知识点正文内容,这是 Value。
你要做的,是用问题(Query)去和每个标题(Key)做匹配,得到一个相关度分数,然后按照相关度去加权提取正文内容(Value)。相关度越高的内容,在最终结果中占的比例越大。
在 Transformer 中,输入序列的每个 token 都会生成自己的 Q、K、V。具体做法是:
Q = X · W_Q K = X · W_K V = X · W_V其中 X 是输入向量矩阵,W_Q、W_K、W_V 是可学习的权重矩阵。
3.2 注意力分数的计算
假设输入序列有 n 个 token,每个 token 的维度是 d_model。那么:
- Q 的维度是 [n, d_k]
- K 的维度是 [n, d_k]
- V 的维度是 [n, d_v]
注意力分数的计算公式如下:
Attention(Q, K, V) = softmax( Q · K^T / sqrt(d_k) ) · V这里每个步骤的含义是:
- 计算 Q 和所有 K 的点积,得到“每个 token 与其他 token 的关注度打分”。
- 除以 sqrt(d_k),防止点积数值过大导致 softmax 梯度消失。d_k 是 K 的维度。
- 用 softmax 归一化,让每个 token 对整句话的注意力权重之和为 1。
- 用权重对 V 加权求和,得到当前 token 的上下文表示。
3.3 一个手动计算的例子
为了看得更明白,我们设计一个极小的例子。假设只有两个 token,“我”和“爱”,每个 token 映射成 2 维向量:
“我”的向量: [1, 0] “爱”的向量: [0, 1]为了方便展示,我们直接忽略 W_Q、W_K、W_V 的具体学习过程,假设:
Q = [[1, 0], [0, 1]] K = [[1, 0], [0, 1]] V = [[1, 0], [0, 1]]第一步,计算 Q 和 K 的点积矩阵:
[1, 0] · [1, 0]^T = 1 [1, 0] · [0, 1]^T = 0 [0, 1] · [1, 0]^T = 0 [0, 1] · [0, 1]^T = 1 得分矩阵 = [[1, 0], [0, 1]]第二步,假设 d_k = 2,那么 sqrt(2) ≈ 1.414。将得分除以 1.414 后做 softmax。
对第一行 [0.707, 0] 做 softmax:
softmax([0.707, 0]) ≈ [0.668, 0.332]这里“我”对“我”自己的关注度约 0.668,对“爱”的关注度约 0.332。
第三步,用这个权重去加权 V:
0.668 × [1, 0] + 0.332 × [0, 1] = [0.668, 0.332]这就是“我”经过自注意力机制后的新向量。新向量同时蕴含了“我”和“爱”的信息,具体比例由注意力权重决定。
3.4 为什么需要缩放
点积的数值会随向量维度增大而变大。当 d_k 很大时,点积结果分布会变得非常尖锐,softmax 之后绝大多数概率集中在一个位置上,其他位置梯度接近 0,这会导致训练困难。除以 sqrt(d_k) 可以把点积分布拉回到方差适中的区间,让梯度更稳定。
很多初学者容易忽略缩放这一步,甚至直接删掉,这在训练深层 Transformer 时往往会导致 loss 不降。
4. 多头注意力与残差连接
4.1 多头注意力的动机
只有一个注意力头时,模型只能从一种关系视角去看输入。但真实文本中,关系是多种多样的。比如句子:
“小狗追着猫跑,它跑得很快。”“它”可能指“小狗”,也可能指“猫”,具体含义需要结合上下文判断。单头注意力可能只关注到一种语法关系,而多头注意力可以让模型同时从多个子空间去捕捉不同关系。
多头注意力的做法是:
- 把 Q、K、V 分成 h 组,每组维度为原维度的 1/h。
- 每组独立计算注意力。
- 把 h 个头的输出拼接起来,通过一个线性层压缩回原始维度。
MultiHead(Q, K, V) = Concat(head_1, ..., head_h) · W_O head_i = Attention(Q·W_Q^i, K·W_K^i, V·W_V^i)这里 W_Q^i、W_K^i、W_V^i 是第 i 个头独立的投影矩阵,W_O 是输出投影矩阵。
4.2 多头注意力的直观理解
可以这么想:一个注意力头相当于一个“专家”,关注某一类关系。一个头关注语法依赖,另一个头关注指代关系,还有一个头关注语义相似度。多头机制让模型能够并行地学习多种不同表示。
在 BERT-base 中,注意力头数为 12,每头维度是 64。在 GPT-3 中,注意力头数达到 96 个,模型容量大幅提升。但不是说头越多越好,实际工程中需要结合模型总参数量和训练数据量做权衡。
4.3 残差连接与 LayerNorm
在多头注意力后面,通常会接一个 Add & Norm 结构。
X = LayerNorm(X + Attention(X))这个设计有两个作用:
- 残差连接:让梯度可以跨层直接回传,缓解深层网络训练困难。即使某一层的输出变化很大,模型也可以选择让信息“直通”。
- LayerNorm:对每个样本的所有特征维度做归一化,让数据分布更加稳定,加速训练收敛。
在原始论文中,LayerNorm 被放在残差之后的 Add & Norm。但在近期很多大模型实现中,更常用 Pre-LN(先归一化再计算),例如:
X = X + Attention(LayerNorm(X))两种方式各有优劣。Post-LN 效果上限高但训练不稳定;Pre-LN 训练更稳,深层次数更多时更常用。工程上优先使用 Pre-LN,更省心。
5. 位置编码:Transformer 如何感知顺序
5.1 为什么需要位置编码
RNN 天然按顺序读取输入,所以序列顺序被编码在结构中。Transformer 不同,它一次性看到所有 token,自注意力计算本身不带顺序信息。如果只是把“我爱学习”变成“学习爱我”的向量输入,模型看到的特征完全相同。
因此必须在输入中额外加入位置信息,这就是位置编码(Positional Encoding)。
5.2 原始 Transformer 的正余弦编码
原始论文使用固定频率的正弦和余弦函数来生成位置编码:
PE(pos, 2i) = sin(pos / 10000^(2i / d_model)) PE(pos, 2i+1) = cos(pos / 10000^(2i / d_model))其中 pos 是 token 在序列中的位置,i 是向量维度下标。
这个设计有两个特点:
- 不同位置得到不同编码向量,且相邻位置的编码比较相似。
- 这种函数形式让模型更容易学习到“相对位置”关系,因为任意两个位置之间的偏移可以通过某种线性变换表示。
5.3 可学习位置编码与相对位置编码
在 BERT 中,位置编码被改成可学习的 Embedding,效果和正余弦编码接近。后来很多模型又引入了更复杂的相对位置编码(如 RoPE、ALiBi),它们对长文本泛化更好。尤其是 RoPE(旋转位置编码)被 LLaMA、ChatGLM 等模型广泛采用。
工程上,如果你只是调用现成大模型,不需要手动实现位置编码。但如果要自己设计序列模型,需要记住:位置信息必须显式注入模型,否则序列顺序对模型而言是无效的。
6. Encoder 与 Decoder:从哪里生成到哪里预测
6.1 Encoder 的编码逻辑
Encoder 的核心任务是理解输入。每个 Encoder Layer 包含两个子层:
- Multi-Head Self-Attention:捕捉输入内部 token 之间的关系。
- Feed-Forward Network:对每个 token 表示逐位置做非线性变换。
Encoder 输出的是一组向量,每个向量对应输入中的一个 token,并且已经融合了全句上下文信息。这组向量被称为“编码表示”。
6.2 Decoder 的掩码自注意力
Decoder 的输入是“已经生成的 token 序列”。在生成第 t 个 token 时,模型不应该看到未来的 token。因此 Decoder 内部使用 Masked Self-Attention。
具体做法是:在计算注意力分数时,把未来位置的分数设为负无穷,这样 softmax 之后它们的权重为 0。
得分矩阵(3个token): [[0.7, -inf, -inf], [0.5, 0.8, -inf], [0.3, 0.6, 0.9]]这样第 1 个 token 只能关注自己,第 2 个 token 只能关注前两个,第 3 个 token 能关注前三个。这个设计保证了模型的自回归特性:每一步生成都只依赖历史输出。
6.3 Cross-Attention:Encoder 与 Decoder 的桥
Decoder 中除了 Masked Self-Attention,还有一个 Cross-Attention(交叉注意力)。在这个子层中:
- Query 来自 Decoder 当前层的输出。
- Key 和 Value 来自 Encoder 最后一层的输出。
这样 Decoder 在生成每一个 token 时,都可以动态地“查阅”原始输入中它最关注的片段。这就是翻译模型能够根据源语言逐步生成目标语言的关键机制。
对于只用 Decoder 的 GPT 类模型,没有 Encoder 和 Cross-Attention,它们纯粹是自回归地生成文本。
7. 从原理到代码:手写核心模块与 Hugging Face 实战
7.1 用 PyTorch 实现多头注意力
下面给出一个最小化但结构完整的 PyTorch 实现。注意,这里重点演示原理,不追求与论文完全一致的最优实现。
import torch import torch.nn as nn import torch.nn.functional as F class MultiHeadAttention(nn.Module): def __init__(self, d_model, num_heads): super().__init__() assert d_model % num_heads == 0 self.d_model = d_model self.num_heads = num_heads self.d_k = d_model // num_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, x, mask=None): batch_size, seq_len, _ = x.size() Q = self.w_q(x) # [batch, seq, d_model] K = self.w_k(x) V = self.w_v(x) # 拆成多头 Q = Q.view(batch_size, seq_len, self.num_heads, self.d_k).transpose(1, 2) K = K.view(batch_size, seq_len, self.num_heads, self.d_k).transpose(1, 2) V = V.view(batch_size, seq_len, self.num_heads, self.d_k).transpose(1, 2) # 计算注意力分数 scores = torch.matmul(Q, K.transpose(-2, -1)) / torch.sqrt(torch.tensor(self.d_k, dtype=torch.float32)) # scores: [batch, num_heads, seq, seq] if mask is not None: scores = scores.masked_fill(mask == 0, float("-inf")) attn = F.softmax(scores, dim=-1) output = torch.matmul(attn, V) # [batch, num_heads, seq, d_k] output = output.transpose(1, 2).contiguous().view(batch_size, seq_len, self.d_model) output = self.w_o(output) return output这里的关键点:
- 通过
view和transpose把 d_model 拆成 num_heads 个子空间。 scores除以 sqrt(d_k) 对应缩放点积注意力。masked_fill对应 decoder 中的掩码操作。- 最终把多头结果拼接后经
w_o输出。
7.2 用 PyTorch 实现一个 Transformer 编码层
有了多头注意力,就可以组装一个完整的 Encoder Layer。
class FeedForward(nn.Module): def __init__(self, d_model, d_ff): super().__init__() self.fc1 = nn.Linear(d_model, d_ff) self.fc2 = nn.Linear(d_ff, d_model) self.relu = nn.ReLU() def forward(self, x): return self.fc2(self.relu(self.fc1(x))) class EncoderLayer(nn.Module): def __init__(self, d_model, num_heads, d_ff, dropout=0.1): super().__init__() self.self_attn = MultiHeadAttention(d_model, num_heads) self.feed_forward = FeedForward(d_model, d_ff) self.norm1 = nn.LayerNorm(d_model) self.norm2 = nn.LayerNorm(d_model) self.dropout = nn.Dropout(dropout) def forward(self, x, mask=None): # Pre-LN 结构 attn_output = self.self_attn(self.norm1(x), mask) x = x + self.dropout(attn_output) ff_output = self.feed_forward(self.norm2(x)) x = x + self.dropout(ff_output) return x你可以用下面这段代码快速验证:
model = EncoderLayer(d_model=512, num_heads=8, d_ff=2048) x = torch.randn(2, 10, 512) # batch_size=2, seq_len=10, d_model=512 y = model(x) print(y.shape) # torch.Size([2, 10, 512])7.3 使用 Hugging Face 加载预训练 Transformer
实际项目中基本不会从零训练 Transformer,而是加载预训练模型做微调。Hugging Face 的 Transformers 库是目前最常用的工具。以下示例演示加载一个 BERT 模型做文本分类。
pip install transformers torchfrom transformers import AutoTokenizer, AutoModelForSequenceClassification import torch # 1. 加载分词器和预训练模型 tokenizer = AutoTokenizer.from_pretrained("bert-base-chinese") model = AutoModelForSequenceClassification.from_pretrained("bert-base-chinese", num_labels=2) # 2. 对输入文本编码 texts = ["这个电影太棒了,剧情精彩,演员演技在线。", "电影很无聊,浪费时间。"] inputs = tokenizer(texts, padding=True, truncation=True, return_tensors="pt") # 3. 前向推理 with torch.no_grad(): outputs = model(**inputs) logits = outputs.logits predictions = torch.argmax(logits, dim=-1) print(predictions)如果你想要一个 GPT 风格的续写示例,可以加载 GPT-2:
from transformers import AutoTokenizer, AutoModelForCausalLM tokenizer = AutoTokenizer.from_pretrained("gpt2") model = AutoModelForCausalLM.from_pretrained("gpt2") text = "Once upon a time" inputs = tokenizer(text, return_tensors="pt") outputs = model.generate(**inputs, max_length=50, do_sample=True, top_p=0.9) print(tokenizer.decode(outputs[0], skip_special_tokens=True))这里需要说明:gpt2的官方权重主要针对英文训练,中文续写效果很一般。做中文生成任务建议使用bert-base-chinese配合分类任务,或者搜索中文本地化生成模型。版本方面,Transformers 4.x 和 PyTorch 2.x 是当前常见组合,具体版本以你的开发环境为准。
8. 常见问题与排查思路
8.1 问题汇总
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 训练 loss 不下降 | 学习率过大/过小;未做缩放;数据未归一化 | 检查学习率,使用 warmup;确认注意力缩放;检查数据预处理 |
| 显存不足 OOM | 序列过长,注意力矩阵复杂度为 O(n²) | 缩短 max_length,使用梯度累积,或用长文本稀疏注意力方案 |
| 生成结果一直重复 | 解码参数不恰当;训练数据存在重复模式 | 调整 no_repeat_ngram_size,增大 top_p 或 temperature |
| 加载模型报错 | 模型权重与 Transformers 版本不匹配 | 升级或回退 Transformers 版本,检查from_pretrained参数 |
| 序列长度超过模型限制 | BERT 一般限制 512,GPT 一般是 1024/2048 | 对长文本切片、摘要,或选择支持长文本的模型 |
| 中文预测效果差 | 使用了不支持中文的预训练模型 | 换成bert-base-chinese、chinese-roberta等中文预训练模型 |
8.2 排查清单
遇到 Transformer 相关问题时,可以按以下顺序排查:
- 确认输入张量形状是否正确,特别是 batch size 和 seq len 的维度。
- 确认注意力掩码是否有效。训练完看不到推理时是否传给模型。
- 打印 attention 最后一层的权重,确认模型是否关注到了合理位置。
- 检查 loss 曲线:如果出现 NaN,优先降低学习率并检查输入是否有异常值。
- 如果是 OOM,先减小 batch size,再考虑序列长度压缩。
- 如果是推理速度慢,先检查是否开启了
torch.no_grad(),再考虑半精度。
9. 最佳实践与工程建议
9.1 数据与 Tokenizer 规范
文本输入前必须走同一个 tokenizer 流程。训练和推理时使用的 tokenizer 必须是同一个版本,否则会出现 token id 无法对齐的问题。中文文本建议直接使用中文预训练模型的专门 tokenizer,不要用英文 tokenizer 处理中文。
9.2 输入长度与注意力复杂度
Transformer 的标准注意力复杂度是 O(n²),也就是说,序列长度翻倍,计算量变成原来的 4 倍。这在实际工程中很容易成为瓶颈。
应对方案:
- 能截断就截断,先保证输入长度在模型支持范围内。
- 使用长文本模型(如 Longformer、BigBird、或者带 RoPE 的 LLaMA)处理超长文本。
- 对文档类任务,先做段落拆分,再按段落处理,最后聚合结果。
9.3 训练稳定性
训练 Transformer 时,最忌讳直接全量微调。推荐顺序是:
- 冻结大部分底层参数,只训练顶层。
- 确认 loss 下降后,再逐步解冻更多层。
- 使用 warmup 学习率策略,前几步让学习率从小慢慢升到目标值。
- 如果显存允许,使用 AdamW 优化器并设置正确的权重衰减。
9.4 推理性能优化
对生产环境,常见的推理加速手段包括:
- 使用半精度(FP16)或 INT8 量化,减少显存占用。
- 使用批处理(batch inference),一次推理多条请求。
- 对 Decoder 模型使用 KV Cache,避免重复计算已经生成的历史 token。
- 通过 ONNX Runtime、TensorRT 等推理加速框架部署。
9.5 数据偏差与公平性
Transformer 训练过程会继承训练数据中的偏见。如果训练语料本身有性别、地域、职业上的偏向,模型输出也可能带有这种偏向。在模型上线前,应该做一定程度上的偏差测试,尤其是面向 C 端用户的产品。这个环节不决定模型效果上限,但决定产品能不能稳定落地。
9.6 成本与 Token 计量
大模型训练和推理的成本往往和 Token 数量直接挂钩。Token 是模型处理文本的基本单位,一个中文汉字可能对应一个或多个 Token,具体要看 tokenizer 切词方式。在生产环境中,建议在调用日志中统计 Token 消耗,用于成本核算和异常流量检测。做计费系统时,要明确“输入 Token”和“输出 Token”是否分别计量,以及上下文字符串如何计算。这个细节在规模化运营时非常重要。
10. 总结与下一步学习路线
Transformer 是当前人工智能大模型的基础架构。本文从 RNN 的并行化瓶颈出发,逐步拆解了 Self-Attention 的 QKV 计算过程、多头注意力的作用、位置编码的必要性,以及 Encoder 和 Decoder 的分工差异。通过手写最小多头注意力实现,又通过 Hugging Face 加载预训练模型,相信你对 Transformer 已经有了一个“既能看懂理论、也能跑通代码”的认识。
接下来你可以从几个方向继续深入:
- 阅读源码:去看 PyTorch 的
nn.Transformer模块,或者 Hugging Face 里的BertSelfAttention源码,代码会比你手写的版本更工程化。 - 动手微调:用一个小规模的中文数据集做一次文本分类或者文本生成微调,亲手踩一遍过拟合、显存不足、loss 不降这些坑。
- 关注大模型架构演进:新一代模型(如 LLaMA、ChatGLM、Qwen)在原始 Transformer 上做了哪些改动?为什么说 RoPE 重要?为什么 GQA 能节省显存?这些问题都可以顺着这次的基础继续探索。
- 回看 Transformer 与 CNN/RNN 的对比:理解为什么在图像任务上 Vision Transformer 也能奏效,核心原因就是自注意力机制本身不依赖输入是文本还是图像。
学习 Transformer 不要等到把所有数学公式都搞懂才开始动手,先跑通一个最小示例,再慢慢完善认知,这条路是最快的。希望这篇文章能帮你省下一些探路时间。