Fairseq 词法受限解码(Lexically Constrained Decoding)完全指南:基于动态束分配的向量化约束生成
2026/9/19 23:49:36 网站建设 项目流程

Fairseq 词法受限解码(Lexically Constrained Decoding)完全指南:基于动态束分配的向量化约束生成

【免费下载链接】fairseqFacebook AI Research Sequence-to-Sequence Toolkit written in Python.项目地址: https://gitcode.com/gh_mirrors/fa/fairseq

导读

本篇技术指南围绕 Fairseq 的examples/constrained_decoding示例,系统讲解如何在序列生成(机器翻译等)推理阶段强制输出指定的词或短语。你将掌握fairseq-interactive --constraints的完整用法、约束的 tab 分隔输入格式与输出格式解析、OrderedConstraintStateUnorderedConstraintState两种约束状态机的底层实现原理,以及动态束分配(Dynamic Beam Allocation)如何让小束宽即可满足约束生成。阅读本文后,你可以直接复现德英翻译中强制输出 "hard to influence" 的完整命令,并理解约束搜索在 fairseq/search.py 与 fairseq/token_generation_constraints.py 中的全部实现细节。

概述:什么是词法受限解码

词法受限解码(Lexically Constrained Decoding)在序列到序列(Seq2Seq)模型的解码阶段,将用户指定的词或短语作为"硬约束"注入搜索过程,保证这些词以零个或多个 token 的间隔出现在最终输出中。Fairseq 的实现在论文层面对应两项工作:

  • Fast Lexically Constrained Decoding With Dynamic Beam Allocation(Post & Vilar, NAACL 2018),提出用动态束分配实现快速受限解码;
  • Improved Lexically Constrained Decoding for Translation and Monolingual Rewriting(Hu et al., NAACL 2019),引入向量化(vectorized)扩展,支持乱序(unordered)的短语级约束。

该特性在 Fairseq 中的入口是fairseq-interactive--constraints命令行参数,其配置项定义于 fairseq/dataclass/configs.py,取值由 fairseq/dataclass/constants.py 中的GENERATION_CONSTRAINTS_CHOICES = ChoiceEnum(["ordered", "unordered"])约束为两种模式。典型应用场景包括:术语翻译(术语表强制)、领域词汇强制、以及受控文本改写。

快速上手:在 fairseq-interactive 中使用 --constraints

输入格式:tab 分隔的约束字段

开启约束搜索只需在fairseq-interactive命令中追加--constraints参数。约束被追加到每行输入的末尾,以 tab 分隔:第一个字段是待翻译的源句,其后每个字段是一个约束(一个或多个 token)。注意约束中的空格会被当作普通字符处理,因此短语约束(如to influence)中的空格需要用连续 token 表达(如toinfluence),具体规则取决于你所用的 BPE 词表。

以下命令使用 Fairseq 官方的 WMT19 德英模型,将德语例句Die maschinelle Übersetzung ist schwer zu kontrollieren.翻译为英文,并强制输出约束 "hard" 与 "to influence":

echo -e "Die maschinelle Übersetzung ist schwer zu kontrollieren.\thard\ttoinfluence" \ | normalize.py | tok.py \ | fairseq-interactive /path/to/model \ --path /path/to/model/model1.pt \ --bpe fastbpe \ --bpe-codes /path/to/model/bpecodes \ --constraints \ -s de -t en \ --beam 10

其中normalize.pytok.py位于 examples/constrained_decoding 目录下,是 WMT19 预处理的两步快捷封装:

  • normalize.py 基于sacremosesMosesPunctNormalizer,对每行执行摩西标点归一化,支持--lang(默认en)与--penn选项;
  • tok.py 使用sacremoses.MosesTokenizer做分词,关键设计是按 tab 切分后逐字段分别分词再以 tab 拼回map(tok, line.split("\t"))),从而保证源句与各约束字段不会被混淆、约束字段内的词间空格得以保留为 tab 之外的普通空格。

输出格式解析

上述命令的典型输出如下:

[snip] S-0 Die masch@@ in@@ elle Über@@ setzung ist schwer zu kontrollieren . W-0 1.844 seconds C-0 hard C-0 influence H-0 -1.5333266258239746 Mach@@ ine trans@@ lation is hard to influence . D-0 -1.5333266258239746 Machine translation is hard to influence . P-0 -0.5434 -0.1423 -0.1930 -0.1415 -0.2346 -1.8031 -0.1701 -11.7727 -0.1815 -0.1511

各行的含义(对应 fairseq_cli/interactive.py 的输出逻辑):

  • S-0:分词并经过 BPE 处理后的源句;
  • W-0:该句的翻译耗时(秒);
  • C-0:该句使用的每个约束(此处即hardinfluence),一行一个,由tgt_dict.string(constraint, ...)还原为文本;
  • H-0:得分(以 2 为底的对数)与 BPE 层级的假设输出,可见约束 token 已精确落入译文;
  • D-0:去除 BPE 与分词后的 detokenized 输出;
  • P-0:每个 token 的位置得分(同样以 2 为底),长度与H行 token 数一致。

有序与乱序约束

