词格原先的整句打分是一份没校准过的启发式 unigram,质量很差。这里内嵌 libime 在用的 kenlm 查询子集,改由它的三元模型 sc.lm 来打路径分。 - engine/ngram 只编 kenlm 的 query / trie-build 路径。不定义 WITH_THREADS, parallel_read.cc 因此退化成串行实现,整个子集零 Boost 依赖。 KENLM_MAX_ORDER=3 必须与建模阶数一致,改它等于读不懂已发布的模型文件。 - ngram::LanguageModel 是唯一 include kenlm 头文件的地方,对外只给出一个纯 std 的头,_HAS_AUTO_PTR_ETC 和 kenlm 的 include 目录都保持 PRIVATE。 - shared_language_model 按资源路径共享一份 mmap。kenlm 的查询是只读的且把 上下文放在调用方的 State 里,所以并发会话不需要额外加锁。 - 模型缺失时 valid() 为假,词格退回原来的启发式打分:整句候选只是变差,不会 停止工作。测试覆盖了缺模型降级与按路径共享。 - 出货的 ARPA 不带 <s>/</s>,所以解码从 null_state() 起算而非 begin_state()。 kenlm 是上游副本,不重排版、不进 clang-format,其 LGPL-2.1+ 与 BSD 例外清单 已补进 THIRD_PARTY_NOTICES.txt。googlepinyinime 那条 Fallback 整句路径不变。
2.9 KiB
ngram
整句候选的打分模型。kenlm/ 是 kpu/kenlm 查询子集的上游副本(提交号见 ../UPSTREAM.md),language_model.h 是引擎唯一看得到的接口。
引擎其余部分不 include 任何 kenlm 头文件:language_model.cpp 是唯一的那个翻译单元,kenlm 的 include 目录和它需要的 _HAS_AUTO_PTR_ETC 因此都能保持 PRIVATE。
用法
#include "engine/ngram/language_model.h"
const auto &model = ngram::shared_language_model(paths.resource(assets::language_model));
if (!model.valid())
{
// 模型缺失不是错误,调用方按没有模型处理(词格会退回按词频的启发式打分)。
}
ngram::State state = model.null_state(), next;
const float score = model.score(state, "你好", next); // log10 概率
几个容易踩的点:
- 从
null_state()起算,不要用begin_state()。 出货的sc.lm是从一份不带<s>/</s>的 ARPA 建的,句首上下文只会带进一个模型没有任何 n-gram 的词。 - 分数是 log10,和词格原先那套自然对数的启发式不能混着加。
- 未登录词按
unknown_penalty()罚分,默认log10(1/6e7) ≈ -7.78,与 libime 的DEFAULT_LANGUAGE_MODEL_UNKNOWN_PROBABILITY_PENALTY取同一个值。换这个数等于整体重调未登录词相对已登录词的代价。 shared_language_model()按路径共享实例。sc.lm有 34 MB,全拼和双拼两本词典各载一份是白白多映射一遍;kenlm 的查询全是只读的,共享实例可以被多个线程同时用。
重新生成语言模型
出货的 sc.lm 由 libime 的 lm_sc.arpa 转换而来,转换工具就是 kenlm 自带的 build_binary,默认不构建。平时不必手工做这件事:scripts/build-language-model.ps1 会按 language-model/lock.json 下载语料、转换、两头校验摘要,打完整包时自动被调用(见 ../../language-model/README.md)。
要手工跑一遍,或者调转换参数时:
# 本目录可以单独 configure,不必为 engine 依赖的 SQLite 拉起 vcpkg
cmake -S engine/ngram -B build-tool -DMETASEQUOIA_IME_BUILD_NGRAM_TOOLS=ON
cmake --build build-tool --config Release --target metasequoia_ime_build_binary
./build-tool/Release/metasequoia_ime_build_binary -s -a 22 -q 4 trie lm_sc.arpa sc.lm
改了参数就要同步改 language-model/lock.json 里的 build.flags 和 output.sha256,否则构建脚本会拿新产物去对旧摘要而报错。
-q 4 -a 22 与 lm::ngram::QuantArrayTrieModel 是一一对应的:改了参数就要改 language_model.cpp 里的 using Model,否则载入时抛 FormatLoadException。-s 对应 config.sentence_marker_missing = lm::SILENT,因为这份 ARPA 没有句首句尾标记。
KENLM_MAX_ORDER=3 必须与建模时的阶数一致,改它等于让运行时读不懂已经发布的模型文件。