Xime 输入法的手写输入技术实现

基于 StrokeTransformer 的手写汉字识别模型设计与 Xime 输入法落地实践。从 CNN 的局限出发,介绍了笔画轨迹序列分类方案,以及模型生命周期管理和两种手写键盘的实现。

发布于 约 3 分钟

大概在去年的时候,我有基于 CASIA-HWDB 1.0 和 CNN 卷积神经网络实现了手写汉字识别模型https://github.com/ximeiorg/ochw。原理是把数据转为图片,再通过卷积神经网络进行分类。

这种方案缺陷非常的大,所以只能是个demo。

基于 CNN 卷积网络 最大的问题是,它携带了在大量的无效信息。对于分类任务来说,它的粒度过于大,无法区分细节。

比如,在“金” 和“全” 这2个字中,CNN 可能会觉得它们的特征会比较像。

想要实用,还得基于汉字笔画轨迹序列来做文章。

StrokeTransformer

设计目标

这个模型要足够的小,足够的快,足够的省电,其次才是准确率。

整体架构

输入轨迹 (B, T, 5)                        输出 logits (B, 7356)
      │                                          ↑
      ▼                                          │
┌─────────────┐    ┌──────────────────┐    ┌──────────┐
│ Input Proj  │───►│  Transformer     │───►│ Classifier│
│ Linear(5→d)  │    │  Encoder ×3      │    │  Head     │
└─────────────┘    └──────────────────┘    └──────────┘
      │                    ↑
      ▼                    │
┌─────────────┐    ┌──────────────┐
│ [CLS] Token │───►│  Positional  │
│  concat     │    │  Encoding    │
└─────────────┘    └──────────────┘

StrokeTransformer 将手写汉字识别建模为笔画轨迹序列的分类问题。其核心思路是:将用户书写时产生的时空轨迹点序列,通过 Transformer 编码器建模点与点之间的长程依赖关系,从而捕捉汉字的笔画结构和整体形态。

核心编码器由 3 层 Pre-Norm 结构的 Transformer Encoder 堆叠而成,每层包含 4 头自注意力和一个 192→384→192 的前馈网络。自注意力机制使每个轨迹点能够聚合全局上下文信息——例如识别“口”字时,末笔横折的位置需要参考首笔起点的位置才能判断是否闭合。3 层的堆叠则逐步构建从局部笔画到全局字形的层次化特征。

分类输出时,取 [CLS] token 在编码器输出端的对应向量,经 LayerNorm 后送入 2 层 MLP 分类头(192→96→7356),输出 7356 个汉字类别的 logits。

整个模型不大,也就大概不到 200 万参数。导出到手机使用也就 6MB 的权重大小。虽然看着参数不多,但是训练完成后,在验证集上达到了 90% 的准确率。

对于这个结果,我的结论是,可用,但是离好用还差很远。但是至少它是离线的,不会收集你的笔画数据去做训练。

示例

复杂的字,比如 曦码输入法的 “曦”:

xi

其他demo

我使用 Rust 的 candle 框架实现了 WASM 版本的 web demo => https://pro.ochw.ximei.me , 如果你感兴趣的话,可以去试一下。

Xime 输入法的实现

这个模型最后还是落地到 Xime 输入法里,做最后的呈现。

可能会有朋友疑问,为什么要给输入法加一个手写功能?

因为会有这么一个场景: 你看到了一个字,但是不知道它怎么念(假设你使用拼音),不知道它怎么拆字(假设你使用五笔),那要如何把它打出来呢? 答案是手写

为了给 Xime 输入法实现手写功能,我这段时间做了大量的底层重构。

当前输入法已经存在了预测联想词、语音转文本、标点符号预测三种模型,这些模型功能不同,而且管理是分散的。因此,我把模型的管理做了重新的整合,首先是下载的整合,现在统一使用索引数据,并提供了统一的下载页面。

模型管理

这么多个模型,初始化、加载、卸载这些功能也需要抽象出来,统一管理模型运行时生命周期。

我这里分了三个等级:

  • HOT([keepWarm]):常驻内存,onTrimMemory 不会释放
  • WARM(默认):引用计数归零后自动释放
  • COLD / OFF:未加载

像联想词,一旦开始了之后,就会常驻,直到这个功能被关闭。

手写键盘

这里做了2种手写键盘:

第一种:手写模式,属于 Main 级别,和全键盘、笔画键盘、未来的T9键盘同属于一个级别。

hw

第二种:手写找字,属于次级键盘,和符号键盘、数字键盘一样,所有键盘共享。

ma

如你所见,手写找字会提供相应的反查编码功能。

这个功能会在2.5.0 上出现。