news 2026/6/10 15:15:20

吃透Transformer:结合翻译实例逐步拆解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
吃透Transformer:结合翻译实例逐步拆解

之前学Transformer一直云里雾里,看了很多教程,大多只堆公式、讲模块,根本不说「为什么这么设计」「不这么做会出什么问题」。我自己结合机器翻译任务,从头梳理了一遍所有流程,用最简单的英译中例子 I love deep learning → 我热爱深度学习,把每一步操作、作用、底层设计原因全部厘清。

读完这篇,你能彻底搞懂Transformer所有核心疑问:

  • 文字为什么不能直接输模型,必须做词嵌入?

  • 位置编码是干嘛的,不加会有什么致命问题?

  • 自注意力Q/K/V到底各自负责什么,多头的意义在哪?

  • 残差、层归一化到底解决了什么实际问题?

  • Encoder和Decoder为什么一定要分成两个模块?

  • 掩码Mask防的是什么,不掩码训练会直接废吗?

  • 交叉注意力凭什么能实现跨语言翻译?

  • 为什么训练和上线推理必须是两套逻辑?

一、先理清整体思路:Transformer做翻译的核心逻辑

任务内容:输入英文句子,输出对应的中文翻译。

本次案例输入输出:

原句(Encoder输入):I love deep learning(4个单词)

译文(Decoder输出目标):我热爱深度学习(5个汉字)

两个模块的真实分工如下:

Encoder只负责“读”:完整吃透整句英文的语义、语序、词语关系,生成一份固定的全局记忆数据,不生成任何新内容。

Decoder只负责“写”:不直接读原英文,只拿Encoder产出的记忆,结合已经生成的中文上文,一点点猜出下一个汉字,最终拼出完整译文。

整体翻译流程流程图

整个Transformer翻译的完整链路如下:

二、文字向量化:所有NLP模型的前置必经步骤

计算机本质只能处理数字,完全看不懂文字符号,所以第一步必须把所有字词转换成可计算的数字格式。

2.1 分词转ID

我们会提前构建词表,给每一个字、每一个单词分配唯一的数字ID,这里做个简化示例:

英文单词ID:I=10、love=20、deep=30、learning=40

中文及特殊符号ID:<START>=0、我=11、热爱=21、深度=31、学习=41、<END>=1

转换后得到三组核心序列:

Encoder输入序列:[10, 20, 30, 40]

Decoder训练输入序列:[0, 11, 21, 31, 41]

训练标签序列:[11, 21, 31, 41, 1]

这里的START、END是训练必备特殊符,START用来开启生成,END用来标记句子结束。

核心逻辑说明:Decoder两组序列是错位预测关系,用前文预测后文,是自回归生成的本质。

2.2 词嵌入:把普通数字变成有语义的向量

单纯的ID只是排序编号,没有任何语义关联。比如love=20、deep=30,只是数字大小差异,不代表词义关系,模型根本学不到东西。

所以需要词嵌入操作,把每一个字词ID,映射成一个512维的稠密浮点向量。这么做的核心价值是:语义相近的词,向量空间距离会更近(后期通过训练达到这个效果)。

放到我们的例子里,deep和learning的向量相似度很高,而I和love的相似度偏低,完全贴合人类语义认知,这也是模型能理解词义的基础。

如果不做稠密嵌入,要么用无意义的纯数字,要么用超高维稀疏独热编码,模型无法捕捉任何词语关联,后续所有训练都无从谈起。

代码示例:ID转语义特征
import torch import torch.nn as nn # 超参数:词表大小、特征维度(Transformer标准512维) vocab_size = 50 embed_dim = 512 # 初始化词嵌入层:数字ID → 512维语义特征 embedding_layer = nn.Embedding(vocab_size, embed_dim) # 案例输入序列 en_seq = torch.tensor([10, 20, 30, 40]) # 英文原句ID zh_in_seq = torch.tensor([0, 11, 21, 31, 41]) # Decoder训练输入ID # 转换为语义特征 en_embed = embedding_layer(en_seq) zh_embed = embedding_layer(zh_in_seq) print("英文语义特征形状:", en_embed.shape) # [4, 512] print("中文语义特征形状:", zh_embed.shape) # [5, 512]

