Skip to content

Xime 输入法的2.0

kingzcheung
Published date:
Edit this post

“Kime” 已经是韩国输入法引擎的名称,为了避免与已有项目冲突,将名称改为 Xime。

Xime 输入法 1.0 只是解决了输入法能用的问题,相对于商业输入法,至少对我来说,其实还差点意思。

1.0 时代,更多是为了快速实现一个可用的 MVP 版本,用于快速验证可行性。事件证明,Xime 真的很不错。

至少,我已经可以 100% 掌控输入法的 DIY 细节了,哪里不用得不舒服,我就可以改哪里,这是商业输入法完全不可能做到的事。

2.0 的目标是从能用做到好用。

全新的名字

Kime 刚好和韩国输入法引擎存在重名。因此,改为 Xime 输入法。目前 Xime 输入法应用名已经显示为 “曦码输入法”。

全新的UI设计

1.0 时代,Xime 输入法 的 UI 界面相对来说比较简陋,没有设计的痕迹。对于产品来说,1.0 的 UI 更接近于原型设计。因此 2.0 专门对 UI 进行了重构。

1.02.0
1.02.0

键盘调节

本质上讲,输入法其实是可覆盖在其他应用界面上的一个窗口。键盘调节就是在控制键盘的窗口大小与位置。

┌─────────────────────────────────────┐
│ CandidateBar (候选栏)               │
├─────────────────────────────────────┤
│ 键盘区                              │
│ (QWERTY/数字/符号/语音等)           │
├─────────────────────────────────────┤
│ Spacer (底部间距)                   │
├─────────────────────────────────────┤
│ 底部按钮区 [收起] [切换]            │
└─────────────────────────────────────┘

键盘调节支持2个功能:

  1. 上下移动键盘的高度: 指的是底部按钮区不变,候选栏和键盘区整体可以上下移动。
  2. 拉伸键盘的高度: 指的是底部按钮区不变,候选栏和键盘区整体可以上下拉伸。

可以看到,上面的 Spacer 区域是为了上下移动键盘的高度时的点位留的。

至于键盘调节层,则完全和键盘区域分离,通过共享状态来达到键盘调节效果。 键盘调节

语音转文本

我一直有一个观点,语音转文本的门槛在于 ASR 模型,而 ASR 参数量决定了语音转文本的质量。在这一点上,个人玩家是无论如何优化,都打不过商业公司的 ASR 模型的。因为商业输入法的语音转文本功能,完全可以不用部署在手机上,而是通过云端服务来获得最佳的效果。

但是我的个人使用场景里,还是会偶尔有使用语音转文本的需求,因此这个功能有必要添加。

我这里实现了2种:

  1. 在线 ASR: 通过云端服务获得结果,这里接入的是阿里百炼的语音转文本服务。这个成本非常低,按我的使用习惯,充5块估计能用2年。
  2. 本地 ASR: 我这里使用的是 sherpa-onnx 模型。

经过实际的测试,本地 ASR 模型的效果确实比较一般,只能说是能用,好处是它真的是纯本地的,没有任何网络访问,也不会有任何人收集隐私。 语音输入

一个小问题: 标点符号怎么办?

在线 ASR 模型一般是自带标点符号(punctuation)预测的,毕竟是商用服务。比如:

“我的名字是张三我来自杭州你呢”

在线 ASR 模型可以自动得到:

“我的名字是张三。我来自杭州,你呢?”

但是本地 ASR 模型怎么办?

看了一下 sherpa-onnx 模型提供的标点预测模型 sherpa-onnx-punct-ct-transformer-zh-en-vocab272727-2024-04-12-int8

total 155776
-rw-r--r--  1 fangjun  staff   1.5K Jun 18 10:34 README.md
-rwxr-xr-x  1 fangjun  staff   1.6K Apr 12  2024 add-model-metadata.py
-rw-r--r--  1 fangjun  staff   810B Apr 12  2024 config.yaml
-rw-r--r--  1 fangjun  staff    72M Jun 18 10:33 model.int8.onnx
-rwxr-xr-x  1 fangjun  staff   745B Apr 12  2024 show-model-input-output.py
-rwxr-xr-x  1 fangjun  staff   4.6K Apr 12  2024 test.py
-rw-r--r--  1 fangjun  staff   4.0M Apr 12  2024 tokens.json

int8 的模型大小竟然也有 72M !

这对于我来说,绝对是无法接受的!因为我的智能联想的模型才不到20mb。

但是认真思考一下,手机使用的标点预测模型真的这么大吗?

答案是否定的。

我们日常使用输入法,很少会用来办公等严肃场景,一般就是用来聊天对话。因此,我们不必预测所有的标点符号,也不必预测英文标点。甚至,因为一般也只用于聊天场景,对标点预测的精度也不需要那么高。

那这个需求很简单,我可以自己设计一个非常非常小的模型,只预测中文标点符号。而且可以直接使用用于智能联想的模型的数据集来训练它。

这个模型的项目名为: srf-punctuation。 它的结构如下:

