ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

从零实现Transformer:深入理解编码器-解码器架构与注意力机制

2026/8/21 13:24:09 拓冰建站 浏览量
从零实现Transformer:深入理解编码器-解码器架构与注意力机制 在深入大模型和自然语言处理领域时我们常常听到“注意力机制”是Transformer的核心。然而很多学习者在理解了自注意力、多头注意力的原理后面对一个完整的Transformer模型时依然感到困惑这些“零件”究竟是如何协同工作最终构建出这个强大的架构的本文将从一个工程化、系统化的视角为你彻底拆解Transformer的搭建过程。我们将从最基础的编码器-解码器结构开始一步步组装嵌入层、位置编码、多头注意力、前馈网络、残差连接与层归一化最终形成一个可运行的模型。无论你是希望深入理解Transformer原理还是计划从零实现一个模型用于自己的任务这篇文章都将提供清晰的路径和可复现的代码。1. Transformer架构全景超越“注意力”的宏观视图在深入细节之前我们必须建立对Transformer的整体认知。Transformer并非一个单一的“注意力模块”而是一个精心设计的、由多个子层堆叠而成的深度神经网络架构。1.1 核心设计思想编码器-解码器范式Transformer沿用了机器翻译中经典的编码器-解码器Encoder-Decoder范式但其内部完全基于自注意力机制摒弃了循环神经网络RNN和卷积神经网络CNN。编码器Encoder负责处理输入序列如源语言句子并将其转换为一系列富含上下文信息的“中间表示”Context Representation。原始论文《Attention Is All You Need》中使用了N6个完全相同的编码器层进行堆叠。解码器Decoder负责根据编码器的输出和已生成的部分输出序列如目标语言已翻译的词自回归地Auto-regressively预测下一个输出 token。解码器同样堆叠了N6层。这种设计使得模型能够并行处理整个输入序列并有效捕捉长距离依赖关系这是其相比RNN的巨大优势。1.2 架构总览图与数据流理解数据在模型中的流动路径至关重要。一个简化的工作流程如下输入嵌入将输入的单词索引序列转换为稠密的向量表示。位置编码为序列中的每个位置生成一个独特的向量并与词向量相加以注入序列的顺序信息。编码器堆叠嵌入向量进入编码器层。每一层包含多头自注意力子层让序列中的每个词与其他所有词进行交互计算全局依赖。前馈神经网络子层对每个位置的表示进行独立的非线性变换。残差连接与层归一化包围上述两个子层用于稳定深度网络的训练。解码器堆叠编码器的输出与目标序列的嵌入同样经过位置编码进入解码器层。每一层包含掩码多头自注意力子层防止解码时“看到”未来的信息确保自回归属性。多头交叉注意力子层让解码器关注编码器的最终输出。前馈神经网络子层与编码器中的相同。残差连接与层归一化同样包围每个子层。输出层最后一个解码器层的输出经过一个线性层通常维度映射到词表大小和一个Softmax层得到下一个词的概率分布。2. 环境准备与核心工具在动手搭建之前我们需要准备好开发环境。本文将使用PyTorch作为主要的深度学习框架因为它动态图的特点非常适合教学和原型设计。2.1 环境配置建议使用 Python 3.8 版本。首先创建一个虚拟环境并安装必要的包。# 创建并激活虚拟环境 (可选) python -m venv transformer_env source transformer_env/bin/activate # Linux/Mac # transformer_env\Scripts\activate # Windows # 安装PyTorch (请根据你的CUDA版本前往官网获取对应命令) # 例如对于CUDA 11.8 pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 # 安装其他辅助库 pip install numpy matplotlib tqdm2.2 项目结构规划一个清晰的项目结构有助于管理复杂的模型代码。建议如下transformer_from_scratch/ ├── model.py # Transformer模型核心架构定义 ├── layers.py # 子层定义注意力、前馈网络等 ├── embeddings.py # 词嵌入和位置编码 ├── train.py # 训练脚本 ├── config.py # 超参数配置 ├── utils.py # 工具函数数据加载、掩码生成等 └── data/ # 存放示例数据3. 基础“零件”的制造从嵌入到注意力让我们从最基础的组件开始实现。3.1 词嵌入与位置编码Transformer本身没有序列概念需要通过位置编码Positional Encoding来注入顺序信息。# embeddings.py import torch import torch.nn as nn import math class TokenEmbedding(nn.Module): 标准的词嵌入层 def __init__(self, vocab_size, d_model): super().__init__() self.embedding nn.Embedding(vocab_size, d_model) self.d_model d_model def forward(self, x): # x: (batch_size, seq_len) # 乘以 sqrt(d_model) 是原始论文中的缩放操作 return self.embedding(x) * math.sqrt(self.d_model) class PositionalEncoding(nn.Module): 正弦余弦位置编码 def __init__(self, d_model, max_len5000, dropout0.1): super().__init__() self.dropout nn.Dropout(pdropout) # 创建位置编码矩阵 (max_len, d_model) pe torch.zeros(max_len, d_model) position torch.arange(0, max_len, dtypetorch.float).unsqueeze(1) # (max_len, 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) # 将其注册为buffer不参与训练 def forward(self, x): # x: (batch_size, seq_len, d_model) x x self.pe[:, :x.size(1), :] # 只取前seq_len个位置 return self.dropout(x)关键点register_buffer将位置编码矩阵注册为模型的“状态”它会随模型移动如从CPU到GPU但不参与梯度更新。dropout在原始论文中被应用于嵌入和位置编码之和之后。3.2 缩放点积注意力机制这是多头注意力的核心计算单元。# layers.py import torch import torch.nn as nn import math def scaled_dot_product_attention(query, key, value, maskNone, dropoutNone): 计算缩放点积注意力。 Args: query, key, value: 形状均为 (batch_size, ..., seq_len, d_k) mask: 可选的掩码张量形状需能广播到 (..., seq_len, seq_len) dropout: nn.Dropout层 Returns: 注意力加权后的输出以及注意力权重 d_k query.size(-1) # 计算 QK^T / sqrt(d_k) scores torch.matmul(query, key.transpose(-2, -1)) / math.sqrt(d_k) if mask is not None: # 将mask中为True/1的位置替换为一个极小的负数使得softmax后概率接近0 scores scores.masked_fill(mask 0, -1e9) p_attn scores.softmax(dim-1) # (..., seq_len, seq_len) if dropout is not None: p_attn dropout(p_attn) return torch.matmul(p_attn, value), p_attn3.3 多头注意力层将注意力机制并行化允许模型同时关注来自不同表示子空间的信息。# layers.py class MultiHeadAttention(nn.Module): 实现多头注意力机制 def __init__(self, d_model, num_heads, dropout0.1): super().__init__() assert d_model % num_heads 0, d_model must be divisible by num_heads 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) # W^Q self.w_k nn.Linear(d_model, d_model) # W^K self.w_v nn.Linear(d_model, d_model) # W^V self.w_o nn.Linear(d_model, d_model) # 输出投影层 W^O self.dropout nn.Dropout(dropout) self.attention None # 可选保存注意力权重用于可视化 def forward(self, query, key, value, maskNone): batch_size query.size(0) # 1. 线性投影并分头 # (batch_size, seq_len, d_model) - (batch_size, seq_len, num_heads, d_k) # 然后转置为 (batch_size, num_heads, seq_len, d_k) 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. 应用缩放点积注意力 # x: (batch_size, num_heads, seq_len, d_k) x, self.attention scaled_dot_product_attention(Q, K, V, mask, self.dropout) # 3. 合并多头 # (batch_size, num_heads, seq_len, d_k) - (batch_size, seq_len, d_model) x x.transpose(1, 2).contiguous().view(batch_size, -1, self.d_model) # 4. 输出投影 return self.w_o(x)为什么需要多头单一注意力头可能只关注一种类型的模式如语法、指代。多个头允许模型在不同的子空间中并行学习不同的依赖关系例如一个头关注局部语法另一个头关注长距离的语义关联从而增强模型的表达能力。3.4 前馈网络与归一化每个编码器和解码器层中都包含一个完全相同的前馈网络。# layers.py class PositionwiseFeedForward(nn.Module): 位置逐点前馈网络对每个位置独立应用相同的两层线性变换 def __init__(self, d_model, d_ff, dropout0.1): super().__init__() self.w_1 nn.Linear(d_model, d_ff) # 第一层通常扩大维度 self.w_2 nn.Linear(d_ff, d_model) # 第二层投影回d_model self.dropout nn.Dropout(dropout) self.activation nn.ReLU() # 原始论文使用ReLU def forward(self, x): # x: (batch_size, seq_len, d_model) return self.w_2(self.dropout(self.activation(self.w_1(x))))残差连接与层归一化是稳定深度Transformer训练的关键。我们将它们实现为一个通用的子层连接模块。# layers.py class SublayerConnection(nn.Module): 残差连接后接层归一化。 注意原始论文是先进行层归一化再进入子层Pre-Norm。 另一种常见变体是后归一化Post-Norm。 def __init__(self, size, dropout): super().__init__() self.norm nn.LayerNorm(size) self.dropout nn.Dropout(dropout) def forward(self, x, sublayer): 应用残差连接到任何与x相同形状的子层。 # Pre-Norm: 先归一化再经过子层和dropout最后加残差 return x self.dropout(sublayer(self.norm(x)))4. 组装编码器与解码器层有了基础零件现在可以搭建编码器和解码器的单层结构。4.1 编码器层一个编码器层包含两个子层多头自注意力和前馈网络每个子层都被残差连接和层归一化包围。# model.py import torch.nn as nn from layers import MultiHeadAttention, PositionwiseFeedForward, SublayerConnection class EncoderLayer(nn.Module): def __init__(self, d_model, num_heads, d_ff, dropout0.1): super().__init__() self.self_attn MultiHeadAttention(d_model, num_heads, dropout) self.feed_forward PositionwiseFeedForward(d_model, d_ff, dropout) self.sublayer nn.ModuleList([SublayerConnection(d_model, dropout) for _ in range(2)]) self.size d_model def forward(self, x, mask): Args: x: (batch_size, src_seq_len, d_model) mask: (batch_size, 1, src_seq_len) 源序列的填充掩码 # 第一子层多头自注意力 x self.sublayer[0](x, lambda x: self.self_attn(x, x, x, mask)) # 第二子层前馈网络 x self.sublayer[1](x, self.feed_forward) return x4.2 解码器层解码器层更复杂包含三个子层掩码多头自注意力、多头交叉注意力和前馈网络。# model.py class DecoderLayer(nn.Module): def __init__(self, d_model, num_heads, d_ff, dropout0.1): super().__init__() self.self_attn MultiHeadAttention(d_model, num_heads, dropout) self.cross_attn MultiHeadAttention(d_model, num_heads, dropout) self.feed_forward PositionwiseFeedForward(d_model, d_ff, dropout) self.sublayer nn.ModuleList([SublayerConnection(d_model, dropout) for _ in range(3)]) self.size d_model def forward(self, x, memory, src_mask, tgt_mask): Args: x: (batch_size, tgt_seq_len, d_model) 解码器输入已嵌入和位置编码的目标序列 memory: (batch_size, src_seq_len, d_model) 编码器的输出 src_mask: (batch_size, 1, src_seq_len) 源序列掩码 tgt_mask: (batch_size, tgt_seq_len, tgt_seq_len) 目标序列的因果掩码防止看到未来 m memory # 第一子层掩码多头自注意力关注已生成的目标序列 x self.sublayer[0](x, lambda x: self.self_attn(x, x, x, tgt_mask)) # 第二子层多头交叉注意力关注编码器输出 x self.sublayer[1](x, lambda x: self.cross_attn(x, m, m, src_mask)) # 第三子层前馈网络 x self.sublayer[2](x, self.feed_forward) return x关键区别解码器的第一个自注意力子层使用了tgt_mask因果掩码确保在预测位置i时只能看到位置1到i-1的信息。而交叉注意力子层则使用src_mask确保解码器只关注源序列中的有效部分。5. 构建完整的Transformer模型现在我们将编码器层、解码器层、嵌入层和输出层组合起来。5.1 编码器与解码器堆叠# model.py class Encoder(nn.Module): N个编码器层的堆叠 def __init__(self, layer, N): super().__init__() self.layers nn.ModuleList([layer for _ in range(N)]) self.norm nn.LayerNorm(layer.size) # 最后再加一个层归一化Pre-Norm架构中有时可省略 def forward(self, x, mask): for layer in self.layers: x layer(x, mask) return self.norm(x) class Decoder(nn.Module): N个解码器层的堆叠 def __init__(self, layer, N): super().__init__() self.layers nn.ModuleList([layer for _ in range(N)]) self.norm nn.LayerNorm(layer.size) def forward(self, x, memory, src_mask, tgt_mask): for layer in self.layers: x layer(x, memory, src_mask, tgt_mask) return self.norm(x)5.2 最终模型集成# model.py from embeddings import TokenEmbedding, PositionalEncoding class Transformer(nn.Module): def __init__(self, src_vocab_size, tgt_vocab_size, N6, d_model512, d_ff2048, num_heads8, dropout0.1, max_len5000): super().__init__() # 1. 嵌入层 self.src_embed nn.Sequential( TokenEmbedding(src_vocab_size, d_model), PositionalEncoding(d_model, max_len, dropout) ) self.tgt_embed nn.Sequential( TokenEmbedding(tgt_vocab_size, d_model), PositionalEncoding(d_model, max_len, dropout) ) # 2. 编码器和解码器 encoder_layer EncoderLayer(d_model, num_heads, d_ff, dropout) decoder_layer DecoderLayer(d_model, num_heads, d_ff, dropout) self.encoder Encoder(encoder_layer, N) self.decoder Decoder(decoder_layer, N) # 3. 输出投影层 self.generator nn.Linear(d_model, tgt_vocab_size) # 4. 参数初始化重要 self._init_parameters() def _init_parameters(self): 使用Xavier均匀初始化参数 for p in self.parameters(): if p.dim() 1: nn.init.xavier_uniform_(p) def forward(self, src, tgt, src_mask, tgt_mask): Args: src: (batch_size, src_len) 源语言序列索引 tgt: (batch_size, tgt_len) 目标语言序列索引训练时输入推理时逐步生成 src_mask: (batch_size, 1, src_len) 源序列填充掩码 tgt_mask: (batch_size, tgt_len, tgt_len) 目标序列因果掩码 Returns: logits: (batch_size, tgt_len, tgt_vocab_size) # 编码 memory self.encode(src, src_mask) # 解码 output self.decode(tgt, memory, src_mask, tgt_mask) # 生成logits logits self.generator(output) return logits def encode(self, src, src_mask): src_embedded self.src_embed(src) return self.encoder(src_embedded, src_mask) def decode(self, tgt, memory, src_mask, tgt_mask): tgt_embedded self.tgt_embed(tgt) return self.decoder(tgt_embedded, memory, src_mask, tgt_mask)5.3 掩码生成工具Transformer需要两种掩码填充掩码Padding Mask和因果掩码Causal Mask。# utils.py import torch def create_padding_mask(seq, pad_idx0): 为填充符创建掩码。seq中等于pad_idx的位置为0需要被掩盖否则为1。 # seq: (batch_size, seq_len) mask (seq ! pad_idx).unsqueeze(1) # (batch_size, 1, seq_len) return mask def create_causal_mask(seq_len): 创建因果掩码下三角矩阵防止解码时看到未来信息。 # 返回一个上三角为1下三角含对角线为0的矩阵 mask torch.triu(torch.ones(seq_len, seq_len), diagonal1).bool() # 我们需要的是需要被掩盖的位置为True/1所以取反不在scaled_dot_product_attention中我们用mask0来填充。 # 更常见的做法是生成一个下三角为False上三角为True的矩阵。 mask torch.triu(torch.ones(seq_len, seq_len), diagonal1).bool() # 将其转换为需要被掩盖的位置为1 return mask.unsqueeze(0) # (1, seq_len, seq_len) 便于广播6. 实战一个简单的复制任务为了验证我们的Transformer实现是否正确最好的方法是运行一个简单的任务。我们设计一个“复制任务”模型学习将输入序列原样输出。6.1 生成模拟数据# train.py (部分) import torch from torch.utils.data import Dataset, DataLoader class CopyDataset(Dataset): def __init__(self, num_samples10000, seq_len10, vocab_size100): self.data torch.randint(1, vocab_size, (num_samples, seq_len)) # 1是起始符0是填充符 # 输入和输出相同 self.src self.data self.tgt_input self.data[:, :-1] # 解码器输入去掉最后一个token self.tgt_output self.data[:, 1:] # 解码器目标去掉第一个token起始符假设 def __len__(self): return len(self.data) def __getitem__(self, idx): return self.src[idx], self.tgt_input[idx], self.tgt_output[idx] def collate_fn(batch, pad_idx0): src_batch, tgt_in_batch, tgt_out_batch zip(*batch) src torch.nn.utils.rnn.pad_sequence(src_batch, padding_valuepad_idx, batch_firstTrue) tgt_in torch.nn.utils.rnn.pad_sequence(tgt_in_batch, padding_valuepad_idx, batch_firstTrue) tgt_out torch.nn.utils.rnn.pad_sequence(tgt_out_batch, padding_valuepad_idx, batch_firstTrue) return src, tgt_in, tgt_out6.2 训练循环# train.py (续) from model import Transformer from utils import create_padding_mask, create_causal_mask import torch.nn as nn import torch.optim as optim from tqdm import tqdm def train_one_epoch(model, dataloader, optimizer, criterion, device, pad_idx): model.train() total_loss 0 for src, tgt_in, tgt_out in tqdm(dataloader, descTraining): src, tgt_in, tgt_out src.to(device), tgt_in.to(device), tgt_out.to(device) # 创建掩码 src_mask create_padding_mask(src, pad_idx) tgt_len tgt_in.size(1) tgt_mask create_causal_mask(tgt_len).to(device) # 也需要为tgt_in创建填充掩码如果序列长度不一 tgt_padding_mask create_padding_mask(tgt_in, pad_idx) # 合并因果掩码和填充掩码 tgt_mask tgt_mask tgt_padding_mask # 逻辑与两个掩码中任一为True的位置都需要被掩盖 # 前向传播 optimizer.zero_grad() logits model(src, tgt_in, src_mask, tgt_mask) # (batch, tgt_len, vocab) # 计算损失 loss criterion(logits.view(-1, logits.size(-1)), tgt_out.view(-1)) loss.backward() optimizer.step() total_loss loss.item() return total_loss / len(dataloader) def main(): # 超参数 VOCAB_SIZE 100 PAD_IDX 0 BATCH_SIZE 32 EPOCHS 20 D_MODEL 128 # 为了快速演示使用较小维度 N_LAYERS 2 N_HEADS 4 D_FF 512 DROPOUT 0.1 LR 0.0001 device torch.device(cuda if torch.cuda.is_available() else cpu) print(fUsing device: {device}) # 数据 dataset CopyDataset(vocab_sizeVOCAB_SIZE) dataloader DataLoader(dataset, batch_sizeBATCH_SIZE, shuffleTrue, collate_fnlambda b: collate_fn(b, PAD_IDX)) # 模型、损失、优化器 model Transformer(src_vocab_sizeVOCAB_SIZE, tgt_vocab_sizeVOCAB_SIZE, NN_LAYERS, d_modelD_MODEL, d_ffD_FF, num_headsN_HEADS, dropoutDROPOUT).to(device) criterion nn.CrossEntropyLoss(ignore_indexPAD_IDX) # 忽略填充位置的损失 optimizer optim.Adam(model.parameters(), lrLR, betas(0.9, 0.98), eps1e-9) # 训练 for epoch in range(EPOCHS): avg_loss train_one_epoch(model, dataloader, optimizer, criterion, device, PAD_IDX) print(fEpoch {epoch1:02d} | Loss: {avg_loss:.4f}) # 简单推理测试 model.eval() with torch.no_grad(): test_src torch.tensor([[1, 2, 3, 4, 5]], devicedevice) # 假设1是起始符 # 贪心解码 max_len 10 memory model.encode(test_src, create_padding_mask(test_src, PAD_IDX).to(device)) ys torch.ones(1, 1).fill_(1).long().to(device) # 起始符 for i in range(max_len-1): tgt_mask create_causal_mask(ys.size(1)).to(device) out model.decode(ys, memory, create_padding_mask(test_src, PAD_IDX).to(device), tgt_mask) prob model.generator(out[:, -1:]) _, next_word torch.max(prob, dim-1) ys torch.cat([ys, next_word], dim1) if next_word.item() PAD_IDX: # 遇到结束符则停止 break print(fInput: {test_src.tolist()}) print(fOutput: {ys.tolist()}) if __name__ __main__: main()运行此脚本如果模型实现正确经过若干轮训练后损失应该会显著下降并且在简单的复制任务上能取得不错的效果。这证明了我们的Transformer“骨架”是正确且可工作的。7. 常见问题与调试技巧在实现和训练Transformer时你可能会遇到以下典型问题7.1 训练不收敛或损失为NaN原因1梯度爆炸。这是深度网络常见问题。解决使用梯度裁剪torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0)。检查参数初始化是否正确如使用Xavier初始化。尝试降低学习率。原因2学习率过高。解决使用更小的学习率如1e-4或1e-5并使用学习率预热Warmup策略这是Transformer训练的标准技巧。原因3掩码错误。错误的掩码可能导致注意力权重计算出错。解决可视化你的src_mask和tgt_mask确保需要被忽略的位置如填充位、未来位被正确设置为一个很大的负数在softmax前。7.2 模型过拟合或欠拟合过拟合训练损失下降验证损失上升。解决增加Dropout率使用更多的数据增强添加权重衰减L2正则化或简化模型减少层数、头数、维度。欠拟合训练和验证损失都很高。解决增加模型容量更多层、更大d_model训练更长时间检查数据是否有问题或降低正则化强度。7.3 推理时输出无意义或重复原因在自回归解码时使用贪心解码容易陷入局部最优或重复循环。解决使用束搜索Beam Search代替贪心解码。或者使用采样方法如Top-k采样、核采样增加多样性。同时确保推理时tgt_mask是正确的因果掩码。7.4 内存不足OOM原因注意力矩阵的大小是序列长度的平方seq_len * seq_len对于长序列会消耗巨大内存。解决对于极长序列考虑使用Transformer-XL、Longformer、Reformer等变体它们引入了稀疏注意力、分块注意力等机制。在训练时可以减小批次大小batch size或序列长度。8. 工程最佳实践与扩展方向当你掌握了基础Transformer的搭建后以下实践和方向能帮助你更好地应用于实际项目。8.1 训练技巧学习率预热与衰减使用线性预热到某个学习率然后按步数或轮数进行逆平方根衰减。这是训练稳定性的关键。标签平滑Label Smoothing在计算交叉熵损失时对真实标签进行平滑可以防止模型对训练数据过度自信提升泛化能力。检查点Checkpointing定期保存模型状态和优化器状态以便从中断处恢复训练。混合精度训练使用torch.cuda.amp进行自动混合精度训练可以大幅减少GPU内存占用并加快训练速度。8.2 架构变体与优化Pre-Norm vs Post-Norm我们实现的是Pre-Norm层归一化在子层前。Post-Norm层归一化在残差连接后是原始论文的做法但Pre-Norm通常训练更稳定。可以根据任务选择。激活函数尝试将ReLU替换为GELU这在许多现代Transformer中表现更好。注意力优化了解Flash Attention等高效注意力实现以加速长序列训练。参数共享可以在编码器和解码器的嵌入层与最终的输出投影层之间共享权重以减少参数量。8.3 应用于不同任务Transformer是通用的序列到序列模型通过调整输入输出可应用于多种任务机器翻译标准的Seq2Seq应用输入源语言输出目标语言。文本摘要输入长文档输出摘要。代码生成输入自然语言描述或部分代码输出完整代码。图像分类Vision Transformer将图像分割成块视为序列进行处理。语音识别输入音频频谱图序列输出文本序列。搭建一个完整的Transformer模型就像组装一台精密的仪器。注意力机制虽然是其最闪耀的“发动机”但只有与嵌入层、位置编码、前馈网络、残差连接和层归一化这些“底盘”、“传动系统”和“控制系统”完美结合才能发挥出真正的威力。希望这篇从零开始的构建指南能帮助你不仅理解Transformer的每个零件更掌握将它们组装成一个强大整体的方法和技能。下一步你可以尝试在更复杂的数据集如WMT翻译数据集上训练它或者将其作为基础模块去探索BERT、GPT等更复杂的预训练模型架构。