三、位置编码:补上Transformer的致命短板

这是很多新手理解不深的点:自注意力机制本身完全没有时序、语序概念。它只看词语的语义特征,不看词语出现的先后顺序。

这就会出现一个离谱问题:「I love you」和「you love I」,在没有位置编码的模型里,计算结果几乎一模一样,根本分不清语序,翻译、语法任务直接崩盘。

位置编码的作用就一个:给每个序列位置加专属的顺序特征,让模型能区分字词的前后位置。

原版Transformer用的是固定正余弦位置编码,为每一个位置生成唯一的512维向量,和词向量逐元素相加,不是拼接,维度不会变化。

对应我们的例句:

第0位I = 单词语义向量 + 0号位置向量

第1位love = 单词语义向量 + 1号位置向量

第2位deep = 单词语义向量 + 2号位置向量

第3位learning = 单词语义向量 + 3号位置向量

做完这一步,每个单词的向量都同时包含「词义信息」和「语序信息」,解决了无序的核心缺陷。最后词嵌入向量先乘以√512做数值放大,再与位置编码相加。目的是平衡两者数值量级,避免固定位置编码的数值覆盖微弱的词向量语义信息,保证词义、语序信息都能被模型有效学习。

代码示例:正余弦位置编码
import math d_model = 512 seq_len = 4 # 初始化位置特征矩阵 pos_embed = torch.zeros(seq_len, d_model) # 正余弦公式生成唯一位置特征(原版Transformer标准公式) for pos in range(seq_len): for i in range(0, d_model, 2): pos_embed[pos, i] = math.sin(pos / (10000 ** (2 * i / d_model))) pos_embed[pos, i+1] = math.cos(pos / (10000 ** (2 * i / d_model))) # 1. 仅放大词嵌入:拉升词义量级,平衡固定位置编码 final_en_feature = en_embed * math.sqrt(d_model) # 2. 语义特征 + 位置特征,融合语序信息 final_en_feature = final_en_feature + pos_embed print("融合语序后特征形状:", final_en_feature.shape) # [4, 512]

四、Encoder编码器:完整读懂整句英文

Encoder的堆叠结构很固定,单层流程是:多头自注意力 → 残差+层归一化 → FFN前馈网络 → 残差+层归一化,重复堆叠6层。整体目标就是把整句英文,提炼成一份包含全部信息的Memory记忆矩阵。

Encoder单层结构流程图

4.1 自注意力:让每个词能看见全句所有词

传统RNN、LSTM只能逐词串行读取上下文,效率低、长距离依赖弱。而自注意力可以一次性让所有字词互相建立关联,全局捕捉上下文,这也是Transformer性能更强的核心原因。

很多人卡壳的Q/K/V,其实不用记复杂公式,通俗理解就够了:

  • Q(查询):当前单词,主动去找其他单词关联的“诉求”

  • K(索引):所有单词对外展示的“特征名片”

  • V(取值):所有单词真正携带的“语义内容”

计算逻辑结合例句一看就懂:单词love作为Q,去和整句所有单词的K做相似度匹配,会发现和I的关联度最高(主谓关系),和deep、learning关联度低。模型根据这个相似度算出权重,再用权重加权所有单词的V向量,最终得到love融合了全句上下文的新语义。

简单总结:Q负责找匹配、K负责做比对、V负责拿最终语义。

代码示例:基础自注意力计算
import torch.nn.functional as F d_k = 64 x = final_en_feature # 1. 生成Q、K、V三组特征 Wq = nn.Linear(512, 512) Wk = nn.Linear(512, 512) Wv = nn.Linear(512, 512) Q = Wq(x) K = Wk(x) V = Wv(x) # 2. 计算关联匹配权重 attn_score = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(d_k) attn_weight = F.softmax(attn_score, dim=-1) # 3. 加权取值,得到全局语境特征 context_feature = torch.matmul(attn_weight, V) print("关联权重矩阵形状:", attn_weight.shape) # [4,4] print("全局语境特征形状:", context_feature.shape) # [4,512]

4.2 多头注意力的实际价值

如果只用单头注意力,模型只能学到一种语义关联规则,要么只看词义相似,要么只看语法结构,表达能力非常有限。

8头注意力相当于开启8个不同的观察视角,并行学习不同维度的关联:有的头学主谓关系、有的头学动宾搭配、有的学修饰关系、有的捕捉语法细节。最后把8头结果拼接融合,模型对句子的理解精度会大幅提升。

4.3 残差连接:解决深层网络梯度消失

Transformer堆叠了6层网络,深层神经网络很容易出现梯度消失,导致参数无法更新、模型学不到东西。

残差连接的设计很巧妙,就是把每层的原始输入,直接叠加到当前层的输出上。相当于给梯度开辟了一条直通通道,无论堆叠多少层,底层的原始信息都能无损传递,保证网络可训练、不失效。

4.4 层归一化LN:稳定训练过程

网络每一层的输出数值分布都会偏移,层数越多偏移越严重,训练会震荡、难以收敛。

层归一化会针对单个样本的512维特征做标准化,把数值稳定在合理区间。和BN不同,NLP任务句子长度不统一,批量归一化无法适配,LN是Transformer场景下唯一合适的归一化方式。

代码示例:残差连接+层归一化
# 初始化层归一化 ln = nn.LayerNorm(512) # 残差连接:原始输入 + 注意力输出 res_out = x + context_feature # 数值规整校准 final_out = ln(res_out) print("残差+归一化后特征形状:", final_out.shape) # [4,512]

4.5 FFN前馈网络:细化单词语义

FFN结构是512维升维到2048维,经过ReLU激活再降回512维。它的特点是不做词与词的交互,只单独细化每个单词的语义特征。

比如经过FFN处理后,love不再是孤立的“热爱”,而是细化成“由主语I发出、作用于深度学习的动作”,语义更贴合整句语境。

代码示例:FFN前馈网络
# 定义前馈网络 class FFN(nn.Module): def __init__(self, d_model=512, d_hidden=2048): super().__init__() self.fc1 = nn.Linear(d_model, d_hidden) self.fc2 = nn.Linear(d_hidden, d_model) self.relu = nn.ReLU() def forward(self, x): x = self.fc1(x) x = self.relu(x) x = self.fc2(x) return x ffn = FFN() ffn_out = ffn(final_out) # 二次残差+归一化 ffn_out = ln(ffn_out + final_out) print("Encoder单层最终输出形状:", ffn_out.shape) # [4,512]

4.6 6层Encoder最终效果

浅层Encoder主要学习单词、短语的局部特征,深层会整合整句的语法、语义、语序全局逻辑。最终产出的Memory矩阵,完整保存了整句英文的所有信息,全程固定不变,专门供给Decoder做翻译使用。

五、Decoder解码器:逐字生成译文的核心

Decoder同样堆叠6层,单层流程:掩码自注意力 → 交叉注意力 → FFN前馈网络。它的核心工作就是依托Encoder的英文记忆,结合已生成的中文上文,逐字推导下一个汉字。

Decoder单层结构流程图

5.1 掩码自注意力:训练的关键约束

训练时为了高效,会给Decoder输入完整的中文标准答案序列,但这里会出现一个致命问题:如果不做约束,模型预测当前字时,会直接看到后面未生成的答案,相当于做题偷看标准答案,训练完全失效,上线直接崩盘。

所以需要掩码操作,把注意力分数矩阵的右上三角全部置为负无穷,经过概率换算后,未来位置的权重直接归零。

对应我们的案例效果很清晰:

  • 预测“我”时,只能看到START

  • 预测“热爱”时,只能看到START、我,看不到后续文字

  • 预测“深度”时,只能看到前面三个字,看不到“学习”

掩码的核心目的,就是强行对齐训练和推理的视野逻辑,避免模型作弊,保证训练出的模型能适配真实上线场景。

代码示例:Decoder未来掩码实现
seq_len = 5 # 生成上三角掩码矩阵 mask = torch.triu(torch.ones(seq_len, seq_len), diagonal=1) # 未来位置填充负无穷,屏蔽权重 mask = mask.masked_fill(mask == 1, -1e9) print("Decoder掩码矩阵:") print(mask) # 下三角可见、上三角屏蔽,完美实现「输入完整、视野残缺」

5.2 交叉注意力:真正的翻译核心

如果说Encoder是读懂原文,那交叉注意力就是实现翻译的本质步骤。没有这一步,模型只能凭空造句,根本不算翻译。

交叉注意力的参数来源是固定的:

Q来自Decoder当前的中文特征,K、V完全来自Encoder产出的英文Memory矩阵。

结合案例很好理解:当模型生成“热爱”这个字时,会以当前中文特征作为Q,去匹配所有英文单词的K向量,计算相似度。最终和love的相似度最高,模型就会加权提取love对应的V语义,把英文“热爱”的语义对齐到中文汉字上。

简单说,交叉注意力就是实现中文语义和英文语义的精准映射,这是机器翻译的核心原理。

代码示例:交叉注意力
# 中文特征作为Q,英文记忆矩阵作为K、V cross_Q = nn.Linear(512, 512)(zh_embed) cross_K = nn.Linear(512, 512)(ffn_out) cross_V = nn.Linear(512, 512)(ffn_out) # 计算跨语言关联权重 cross_score = torch.matmul(cross_Q, cross_K.transpose(-2, -1)) / math.sqrt(d_k) cross_weight = F.softmax(cross_score, dim=-1) cross_feature = torch.matmul(cross_weight, cross_V) print("交叉注意力输出特征形状:", cross_feature.shape) # [5,512]

5.3 多层堆叠的作用

经过6层Decoder迭代,模型会反复融合“已生成的中文上文”和“完整英文语义”,不断细化特征,最终输出融合完毕的全局特征向量,用于预测最终汉字。

六、输出层:把特征向量转为真实文字

经过Decoder输出的512维特征,还不能直接输出文字,需要两层转换。

首先通过线性层,把512维特征映射到中文词表维度,给每一个汉字打出一个专属分数。这里Transformer做了参数共享优化,输出层权重和输入词嵌入矩阵转置共用,既能减少参数量,又能统一语义空间,训练更稳定。

再通过Softmax函数,把所有汉字的分数转换成0-1的概率分布,概率最高的汉字,就是当前位置的最优翻译结果。

代码示例:输出层文字预测
# 输出层:特征映射词表 + 概率换算 fc_out = nn.Linear(512, vocab_size) logits = fc_out(cross_feature) # 转换为概率分布 prob = F.softmax(logits, dim=-1) # 取概率最大的ID作为预测结果 pred_ids = torch.argmax(prob, dim=-1) print("预测汉字ID序列:", pred_ids.tolist())

七、核心重难点:训练 vs 线上推理

Transformer训练和上线推理,是两套完全不一样的运行逻辑。很多模型训练效果极好,上线翻车,根源就在这里。

7.1 训练阶段(Teacher Forcing 教师强制)

训练的核心目标是快速、稳定收敛,所以会采用“抄作业”的学习模式。

训练时不会让模型自己逐字猜答案,而是直接喂给它完整的标准答案上文。我们的案例中,Decoder输入是完整的[START、我、热爱、深度、学习],所有上文都是绝对正确的人工标注文本,没有一个是模型自己生成的。

为了防止模型偷看未来答案,必须搭配未来掩码使用,保证模型只能看当前合法上文。这种模式最大的优势是可以整句并行计算损失,不用逐字串行等待,训练速度提升几十倍,收敛效率极高。

训练阶段流程图

代码示例:训练核心逻辑
# 训练输入、标签(错位对应) train_input = torch.tensor([0, 11, 21, 31, 41]) train_label = torch.tensor([11, 21, 31, 41, 1]) print("训练输入(完整标准答案上文):", train_input) print("训练标签(逐字预测答案):", train_label) # 核心优势:并行计算、训练极速、收敛稳定

7.2 线上推理阶段(真实自回归生成)

真实上线翻译时,没有任何标准答案可以参考,模型只能自力更生。

初始输入只有一个START标识符,整个生成过程完全自回归:

  1. START → 预测出第一个字“我”

  2. 拼接为[START、我] → 预测出“热爱”

  3. 拼接为[START、我、热爱] → 预测出“深度”

  4. 持续拼接上文迭代生成,直到输出END结束符,翻译终止

推理是纯串行流程,速度比训练慢,而且存在致命的误差累积问题:只要前面生成错一个字,后续所有文字都会跟着跑偏,这也是生成类模型的天然短板。

推理阶段流程图

代码示例:推理自回归生成
# 模拟线上推理:无标准答案,逐字自回归生成 # 核心逻辑:每一轮拼接新上文,都要重新走完整模型前向传播 # 1.定义网络层 cross_K_layer = nn.Linear(512, 512) cross_V_layer = nn.Linear(512, 512) output_layer = nn.Linear(512, vocab_size) gen_seq = [0] # 初始输入只有START标识符(0) while True: # 2. 截取当前已生成的所有上文序列,并转为张量 input_seq = torch.tensor([gen_seq]) # 注意:加上[]变成二维张量 [1, seq_len] # 3. 对新上文做完整特征变换(词嵌入 + 位置编码缩放) cur_zh_embed = embedding_layer(input_seq) cur_zh_feature = cur_zh_embed * math.sqrt(d_model) # 注:这里省略了位置编码的相加和Decoder自注意力,仅演示交叉注意力核心链路 # 4. 交叉注意力:复用Encoder产出的固定记忆矩阵(ffn_out) cross_Q = cur_zh_feature cross_K = cross_K_layer(ffn_out) cross_V = cross_V_layer(ffn_out) cross_score = torch.matmul(cross_Q, cross_K.transpose(-2, -1)) / math.sqrt(d_k) cross_weight = F.softmax(cross_score, dim=-1) cross_feature = torch.matmul(cross_weight, cross_V) # 5. 重新计算当前轮次的预测概率(每一轮迭代都会更新) logits = output_layer(cross_feature) # 6. 【关键】取最后一个位置(即刚刚生成的上文)的预测结果 pred_next = torch.argmax(logits[:, -1, :], dim=-1).item() # 7. 拼接生成序列,进入下一轮迭代 gen_seq.append(pred_next) # 终止条件:遇到END结束符(1) 或 超长防死循环 if pred_next == 1 or len(gen_seq) > 10: break print("最终生成ID序列:", gen_seq)

7.3 训练与推理核心区别汇总

对比维度

模型训练(Teacher Forcing)

线上推理(真实翻译)

Decoder输入来源

人工标注的标准答案文本

模型上一轮自己生成的文本

生成方式

整句并行一次性计算

逐字串行自回归生成

掩码需求

必须加未来掩码,防止偷看答案

无需手动掩码,无未来文本可查看

运行速度

极快,适合大规模批量训练

较慢,逐字迭代耗时

误差累积

无,输入全部为正确文本

有,前文错误会持续影响后文

核心用途

学习语义规则、更新模型参数

真实业务落地、输出翻译结果

7.4 为什么一定要设计两套不同的逻辑?

很多人疑惑:既然上线是逐字生成,为什么训练不照搬这个逻辑,非要搞Teacher Forcing?其实这是Transformer最关键的工程取舍,兼顾了训练可行性和业务可用性。

首先,纯逐字训练完全不现实。串行生成效率极低,海量数据集训练一轮需要数十天,根本没法迭代优化。而Teacher Forcing的并行计算模式,是大规模模型训练的唯一可行方案。

其次,逐字训练会导致模型彻底学废。模型初始化时参数随机,生成的文字全是错误的,如果用错误上文继续迭代,会形成“错上加错”的恶性循环,梯度完全无法收敛。Teacher Forcing全程提供正确上文,能保证模型每一步都在有效学习。

而上线必须用自回归生成,原因也很简单:真实翻译、对话场景中,根本不存在提前写好的标准答案,模型只能靠自己生成上文、推导下文。