默认情况下(--constraints不带参数时等价于ordered,见 configs.py 的argparse_const: "ordered"),约束按提供的顺序生成,约束之间允许出现零个或多个任意 token。例如hard之后可以间隔任意 token 再出现influence,但顺序必须保持。

如果希望解码器自行决定约束的排列顺序(例如约束为独立的术语、不关心先后),改用--constraints unordered。此时解码器会尝试约束的所有排列(对短语约束为C!种顺序)。注意乱序模式搜索空间更大,通常需要更大的束宽(beam)。

实现细节:从命令行到约束状态机

约束的打包与解析链路

约束从命令行文本到解码器的完整链路为:

  1. fairseq_cli/interactive.py 的make_batches将每行按 tab 拆出源句与约束列表,每个约束字符串经task.target_dictionary.encode_line(..., append_eos=False, add_if_not_exist=False)编码为 token 张量;
  2. 调用pack_constraints(batch_constraints)(见 fairseq/token_generation_constraints.py)将"句子 × 约束"的三层列表打包成(batch_size, maxlen)的稠密张量:每行首元素是该句的约束数量,其后依次拼接每个约束的 token,每个约束末尾追加一个 0 作为分隔符maxlen取各句"约束数 + 各约束长度之和 + 1"的最大值,其余位置补零。示例中一个含 3 条约束([3 1 2][3][4 5 6 7])的句子被打包为[3 3 1 2 0 3 0 4 5 6 7 0]
  3. 打包张量随 batch 进入task.inference_step(..., constraints=constraints),最终在 fairseq/sequence_generator.py 调用self.search.init_constraints(constraints, beam_size)初始化每个句子的约束状态。

反向的unpack_constraints(token_generation_constraints.py)在输出阶段把打包张量的一行还原为约束张量列表,用于打印C-行。

LexicallyConstrainedBeamSearch:动态束分配

约束搜索的核心是 fairseq/search.py 中的LexicallyConstrainedBeamSearch。它继承自Search基类,并设置self.supports_constraints = True——sequence_generator在收到约束时会检查该标志,若搜索方法不支持约束则直接报错(sequence_generator.py)。

该类为束中的每个假设(hypothesis)维护一个独立的ConstraintState,用于跟踪该假设已生成到约束序列的什么位置,并据此重塑每一步的束分布。init_constraintsrepresentationordered/unordered)为每个句子创建beam_size份约束状态;prune_sentences在句子提前完成时移除其状态;update_constraints在每步把束裁剪到beam_size后,同步裁剪对应的约束状态(search.py)。

step方法体现动态束分配的关键步骤:

  • STEP 0:对所有未完成(not state.finished)的假设,将其EOStoken 的对数概率置为-inf防止在约束全部满足前提前终止
  • 候选构建:从整个束中取全局 top-2*beam_size,再加上每个假设的 top-1 以及所有"下一步可生成的约束 token"(state.next_tokens()),形成候选池;
  • 银行(bank)计算:对每个候选,用state.advance(token)推进约束状态,bank即为该假设已生成的约束 token 数;
  • 排序与去重:先按(bank, score)排序,再通过beams_buf * (vocab_size + 1) + indices_buf的滚动比较去除重复的(beam, token)扩展;
  • round-robin 条带化(striping):这是"动态束分配"的核心——候选按银行从高到低做轮询分配(每轮从 bank 最高的开始取一个,再取次高的……),等价于在排序键中加入跨银行的偏移量(见代码中stripe_offsets与注释示例),保证处于不同完成阶段的假设都能分到束位,从而不必像早期方法那样剪枝;
  • 截断:最终保留num_cands = 2 * beam_size个候选返回。

step_sentence中"bank 越高越靠前"的分配策略保证了束空间优先供给已推进约束最远的假设,使模型在保持束搜索的同时持续向满足全部约束推进。

两种约束状态:OrderedConstraintState 与 UnorderedConstraintState

两个状态类都位于 fairseq/token_generation_constraints.py,它们回答同一个问题:"给定当前已生成 token 序列,下一步允许生成哪些 token?"通过next_tokens()给出候选集合,通过advance(token)生成新状态。

OrderedConstraintState(token_generation_constraints.py)假设C个约束将按给定顺序生成。所有约束被拼接为单个ConstraintSequence,内部用endpoints标记每个约束的结尾。其advance逻辑为:

  • 若已finished,接受任意 token(保持状态不变);
  • 若当前 token 匹配下一个约束 token,则推进state
  • 若正处于约束边界(endpoints[state]为真),接受任意"间隙" token;
  • 若 token 等于第一个约束 token,则重新从第一个约束开始;
  • 否则回到根状态(state = -1)。

UnorderedConstraintState(token_generation_constraints.py)尝试以全部C!种顺序满足(短语级)约束。其核心数据结构是ConstraintNode构建的前缀树(trie)add_sequence将每条约束写入 trie,节点维护terminal(该处结束的约束数)与num_constraints(以该节点为前缀的剩余约束数)。advance在 trie 中移动:若 token 匹配当前节点的某个子节点且该路径未饱和(generated[child] < child.num_constraints),则深入;否则"回退(rewind)"到根,并在回退路径上检查是否有前缀构成完整约束(node.terminal),将其标记为completedfinished当且仅当root.num_constraints - num_completed == 0