PunctuationPredictor(
  (encoder): SmallTransformer(
    (embedding): Embedding(8000, 64, padding_idx=0)
    (pos_encoding): PositionalEncoding(
      (dropout): Dropout(p=0.1, inplace=False)
    )
    (transformer): TransformerEncoder(
      (layers): ModuleList(
        (0-1): 2 x TransformerEncoderLayer(
          (self_attn): MultiheadAttention(
            (out_proj): NonDynamicallyQuantizableLinear(in_features=64, out_features=64, bias=True)
          )
          (linear1): Linear(in_features=64, out_features=128, bias=True)
          (dropout): Dropout(p=0.1, inplace=False)
          (linear2): Linear(in_features=128, out_features=64, bias=True)
          (norm1): LayerNorm((64,), eps=1e-05, elementwise_affine=True)
          (norm2): LayerNorm((64,), eps=1e-05, elementwise_affine=True)
          (dropout1): Dropout(p=0.1, inplace=False)
          (dropout2): Dropout(p=0.1, inplace=False)
        )
      )
    )
  )
  (classifier): Sequential(
    (0): Linear(in_features=64, out_features=64, bias=True)
    (1): GELU(approximate='none')
    (2): Dropout(p=0.1, inplace=False)
    (3): Linear(in_features=64, out_features=5, bias=True)
  )
)

量化后为 int8 后实际大小为2.24mb,这直接比 sherpa-onnx 提供的模型小了 96.89%

同时,srf-punctuation 也有一定的标点纠正能力。

不过,如上面模型结构看到的,它只支持4个标点符号(第1位是正常字符位),对于日常聊天是够用了,但是不适合用在其他场景。

智能联想

事实上,我并不太了解其他输入法(无论是商业的还是开源的)的智能联想功能是怎么实现的。N-Gram 或者大模型? 1.0 版本的智能联想功能,我采用的方案是 transformer 模型 + N-Gram 融合实现。transformer 模型负责固定的预测,N-Gram 负责用户过往的输入预测。

但是如果你有在用我的输入法,你会发现,这个智能联想功能,好像不是很智能,至少预测的词都有一点点的奇怪。

根本原因在于,由于我要快速验证 MVP 原型,这个 transformer 模型我当初并没有训练到一个非常好的效果,还有就是数据集方面也不够丰富。

另外一个原因是,手机的资源是有限的,而模型推理,是一个高能耗的任务。因此这个 transformer 模型的上限就被限制死了,参数量一定要足够的小,才能不造成过多地消耗手机的资源。

词表也要做一定的精简,在日常的生活中,我们常用字其实只有4000个左右,再加上一些常用的词组,因此我的设计词表大小目标是 8000。一半用于常用字,一半用于常用的词组。

生成办法是,使用常见汉字表label.txt + 频率分析做剪枝。

  1. 训练标准 BPE tokenizer(12000 词表)
  2. 采样 30% 语料统计每个 token 频次
  3. 分类:
    • 单字在 label.txt 中 → 保留
    • 单字不在 label.txt 中 → 丢弃(覆盖率 < 0.2%)
    • 多字组合 → 按频率排序保留 top-N
  4. 输出 vocab.json(~8000 词表)+ train.bin + val.bin

常见汉字表 label.txt 来源于 CASIA-HWDB 1.0 数据集。

通过一些融合实验,终于有一些不错的效果:

联想预测示例:
  "今天天气" -> 晴 | 怎么样 | 预 | 炎 | 太 | 很 | 真 | 非常 | 如何 | 确实
  "我们一起去" -> 看 | 看看 | 公园 | 找 | 。 | 吧 | 探索 | 体验 | 做 | 吃
  "我觉得" -> 自己 | 这个 | 这 | 你 | 我 | 这是 | 很 | 你的 | 我们 | 他
  "明天早上" -> 点 | 起来 | 上 | 起 | 去 | 时 | 要 | 就 | 来 | 睡
  "我在上海的" -> 路 | 房 | 街 | 妈妈 | 地铁 | 东 | 火车 | 夜 | 那个 | 交通
  "正在吃饭" -> 。 | 的 | 时 | 的时候 | 和 | 了 | ? | 前 | 的人 | 吃
  "无论如何" -> 选择 | 选 | 是 | 处理 | 使用 | 坚持 | 如何 | 成立 | 计算 | 做
  "你" -> 好 | 提到 | 现在 | 怎么 | 听 | 是一 | 愿意 | 说 | 已经 | 看
  "我真的是受" -> 不 | 过 | 了 | 伤 | 着 | 人 | 骗 | 我 | 惊 | 歧视
  "好" -> 像 | 奇 | ! | 多 | 玩 | 了 | 处 | 象 | 好 | 事
  "是我" -> 不 | 刚 | 爸 | 现在 | 自己 | 看 | 最 | 朋友 | 喜欢 | 说
  "不想出" -> 去 | 什么 | 来 | 一 | 话 | 国 | 门 | 问题 | 事 | 差

最后

这个输入法主要还是为我自己服务,经过之前的一些 breaking 的乱改,目前应该终于稳定下来了。

如果你觉得适合你的话: https://github.com/ximeiorg/Xime/releases

Previous
使用 Rust 实现 Windows 输入法
Next
The Difficult Plugin System of Kime 2.0