至于掩码的设计,就是为了折中:既保留Teacher Forcing的高效训练优势,又通过屏蔽未来信息,对齐上线推理的视野逻辑,避免训练和推理场景脱节、模型泛化失效。

最后说一个延伸点:为什么训练效果好,上线却容易翻车?核心就是分布偏移。训练时输入的上文100%正确,模型从未见过错误上下文,一旦上线生成错字,就会持续跑偏,这是所有自回归生成模型的固有缺陷。

八、整体核心总结

通篇梳理下来,Transformer的整套设计逻辑其实特别清晰,每一个模块都是为了解决具体问题:

1. 词嵌入:将离散文字转为连续语义向量,让模型读懂词义;

2. 位置编码:弥补自注意力无序缺陷,补充语序信息;

3. 多头自注意力:全局捕捉字词关联,多维度理解句子语义;

4. 残差+层归一化:解决深层网络梯度消失、训练不稳定问题;

5. Encoder:完整编码源文本,产出全局语义记忆;

6. Decoder掩码自注意力:杜绝训练作弊,对齐训练推理逻辑;

7. 交叉注意力:实现跨语言语义对齐,是机器翻译的核心;

8. 自回归生成:适配真实业务场景,逐字输出最终译文。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/10 15:14:15

Windows部署原生Docker,解决Docker Desktop卡在Starting界面的问题

本人在复现小智开源项目时&#xff0c;需要使用Docker Desktop来部署MySQL和Redis容器&#xff1b;但是在每次关闭Docker Desktop后&#xff0c;都遇到卡在Starting界面&#xff0c;并且在网上找了很多解决方法都无效。&#xff08;当然如果大家有更好的解决方法&#xff0c;欢…

作者头像 李华
网站建设 2026/6/10 15:12:10

Linux的基本命令的演示

1.在 root 用户的家目录下创建两个目录分别为 haha 和 hehe&#xff0c;复制 hehe 目录到 haha 目录并重命名为 apple。[rootlocalhost ~]# mkdir -vp haha hehe mkdir: 已创建目录 haha mkdir: 已创建目录 hehe [rootlocalhost ~]# cp -r hehe haha [rootlocalhost ~]# ls ha…

作者头像 李华
网站建设 2026/6/10 15:10:26

告别熬夜凑论文!paperxie 课程论文 AI 写作,一键解锁高效出稿新方式

paperxie-免费查重复率aigc检测/开题报告/毕业论文/智能排版/文献综述/课程论文课程论文 - PaperXie智能写作PaperXieAi论文智能生成软件&#xff0c;10分钟生成万字毕业论文、期刊论文、文献综述、PPT&#xff0c;Aigc查重、降重报告、文献资料。只需一个标题&#xff0c;从开…

作者头像 李华
网站建设 2026/6/10 15:10:22

龙虾很强,但企业需要「帝王蟹」!

近日&#xff0c;网易智企「帝王蟹」企业AI落地实战营来到北京&#xff0c;一场聚焦AI Agent规模化落地的深度专场燃爆京城。继杭州启航&#xff0c;「帝王蟹」陆续来到广州、深圳、上海&#xff0c;本次北京站以"AI 赋能、企业级交付、落地实操"为核心&#xff0c;从…

作者头像 李华
网站建设 2026/6/10 15:09:22

最大规模预训练具身世界模型,真机遥操作数据高达17800小时!

真机数据从微调配角&#xff0c;变身具身预训练绝对主力 ——真机预训练 目录 01 真机数据从“微调耗材”变成“预训练根基” 02 一个共享骨干&#xff0c;同时当“策略”和“模拟器” VAM视频动作模型&#xff1a;直接输出可执行动作 ACVS动作条件模拟器&#xff1…

作者头像 李华
网站建设 2026/6/10 15:08:28

Java作业3

文本文件复制&#xff1a;import java.io.BufferedReader;import java.io.BufferedWriter;import java.io.File;import java.io.FileReader;import java.io.FileWriter;import java.io.IOException;public class TextCopy {public static void main(String[] args) {String src…

作者头像 李华