两者的bank属性含义不同:ordered 状态中bank = state + 1(已消费的约束 token 数),unordered 状态中bank = sum(generated.values())——但两者都为LexicallyConstrainedBeamSearch的 round-robin 分配提供统一的"进度"度量。

与 Sockeye 实现的差异

Fairseq 的约束解码与 AWS Sockeye 的实现相比,存在以下关键差异(对应原 README 的说明):

  • 顺序约束支持:按给定顺序生成约束(即默认的ordered模式)在 Sockeye 中不可用,这是 Fairseq 独有的能力;
  • 无需束剪枝:得益于改进的动态束分配方法(round-robin 条带化),不需要像早期实现那样对束进行额外剪枝;
  • 更小的束宽:同样由于分配更优,--beam 10甚至--beam 5往往就足够;README 示例即采用--beam 10,并提示乱序模式可考虑更大的束;
  • 向量化扩展落地:Hu et al.(NAACL 2019)描述的向量化扩展(trie 约束)从未被合并进 Sockeye 主线,而 Fairseq 的UnorderedConstraintState正是该扩展的完整实现。

测试与验证:约束状态机的正确性保障

仓库的 tests/test_constraints.py 对约束状态机做了系统测试,可作为读者理解行为语义的补充材料:

  • test_packing:验证pack_constraints对多句、多约束、变长约束的打包结果与预期稠密张量一致;
  • test_graphs/test_next_tokens:验证ConstraintNodetrie 的构建、next_tokens候选集合与 token 计数;
  • test_sequences(针对UnorderedConstraintStateOrderedConstraintState各一组):对给定的约束序列与 token 输入流,断言每一步advance后状态的banknum_completedfinished等属性符合预期。

这些测试用例实际上给出了两类状态机的"行为规格",例如验证 ordered 状态在约束间隙接受任意 token、unordered 状态能跨顺序满足多条短语约束。读者在阅读实现时,可以对照这些用例逐条推演advance的转移逻辑。

使用注意事项与适用前提

  • 共享子词词表假设fairseq-interactive在启用约束时会打印警告——"Constrained decoding currently assumes a shared subword vocabulary"(interactive.py)。约束 token 直接取自target_dictionary,因此要求约束文本与目标端共享同一套子词(BPE)编码;
  • 约束编码失败风险encode_line使用add_if_not_exist=False,即词表外的 token 不会被加入词表;若约束含 OOV 词,需在 BPE 编码层面处理;
  • 批处理:输入可多句批量提交(tab 分隔约束),make_batches逐句解析;--buffer-size--batch-size的关系约束参见 interactive.py;
  • 乱序模式开销unordered需要探索更多排列,建议增大--beam(README 明确提示 "you may want to use a larger beam")。

引用

词法约束解码的首篇论文(grid beam search,该方向的开创性工作):

@inproceedings{hokamp-liu-2017-lexically, title = "Lexically Constrained Decoding for Sequence Generation Using Grid Beam Search", author = "Hokamp, Chris and Liu, Qun", booktitle = "Proceedings of the 55th Annual Meeting of the Association for Computational Linguistics (Volume 1: Long Papers)", month = jul, year = "2017", address = "Vancouver, Canada", publisher = "Association for Computational Linguistics", url = "https://www.aclweb.org/anthology/P17-1141", doi = "10.18653/v1/P17-1141", pages = "1535--1546", }

Fairseq 实现所采用的扩展分别来自:

@inproceedings{post-vilar-2018-fast, title = "Fast Lexically Constrained Decoding with Dynamic Beam Allocation for Neural Machine Translation", author = "Post, Matt and Vilar, David", booktitle = "Proceedings of the 2018 Conference of the North {A}merican Chapter of the Association for Computational Linguistics: Human Language Technologies, Volume 1 (Long Papers)", month = jun, year = "2018", address = "New Orleans, Louisiana", publisher = "Association for Computational Linguistics", url = "https://www.aclweb.org/anthology/N18-1119", doi = "10.18653/v1/N18-1119", pages = "1314--1324", }
@inproceedings{hu-etal-2019-improved, title = "Improved Lexically Constrained Decoding for Translation and Monolingual Rewriting", author = "Hu, J. Edward and Khayrallah, Huda and Culkin, Ryan and Xia, Patrick and Chen, Tongfei and Post, Matt and Van Durme, Benjamin", booktitle = "Proceedings of the 2019 Conference of the North {A}merican Chapter of the Association for Computational Linguistics: Human Language Technologies, Volume 1 (Long and Short Papers)", month = jun, year = "2019", address = "Minneapolis, Minnesota", publisher = "Association for Computational Linguistics", url = "https://www.aclweb.org/anthology/N19-1090", doi = "10.18653/v1/N19-1090", pages = "839--850", }

【免费下载链接】fairseqFacebook AI Research Sequence-to-Sequence Toolkit written in Python.项目地址: https://gitcode.com/gh_mirrors/fa/fairseq

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询