libtextinput, a minimal input method framework, as a library

Lately, I’m working on a new input method framework called libtextinput. Well, actually, I was doing some experiments making ibus-daemon lighter, but later I decided to write a new one with my own design. Yes, I know I’m reinventing the wheel but the design is quite different from other frameworks:

  • It is not a session service, but a library, so it can be directly linked to a compositor, etc. The API is modelled after the Wayland input-method protocol and much simpler than other frameworks. The main difference is that there is no concept of “input context”, so clients can directly interact with IMEs.
  • While it mainly supports in-process IMEs, it also supports IMEs running as a separate process. This feature is particularly useful for heavy IMEs like Chinese Intelligent Pinyin or Japanese Kana Kanji conversion. Out-process IMEs are automatically restarted after crash.

For more details, see the overview and TiEngineManager chapters in the reference (still terse though). It is still in early development stage but a demo program has just started working:

If you are unable to play the movie with your browser, you can download it from here (WebM, 3.1MB).

DSO experiment

At the GNU 30th meeting, I was doing some experiments toward a lightweight input method architecture.

IBus is based on “everything is a process” model, where each engine runs as a separate process. This is good for security and also helps developers to prototype IME in their favourite programming language, like Python. On the other hand, the approach has a potential drawback: performance. To handle a single key event, it requires context switches between processes and D-Bus IPC. As mentioned at the input method BOF at GUADEC, the performance penalty could be significant when used under Wayland, as there will be more processes involved: application, compositor, protocol translator (ibus-wayland), ibus-daemon, and engine.

So, basically, the idea is to reduce the number of processes. For IBus, given that almost all major IME have been ported to C, it should be possible to load them as a DSO instead of spawning them as a separate process.

Here’s the code for this experiment, called gisl (g* input source loader).

As noted in README, engine binary needs to be linked as PIE (Position Independent Executable) and export a stub function. The engine can then be called through the simple API of gisl, as follows:

#include <gisl/gisl.h>

static void
commit_text_cb (GislInputSource *source, const gchar *text)
  g_print ("Got commit_text ('%s')\n", text);

