为什么做输入法
严格来说是四年前了,某出版社和公司合作策划一系列技术书籍,我摊到了 NLP 部分。当时我有点不合时宜地写了一个包含 Trie、Regex、CRF 等底层原理的提纲。评审是一些高校 NLP 方向的教授,他们认为这个提纲不够新,对潮流技术涵盖得也不够。现在看,人家批评得太对了,当时直接写 GPT 就好了。
不过我当时也反驳说,这些底层技术还是很重要。比如输入法,它不一定需要神经网络,统计语言模型就可以做得很好。现在回头做 Sime,多少也是为了完成当年那场没有结果的辩论。当然,我并不是想证明谁对谁错,只是想看看:在大语言模型已经无处不在的今天,这些“老技术”到底还能做到什么程度。
第二个原因更实际。我一直是 Arch Linux 用户,但始终没有找到一个完全合自己心意的拼音输入法。既然每天都要用,那就干脆自己做一个。现在我也确实用上了自己的输入法,而且是每天都在用。
第三个原因,是作为一个独立算法工程师,真正能做的产品项目实在太少了。大模型需要的算力太多,个人很难负担;纯模型架构的项目,即使做出来了,自己也没有什么竞争力。输入法却是一个不错的切入点:它需要算法,也需要工程,最后还能变成一个真正可以使用的产品。
Sime 前后参考了 SunPinyin 和 Fcitx5 的不少设计,也加入了自己对词典分词、语言模型和动态解码的理解。最初只是 Linux 下的一个想法,后来又做了 Android,现在也有了 macOS 版本。
它现在能做什么
Sime 是一个开源、离线运行的中文输入法。目前支持 Linux、Android 和 macOS,核心能力包括:
- 全键盘与九宫格输入;
- 全拼、简拼以及全拼和简拼混输;
- 中文、英文和中英混输;
- 整句解码、上下文联想和英文前缀补全;
- 用户句子学习、繁简切换、表情与符号输入。
这些功能都在本地完成。输入内容不会上传,程序也不依赖网络服务。词典、语言模型和用户学习数据都保存在设备上。对输入法来说,这并不只是一个隐私承诺,也是一条很明确的技术边界:断网时仍然可用,按键之后必须立刻响应,模型不能无限膨胀,更不能把每次输入都交给云端处理。
语料到模型
Sime 比较特别的一点,是训练和运行流程基本都在同一个项目里。从原始语料开始,先分词并统计词频,生成 Token 词表、拼音词典和英文词典;然后统计一元、二元和三元 N-gram,构建统计语言模型;再经过平滑、熵裁剪和量化压缩,最终生成输入法运行时需要的 .dict 和 .cnt 文件。
这件事看起来只是把很多工具串在一起,实际做起来却有不少细节。若是随便找一个分词器处理语料,再直接统计语言模型,为了控制词表大小,很容易引入大量 OOV。对一般 NLP 任务来说,少量 OOV 也许可以接受;但输入法不行,输入法至少应该能够输入全部合法汉字。因此,词表裁剪不能只是删掉低频词,还要兼顾汉字覆盖、分词效果、模型大小和最终的输入体验。
运行时的核心是一个 C++20 输入引擎。拼音和英文词典被编译成 Double-Array Trie,语言模型经过紧凑存储后通过 mmap 加载。用户输入会被展开成一张候选路径网络,再通过 Beam Viterbi 搜索得到整句结果。全拼、简拼、九宫格、中英混输,看起来是几种不同的输入方式,最后都要回到同一个问题:怎样在有限的候选和上下文中,尽快找出最合理的句子。
目前发布的模型使用百亿字规模的语料训练。它当然没有大语言模型那么强,也不理解用户究竟想表达什么,但在输入法这个任务上,统计语言模型仍然有很合适的位置:确定、快速、占用可控,而且可以完全离线。再加上本地用户句子学习,它也能逐渐适应一个人的常用表达,而这些数据不需要离开设备。
技术都有价值
最初可能只是想做一个实验,或者为当年的争论补上一份迟到的答案。但真正做下去以后,Trie、N-gram、平滑、熵裁剪、量化、mmap、Beam Search,这些原本散落在书本和论文里的技术,最后真的变成了一个每天可以使用的输入法。
技术是否“新”当然重要。GPT 当然重要,而且统计语言模型也确实问题很多。但技术最终还是要落到约束里看:能不能联网,允许多少内存,需要多低的延迟,数据能不能离开设备。在这些条件下,有些看起来古老的方法并没有过时,只是它们不再站在潮流中央。
Sime 还远没有完成,还有很多词典、模型、解码和交互上的问题要继续改。但至少现在,它已经不只是当年辩论里随口举出的一个例子了。
项目地址:引擎 Ismantic/Sime 分词 Ismantic/DictCut