1. 从零开始Transformer到底是个啥大家好我是老张在AI这个行当里摸爬滚打了十几年从早期的统计模型一路跟到现在的千亿参数大模型。要说这几年最让我兴奋的技术Transformer绝对排第一。它就像AI领域的“内燃机”彻底改变了整个行业的动力系统。你可能天天听人说GPT、BERT、T5这些大模型它们背后都有一个共同的灵魂架构就是Transformer。那么Transformer到底是什么呢简单说它是一个用来处理序列数据的深度神经网络架构。什么是序列数据你正在读的这句话就是由“我”、“正在”、“读”、“这”、“句”、“话”这些词按顺序组成的序列。翻译任务里一句中文和它对应的英文也是序列。传统上处理这类任务用的是RNN循环神经网络或者CNN卷积神经网络但它们都有各自的“硬伤”。RNN像是个记性不太好的老人处理长句子时开头的信息传到后面可能就忘得差不多了而且计算必须一个字一个字串着来速度慢。CNN呢像是个视野有限的放大镜一次只能看到局部几个词的关系要理解整句话的意思就得一层层往上堆也挺费劲。2017年谷歌大脑团队那篇名为《Attention Is All You Need》的论文就像扔下了一颗重磅炸弹。论文里说别整那些复杂的循环和卷积了咱们就用“注意力”这一招包打天下。这个Transformer模型完全基于“自注意力机制”让它能一眼看到输入序列里所有词之间的关系不管这两个词隔得多远。这种“全局视野”的能力让它处理长文本、理解复杂上下文关系时表现出了惊人的效果。更妙的是它的计算可以高度并行化这意味着我们可以用强大的GPU集群来疯狂加速训练这才催生了后来参数规模动辄百亿、千亿的“大模型”时代。所以你今天能用ChatGPT流畅对话用Midjourney生成惊艳的图片背后都离不开Transformer这个基石。理解它不仅是理解一项技术更是理解当今AI何以至此的一把钥匙。接下来我就带你一层层剥开Transformer的“洋葱”看看它到底是怎么工作的以及我们怎么亲手把它用起来。2. Transformer核心架构拆解它到底长什么样光说概念可能有点虚咱们直接上图看看Transformer的“全家福”。你可以把它想象成一个精密的翻译官大脑由编码器和解码器两大部分组成中间通过注意力机制紧密相连。整个模型的工作流程很像我们人类做翻译的过程首先编码器部分像是一个“理解者”它把输入的中文句子从头到尾读一遍充分理解每个词的意思和词与词之间的关系然后把理解后的“思想精华”打包成一个富含上下文信息的向量表示。接着解码器部分像是一个“生成者”它一边回顾编码器给的“思想精华”一边结合自己已经翻译出来的英文单词思考下一个最合适的英文词是什么如此循环直到生成完整的英文句子。这个架构图里有几个关键部分我会在接下来的小节里详细唠唠输入层怎么把文字变成模型认识的数字、编码器堆栈理解的核心、解码器堆栈生成的核心以及输出层怎么把数字变回文字。你会发现它的设计非常模块化每个部分各司其职这种清晰的结构也是它后来能被广泛应用和魔改的原因之一。2.1 输入层把文字“喂”给模型的第一步模型不认识汉字也不认识英文单词它只认识数字。所以输入层的第一件事就是当好“翻译官”把人类语言翻译成机器语言——也就是向量。分词这是第一步。比如“我爱北京天安门”这句话分词器会把它切成“我”、“爱”、“北京”、“天安门”这几个基本单元每个单元称为一个“token”。现在流行的分词方法有很多比如按字切分、按词切分或者像BPE、WordPiece这种更智能的子词切分法。选哪种方法直接影响模型对词汇的掌握程度。我刚开始用的时候曾因为分词器没选好导致模型总是学一些奇怪的词组效果大打折扣。输入嵌入每个token比如“爱”会被转换成一个固定长度的向量比如512维。这个向量不是随机的它应该在向量空间里蕴含这个词的语义。例如“国王”和“王后”的向量在某个方向上的差值可能近似于“男人”和“女人”的差值。这个映射关系是通过一个可学习的“嵌入矩阵”查表得到的。你可以把这个矩阵想象成一本巨大的“词向量字典”。位置编码这里有个精妙的设计Transformer不像RNN那样天然有顺序感它是同时处理所有token的。那它怎么知道“我爱北京”和“北京爱我”意思完全不同呢全靠“位置编码”。我们给每个位置比如第一个词、第二个词也生成一个独特的、固定长度的向量然后把它加到对应词的嵌入向量上。这样模型就能同时知道“你是谁”语义和“你在哪”位置。论文里用的是正弦和余弦函数来生成这个编码我实测下来这种方法对处理比训练时更长的句子特别友好。2.2 编码器模型如何“读懂”一句话编码器是Transformer理解输入内容的核心它通常由N个比如原论文是6个完全相同的层堆叠而成。每一层都有两个核心子层多头自注意力机制和前馈神经网络。而且每个子层外面都套着两个“神器”残差连接和层归一化。自注意力机制这是Transformer的灵魂。咱们用个生活场景来理解你读这句话时“它”这个代词指代的是前面提到的哪个名词你的大脑会自动聚焦回前文去寻找关联。自注意力机制干的就是这个事对于句子里的每一个词比如“它”自注意力机制会计算它和句子中所有词包括它自己的关联程度注意力分数。关联度高的词在更新“它”的表示时权重就大。那这个关联度怎么算呢这里就引出了著名的QKV三元组。每个词向量会通过三个不同的可学习矩阵被投影成三个新的向量Query查询、Key键、Value值。你可以把Query理解为“我当前词想知道什么”Key是“我其他词有什么信息”Value就是“我其他词具体的信息内容”。计算注意力时就是用当前词的Query去和所有词的Key做点积衡量匹配度得到一组分数经过缩放和Softmax变成概率分布最后用这个分布对所有词的Value进行加权求和。这样新生成的“它”的向量就融合了与它最相关的那些词的信息。多头注意力只用一套QKV矩阵就像只用一种视角看世界。多头注意力就是准备多套比如8套不同的QKV矩阵让模型同时从多个不同的“子空间”去学习关系。可能一个“头”专注于捕捉语法主谓关系另一个“头”专注于捕捉实体指代关系。最后把8个头的输出拼接起来再通过一个线性层融合。我做过对比实验多头注意力比单头注意力的模型效果确实有显著提升尤其是在处理一词多义或者复杂句法结构时。前馈神经网络注意力层完成了信息交互前馈神经网络则负责对每个位置的特征进行独立加工和变换。它就是一个简单的两层全连接网络中间用ReLU激活函数引入非线性。它的作用是把注意力层提取到的混合特征进行更深层次的抽象和提炼。你可以把它理解为一个“特征增强器”。残差连接与层归一化这是保证深层网络能顺利训练的关键“技巧”。残差连接就是把子层比如注意力层的输入和输出直接相加。这相当于开了一条“高速公路”让梯度可以直接回流有效缓解了深度网络中的梯度消失问题。层归一化则是对每个样本的所有特征维度进行标准化让每一层的输入数据分布保持稳定加速模型收敛。我常跟团队里的新人说把这俩组合用好训练深度Transformer模型就成功了一半。2.3 解码器模型如何“生成”一句话解码器负责根据编码器的“理解结果”来生成目标序列。它的结构和编码器很像也是N层的堆叠但有三个关键区别。第一掩码多头自注意力层解码器在生成时是一个词一个词往外蹦的。在生成第t个词时它不应该“偷看”到第t个词之后的内容未来的信息。所以这一层的自注意力是带掩码的。具体做法就是在计算注意力分数时把当前词之后所有位置的分数都盖住设为负无穷这样Softmax之后它们的概率就为0了。这就确保了生成过程的“自回归”特性即当前输出只依赖于已知的过去输出。第二编码器-解码器注意力层这是连接编码器和解码器的桥梁。这一层里Query来自解码器上一层的输出而Key和Value则来自编码器最终的输出。这样解码器在生成每一个词的时候都可以有选择地去“关注”输入序列中最相关的部分。比如在翻译时生成英文的“apple”时模型可能会高度关注中文输入里的“苹果”。第三输出生成是循环的解码器不是一次性输出整个序列。它先从特殊的开始符sos开始结合编码器输出预测第一个词然后把预测出的第一个词作为输入再预测第二个词如此循环直到预测出结束符eos。在训练时为了效率我们会使用“教师强制”策略即把完整的目标序列右移一位后作为解码器输入。但在推理时就必须老老实实一步步来。2.4 输出层从概率到文字的“临门一脚”解码器最后一层会输出一个向量序列每个向量对应目标序列的一个位置。输出层的工作就是把这个高维向量“翻译”回人类的词汇表。线性层首先通过一个线性变换全连接层把解码器输出的向量例如512维投影到词汇表大小的维度例如50000维。这个投影后的向量每个维度上的数值对应词汇表中一个词的“得分”。Softmax层然后对这个得分向量进行Softmax操作将其转换为一个概率分布。这个分布就代表了模型认为下一个词是词汇表中每个词的可能性有多大。比如在翻译任务中输入“猫”模型输出向量经过Softmax后“cat”对应的概率可能是0.85“kitty”是0.1其他词的概率非常小。输出选择在训练时我们通常用这个概率分布和真实标签计算交叉熵损失。在推理生成时我们则需要根据这个分布来选择最终的词。最简单的方法是“贪婪搜索”直接选择概率最大的那个词。但这样容易导致生成结果重复、枯燥。更常用的方法是“束搜索”保留几个概率较高的候选序列一步步扩展最终选择整体概率最高的序列。或者用“采样”方法按概率随机抽取这样生成的内容更有创意但不确定性也高。在实际项目中选择哪种生成策略需要根据任务需求反复调试。3. 动手实战用PyTorch搭建一个迷你Transformer理论说了这么多不敲代码都是纸上谈兵。咱们这一部分就动手用PyTorch实现一个用于机器翻译的迷你Transformer。我会把关键代码贴出来并解释每一步在干什么。你可以跟着在Jupyter Notebook里跑一遍感受会更深。首先我们定义一些超参数和基础模块。import torch import torch.nn as nn import torch.optim as optim import math # 超参数 d_model 512 # 词向量和位置编码的维度 n_heads 8 # 注意力头的数量 num_encoder_layers 6 # 编码器层数 num_decoder_layers 6 # 解码器层数 dim_feedforward 2048 # 前馈神经网络中间层维度 dropout 0.1 # Dropout比率 max_seq_length 100 # 最大序列长度 src_vocab_size 10000 # 源语言如中文词表大小 tgt_vocab_size 10000 # 目标语言如英文词表大小 # 1. 位置编码 class PositionalEncoding(nn.Module): def __init__(self, d_model, max_len5000): super(PositionalEncoding, self).__init__() 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) # 偶数维度用sin pe[:, 1::2] torch.cos(position * div_term) # 奇数维度用cos pe pe.unsqueeze(0).transpose(0, 1) # 形状: [max_len, 1, d_model] self.register_buffer(pe, pe) # 这不是模型参数但需要保存 def forward(self, x): # x: [seq_len, batch_size, d_model] x x self.pe[:x.size(0), :] return x位置编码类实现了论文中的正弦余弦公式。这里用了register_buffer来注册一个不是参数但需要随模型保存的张量。接下来我们实现最核心的Transformer模型。# 2. 构建Transformer模型 class TransformerModel(nn.Module): def __init__(self, src_vocab_size, tgt_vocab_size, d_model, n_heads, num_encoder_layers, num_decoder_layers, dim_feedforward, dropout, max_seq_length): super(TransformerModel, self).__init__() self.d_model d_model # 词嵌入层 self.src_embedding nn.Embedding(src_vocab_size, d_model) self.tgt_embedding nn.Embedding(tgt_vocab_size, d_model) # 位置编码 self.positional_encoding PositionalEncoding(d_model, max_seq_length) # 使用PyTorch内置的Transformer简化实现其内部已包含编码器解码器堆栈 # 注意PyTorch的Transformer模块要求输入形状为 [seq_len, batch_size, d_model] self.transformer nn.Transformer(d_modeld_model, nheadn_heads, num_encoder_layersnum_encoder_layers, num_decoder_layersnum_decoder_layers, dim_feedforwarddim_feedforward, dropoutdropout, batch_firstFalse) # 我们使用 (seq_len, batch, feature) 格式 # 输出线性层 self.fc_out nn.Linear(d_model, tgt_vocab_size) self.dropout nn.Dropout(dropout) def forward(self, src, tgt, src_maskNone, tgt_maskNone, src_padding_maskNone, tgt_padding_maskNone, memory_key_padding_maskNone): # src: 源语言序列 [src_seq_len, batch_size] # tgt: 目标语言序列 [tgt_seq_len, batch_size] # 1. 词嵌入 位置编码 src_emb self.dropout(self.positional_encoding(self.src_embedding(src) * math.sqrt(self.d_model))) tgt_emb self.dropout(self.positional_encoding(self.tgt_embedding(tgt) * math.sqrt(self.d_model))) # 2. 通过Transformer模块 # 注意我们需要为解码器生成一个防止看到未来的掩码下三角掩码 if tgt_mask is None: tgt_mask self.generate_square_subsequent_mask(tgt.size(0)).to(tgt.device) output self.transformer(src_emb, tgt_emb, src_masksrc_mask, tgt_masktgt_mask, src_key_padding_masksrc_padding_mask, tgt_key_padding_masktgt_padding_mask, memory_key_padding_maskmemory_key_padding_mask) # output: [tgt_seq_len, batch_size, d_model] # 3. 线性层投影到词表大小 output self.fc_out(output) # [tgt_seq_len, batch_size, tgt_vocab_size] return output def generate_square_subsequent_mask(self, sz): 生成一个下三角矩阵掩码用于解码器掩码自注意力 mask (torch.triu(torch.ones(sz, sz)) 1).transpose(0, 1) mask mask.float().masked_fill(mask 0, float(-inf)).masked_fill(mask 1, float(0.0)) return mask这里我直接使用了PyTorch内置的nn.Transformer模块它已经帮我们实现了编码器解码器堆栈、注意力机制等复杂部分让我们可以更专注于模型的使用和训练流程。我们自己实现了位置编码和生成解码器掩码的函数。接下来我们看看如何准备数据、定义训练循环。# 3. 实例化模型、定义损失函数和优化器 device torch.device(cuda if torch.cuda.is_available() else cpu) model TransformerModel(src_vocab_size, tgt_vocab_size, d_model, n_heads, num_encoder_layers, num_decoder_layers, dim_feedforward, dropout, max_seq_length).to(device) criterion nn.CrossEntropyLoss(ignore_index0) # 忽略填充符假设pad_id0 optimizer optim.Adam(model.parameters(), lr0.0001, betas(0.9, 0.98), eps1e-9) # 4. 模拟一个训练步骤实际中你需要真实的DataLoader def train_step(model, src_batch, tgt_batch): model.train() optimizer.zero_grad() # src_batch: [batch_size, src_seq_len], 需要转置为 [src_seq_len, batch_size] # tgt_batch: [batch_size, tgt_seq_len] src src_batch.transpose(0, 1).to(device) tgt_input tgt_batch[:, :-1].transpose(0, 1).to(device) # 解码器输入去掉最后一个词 tgt_output tgt_batch[:, 1:].contiguous().view(-1).to(device) # 解码器目标去掉第一个词sos并展平 # 生成解码器掩码 tgt_mask model.generate_square_subsequent_mask(tgt_input.size(0)).to(device) # 前向传播 output model(src, tgt_input, tgt_masktgt_mask) # output: [tgt_seq_len, batch_size, vocab_size] # 计算损失需要将output reshape成 [batch_size * seq_len, vocab_size] loss criterion(output.view(-1, output.size(-1)), tgt_output) # 反向传播 loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0) # 梯度裁剪防止爆炸 optimizer.step() return loss.item() # 5. 模拟一个推理生成步骤 def greedy_decode(model, src, max_len, start_symbol): 贪婪搜索生成序列 model.eval() src src.transpose(0, 1).to(device) # [src_seq_len, 1] memory model.transformer.encoder(model.positional_encoding(model.src_embedding(src) * math.sqrt(model.d_model))) ys torch.ones(1, 1).fill_(start_symbol).long().to(device) # 起始符 for i in range(max_len-1): tgt_mask model.generate_square_subsequent_mask(ys.size(0)).to(device) out model.transformer.decoder(model.positional_encoding(model.tgt_embedding(ys) * math.sqrt(model.d_model)), memory, tgt_masktgt_mask) prob model.fc_out(out[-1, :]) # 取最后一个位置的输出 _, next_word torch.max(prob, dim1) next_word next_word.item() ys torch.cat([ys, torch.ones(1, 1).long().fill_(next_word).to(device)], dim0) if next_word 2: # 假设2是结束符eos的id break return ys # 模拟数据实际中应使用真实分词后的数据 batch_size 32 src_seq_len, tgt_seq_len 20, 25 src_dummy torch.randint(1, src_vocab_size, (batch_size, src_seq_len)) # 假设0是pad tgt_dummy torch.randint(1, tgt_vocab_size, (batch_size, tgt_seq_len)) # 运行一个训练批次 loss train_step(model, src_dummy, tgt_dummy) print(f单个训练批次的损失: {loss:.4f})这段代码构建了一个完整的、可训练的Transformer翻译模型框架。在实际项目中你需要替换掉模拟数据使用真实的双语平行语料库并构建完善的DataLoader来处理批次、填充和掩码。训练这样一个模型需要大量的数据、计算资源和时间。但通过这个迷你实现你应该能清晰地看到数据是如何流动的从嵌入开始经过编码器的多层理解再通过解码器一步步生成最后通过线性层和Softmax得到预测结果。4. Transformer的威力与边界它为何成功又受何限制Transformer能火遍全球不是没有道理的。我结合自己这些年的项目经验总结一下它的核心优势和目前面临的挑战。并行计算能力这是它碾压RNN的最大杀器。RNN必须等上一个时间步算完才能算下一个是串行的。而Transformer的自注意力层可以同时对序列中所有位置的关系进行计算这种天生的并行性让它能充分利用GPU的成千上万个核心训练速度极快。处理长文档时这个优势更加明显。强大的长程依赖建模传统的RNN和LSTM有“遗忘”问题CNN需要堆叠很多层才能获得较大的感受野。Transformer的自注意力机制在理论上一步到位每个词都能直接“看到”序列中所有其他词无论距离多远。这使得它在需要理解全文语境的任务如阅读理解、长文本摘要上表现卓越。可扩展性极强Transformer的架构非常规整就是编码器/解码器块的堆叠。这种设计让研究者可以轻松地通过增加层数深度、增加注意力头数或隐藏层维度宽度来扩大模型规模。我们现在看到的百亿、千亿参数模型基本都是在这个架构上“大力出奇迹”堆出来的。而且更大的模型通常意味着更强的能力虽然效率是另一回事。然而Transformer也并非完美无缺它有几个显著的“阿克琉斯之踵”计算复杂度高自注意力机制要计算所有词对之间的关系其时间和空间复杂度是序列长度的平方级O(n²)。这意味着当序列长度翻倍时所需的内存和计算量会变为原来的四倍。这严重限制了它处理超长文本比如一整本书、长达一小时的音频的能力。虽然有一些改进方法如“稀疏注意力”、“局部注意力”等但都在效果和效率之间做权衡。位置信息编码的局限性虽然正弦位置编码很巧妙但它是一种绝对位置编码对于序列长度的外推性一般。也就是说如果模型是在最长512个token的文本上训练的你直接给它一个1000个token的文本效果可能会下降。后续也有研究者提出了相对位置编码等改进方案。巨大的资源消耗训练一个大型Transformer模型需要海量的数据和惊人的算力。动辄需要成千上万的GPU训练数月这导致了极高的经济成本和碳排放也让AI研究在一定程度上出现了资源集中的现象。如何在保持性能的同时降低模型能耗是当前一个重要的研究方向。“黑盒”特性与不可解释性尽管注意力权重图有时能给我们一些直观感受比如模型在翻译时关注了源句子的哪个部分但Transformer内部高达数百层的复杂交互其决策过程对人类来说依然极其晦涩。这在高风险应用场景如医疗、司法中是一个需要严肃对待的问题。在我做过的项目中处理长文档摘要时就曾深受其O(n²)复杂度之苦不得不将文档切分成块损失了整体的连贯性。而在一些对延迟要求极高的在线服务中即使是对小型Transformer模型进行推理其速度也可能成为瓶颈。所以了解它的优势更要看清它的边界才能在实际工作中做出最合适的技术选型。Transformer是强大的工具但绝不是万能钥匙。