unilm/EdgeLM 无监督机器翻译质量估计:基于 MC-Dropout 的翻译不确定性估计完整复现指南
【免费下载链接】unilmLarge-scale Self-supervised Pre-training Across Tasks, Languages, and Modalities项目地址: https://gitcode.com/GitHub_Trending/un/unilm
本篇指南基于 unilm 仓库edgelm(EdgeLM)分支下的 无监督质量估计示例文档,讲解如何在神经机器翻译(NMT)场景中复现 Fomicheva 等人 2020 年论文Unsupervised Quality Estimation for Neural Machine Translation的核心方法:通过在推理时保留 dropout(MC-Dropout 思想)进行多次随机前向/解码,利用「评分方差」与「多假设译文相似度」两类不确定性信号,在无参考、无标注的条件下估计每条机器翻译的质量。读完本文,你将掌握从数据预处理、fairseq 二值化、GPU 批量翻译,到 dropout 评分聚合与 Meteor 相似度计算的完整命令流程,并能从 FairseqDropout 等源码理解--retain-dropout的底层生效机制。
1. 方法背景:为什么推理时的随机性能反映翻译质量
该示例对应 Fomicheva 等人 2020 年发表的无监督质量估计方法。其基本假设是:当源句对模型而言「容易」时,推理中保留的随机扰动(dropout)不会显著改变模型行为——无论是对给定译文的打分还是重新生成译文;而当源句困难、模型不确定时,多次随机前向会给出差异较大的分数或互不相同的译文。
仓库中该示例提供了两条可互相印证的估计路径:
- Scoring 路径:把同一条源句和同一句机器译文重复 N 次输入模型打分,
--retain-dropout使每次前向的 dropout 掩码不同,N 个分数的聚合统计(默认均值,可换标准差等)即不确定性估计; - Generation 路径:对每条源句在 dropout 开启下解码出 N 个候选译文,用 Meteor 计算这些候选两两之间的平均相似度,相似度越低说明翻译越不确定。
两条路径都不需要参考译文,因此可以部署在生产环境中对 NMT 输出做逐句质量打分、筛选可疑译文。
2. 环境依赖与准备工作
2.1 外部依赖
根据 README 的 Requirements 一节,复现需要安装以下外部工具:
- mosesdecoder:提供分词脚本
tokenizer.perl与detokenizer.perl; - subword-nmt:提供
apply_bpe.py用于应用 BPE 子词切分; - flores:评测语言对数据;
- MLQE dataset repository:翻译模型(
.ptcheckpoint)与测试数据的官方下载地址,FLORES-101 上的训练/测试模型与数据均从该仓库获取。
另外 Generation 路径的 Meteor 计算需要本机安装 Java(脚本内部通过java -Xmx2G -jar调用 Meteor jar,见第 5.3 节)。
2.2 关键变量约定
原文档为整个流程定义了一组环境变量,复现前需先设置好:
| 变量 | 含义 |
|---|---|
SRC_LANG/TGT_LANG | 源语言 / 目标语言 |
INPUT | 输入前缀:$INPUT.$SRC_LANG是源句文件,$INPUT.$TGT_LANG是参考译文文件 |
OUTPUT_DIR | 存放最终结果的路径 |
MOSES_DECODER | mosesdecoder 安装路径 |
BPE_ROOT | subword-nmt 安装路径 |
BPE | BPE 模型(bpe.codes之类)路径 |
MODEL_DIR | 包含 NMT.pt模型及源/目标词典dict.$LANG.txt的目录 |
TMP | 中间临时文件目录 |
GPU | GPU 推理时使用的 GPU id |
DROPOUT_N | 随机前向(或重复解码)次数 N |
关于 N 的取值,原文档给出明确经验:论文实验使用DROPOUT_N=30,但作者观察到超过 10 次之后提升已不显著——因此实际部署时可根据算力预算把 N 降到 10 左右,性价比更高。
3. 第一步:标准解码产出机器译文
质量估计的对象是「模型自己产出的译文」,因此先按标准流程翻译一次测试集。
3.1 预处理(分词 + BPE)
for LANG in $SRC_LANG $TGT_LANG; do perl $MOSES_DECODER/scripts/tokenizer/tokenizer.perl -threads 80 -a -l $LANG < $INPUT.$LANG > $TMP/preprocessed.tok.$LANG python $BPE_ROOT/apply_bpe.py -c ${BPE} < $TMP/preprocessed.tok.$LANG > $TMP/preprocessed.tok.bpe.$LANG done注意-a参数表示按空格对齐分词,后续detokenizer.perl依赖这种对齐来还原原文空格;preprocessed.tok.bpe.$SRC_LANG这份 BPE 源句文件在第 4 节还会被重复使用,不要清理。
3.2 二值化加速
fairseq-preprocess --srcdict $MODEL_DIR/dict.$SRC_LANG.txt --tgtdict $MODEL_DIR/dict.$TGT_LANG.txt \ --source-lang ${SRC_LANG} --target-lang ${TGT_LANG} --testpref $TMP/preprocessed.tok.bpe --destdir $TMP/bin --workers 43.3 fairseq-generate 翻译并整理输出
CUDA_VISIBLE_DEVICES=$GPU fairseq-generate $TMP/bin --path ${MODEL_DIR}/${SRC_LANG}-${TGT_LANG}.pt --beam 5 \ --source-lang $SRC_LANG --target-lang $TGT_LANG --no-progress-bar --unkpen 5 > $TMP/fairseq.out grep ^H $TMP/fairseq.out | cut -d- -f2- | sort -n | cut -f3- > $TMP/mt.outfairseq-generate的标准输出格式为Hypo N <译文>(N 为句子序号),这里用grep ^H+sort -n保证多 worker 并行时句子顺序不乱,cut -f3-取出译文。
3.4 后处理(合并 BPE 标记 + 反分词)
sed -r 's/(@@ )| (@@ ?$)//g' < $TMP/mt.out | perl $MOSES_DECODER/scripts/tokenizer/detokenizer.perl \ -l $TGT_LANG > $OUTPUT_DIR/mt.outsed去掉 subword-nmt 的@@子词连接符,detokenizer.perl依据 3.1 中的空格对齐恢复标点与空格,最终得到可读的机器译文mt.out,作为 Scoring 路径的打分对象。
4. 第二步:产生不确定性估计
4.1 Scoring 路径:MC-Dropout 打分
(1) 将源句与译文各重复 N 次
python ${SCRIPTS}/scripts/uncertainty/repeat_lines.py -i $TMP/preprocessed.tok.bpe.$SRC_LANG -n $DROPOUT_N \ -o $TMP/repeated.$SRC_LANG python ${SCRIPTS}/scripts/uncertainty/repeat_lines.py -i $TMP/mt.out -n $DROPOUT_N -o $TMP/repeated.$TGT_LANG其中repeat_lines.py即仓库中的 repeat_lines.py,它把输入文件逐行重复 N 次写出(重复前会先做空格归一化)。
(2) 对重复后的「源句-译文」对重新二值化
fairseq-preprocess --srcdict ${MODEL_DIR}/dict.${SRC_LANG}.txt --tgtdict ${MODEL_DIR}/dict.${TGT_LANG}.txt \ --source-lang ${SRC_LANG} --target-lang ${TGT_LANG} --testpref ${TMP}/repeated --destdir ${TMP}/bin-repeated复现提示:原文档该行写的是
$TGT_DIC,是变量名笔误,执行时应统一为--tgtdict ${MODEL_DIR}/dict.${TGT_LANG}.txt;打分模型路径原文档写作${LP}.pt(语言对缩写),与前面${SRC_LANG}-${TGT_LANG}.pt指向同一 checkpoint,请保持命名一致。
(3) 带 dropout 的推理打分
CUDA_VISIBLE_DEVICES=${GPU} fairseq-generate ${TMP}/bin-repeated --path ${MODEL_DIR}/${LP}.pt --beam 5 \ --source-lang $SRC_LANG --target-lang $TGT_LANG --no-progress-bar --unkpen 5 --score-reference --retain-dropout \ --retain-dropout-modules '["TransformerModel","TransformerEncoder","TransformerDecoder","TransformerEncoderLayer","TransformerDecoderLayer"]' \ --seed 46 > $TMP/dropout.scoring.out grep ^H $TMP/dropout.scoring.out | cut -d- -f2- | sort -n | cut -f2 > $TMP/dropout.scores这条命令的三个要点:
--score-reference:不再生成新译文,而是计算模型对「源句 + 参考译文(此处即机器译文)」的对数似然分;--retain-dropout:推理阶段保留 dropout,使 N 次前向的分数呈现随机波动;--retain-dropout-modules:指定哪些模块启用推理期 dropout。原文档说明:若不指定,dropout 会在与训练时相同的位置生效;示例中显式列出了TransformerModel、TransformerEncoder、TransformerDecoder、TransformerEncoderLayer、TransformerDecoderLayer五类模块(命令行里写成 JSON 数组字符串)。--seed 46固定随机种子,保证实验可复现。
最后cut -f2取出每条记录的分数(Hypo N score格式的第二字段),得到 N×句数的分数流dropout.scores——同一句子的 N 个分数在文件中是连续排列的。
(4) 聚合分数
python $SCRIPTS/scripts/uncertainty/aggregate_scores.py -i $TMP/dropout.scores -o $OUTPUT_DIR/dropout.scores.mean \ -n $DROPOUT_Naggregate_scores.py 按每 N 行一组做聚合。源码内置了六种可选聚合函数(-f参数):std、var、median、mean、min、max,默认mean。需要强调的是:作为不确定性指标,通常应选std(分数波动越大越不确定);mean更像是对单次打分的去噪平滑。这正是该脚本支持多种聚合函数的价值所在。
4.2 Generation 路径:多假设解码 + Meteor 相似度
(1) 带 dropout 解码出 N 个假设
CUDA_VISIBLE_DEVICES=${GPU} fairseq-generate ${TMP}/bin-repeated --path ${MODEL_DIR}/${LP}.pt \ --beam 5 --source-lang $SRC_LANG --target-lang $TGT_LANG --no-progress-bar --retain-dropout \ --unkpen 5 --retain-dropout-modules TransformerModel TransformerEncoder TransformerDecoder \ TransformerEncoderLayer TransformerDecoderLayer --seed 46 > $TMP/dropout.generation.out grep ^H $TMP/dropout.generation.out | cut -d- -f2- | sort -n | cut -f3- > $TMP/dropout.hypotheses_ sed -r 's/(@@ )| (@@ ?$)//g' < $TMP/dropout.hypotheses_ | perl $MOSES_DECODER/scripts/tokenizer/detokenizer.perl \ -l $TGT_LANG > $TMP/dropout.hypotheses注意与 Scoring 命令的差异:这里没有--score-reference,因此是对每条(重复的)源句做 beam=5 解码,得到 N 个受 dropout 扰动的译文假设;--retain-dropout-modules以空格分隔的形式传入五个模块名(而非 JSON 字符串)。
(2) 计算 N 个假设之间的 Meteor 平均相似度
python meteor.py -i $TMP/dropout.hypotheses -m <path_to_meteor_installation> -n $DROPOUT_N \ -o $OUTPUT_DIR/dropout.gen.sim.meteormeteor.py 的输出dropup.gen.sim.meteor即每个源句对应的生成一致性得分:同一句子的 N 个假设互相越像,分数越高,翻译越确定。
5. 源码纵深:--retain-dropout在 EdgeLM 中的实现
5.1 配置项定义:GenerationConfig
retain_dropout与retain_dropout_modules是 fairseq 生成配置的正式字段,定义在 GenerationConfig:
retain_dropout: bool = field( default=False, metadata={"help": "Use dropout at inference time"}, ) # temporarily set to Any until https://github.com/facebookresearch/hydra/issues/1117 is fixed retain_dropout_modules: Any = field( default=None, metadata={ "help": "if set, only retain dropout for the specified modules; " "if not set, then dropout will be retained for all modules" }, )两点实现细节值得注意:
retain_dropout_modules被临时声明为Any类型(注释说明是等待 hydra 的一个 issue 修复),因此命令行既可以传 JSON 数组字符串'["TransformerModel",...]',也可以像 Generation 命令那样传空格分隔的裸模块名,两种写法在本仓库都能被接受;- 字段帮助文本明确:设置为 None 时,所有模块都保留 dropout,这正对应原文档「By default, dropout is applied in the same places as for training」的说法。
5.2 生效链路:从模型到 Dropout 模块
在 fairseq_model.py 中,生成流程构建解码器参数时会读取上述配置:
if getattr(cfg.generation, "retain_dropout", False): kwargs["retain_dropout"] = cfg.generation.retain_dropout kwargs["retain_dropout_modules"] = cfg.generation.retain_dropout_modules这些参数最终经make_generation_fast_(retain_dropout=..., retain_dropout_modules=...)分发到每个 dropout 模块。核心实现在 FairseqDropout:
class FairseqDropout(nn.Module): def __init__(self, p, module_name=None): super().__init__() self.p = p self.module_name = module_name self.apply_during_inference = False def forward(self, x, inplace: bool = False): if self.p > 0 and (self.training or self.apply_during_inference): return F.dropout(x, p=self.p, training=True, inplace=inplace) else: return x从源码结构看,其工作机制是:
- 每个
FairseqDropout实例携带module_name(所属模块名,如TransformerEncoderLayer); make_generation_fast_被调用时,若retain_dropout=True且(未指定模块白名单,或自身module_name命中白名单),则把apply_during_inference置为 True;- 之后
forward中self.training or self.apply_during_inference为真,即使模型处于eval()模式也会以training=True的方式执行 dropout——这正是「推理时保留随机性」的实现关键:N 次前向共享同一 eval 模型,但 dropout 掩码逐次重新采样。
如果某个 dropout 实例的module_name为 None 而又指定了白名单,源码会打印 warning 提示「无法为该模块启用推理期 dropout」。
仓库中的测试 tests/test_inference_dropout.py 专门验证了这一机制:对同一输入重复前向,开启retain_dropout后输出分数应发生变化,可用于回归验证你本地版本的--retain-dropout是否生效。
5.3 三个辅助脚本的实现细节
- repeat_lines.py:逐行读入、每行重复 N 次写出,写入前用
" ".join(line.split())归一化空格;-o缺省时输出到 stdout。它保证 Scoring 阶段「源句与译文第 i 份严格对齐」,是后续按连续 N 行聚合的前提。 - aggregate_scores.py:滑动窗口式聚合——累计
repeat_times个分数后立即输出聚合值并清窗,因此要求输入文件中同一句子的 N 个分数必须连续(由 4.1 的sort -n步骤保证)。std/var等聚合基于 numpy,一行一个输出值。 - meteor.py:这是三个脚本中最复杂的一个,值得细看:
read_translations:按连续 N 行切分,把 N 个假设归到同一个句段 id 下;generate_input:对每个句段取combinations(range(N), 2)生成C(N,2)个两两配对(第 i 份做系统输出、第 j 份做参考),写入临时文件——因此 Meteor 比较的不是「假设 vs 参考译文」,而是「假设 vs 假设」;run_meteor:以java -Xmx2G -jar <meteor_jar>调用,参数为-p "0.5 0.2 0.6 0.75" -norm -l <lang>。注释特别指出:这是 Meteor 默认参数,仅把 alpha 从默认值改为 0.75,使精确率与召回率在评分中获得等权重;read_output:只解析以Segment开头的行,每攒满 C(N,2) 个原始分段得分,就取平均输出一个句段级相似度。也就是说,对 N 个假设共有 C(N,2) 次配对比较,最终每个源句得到一个均值。
6. 结果解读与适用边界
两条路径的输出文件对应关系:
| 输出文件 | 含义 | 不确定性方向 |
|---|---|---|
$OUTPUT_DIR/dropout.scores.mean | N 次 dropout 打分的聚合值(默认均值,可换std) | 用-f std时:越大越不确定 |
$OUTPUT_DIR/dropout.gen.sim.meteor | N 个 dropout 假设两两 Meteor 相似度的均值 | 越小越不确定 |
实践注意事项(均来自原文档与源码事实):
- N 的取值:论文用 30,作者实测超过 10 无显著提升,资源受限时 N=10 即可;
- 随机种子:示例固定
--seed 46,更换种子会改变结果,跨实验对比时务必保持一致; - 模块白名单:Scoring 与 Generation 两条命令的
--retain-dropout-modules传参格式不同(JSON 数组 vs 空格分隔),两者在 GenerationConfig 的Any类型下均可解析; - 适用前提:模型 checkpoint、词典、BPE 与 moses 分词流程必须来自同一 MLQE 训练配置,且
fairseq-generate使用--beam 5 --unkpen 5与示例保持一致,才能对齐论文中的复现口径; - 语言相关:
meteor.py默认-l en,评测非英语目标语时应显式指定目标语言。
综上,该示例目录(README + 三个脚本)构成了一套自包含的 NMT 无监督质量估计流水线:标准解码产出译文,MC-Dropout 打分与多假设 Meteor 相似度分别从「置信度」和「一致性」两个角度给出逐句质量信号,配合 FairseqDropout 的推理期 dropout 机制,即可在完全无参考、无标注的数据上部署翻译质量监控。
【免费下载链接】unilmLarge-scale Self-supervised Pre-training Across Tasks, Languages, and Modalities项目地址: https://gitcode.com/GitHub_Trending/un/unilm
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考