资讯动态

机器翻译核心算法:Seq2Seq与注意力机制全解析

发布时间:2026/10/14 10:49:42 来源:尧图企业网站定制
我做 NLP 这几年有个特别直观的感受机器翻译几乎是深度学习模型的“试金石”。很多后来被大规模复用的机制最早都是在翻译任务上跑通的。CS224n 第八讲把 Machine Translation、Seq2Seq 和 Attention 放进同一节课因为它们正好是一条逻辑线翻译任务是需求Seq2Seq 是基于循环网络的核心框架Attention 是修复这个框架短板的关键设计。理解这三者如何递进比单独背下任何一个模型的定义都重要。这篇文章会照着课程的思路把这条线完整串一遍。如果你是正在啃公开课的学生或者刚接触 NLP 想搞懂这几件事的开发者又或者准备面试想把这几个知识点讲明白应该都能找到有用信息。除了理论我还会聊一些课堂之外的实际训练经验包括那些真正让人头疼的翻车现场。1. 从“拼装式翻译”到“端到端翻译”先搞清楚这门课的主线在神经机器翻译NMT出现之前主流的统计机器翻译SMT系统走的是“流水线拼装”路线。你需要分别维护双语词对齐、短语抽取、调序模型、语言模型等多个独立组件。听起来很工程化但问题很明显每个组件单独优化拼在一起却未必最优新加一个语种几乎等于把流水线重新搭一遍。NMT 的思路完全不同直接训练一个神经网络从源语言句子映射到目标语言句子中间只有一个模型端到端优化。CS224n 第八讲正是在这个背景下展开的。搞懂这条主线后面看 Attention、看 Transformer、甚至看大规模语言模型的架构都会顺畅很多。1.1 统计机器翻译为什么让人头疼如果你想更直观地理解 SMT 的痛点可以想象一下“做菜”这件事。传统方案是先去买一堆处理好的食材每种食材都按独立的标准处理一遍最后下锅的时候全靠厨师的个人经验来判断火候和顺序。SMT 也一样词对齐模块负责找“哪个词对应哪个词”短语抽取模块负责找“哪些连续片段可以整体翻译”调序模块负责调整语序语言模型负责让最终句子读起来通顺。这套体系在资源充足的大语种上能跑出不错的效果但它有几个天生的硬伤。第一错误会传播前面模块的误差到后面会被放大而且没有统一的目标函数来平衡各模块。第二特征工程重为了对齐和调序你需要设计大量人工特征规则写多了还会互相打架。第三短语表的存储和查询在长尾表达上非常吃力遇到没有收录的表达基本只能硬凑。结果就是系统里堆了很多“零件”但每个零件都只负责自己的局部任务没有一个机制能在整体上优化“产出译文的最终质量”。所以在端到端方案出现后SMT 在绝大多数语言对上迅速退居二线不是没有原因的。1.2 神经机器翻译的“端到端”到底改变了什么NMT 的端到端思路是把上面那一整套流水线压缩成一个编码器-解码器网络。源语言句子从一端进去目标语言译文从另一端出来中间的所有变换都发生在网络参数里不再需要单独的词对齐模型和调序模块。这个转变在当时是极其激进的你不是在优化某一个中间步骤而是在优化“源语言句子到目标语言句子的整体映射概率”。模型自己会去学对齐关系自己会去学语序调整自己会去学目标语言的流利程度代价是你需要大量平行语料和足够的算力把网络参数训练出来。CS224n 第八讲提到的 Seq2Seq 框架就是这种端到端思想的经典载体。理解它你才能真正理解为什么后来很多人说“深度学习的表征学习能力把特征工程替代了”。理解了这一点你再看后面所有的注意力变体都会有一种“原来是在解决这个具体瓶颈”的感觉。2. Seq2Seq 编码器-解码器把“理解”和“生成”焊在一起Seq2Seq 这个名字直译就是“从序列到序列”适用的场景远不止翻译还包括文本摘要、对话生成、甚至语音识别里的若干子任务。它在机器翻译里的经典结构是一个编码器Encoder读入源语言句子把整句话的信息压缩成一个或一组向量一个解码器Decoder基于这些向量逐步生成目标语言句子。这个架构最大的特点是“理解”和“生成”被焊接在同一个可微分的计算图里。编码器的输出是解码器的输入条件两个模块的梯度可以一路回传因此整个模型的参数能得到全局优化。2.1 编码器是怎么把句子变成向量的编码器通常采用循环网络RNN、LSTM 或 GRU。它的处理方式是逐词扫描把源语言句子的每一个词向量依次送入单元每个时间步更新一次隐藏状态。这个隐藏状态可以理解为“读到此处的上下文摘要”。一个常见的直觉是最后一个时间步的隐藏状态理论上应该“浓缩”了整个源句子。但实际上这句话只说对了一半。由于 RNN 遗忘门的特性句子前面较早的信息会被后面不断进来的新词覆盖最后一步的状态往往只对句子末尾比较敏感。这也是为什么早期 Seq2Seq 在长句翻译上表现很差的根本原因之一。为了解决这个问题后来的实现通常会使用双向编码器一个前向 RNN 从左往右读一个反向 RNN 从右往左读每个位置把两个方向的隐藏状态拼接起来。这样句首信息也能被前面的位置充分刻画。这一小改动在翻译任务上带来的增益非常明显。2.2 解码器如何逐词“算”出译文解码器同样是一个循环网络但它的工作逻辑和编码器有本质区别编码器是“读取并压缩”解码器是“生成并输出”。它把上一步生成的目标语言词作为当前步输入再结合编码器传过来的上下文向量逐步预测下一个目标词。从概率角度看解码器建模的是一个条件语言模型即给定源语言句子 X 的情况下目标语言句子 Y 的概率分布P(Y | X) P(y1) * P(y2 | y1, X) * ... * P(yT | y1...y_{T-1}, X)每个时间步解码器都会输出一个词表大小的概率分布再用某种采样或搜索策略决定当前步到底输出哪个词。训练阶段使用的通常是“教师强制”Teacher Forcing不管模型上一步预测得对不对都拿真实的目标词喂给下一步。这样能极大提升训练稳定性但也留下一个隐患——推理时模型看到的是自己生成的词和训练时的输入分布不一致。2.3 训练与推断的交界处Beam Search 在做什么推理阶段解码器不能像训练时那样拿到“标准答案”它必须自己一步步往下走。最简单的策略是贪心解码每一步都选概率最高的那个词。这听起来合理但序列生成里有一个“局部最优不等于全局最优”的经典陷阱某个词的选择会影响后续所有词的概率贪心决策很容易把整句话锁进次优的轨迹。于是工程上普遍改用集束搜索Beam Search。它的做法是每一时间步保留概率最高的 K 个候选序列K 被称为束宽。搜索结束时再从中选出整体得分最高的结果。K 的取值很影响效果K1 等价于贪心K 太大则计算开销增长显著而且容易生成过长或不自然的句子。一个朴素的经验值是翻译任务里 4 到 8 之间通常已经不错。这里想多说一句很多人把 Beam Search 当成一个可调的超参数实际上它和训练时的目标存在错位——训练时模型看到的是真实句子推理时却要面对自己生成的序列。这个 gap 在后来的强化学习中才被更系统地讨论但了解它能帮你避免很多“训练分高测试分低”的疑惑。3. 注意力机制解决 Seq2Seq 的“信息瓶颈”问题如果说 Seq2Seq 是 NMT 的第一个重大突破那么注意力机制就是紧随其后的第二个。它的动机非常朴素让解码器在生成每个目标词的时候都能“回头看一眼”源句子中和当前步最相关的部分而不是被迫依赖一个装得满满当当的固定向量。我在第一次接触这个想法的时候觉得它更像一种工程直觉而不是凭空的理论推导。但正是这个直觉把机器翻译的效果又推高了一个台阶也直接启发了后来的 Transformer 架构。3.1 固定向量这个“书包”到底有多挤你可以把早期 Seq2Seq 编码器的最后一个隐藏状态想象成一个小书包不管源句子是 5 个词还是 50 个词都要把所有信息硬塞进去然后让解码器只背着这个书包翻译。词少的时候还好词一多书包就“漏”了。最早一批 NMT 实验里有一个很常见的现象句子稍长翻译质量就断崖式下跌尤其是序号在句子前半部分的信息经常被“忘掉”。这不是模型的参数不够多而是信息压缩方式出了问题——一个固定维度的向量无论怎么调都不可能无失真地承载一个长度任意的句子。注意力机制的目标正是拆掉这堵墙不再强求“记住所有东西”而是让解码器在每一步动态地查询源句子的具体位置。翻译“dog”的时候它应该能主动去“看”源句子里和狗对应的那个词附近。3.2 注意力权重一种可微的“定向检索”注意力机制的计算可以拆成三步。第一步拿解码器当前的隐藏状态去和编码器的每一个时间步的隐藏状态做“相关性打分”得到一组标量分数。第二步对这组分数做 softmax 归一化变成权重它表示“解码当前位置时源句子第 i 个词应该占多少注意力”。第三步用这些权重对编码器的隐藏状态做加权求和得到上下文向量。用代码写出来核心其实就是这样的逻辑# enc_states: [src_len, hidden_dim] # query: [hidden_dim] 解码器当前隐藏状态 scores torch.matmul(enc_states, query) # 点积相关性 weights torch.softmax(scores, dim0) # 归一化为权重 context torch.sum(weights.unsqueeze(1) * enc_states, dim0)注意这里的“检索”和普通数据库查询不同它不返回某一个确定的词而是返回整个源句子的软性加权组合。所以它要照顾的是整体信息只是让模型在翻译当前词时把“重心”放到对应的位置上。很多第一次接触的人会误以为注意力就是“挑选一个源词”其实不是。它是把所有源词的信息都保留下来只是让当前位置最相关的词占据主导。这个“软”的特性很重要它让整个计算过程保持可微模型可以有梯度地学习“注意力该放在哪里”。3.3 对齐可视化让注意力“看得见”注意力机制有一个很受日常调试欢迎的副产品——对齐矩阵。把源语言每个词作为横轴、目标语言每个词作为纵轴把注意力权重画成热力图你就能直观看到模型翻译每个目标词时在源句子里看了哪些地方。很多初学翻译任务的开发者在做可视化之后都会经历一个“恍然大悟”的时刻模型学到的大部分对齐关系居然符合人工直觉。比如翻译法语单词时会注意对应的英语单词翻译长句时注意力也会更分散。虽然注意力和严格意义上的“词对齐”并非同一件事但在调试时它确实是一个特别好用的解释性工具。我自己在做可视化的时候还发现一个规律如果某个源语言关键词在整个解码过程中都没有获得过明显较高的注意力峰值那它大概率会被漏译。这个特征比很多抽象指标都要直观适合作为训练不充分时的第一排查信号。4. 从公式到实现注意力机制的几种经典形态对比CS224n 第八讲里花了不少篇幅讲解注意力机制的具体形态。课程里主要区分了两种计算相关性分数的方式后续 Transformer 里使用的多头注意力则是在这些基础上发展出来的变体。把这几种方案放在一起对比能更好地理解它们各自的偏好和代价。4.1 加性注意力早期经典方案早期经典的注意力模型在算分数时把解码器当前状态和编码器状态分别乘上不同的参数矩阵再拼在一起通过一个非线性层最后乘以一个向量得到标量。整个过程看起来像score(h_t, s_i) v^T * tanh(W1 * h_t W2 * s_i)这种设计让模型可以学习一个比较灵活的相关性函数而不只是简单的点积相似度。它不要求两个状态的维度一致所以实现起来比较自由。代价是参数多、计算量稍大而且 tanh 这个非线性在训练时对初始化和缩放比较敏感需要多花点时间调参。4.2 乘性注意力更便宜也更容易并行后来的经典方法是直接用点积或加权点积来计算相关性score(h_t, s_i) h_t^T * W * s_i当 W 为单位矩阵退化成纯点积时这个方案的直觉是两个向量在同一个表示空间里越接近内积越大注意力权重越高。它几乎不引入额外参数计算快而且在维度不太大的时候表现非常稳定。经验上有一个细节当向量维度很大时纯点积的数值方差会随维度增大而增大softmax 会更容易走向极端。所以 Transformer 里用了“缩放点积注意力”即把点积结果除以一个和维度相关的缩放因子。这个小改动看似不起眼在训练稳定性上的影响很实在。我见过不少初学者在复现 Transformer 时漏了这一步结果训练没几步就梯度异常最后发现就是缩放因子的问题。4.3 从循环网络到 Transformer注意力全面接管从加性注意力到乘性注意力核心算子在简化但注意力机制本身却从“辅助组件”变成了“主架构”。Transformer 的思路是既然注意力可以给解码器提供灵活的上下文那是不是连编码器的信息传递也可以全部用注意力来完成而不是用循环网络一步步递归多头注意力则是让模型在多个子空间里分别做注意力再把结果拼起来相当于一次并行做多组“定向检索”。从对比表格看思路会更清晰注意力形态相关性计算参数量关键优点主要代价加性注意力v^T tanh(W1 h W2 s)较多函数灵活不限维度计算较慢调参敏感乘性注意力h^T W s 或 h^T s很少简洁高效易并行大维度下需缩放多头注意力多组缩放点积并拼接中等多子空间并行检索序列长度成本仍是二次方我个人在实际项目里的选择倾向是如果模型是 RNN 系用小维度的乘性注意力通常能又快又好地完成任务如果是在 Transformer 里那就按缩放点积多头注意力走别自己发明特别花哨的变体。矩阵形状没设计好的话收益未必比简洁方案高。5. 课后的工程化试炼训练一个真正的机器翻译模型课程理论听起来再顺落到代码里还是会遇到各种具体问题。为了写这个部分我把之前在一个模拟项目里做过的中英翻译小模型重新梳理了一遍下面这些工程经验基本都能直接复用。5.1 数据与分词BPE 子词切分是绕不开的一步训练机器翻译模型的第一步不是搭网络而是处理词表。如果你直接用空格分词构建词表会立刻遇到一个严重问题英文的词形变化会产生海量稀疏词而中文的连续字符则根本没有词边界。这两个问题叠加起来词表很容易膨胀到几十万训练效率和泛化能力都很差。子词切分BPE 或其变体是解决这个问题的标准手段。它的核心逻辑很朴素从字符粒度开始统计语料里最频繁的相邻子词对反复合并直到词表达到预设大小。这样既能压缩词表又保留了对未登录词的还原能力。以英文为例“walking”可能被拆成“walk”和“ing”模型只要学过“walk”就能对“walking”有一些表征能力。这里有一个常见的坑BPE 分词器必须在平行语料的源语言和目标语言上分别训练不能图省事共用同一个分词器。中文和英文的子词统计规律完全不同强行共用会让词表里挤满大量无效子词模型学起来非常吃力。我最初为了省事做过一次共用处理结果中英方向的译文质量明显下降切分回来的中文词汇边界也乱七八糟后来老老实实分开训练才恢复正常。5.2 训练工程里的参数选择与“玄学”模型结构确定之后训练过程里有一些参数会显著影响最终效果。我整理几个自己常用的初始基线配置超参数常用初始值说明词表大小16k 到 32k太小欠表达太大参数爆炸编码器层数/解码器层数2 到 4 层小语料别贪深隐藏维度256 到 512与词嵌入维度联动优化器Adam初始学习率 5e-4 附近梯度裁剪5.0防止 RNN 梯度爆炸束宽4 到 8太小近似贪心太大开销高另外有一个很容易踩的坑RNN 系模型对学习率非常敏感。学习率太高注意力权重的 softmax 会很快烧成“几乎 one-hot”模型退化成只顾某一个源词失去软对齐能力学习率太低收敛又很慢。我在实践里会先用一个小批量跑几步观察注意力分布是否合理再决定要不要动学习率。还有一个容易被忽略的点是梯度裁剪。RNN 在长序列上的梯度特别容易出现爆炸不设裁剪的话经常一个 batch 就能把训练好的参数打回原形。不要觉得这是小模型的专属问题规模大了以后梯度异常反而更隐蔽。5.3 评价指标BLEU 不是万能的训练完模型自然要评估翻译质量。BLEU 是业界用得最广泛的自动评价指标它通过看译文和参考译文在 n-gram 上的重合度来打分计算简单、跨语言通用。但它有一个众所周知的问题只衡量表面词汇的相似度不理解语义。两个句子意思完全相同但用词不同BLEU 可能打很低而一个语法错乱但 n-gram 重合度高的句子BLEU 反而可能不低。所以我的建议是BLEU 用来做模型筛选和回归测试没问题但真正判断一个模型能不能上线还是要人工抽看翻译结果。尤其要注意那些 BLEU 相近但实际质量天差地别的实验组把人工抽评的样例记录下来比单纯堆分数有用得多。我在项目里养成了一个习惯每轮实验跑完固定抽 50 条代表性句子人工粗略评分再和 BLEU 对照着看这样选出来的模型往往更可靠。5.4 几个让我印象深刻的翻车现场最后分享几个我实际遇到过的问题权当给后来者提个醒。第一个是“复读机”问题。低层模型在长句解码时经常出现同一个片段反复输出的情况尤其在集束搜索下更容易出现。这通常和训练语料里目标语言长度分布不平衡、或者模型没有充分学习“停止符”的位置有关。应对思路是加大停止符的权重并且在测试时对重复片段做惩罚。第二个是“漏译”问题。源句子里的某些词在译文中完全不见了多发生在长句和中低频词上。注意力机制虽然能帮助定位但训练不充分时它也会漏看。一个有效的检查手段是可视化注意力矩阵如果翻译某些必须出现的词时注意力分散得离谱就该考虑增加训练步数、调整束宽或检查句子预处理有没有错误。第三个是“分词不一致”带来的噩梦。我试过在数据清洗时不小心把中文标点处理成了半角导致模型把逗号和句点混用训练出来的译文标点一塌糊涂。这种错误用 BLEU 都测不出来最后是人工看译文才发现的。所以数据清洗环节宁可慢一点也要保证全半角、大小写、特殊符号的处理规则完全一致。如果你现在正准备啃这门课的第八讲我的建议是别只盯着讲义里的公式看。把课堂内容、论文原文和简单实现配合着学在注意力矩阵的热力图里直观感受一次“对齐”会比单纯推公式理解深得多。我自己在复现这个过程时最大的体会是Attention 之所以重要不是因为它是一个“插件”式的技巧而是因为它第一次让模型在每一步生成时都拥有“选择关注什么”的能力这种能力后来几乎成了所有序列建模架构的核心。最后再分享一个小方法自己跑一个中英翻译小模型从数据清洗到 BPE 再到注意力可视化完整走一遍。踩过的坑比看十篇讲义都管用尤其是当你亲眼看到注意力矩阵从混乱逐渐变得清晰时那种成就感是很值得的。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