从RNN到Transformer:吴恩达教程与PyTorch实战,彻底搞懂注意力机制与序列建模
如果你在2024年还在用RNN或CNN死磕序列任务,感觉模型效果总差那么一口气,或者看着大语言模型(LLM)的论文里满屏的“Attention”、“Transformer”感到无从下手,那么这篇文章就是为你准备的。
最近,吴恩达(Andrew Ng)在DeepLearning.AI发布的最新教程,用他标志性的“化繁为简”风格,把Transformer这个听起来高大上的架构,拆解成了人人都能看懂的拼图。这不仅仅是又一个“原理讲解”,而是一份直指核心的“工程理解指南”。很多人学完Transformer,记住了Self-Attention的公式,却依然不知道它到底解决了RNN的什么致命伤,更不清楚那些“位置编码”、“多头注意力”的工程设计,在实际构建模型时究竟该如何抉择。
本文将以吴恩达的讲解为主线,结合代码实践,为你彻底讲透Transformer。我们的目标不是复述论文,而是让你获得两种能力:第一,能清晰地向别人解释Transformer为什么比RNN/CNN更适合处理序列;第二,能亲手搭建一个最小可运行的Transformer模块,并理解每一行代码背后的设计意图。学完本文,你将对支撑当今AGI(通用人工智能)浪潮的基石架构,有一个坚实且可操作的理解。
1. 从RNN的困境到Transformer的破局:我们到底在解决什么问题?
在Transformer出现之前,处理序列数据(如自然语言、时间序列)的主流是循环神经网络(RNN)及其变体LSTM、GRU。它们的工作原理是“递归”:按顺序处理输入序列的每一个元素,并将当前步骤的“记忆”(隐藏状态)传递给下一步。
这个设计带来了两个几乎无解的工程难题:
- 并行化灾难:因为必须等待
t-1步完成才能计算第t步,训练过程无法有效利用GPU强大的并行计算能力,导致训练极其缓慢。 - 长程依赖衰减:信息在一步步的传递中就像“传话游戏”,序列一长,开头的关键信息到末尾就所剩无几或完全扭曲。尽管LSTM用门控机制缓解了这个问题,但根本性的顺序依赖没有改变。
Transformer在2017年论文《Attention Is All You Need》中提出的核心思想是:抛弃递归,完全依赖注意力机制(Attention Mechanism)来建立序列中任意两个元素之间的直接关联。
想象一下,你要理解一句话“The animal didn't cross the street because it was too tired.”中的“it”指代什么。RNN需要从“The”开始,一步步将信息传递到“it”,再结合后面的“tired”来推断。而Transformer的做法是,让“it”这个词直接去“看”句子中的所有其他词(包括远处的“animal”和“tired”),通过计算相关性分数(Attention Score),瞬间找到“it”与“animal”和“tired”的强关联,从而完成指代消解。
这就是Transformer的革命性所在:它通过注意力机制实现了序列内部关系的全局、并行计算。这不仅让训练速度飞跃,更重要的是,它让模型能够直接捕获任意距离的依赖关系,为后续BERT、GPT等大模型的涌现奠定了架构基础。
2. Transformer核心组件拆解:不只是Attention
很多人把Transformer等同于Self-Attention,这是一个常见误区。Transformer是一个精心设计的系统,Self-Attention是其核心引擎,但周围的“传动装置”同样关键。我们将其拆解为以下关键部件:
2.1 输入嵌入与位置编码
Transformer本身没有递归或卷积,因此它天生不具备感知词序的能力。输入“猫追老鼠”和“老鼠追猫”的词向量加和是一样的。为了解决这个问题,Transformer引入了位置编码。
- 输入嵌入:将每个输入词(Token)转换为一个高维向量(如512维)。
- 位置编码:为序列中的每个位置生成一个同样维度的向量,这个向量包含了该位置的绝对或相对位置信息。然后,将词向量和位置向量相加,作为编码器的输入。
# 一个简单的位置编码实现示例(正弦余弦版本) import torch import torch.nn as nn import math class PositionalEncoding(nn.Module): def __init__(self, d_model, max_len=5000): super(PositionalEncoding, self).__init__() pe = torch.zeros(max_len, d_model) position = torch.arange(0, max_len, dtype=torch.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) # 偶数维度用sin pe[:, 1::2] = torch.cos(position * div_term) # 奇数维度用cos pe = pe.unsqueeze(0) # 形状: [1, max_len, d_model] self.register_buffer('pe', pe) # 注册为缓冲区,不参与训练 def forward(self, x): # x: [batch_size, seq_len, d_model] return x + self.pe[:, :x.size(1)]关键点:位置编码与词嵌入相加,意味着模型在学习时,会同时考虑词的语义和它的位置。
2.2 自注意力机制:模型如何“聚焦”
这是Transformer的灵魂。其核心计算可以用一个公式概括:Attention(Q, K, V) = softmax(QK^T / sqrt(d_k)) V
听起来很抽象,我们用“图书馆查资料”来类比:
- 你有一个查询:你想了解“注意力机制”(Query, Q)。
- 图书馆里每本书都有一个标题,概括了它的内容(Key, K)。
- 每本书都有详细的正文内容(Value, V)。
- 自注意力的过程就是:用你的“查询”(Q)去和所有书的“标题”(K)计算匹配度(相似度),得到一个权重分布(通过softmax归一化)。然后,用这个权重去加权求和所有书的“正文”(V),最终得到一份融合了所有相关书籍精华的“摘要”。
在Transformer的“自注意力”中,Q, K, V都来自于同一输入序列的不同线性变换。这意味着,序列中的每个词都在同时扮演三种角色:作为查询去关注别人,作为键被他人查询,以及提供自己的值。
缩放因子sqrt(d_k):是为了防止点积结果过大,导致softmax函数梯度消失,是一个重要的工程trick。
2.3 多头注意力:为什么需要多个“视角”?
只用一套Q、K、V进行注意力计算,相当于只用一种方式去理解句子关系。多头注意力(Multi-Head Attention)并行地运行多组独立的注意力计算(即多个“头”),每组可以学习到不同的关系模式。
例如,在“苹果公司发布了新款手机”这句话中:
- 一个头可能专注于“苹果”与“公司”的修饰关系。
- 另一个头可能专注于“发布”与“手机”的动宾关系。
- 第三个头可能专注于“新款”与“手机”的属性关系。
最后,将所有头的输出拼接起来,再经过一个线性层融合,得到最终的多头注意力输出。这极大地增强了模型的表征能力。
import torch.nn as nn import torch.nn.functional as F import math class MultiHeadAttention(nn.Module): def __init__(self, d_model, num_heads): super(MultiHeadAttention, self).__init__() assert d_model % num_heads == 0 self.d_model = d_model self.num_heads = num_heads self.d_k = d_model // num_heads # 定义生成Q, K, V的线性层 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, query, key, value, mask=None): batch_size = query.size(0) # 1. 线性投影并分头 Q = self.W_q(query).view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2) K = self.W_k(key).view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2) V = self.W_v(value).view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2) # 2. 计算缩放点积注意力 scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(self.d_k) if mask is not None: scores = scores.masked_fill(mask == 0, -1e9) attn_weights = F.softmax(scores, dim=-1) # 3. 应用注意力权重到V上 context = torch.matmul(attn_weights, V) # 4. 合并多头输出 context = context.transpose(1, 2).contiguous().view(batch_size, -1, self.d_model) # 5. 最终线性投影 output = self.W_o(context) return output, attn_weights2.4 前馈网络与残差连接
- 前馈网络:一个简单的两层全连接网络(例如,维度从512->2048->512),通常使用ReLU激活。它为每个位置的表示进行独立的、相同的非线性变换,增加模型的表达能力。
- 残差连接:在每个子层(自注意力层、前馈网络层)周围,都添加一个残差连接,然后进行层归一化。即
LayerNorm(x + Sublayer(x))。这是训练深层网络的关键,能有效缓解梯度消失问题,让模型更容易优化。
2.5 编码器与解码器堆叠
- 编码器:由N个(原论文N=6)相同的层堆叠而成。每层包含一个多头自注意力子层和一个前馈网络子层,每个子层外都有残差连接和层归一化。编码器的任务是提取输入序列的丰富语义表示。
- 解码器:同样由N个相同的层堆叠。每层包含三个子层:
- 掩码多头自注意力层:防止当前位置关注到未来的信息(在训练时,这是必须的)。
- 编码器-解码器注意力层:这是解码器独有的。它的Query来自解码器上一层的输出,而Key和Value来自编码器的最终输出。这使得解码器在生成每一个词时,都能有选择地聚焦于输入序列的不同部分。
- 前馈网络层。
3. 环境准备:用PyTorch搭建你的第一个Transformer
理论之后,实践是理解的唯一途径。我们将使用PyTorch搭建一个简化版的Transformer,用于完成一个经典的序列到序列任务:数字序列排序(例如,将[5, 2, 9, 1]转换为[1, 2, 5, 9])。这个任务直观,能清晰展示模型如何学习序列关系。
环境要求:
- Python 3.8+
- PyTorch 1.9+ (推荐1.13或更高版本)
- torchtext (可选,用于数据工具)
- Jupyter Notebook 或任意Python IDE
# 使用pip安装核心依赖 pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 # 请根据你的CUDA版本调整 pip install numpy matplotlib tqdm4. 构建最小化Transformer模型
我们将构建一个包含编码器和解码器的完整Transformer模型。为了简化,我们使用固定的词汇表(0-9的数字和起止符)。
4.1 定义模型组件
首先,我们复用之前定义的PositionalEncoding和MultiHeadAttention类。接下来定义编码器层和解码器层。
# 编码器层 class EncoderLayer(nn.Module): def __init__(self, d_model, num_heads, d_ff, dropout=0.1): super(EncoderLayer, self).__init__() self.self_attn = MultiHeadAttention(d_model, num_heads) self.feed_forward = nn.Sequential( nn.Linear(d_model, d_ff), nn.ReLU(), nn.Dropout(dropout), 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, mask=None): # 子层1: 多头自注意力 + 残差 & 层归一化 attn_output, _ = self.self_attn(x, x, x, mask) x = self.norm1(x + self.dropout(attn_output)) # 子层2: 前馈网络 + 残差 & 层归一化 ff_output = self.feed_forward(x) x = self.norm2(x + self.dropout(ff_output)) return x # 解码器层 class DecoderLayer(nn.Module): def __init__(self, d_model, num_heads, d_ff, dropout=0.1): super(DecoderLayer, self).__init__() self.self_attn = MultiHeadAttention(d_model, num_heads) self.cross_attn = MultiHeadAttention(d_model, num_heads) # 编码器-解码器注意力 self.feed_forward = nn.Sequential( nn.Linear(d_model, d_ff), nn.ReLU(), nn.Dropout(dropout), nn.Linear(d_ff, d_model) ) self.norm1 = nn.LayerNorm(d_model) self.norm2 = nn.LayerNorm(d_model) self.norm3 = nn.LayerNorm(d_model) self.dropout = nn.Dropout(dropout) def forward(self, x, enc_output, src_mask=None, tgt_mask=None): # 子层1: 掩码多头自注意力 attn_output, _ = self.self_attn(x, x, x, tgt_mask) x = self.norm1(x + self.dropout(attn_output)) # 子层2: 编码器-解码器注意力 attn_output, _ = self.cross_attn(x, enc_output, enc_output, src_mask) x = self.norm2(x + self.dropout(attn_output)) # 子层3: 前馈网络 ff_output = self.feed_forward(x) x = self.norm3(x + self.dropout(ff_output)) return x4.2 组合成完整Transformer
现在,我们将编码器层、解码器层、嵌入层和位置编码组合起来。
class Transformer(nn.Module): def __init__(self, src_vocab_size, tgt_vocab_size, d_model=512, num_heads=8, num_encoder_layers=6, num_decoder_layers=6, d_ff=2048, max_seq_len=100, dropout=0.1): super(Transformer, self).__init__() self.encoder_embedding = nn.Embedding(src_vocab_size, d_model) self.decoder_embedding = nn.Embedding(tgt_vocab_size, d_model) self.positional_encoding = PositionalEncoding(d_model, max_seq_len) self.encoder_layers = nn.ModuleList([ EncoderLayer(d_model, num_heads, d_ff, dropout) for _ in range(num_encoder_layers) ]) self.decoder_layers = nn.ModuleList([ DecoderLayer(d_model, num_heads, d_ff, dropout) for _ in range(num_decoder_layers) ]) self.fc_out = nn.Linear(d_model, tgt_vocab_size) self.dropout = nn.Dropout(dropout) def encode(self, src, src_mask): src_embedded = self.dropout(self.positional_encoding(self.encoder_embedding(src))) enc_output = src_embedded for layer in self.encoder_layers: enc_output = layer(enc_output, src_mask) return enc_output def decode(self, tgt, enc_output, src_mask, tgt_mask): tgt_embedded = self.dropout(self.positional_encoding(self.decoder_embedding(tgt))) dec_output = tgt_embedded for layer in self.decoder_layers: dec_output = layer(dec_output, enc_output, src_mask, tgt_mask) return dec_output def forward(self, src, tgt, src_mask=None, tgt_mask=None): enc_output = self.encode(src, src_mask) dec_output = self.decode(tgt, enc_output, src_mask, tgt_mask) output = self.fc_out(dec_output) return output5. 数据准备与训练流程
5.1 生成排序任务数据
我们创建一个简单的数据生成器,用于生成随机长度的数字序列及其排序后的目标序列。
import random def generate_batch(batch_size, max_len=10, vocab_size=10): """生成一批数据,源序列是随机数字,目标序列是排序后的数字。""" src_batch = [] tgt_batch = [] # 假设0是填充符,1是序列开始符,2是序列结束符 for _ in range(batch_size): length = random.randint(3, max_len) seq = [random.randint(3, vocab_size-1) for _ in range(length)] # 从3开始,避免与特殊符号冲突 src_batch.append([1] + seq + [2]) # 添加起止符 tgt_batch.append([1] + sorted(seq) + [2]) # 填充到相同长度 src_max_len = max(len(s) for s in src_batch) tgt_max_len = max(len(t) for t in tgt_batch) src_batch = [s + [0]*(src_max_len - len(s)) for s in src_batch] tgt_batch = [t + [0]*(tgt_max_len - len(t)) for t in tgt_batch] return torch.LongTensor(src_batch), torch.LongTensor(tgt_batch)5.2 创建掩码
Transformer需要两种掩码:
- 填充掩码:忽略序列中用于填充的无意义部分。
- 序列掩码(仅用于解码器):防止当前位置关注到未来的词。
def create_masks(src, tgt, pad_idx=0): # 填充掩码 src_mask = (src != pad_idx).unsqueeze(1).unsqueeze(2) # [batch_size, 1, 1, src_len] tgt_pad_mask = (tgt != pad_idx).unsqueeze(1).unsqueeze(3) # [batch_size, 1, tgt_len, 1] tgt_len = tgt.size(1) # 序列掩码(下三角矩阵,包含对角线) tgt_sub_mask = torch.tril(torch.ones((tgt_len, tgt_len), device=src.device)).bool() tgt_mask = tgt_pad_mask & tgt_sub_mask # [batch_size, 1, tgt_len, tgt_len] return src_mask, tgt_mask5.3 训练循环
现在,我们可以编写一个简单的训练循环。
import torch.optim as optim from tqdm import tqdm # 超参数 VOCAB_SIZE = 13 # 0-9数字 + 起止符 + 填充符 D_MODEL = 128 # 为了快速演示,使用较小维度 NUM_HEADS = 8 NUM_LAYERS = 3 D_FF = 512 DROPOUT = 0.1 BATCH_SIZE = 32 NUM_EPOCHS = 20 LEARNING_RATE = 0.0001 device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') model = Transformer(src_vocab_size=VOCAB_SIZE, tgt_vocab_size=VOCAB_SIZE, d_model=D_MODEL, num_heads=NUM_HEADS, num_encoder_layers=NUM_LAYERS, num_decoder_layers=NUM_LAYERS, d_ff=D_FF, dropout=DROPOUT).to(device) criterion = nn.CrossEntropyLoss(ignore_index=0) # 忽略填充符的损失 optimizer = optim.Adam(model.parameters(), lr=LEARNING_RATE) model.train() for epoch in range(NUM_EPOCHS): epoch_loss = 0 # 假设我们每个epoch训练100个batch for _ in tqdm(range(100), desc=f'Epoch {epoch+1}'): src, tgt = generate_batch(BATCH_SIZE, vocab_size=VOCAB_SIZE) src, tgt = src.to(device), tgt.to(device) # 目标序列的输入和输出错位一位,用于教师强制训练 tgt_input = tgt[:, :-1] tgt_output = tgt[:, 1:] src_mask, tgt_mask = create_masks(src, tgt_input) optimizer.zero_grad() output = model(src, tgt_input, src_mask, tgt_mask) # output: [batch, tgt_len-1, vocab_size] loss = criterion(output.reshape(-1, VOCAB_SIZE), tgt_output.reshape(-1)) loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1) # 梯度裁剪 optimizer.step() epoch_loss += loss.item() print(f'Epoch {epoch+1}, Loss: {epoch_loss / 100:.4f}')6. 模型推理与效果验证
训练完成后,我们需要一个推理函数来使用模型进行预测。推理时,解码器需要自回归地生成序列。
def greedy_decode(model, src, src_mask, max_len=20, start_symbol=1, end_symbol=2): """贪心解码""" model.eval() with torch.no_grad(): src = src.to(device) src_mask = src_mask.to(device) # 编码器前向传播 memory = model.encode(src, src_mask) # 初始化解码器输入(起始符) ys = torch.ones(1, 1).fill_(start_symbol).type_as(src).to(device) for i in range(max_len-1): tgt_mask = (torch.triu(torch.ones((1, ys.size(1), ys.size(1))), diagonal=1)).bool().to(device) out = model.decode(ys, memory, src_mask, tgt_mask) prob = model.fc_out(out[:, -1]) _, next_word = torch.max(prob, dim=1) next_word = next_word.item() ys = torch.cat([ys, torch.ones(1, 1).type_as(src).fill_(next_word).to(device)], dim=1) if next_word == end_symbol: break return ys # 测试推理 model.eval() test_src = torch.LongTensor([[1, 7, 3, 9, 2]]) # 输入序列 [7, 3, 9] src_mask = (test_src != 0).unsqueeze(1).unsqueeze(2).to(device) result = greedy_decode(model, test_src, src_mask) print(f"输入序列: {test_src.tolist()[0][1:-1]}") # 去掉起止符 print(f"预测排序: {result.tolist()[0][1:-1]}") # 去掉起止符 # 期望输出: [3, 7, 9]运行结果验证:如果模型训练良好,对于输入[7, 3, 9],模型应该能输出排序后的序列[3, 7, 9]。这表明模型已经学会了“排序”这一序列到序列的映射关系,其核心在于通过自注意力机制理解了序列元素之间的大小比较关系。
7. 常见问题与排查思路
在实现和训练Transformer时,你可能会遇到以下典型问题:
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| Loss不下降或为NaN | 1. 学习率过高。 2. 梯度爆炸。 3. 数据或标签有问题。 | 1. 打印前几个batch的loss。 2. 检查梯度范数 ( torch.nn.utils.clip_grad_norm_)。3. 检查数据生成逻辑,确保src/tgt对应正确。 | 1. 降低学习率(如从1e-3降到1e-4或1e-5)。 2. 使用梯度裁剪(如 clip_grad_norm_(model.parameters(), 1.0))。3. 验证数据生成函数。 |
| 模型输出全是同一个词或乱码 | 1. 解码器掩码错误,导致信息泄露。 2. 教师强制训练时,输入输出未错位。 3. 模型容量太小或训练不足。 | 1. 可视化tgt_mask,确保是下三角矩阵。2. 检查训练循环中 tgt_input和tgt_output的切片是否正确。3. 增加模型维度 ( d_model) 或层数 (num_layers),延长训练时间。 | 1. 仔细检查create_masks函数。2. 确保训练时预测的是下一个词。 3. 增加模型复杂度和训练轮数。 |
| 训练速度极慢 | 1. 未使用GPU。 2. 序列长度过长,注意力计算复杂度O(n²)导致。 3. Batch size太小。 | 1. 检查model.to(device)和tensor.to(device)。2. 分析代码性能热点。 3. 监控GPU利用率。 | 1. 确保使用CUDA。 2. 对于超长序列,考虑使用线性注意力变体。 3. 在内存允许下增大batch size。 |
| 验证集效果远差于训练集 | 过拟合。 | 观察训练loss下降但验证loss上升。 | 1. 增加Dropout比率。 2. 使用更多的数据增强。 3. 添加L2权重衰减。 4. 使用早停法。 |
| 位置编码似乎没起作用 | 位置编码被Dropout或LayerNorm淹没。 | 检查PositionalEncoding的输出值是否与输入在同一量级。 | 确保位置编码在嵌入和Dropout之后相加,或在初始化时调整位置编码的缩放因子。 |
8. 最佳实践与工程建议
要将Transformer从玩具任务应用到实际项目,需要注意以下几点:
- 规范化与初始化:Transformer对参数初始化敏感。使用Xavier或Kaiming初始化权重。对嵌入层使用
nn.init.normal_(weight, mean=0, std=d_model**-0.5)是论文中的做法。 - 学习率调度:使用带热启动的学习率调度器,如论文中的
Noam调度器(学习率随步数先线性增加再平方根衰减),这对收敛至关重要。# Noam调度器简化版 class NoamOpt: def __init__(self, model_size, factor, warmup, optimizer): self.optimizer = optimizer self._step = 0 self.warmup = warmup self.factor = factor self.model_size = model_size self._rate = 0 def step(self): self._step += 1 rate = self.rate() for p in self.optimizer.param_groups: p['lr'] = rate self._rate = rate self.optimizer.step() def rate(self, step=None): if step is None: step = self._step return self.factor * (self.model_size ** (-0.5) * min(step ** (-0.5), step * self.warmup ** (-1.5))) - 标签平滑:在分类损失(如CrossEntropy)中使用标签平滑,可以防止模型对预测结果过于自信,提升泛化能力。
- 批量归一化 vs 层归一化:Transformer使用层归一化,因为它对序列长度不敏感,更适合变长序列和RNN/Transformer结构。批量归一化在batch维度上归一化,对batch size敏感。
- 注意力可视化:调试和理解模型时,将注意力权重 (
attn_weights) 可视化是极其有效的手段。你可以看到模型在生成某个词时,到底“注意”了输入序列的哪些部分。 - 从预训练模型开始:对于绝大多数NLP任务,不要从头训练Transformer。应使用Hugging Face Transformers库中的BERT、GPT、T5等预训练模型进行微调,这能节省大量计算资源和时间。
9. 总结与通向AGI的基石
通过本文的拆解与实践,你应该已经清晰地认识到,Transformer的成功并非源于某个神秘的“黑科技”,而是一系列精妙设计组合产生的系统效应:自注意力机制解决了长程依赖与并行化的根本矛盾,残差连接和层归一化保障了深层网络的稳定训练,位置编码赋予了模型序列感知能力,而编码器-解码器架构则完美适配了序列生成任务。
吴恩达的教程之所以有效,正是因为他将这套复杂的系统分解为一个个可理解、可实现的模块。理解Transformer,不仅仅是理解一个模型架构,更是理解现代深度学习如何通过巧妙的工程设计来解决根本性的计算瓶颈。
从BERT、GPT-3到如今的GPT-4、Sora,所有令人瞩目的AGI进展都建立在Transformer或它的变体之上。掌握Transformer,你就拿到了理解当今人工智能核心进展的钥匙。下一步,建议你:
- 精读原论文:《Attention Is All You Need》,关注其中的设计细节和实验分析。
- 探索变体:了解Transformer-XL(处理更长序列)、Reformer(更高效的注意力)、Vision Transformer(ViT,用于图像)等重要改进。
- 上手实战:使用Hugging Face库,在一个真实任务(如文本分类、翻译、摘要)上微调一个预训练的Transformer模型,感受其强大威力。
- 深入源码:研究PyTorch官方
nn.Transformer模块或Hugging Facetransformers库的源码,这是从“会用”到“懂原理”的关键一步。
希望这篇融合了原理深度与代码实践的文章,能成为你深入Transformer世界和AGI技术栈的坚实起点。建议收藏本文,在后续的学习和开发中随时回顾。