main (int argc, char **argv)
  GislLoader *loader;
  GislInputSource *source;
  GError *error;

  error = NULL;
  loader = gisl_loader_new ("/usr/libexec/ibus-engine-enchant", &error);
  if (!loader)
      g_printerr ("Cannot load ibus-engine-enchant: %s\n",
      g_error_free (error);
      exit (1);

  error = NULL;
  source = gisl_loader_create_input_source (loader, "enchant", &error);
  if (!source)
      g_printerr ("Cannot create enchant input source: %s\n",
      g_error_free (error);
      exit (1);

  g_signal_connect (source, "commit-text",
                    G_CALLBACK (commit_text_cb), NULL);

  g_print ("Calling focus_in\n");
  gisl_input_source_focus_in (source);

  g_print ("Calling process_key_event ('a')\n");
  gisl_input_source_process_key_event (source, 0x61, 38, 0);

  g_print ("Calling process_key_event ('\\n')\n");
  gisl_input_source_process_key_event (source, 0xff0d, 36, 0);

  g_object_unref (source);
  g_object_unref (loader);

  return 0;

Note that the engine binary itself still works with IBus. Also the API is not IBus specific, though it currently only supports IBus enignes.

I don’t know where this project is going though, I’ll show some benchmark results with a complete IM example in the next post.

shortcut editor for ibus-kkc

少し間があきましたが、libkkc 0.1.10 と ibus-kkc 1.5.10 をそれぞれリリースしました。




libkkc update week four

libkkc 0.1.8 と ibus-kkc 1.5.8 をそれぞれリリースしました。

今回の主な変更点は文節変換の改良です。これまでは、たとえば「行って来る」を「言って来る」に修正するためには、最初の2文字(「いっ」)だけを選択してから候補を選ぶ必要がありました。0.1.8 では、3文字以上(「いって」「いってく」など)を選択しても変換が可能です。

これは、これまで単にSKK辞書を「いt」で引いていたのを、文節を文として変換するようにしたためです(つまり一週目の記事 で「使っていない」と書いていた A* 探索を結局使うようにした)。



この他に ibus-kkc のメニュー項目をガイドライン(ドラフト)に沿うようにしたり、ショートカットで入力モードを変更する際にラジオボタンが複数選択されてしまう問題が修正されています。


余談ですが、やはり NLP を勉強しておく必要があるかと思い、今年は Coursera の NLP コースを取ってみました。昨年は機械学習のコースを取ったのですが、本をつまみ読みするのに比べて、体系的に知識が得られるのと、トレンド(?)がなんとなく分かるのが良いですね。

libkkc update week three

別件の締切に伴う作業があったため、今週の変更は少なめです。最新版はそれぞれ libkkc 0.1.7, libkkc-data 0.1.6 (変更なし), ibus-kkc 1.5.7 です。主な変更点は以下の通り。

  • かな配列で長音記号に対応
  • かな配列で送り仮名の切り出しに失敗するバグの修正
  • 行頭に現れる単語列に学習結果がきちんと反映されないバグの修正

ダウンロードはこちらから。次の締切は 3/12 なので、それまでにはショートカットの設定画面を仕上げたいところです。

libkkc update week two

libkkc 0.1.6, libkkc-data 0.1.6, ibus-kkc 1.5.6 をそれぞれリリースしました。

最近のパソコンは高速なので、通常の日本語入力では問題ありませんが、ibus-kkc で長い文章を入力すると、たまにもたつくことがありました。試しに3000文を変換するベンチマークをとってみると、

real user system
libkkc 0.1.5 282.17 279.95 0.07
Anthy 133.92 132.19 0.58

Anthy に2倍以上の差をつけられています。

gprof でプロファイルをとってみると、言語モデルから 2-gram を引く処理が全体の 67% を占めています。libkkc は この論文 の SORTED と呼ばれるタイプの索引を使っており、検索は二分探索なので、要素が多いとそれなりに時間がかかります。

高速化の手始めに、検索に使われるキーの傾向を調べてみると、3-gram のコストを求めるのに直近の検索キーを何度も検索していることがわかりました。そこで、直前に使われたキーと値のペアを一組だけ記憶しておくようにすると、これだけで 30% ほど速くなりました。

また、検索の成功・失敗の回数を数えてみると 95% 失敗しています。これは ブルームフィルタであらかじめ失敗判定をしてやれば大幅に高速化できそうに思えたのですが、そこまで思惑通りには行かず、20% ほどの速度向上にとどまりました。ハッシュの計算 (murmur hash 3 を利用) が悪いのかな?


real user system
libkkc 0.1.6 130.11 129.15 0.05
libkkc 0.1.5 282.17 279.95 0.07
Anthy 133.92 132.19 0.58

Anthy と同じくらいにはなりました。

libkkc update week one

前回の続きです。一週間ほど毎日、新聞の社説を打ち込むなどして、使いにくいと感じたところを直してきました。最新版はそれぞれ、libkkc 0.1.2, libkkc-data 0.1.1, ibus-kkc 1.5.5 です。ダウンロードはこちらから。

主な変更点は以下の通りです。キーバインディングは wiki に少し書いてあります。

  • 直接入力モードの追加 (Alt-grave または Alt-@)
  • 単語登録機能 (選択 + Alt-r + 見出し語入力&変換確定)
  • 言語モデルの精度向上
  • 連分節辞書のバグ修正

参考程度ですが、libkkc 0.1.2, libkkc-data 0.1.1 を使って、こちらと同様の評価を行なったところ、結果は以下のような感じでした。

precision recall
libkkc 91.7% 91.4%
Anthy 82.8% 83.2%

アルゴリズムは比較的単純な単語 3-gram で、単語の表記だけを素性として用いています。

文の学習についても、再現性を重視して特に目新しいことはしていません。文節の区切り位置と、それぞれの文節に割り当てられた単語を 2-5 gram 覚えておき、次の変換時に1単語ずつずらしながらマッチングしていくだけ。また、文字数があまりに短い N-gram は学習しません。たとえば、未学習の Anthy では、「ほんをかう」→「本を買う」、「いぬをかう」→「犬を飼う」、と正しく変換されますが、いちど間違って「本を交う」のように変換してしまった場合、以降「いぬをかう」も「犬を交う」のように変換されてしまう問題があります。libkkc では「を」「か」「う」のような、3文節ではあるが全体の文字数が3文字と少ない N-gram は学習の対象としないので、このようなことは起こりません。

因みに私は 15 年来の SKK ユーザーなので、使える場面では積極的に単漢字変換を使う設計になっています。たとえば、「しぶや」を Anthy で変換すると「支部や」になりますが、libkkc は最初に文全体を単語とみなして辞書を引くので、こういうことは起こりません。また、徳永さんの本では第一候補以外の文を得るために後ろ向き A* 探索を使うアイデアが示されていますが、実際の IME では文全体について複数の候補を表示することは稀なので、実装してはみたものの使っていません。かわりに各分節について単漢字変換の候補を提示するようになっています。

ibus-skk now rewritten in Vala

ibus-skk is an IBus IME implementing SKK (abbrev of Simple Kana Kanji), an alternative Japanese input-method popular for geeks. ibus-skk was originally developed in Python in 2009 and recently had become an unmaintainable mess because the SKK algorithm was not that simple as I thought.

I decided to rewrite it in Vala to separate out reusable component into a GObject-based library called libskk. Today I released the first Vala version of ibus-skk, it is still experimental but basically works.

Here are Fedora packages and the source code.

As a bonus, it gets more sophisticated dictionary selection UI, which allows both file and network dictionaries to be used simultaneously: