尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

从零理解Transformer:自注意力机制、多头注意力与PyTorch实现详解

从零理解Transformer:自注意力机制、多头注意力与PyTorch实现详解 1. 项目概述为什么今天还要从0开始学Transformer如果你最近关注过任何科技新闻或者和搞技术的朋友聊过天大概率会听到“大模型”、“AI”、“GPT”这些词。它们背后那个共同的、革命性的引擎就是Transformer。2017年谷歌大脑团队在论文《Attention Is All You Need》里提出了这个架构当时可能没人能想到它会成为今天人工智能浪潮的基石。从ChatGPT的对话到Midjourney的绘画再到代码自动补全Transformer的身影无处不在。那么一个诞生了七年的东西为什么今天还要“从0开始学”原因很简单它已经从一篇学术论文变成了一个时代的基础设施。理解Transformer不再是NLP研究员或算法工程师的专属任务。产品经理需要用它来评估AI产品的边界开发者需要用它来调用和微调API创业者需要用它来构想新的应用场景甚至每一个对技术趋势感兴趣的人都应该了解这个正在重塑我们数字世界的核心原理。它不再是高深的数学而是理解当下AI为何如此“智能”的一把钥匙。很多人第一次接触Transformer时会被它的结构图吓到左边一个编码器堆右边一个解码器堆中间满是“多头注意力”、“前馈网络”、“层归一化”这些术语。网上的教程要么一上来就扔出一堆矩阵公式要么过于简化地说“它就是靠注意力机制”。这两种方式都让人难以建立直观感受。我最初学习时也走了不少弯路后来在项目里真正用它处理过文本、图像甚至时序数据后才恍然大悟Transformer的核心思想其实非常“人性化”它解决的是一个我们大脑每天都在处理的问题——如何从一堆杂乱的信息中抓住重点并理解它们之间的关系。所以这篇内容不会堆砌公式来吓跑你也不会停留在表面。我会带你像搭积木一样从最根本的需求出发看看Transformer的每个部件为什么被设计出来它们是如何协同工作的以及最重要的是你自己如何用代码“感受”这个过程。我们会从最经典的文本翻译场景切入因为这是Transformer的“出生地”最能体现其设计精髓。当你理解了这一套逻辑再看视觉Transformer、多模态大模型就会有一种“万变不离其宗”的通透感。2. 核心思想拆解注意力机制Transformer的灵魂在Transformer出现之前处理序列数据比如一句话、一段音频、一串股价的主流模型是RNN循环神经网络和它的变体LSTM。它们的工作方式像是一个有短期记忆的人按顺序阅读句子读下一个词时会带着上一个词的“记忆”。这种方式有两个天生的瓶颈一是难以并行计算速度慢二是对于长序列开头的信息在传递到末尾时很容易被稀释或遗忘也就是“长程依赖”问题。Transformer用一套完全不同的思路解决了这些问题。它的核心是一个叫做“自注意力机制”的模块。你可以把它想象成你在阅读一段复杂文本时的大脑活动。2.1 用“读论文”来类比自注意力假设你现在读一篇技术论文里面满是陌生概念。你的眼睛虽然是一个词一个词扫过去的但你的大脑绝不是简单地线性处理。比如当你看到“Transformer”这个词时你会下意识地回顾前文去找之前哪里第一次定义了它。联系后文去看后面哪些句子是在解释它的特性。聚焦关键忽略“的”、“了”、“在”这些虚词把注意力集中在“注意力”、“编码器”、“位置编码”这些实词上。自注意力机制干的就是这件事。它让序列中的每一个元素比如一个词都能直接与序列中所有其他元素包括它自己进行交互和“沟通”从而动态地计算每个元素对于理解当前元素的重要性。这个“重要性”就是“注意力权重”。举个例子处理句子“The animal didnt cross the street because it was too tired.”那只动物没有过马路因为它太累了。当模型处理到“it”它这个词时自注意力机制会允许“it”直接去“看”句子中的所有词并计算出与每个词的关联分数。一个训练好的模型会给“animal”动物非常高的注意力分数从而明确“it”指代的是“animal”而不是“street”马路。这种跨越任意距离的直接连接完美解决了RNN的长程依赖问题。2.2 从“单头”到“多头”分工协作的专家团如果只有一套自注意力就像只让一个专家来评判所有词之间的关系。这个专家可能擅长判断语法指代如“it”指代谁但不一定擅长判断语义情感如“tired”是正面还是负面。Transformer的设计更聪明它采用了“多头注意力”。想象一下你有一个专家团来共同分析这句话专家A头1专门分析语法结构和指代关系。他主要负责搞清楚“it”指的是“animal”。专家B头2专门分析动作和状态。他关注“didnt cross”和“tired”之间的联系。专家C头3专门分析实体和属性。他强化“animal”和“tired”的关联。专家D头4……可能关注其他更细微的模式。每个“头”都独立进行一套自注意力计算但它们的侧重点通过不同的参数矩阵实现不同。最后把所有专家的分析结果拼接起来再做一个综合处理就得到了一个更全面、更丰富的序列表示。这就是“多头”的魅力——并行化地捕捉不同类型的依赖关系。实操心得多头注意力的“头数”是一个超参数。不是头越多越好。在经典Transformer中头数通常设为8。头数太多会增加计算量且可能引入冗余头数太少则可能无法充分捕捉多样化的关系。在实际项目中这通常需要根据任务复杂度和数据量进行调优。2.3 Q, K, V注意力计算的三个核心角色这是理解注意力机制公式的关键。别怕我们不用死记硬背公式而是理解这三个向量的角色。 每个输入词更准确地说是它的向量表示都会被线性变换成三个不同的向量查询向量代表这个词“想问什么”。What am I looking for?键向量代表这个词“能回答什么”或者说它的“身份标签”。What I can offer?值向量代表这个词“真正的信息内容”。My actual content.注意力权重的计算过程可以类比为在一个资料库由所有词的K组成里用当前词的Q去检索。Q和每个K做点积衡量相似度得到一组分数经过缩放和Softmax归一化就变成了权重。最后用这组权重对所有词的V进行加权求和得到当前词的输出。为什么要把K和V分开这是非常精妙的设计。K用于计算相关性“谁和我相关”V用于携带被提取的信息“相关者的内容是什么”。这样即使两个词的K很相似导致注意力权重高它们完全不同的V也能保证输出信息的差异性。这增加了模型的表达能力。3. Transformer架构全景编码器与解码器的交响乐理解了注意力机制这个核心发动机我们现在可以来看整台“Transformer机器”是如何组装的。原始论文的架构主要用于序列到序列的任务如机器翻译所以它由编码器和解码器两部分组成。3.1 编码器理解与消化输入信息编码器的任务是把输入的源语言句子如中文“理解”并压缩成一个富含语义信息的“上下文矩阵”。它由N个原论文N6完全相同的层堆叠而成。每一层都包含两个子层多头自注意力子层让输入序列的每个词充分交互理解彼此在上下文中的关系。注意编码器里的注意力是“自注意力”它的Q, K, V全部来自上一层编码器的输出目的是整合句子自身的上下文信息。前馈神经网络子层这是一个简单的全连接网络通常包含一个隐藏层和ReLU激活函数。它的作用是对自注意力子层输出的每个位置的表示进行独立、非线性的变换增加模型的表达能力。关键点这个FFN对序列中每个位置的处理是独立且相同的这有利于并行计算。每一个子层周围都有一个“残差连接”和“层归一化”。这是训练深度网络的关键技巧。残差连接把子层的输入直接加到它的输出上。这就像给你一条“高速公路”让梯度在反向传播时可以直接流过极大缓解了深度网络中的梯度消失问题使得堆叠很多层成为可能。层归一化对每个样本的所有特征维度进行归一化稳定每一层的输入分布加速模型训练收敛。所以编码器一层的工作流是输入 - 层归一化 - 多头自注意力 - 残差相加 - 层归一化 - 前馈网络 - 残差相加 - 输出。3.2 解码器基于理解进行创造解码器的任务是根据编码器输出的“上下文”和已经生成的部分目标句子如英文预测下一个词。它也是由N个相同的层堆叠而成但结构比编码器层更复杂一些包含三个子层掩码多头自注意力子层这是解码器特有的。为什么需要“掩码”因为在训练时我们是已知完整目标句子的但为了模拟实际生成时“只能看到前面词”的情景我们必须防止当前位置的词“偷看”到未来的词。掩码通过在注意力权重矩阵的上三角区域代表未来位置填充一个极大的负数如-1e9使得经过Softmax后未来位置的权重几乎为0。编码-解码多头注意力子层这是连接编码器和解码器的桥梁。在这个子层中Q来自解码器上一层的输出代表当前需要生成的部分的查询。K和V来自编码器最终的输出代表源句子的全部上下文信息。这样解码器在生成每一个目标词时都能有选择地“聚焦”到源句子中最相关的部分。这完美模拟了人类翻译时的“对照原文”过程。前馈神经网络子层与编码器中的相同对每个位置进行独立变换。同样每个子层也都有残差连接和层归一化。解码器一层的工作流是输入 - 层归一化1 - 掩码自注意力 - 残差相加 - 层归一化2 - 编码解码注意力 - 残差相加 - 层归一化3 - 前馈网络 - 残差相加 - 输出。3.3 位置编码给无序的模型注入顺序感自注意力机制有一个“缺陷”它对输入序列的顺序是不敏感的。打乱词的顺序计算出的注意力权重可能是一样的因为只是求和与顺序无关。但语言是有顺序的“猫抓老鼠”和“老鼠抓猫”意思完全不同。为此Transformer引入了“位置编码”。它不是像RNN那样隐式地学习顺序而是显式地将位置信息“加”到词嵌入向量中。原论文使用了一组固定公式正弦和余弦函数来生成位置编码。其优点是可以处理比训练时更长的序列因为公式定义任意位置可算。能让模型轻松学习到相对位置关系因为正弦余弦函数的特性某个位置编码可以被表示为附近位置编码的线性函数。注意事项虽然原论文用了固定位置编码但在后来的很多模型如BERT、GPT中更常用的是可学习的位置编码即把位置也当作一个可以训练的嵌入向量。在实践中对于不同领域的数据如代码、音乐可学习的位置编码往往表现更好因为它能自适应数据的顺序模式。4. 动手实现核心模块用PyTorch感受Transformer理论说了这么多不写代码总觉得隔了一层。下面我们用PyTorch来一步步实现Transformer最核心的多头注意力模块。即使你不是资深程序员跟着注释看一遍也能对上面的概念有更具体的认识。4.1 实现缩放点积注意力这是最基础的注意力函数。import torch import torch.nn as nn import torch.nn.functional as F import math class ScaledDotProductAttention(nn.Module): 缩放点积注意力 def __init__(self, dropout0.1): super().__init__() self.dropout nn.Dropout(dropout) def forward(self, Q, K, V, maskNone): 参数: Q: 查询向量 [batch_size, n_heads, seq_len_q, d_k] K: 键向量 [batch_size, n_heads, seq_len_k, d_k] V: 值向量 [batch_size, n_heads, seq_len_v, d_v] (通常 seq_len_k seq_len_v, d_k d_v) mask: 掩码张量 [batch_size, 1, 1, seq_len_k] 或 [batch_size, 1, seq_len_q, seq_len_k] 返回: output: 注意力输出 [batch_size, n_heads, seq_len_q, d_v] attention_weights: 注意力权重 [batch_size, n_heads, seq_len_q, seq_len_k] # 计算Q和K的点积并缩放 d_k Q.size(-1) scores torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(d_k) # [batch, heads, len_q, len_k] # 如果提供了掩码将其应用到scores上通常在解码器中使用 if mask is not None: # 将mask中为1的位置需要被掩盖替换为一个极小的负数这样softmax后权重接近0 scores scores.masked_fill(mask 0, -1e9) # 对最后一个维度seq_len_k进行Softmax得到注意力权重 attention_weights F.softmax(scores, dim-1) # [batch, heads, len_q, len_k] # 可选应用Dropout一种正则化手段防止过拟合 attention_weights self.dropout(attention_weights) # 用注意力权重对V进行加权求和得到最终输出 output torch.matmul(attention_weights, V) # [batch, heads, len_q, d_v] return output, attention_weights关键点解析缩放点积Q·K^T的结果其方差会随着维度d_k的增大而增大。方差过大会导致Softmax后的分布非常尖锐大部分权重接近0极小部分接近1梯度会很小不利于训练。除以sqrt(d_k)就是为了将方差拉回至1左右稳定训练。掩码masked_fill操作是解码器实现“不可见未来”的关键。它把需要屏蔽的位置mask0的分数设为一个极大的负数这样经过Softmax后这些位置的权重就几乎为0。4.2 实现多头注意力层现在我们把多个“头”组装起来。class MultiHeadAttention(nn.Module): 多头注意力机制 def __init__(self, d_model512, n_heads8, dropout0.1): super().__init__() assert d_model % n_heads 0, d_model必须能被n_heads整除 self.d_model d_model self.n_heads n_heads self.d_k d_model // n_heads # 每个头的维度 self.d_v d_model // n_heads # 定义线性变换层用于生成Q, K, V以及最后的输出投影 self.W_Q nn.Linear(d_model, d_model) # 实际上输出是 n_heads * d_k 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) self.attention ScaledDotProductAttention(dropout) self.dropout nn.Dropout(dropout) self.layer_norm nn.LayerNorm(d_model) def forward(self, Q, K, V, maskNone): 参数: Q, K, V: 输入张量 [batch_size, seq_len, d_model] mask: 掩码 [batch_size, 1, seq_len, seq_len] 或类似形状 返回: output: 多头注意力输出 [batch_size, seq_len, d_model] batch_size, seq_len_q, _ Q.size() _, seq_len_k, _ K.size() # 1. 残差连接保留输入 residual Q # 2. 线性投影并分头 # 线性变换后形状: [batch, seq_len, d_model] Q self.W_Q(Q) K self.W_K(K) V self.W_V(V) # 分头: 将d_model维度拆分成 n_heads 个 d_k/d_v 的小头 # 变换后形状: [batch, seq_len, n_heads, d_k] Q Q.view(batch_size, -1, self.n_heads, self.d_k).transpose(1, 2) K K.view(batch_size, -1, self.n_heads, self.d_k).transpose(1, 2) V V.view(batch_size, -1, self.n_heads, self.d_v).transpose(1, 2) # 3. 计算缩放点积注意力 (所有头并行计算) if mask is not None: # 确保mask的维度能广播到所有头上 [batch, 1, seq_len_q, seq_len_k] mask mask.unsqueeze(1) # 如果mask是[batch, seq_len_q, seq_len_k] context, attn_weights self.attention(Q, K, V, maskmask) # context: [batch, heads, len_q, d_v] # 4. 合并多头 # 将头维度移回并合并到特征维度上 context context.transpose(1, 2).contiguous().view(batch_size, -1, self.d_model) # [batch, len_q, d_model] # 5. 输出投影 output self.W_O(context) # 6. Dropout 和 残差连接 层归一化 output self.dropout(output) output self.layer_norm(output residual) return output, attn_weights关键点解析分头与合并这是多头注意力的实现关键。我们通过view和transpose操作将[batch, seq_len, d_model]的矩阵转换成[batch, n_heads, seq_len, d_k]。这样在batch和heads这两个维度上计算可以完全并行。计算完成后再通过transpose和view合并回去。参数共享注意W_Q, W_K, W_V, W_O都是nn.Linear(d_model, d_model)。这意味着它们是在所有头上共享参数的线性变换。分头操作是在这个共享变换之后进行的。另一种实现方式是为每个头单独定义线性层但共享参数的方式更简洁参数更少效果通常相当。层归一化的位置这里采用了Transformer论文中的“后归一化”方案即LayerNorm(x Sublayer(x))。后来有些变体如GPT使用了“前归一化”即x Sublayer(LayerNorm(x))据说训练更稳定。4.3 一个简单的前馈网络编码器和解码器中都用到的前馈网络非常简单。class PositionwiseFeedForward(nn.Module): 位置级前馈网络每个位置独立处理 def __init__(self, d_model512, d_ff2048, dropout0.1): super().__init__() self.linear1 nn.Linear(d_model, d_ff) self.linear2 nn.Linear(d_ff, d_model) self.dropout nn.Dropout(dropout) self.layer_norm nn.LayerNorm(d_model) self.activation nn.ReLU() # 原论文使用ReLU后来常用GELU def forward(self, x): residual x x self.linear1(x) x self.activation(x) x self.dropout(x) x self.linear2(x) x self.dropout(x) x self.layer_norm(x residual) return x注意这里的d_ff前馈网络中间层维度通常设置为d_model的4倍如512 - 2048这是原论文的经验值。它为模型提供了强大的非线性变换能力。5. 训练与推理全流程从数据到预测理解了模块我们再把视角拉高看一个完整的Transformer模型是如何被训练并用于推理预测的。5.1 训练阶段教师强制与损失计算以机器翻译为例训练数据是成对的源语言句子和目标语言句子。输入处理源语言句子经过词嵌入层和位置编码送入编码器。目标语言句子同样经过词嵌入和位置编码送入解码器。但是在训练时我们采用“教师强制”策略。即无论解码器上一步预测的是什么这一步的输入都是真实的目标序列只是整体向右偏移一位并在开头加上一个sos开始符号。这能加速模型收敛提供更稳定的梯度。编码器工作源句子通过编码器的N层最终输出一个代表整个源句上下文的矩阵。解码器工作解码器的第一层掩码自注意力处理目标序列但使用掩码确保每个位置只能看到它之前的位置。第二层编码-解码注意力接收编码器的输出作为K, V接收第一层的输出作为Q从而让解码器“关注”源句的相关部分。经过N层后解码器输出一个序列每个位置对应一个高维向量。输出层解码器的输出通过一个线性层将d_model维映射到目标词汇表大小vocab_size维再经过Softmax得到每个位置对词汇表中每个词的预测概率分布。损失计算使用交叉熵损失函数。对于目标序列的每一个位置计算模型预测的概率分布与真实词one-hot编码之间的交叉熵然后对所有位置求和或平均。这就是模型需要最小化的目标。5.2 推理阶段自回归生成推理或叫预测、生成与训练有一个根本区别我们没有完整的目标句子作为解码器的输入。编码源句子通过编码器得到上下文矩阵。这一步和训练时一样。自回归解码第一步解码器输入通常只是一个开始符号sos。结合编码器输出解码器预测出第一个词w1。第二步将sos, w1作为输入再次送入解码器预测第二个词w2。第三步将sos, w1, w2作为输入预测w3。… 如此循环直到预测出结束符号eos或达到最大生成长度。这个过程被称为“自回归”因为每一步的输入都依赖于之前生成的输出。显然这比训练时的并行计算要慢得多。实操心得束搜索贪婪解码每一步只选概率最大的词容易陷入局部最优导致生成的句子不通顺。工业界普遍使用束搜索。它维护一个大小为k束宽的候选序列列表。在每一步对列表中的每个候选序列扩展vocab_size个可能的下一个词然后从k * vocab_size个新序列中保留总体概率或对数概率之和最高的k个。束搜索在生成质量和效率之间取得了很好的平衡。5.3 关键超参数与调优经验构建一个Transformer模型你需要设定一系列超参数。以下是一些核心参数及其典型值和影响超参数典型值/范围作用与影响调优建议d_model512, 768, 1024模型的主维度决定了词嵌入、注意力内部向量的维度。越大模型能力越强但也更耗资源。根据任务复杂度和计算资源选择。768是BERT-base的配置是一个很好的起点。n_heads8, 12, 16注意力头的数量。需要保证d_model % n_heads 0。通常与d_model成正比。d_model512时用8d_model768时用12。头数过多可能带来冗余。num_layers6, 12, 24编码器和解码器的层数。层数越多模型越深表征能力越强。是影响模型大小的主要因素之一。GPT-3有96层。需要大量数据和计算来训练深模型。d_ff2048, 3072, 4096前馈网络中间层的维度。通常是d_model的4倍。原论文设为4倍这是一个经验值一般不需要大改。dropout0.1, 0.2, 0.3随机失活率用于防止过拟合。在注意力权重和FFN输出后使用。数据量小或模型大时可以适当调高如0.2。数据量大时可以调低。learning_rate1e-4, 5e-5学习率。对Transformer训练至关重要。常用带热身的学习率调度器如AdamW优化器配合线性热身与衰减。小模型可用稍大的学习率。batch_size32, 64, 128, 256批次大小。影响训练稳定性和速度。在GPU内存允许范围内尽可能调大。大的batch size通常需要配合调整学习率。我的经验对于全新的任务从一个较小的配置开始如d_model512, layers6, heads8进行快速实验验证流程和损失下降趋势。然后再根据结果逐步放大模型。直接使用大模型不仅训练慢调试也更困难。6. 超越NLPTransformer的泛化与变体Transformer的魅力在于其通用性。它处理的不再是“序列”而是一组“元素”及其“关系”。只要数据能表示成一组向量元素并且元素间的关系很重要Transformer就可能派上用场。6.1 视觉TransformerViT是里程碑式的工作。它将一张图片分割成固定大小的图像块每个块拉平成一个向量加上位置编码就变成了一个“序列”。然后直接送入标准的Transformer编码器去掉了解码器最后用一个特殊的[CLS]token的输出来做图像分类。ViT证明了在足够多的数据上预训练后纯Transformer架构在计算机视觉任务上可以超越传统的CNN。核心变化输入表示图像块嵌入代替词嵌入。位置编码视觉任务中可学习的位置编码或二维位置编码效果更好。架构通常只使用编码器堆栈。6.2 多模态Transformer例如连接文本和图像的CLIP模型。它将文本和图像分别通过一个文本编码器Transformer和一个图像编码器ViT映射到同一个共享的向量空间。训练的目标是让匹配的图像文本对的向量相似度尽可能高不匹配的尽可能低。这样模型就学会了文本和视觉概念的关联。核心思想Transformer作为强大的特征提取器和融合器。不同模态的数据先通过各自的编码器可以是Transformer或其他网络然后将得到的特征序列拼接或通过交叉注意力机制进行融合最后由一个共享的Transformer进行深层理解。6.3 大语言模型与仅解码器架构GPT系列和LLaMA等模型采用了仅解码器的架构。它们去掉了编码器只保留了解码器堆栈并且去掉了第二层的“编码-解码注意力”因为根本没有编码器输入。这种架构非常适合自回归生成任务。训练在超大规模的无标注文本语料上进行“下一个词预测”任务。这本质上是一个无监督学习过程让模型学会语言的统计规律和世界知识。推理通过精心设计的文本提示引导模型生成我们想要的内容。这就是“提示工程”和“指令微调”发挥作用的地方。注意事项仅解码器模型在训练时同样需要使用掩码自注意力确保每个位置只能看到历史信息。这种架构天生适合生成但在需要同时理解两个输入序列的任务如翻译、问答上不如编码器-解码器架构直观。7. 常见问题与实战排坑指南在实际使用和实现Transformer时你会遇到各种各样的问题。这里我总结了一些最常见的“坑”和解决思路。7.1 训练不稳定或损失为NaN这是训练深度学习模型尤其是Transformer初期最常见的问题。梯度爆炸Transformer层数深注意力点积操作可能导致梯度变得非常大。解决使用梯度裁剪。在PyTorch中可以用torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0)将梯度范数限制在一个阈值内。学习率太大Transformer通常对学习率很敏感。解决使用学习率热身。在训练开始时从一个很小的学习率如1e-7线性增加到预设值如1e-4持续几千步。这能让模型稳定地进入训练区域。之后可以再使用余弦衰减等策略。初始化问题参数初始化不当可能导致某些层输出过大。解决使用更稳健的初始化方法如Xavier均匀初始化PyTorch线性层默认。对于非常深的模型可以考虑使用Pre-LN层归一化放在子层之前的变体它通常比原始的后归一化更稳定。损失函数或Softmax溢出当模型预测的logits值非常大时Softmax计算可能溢出。解决确保你的损失函数如CrossEntropyLoss是稳定的它内部通常已经做了数值稳定处理。也可以手动对logits进行缩放。7.2 模型过拟合当训练数据不足而模型能力参数过多太强时模型会记住训练集但在验证集上表现很差。增加正则化Dropout确保在注意力层和FFN层后都正确设置了Dropout。可以尝试稍微调高dropout率如从0.1调到0.2或0.3。权重衰减在优化器如AdamW中设置一个小的权重衰减系数如0.01对参数范数进行惩罚。数据增强对于NLP任务可以使用回译、随机删除/替换/交换词语、同义词替换等方法增加数据多样性。对于CV任务方法就更多了。早停持续监控验证集损失或指标当其在连续多个epoch不再提升时停止训练。简化模型如果数据量确实很小考虑减少d_model、层数或头数。7.3 推理速度慢Transformer的解码是自回归的无法并行这是其推理慢的根本原因。优化技术KV缓存在自回归生成时对于已经生成的序列其对应的K和V向量在计算下一个词时是重复使用的。可以将它们缓存起来避免重复计算这是最有效的加速手段之一。束搜索剪枝使用束搜索时较小的束宽如k4比贪婪解码k1质量好但比k10慢很多。需要在质量和速度间权衡。模型量化将模型参数从32位浮点数转换为8位整数可以大幅减少内存占用和加速计算精度损失通常很小。使用更高效的实现如NVIDIA的FasterTransformer或针对特定硬件优化的推理库。7.4 注意力权重可视化与调试理解模型在“看”哪里对于调试和解释模型行为至关重要。如何获取在我们实现ScaledDotProductAttention时forward函数返回了attention_weights。这个张量形状是[batch, heads, len_q, len_k]。如何解读对于一个给定的样本batch0和一个给定的头head0你可以得到一个[len_q, len_k]的矩阵。用热力图画出这个矩阵横轴是输入序列K的来源纵轴是查询序列Q的来源。颜色越亮表示注意力权重越高。有什么用检查掩码在解码器第一层你应该看到严格的下三角矩阵未来位置权重为0。理解模型焦点在翻译任务中看编码-解码注意力可以看到生成某个目标词时模型主要关注源句子的哪些词。诊断异常如果注意力图非常分散或出现奇怪的模式可能意味着模型没有正常学习。学习Transformer最好的方式就是结合理论、代码和实验。不要只停留在读论文和博客动手实现一个迷你版的Transformer比如在字符级文本或简单数字序列上做预测你会对它有刻骨铭心的理解。从“知道”到“懂得”中间隔着的就是实践。
返回列表