Figure 1: IME(例、Gboard)は 漢字の選択肢を表示
Figure 1: IME(例、Gboard)は 漢字の選択肢を表示



入力処理を行う IME アプリには、下記のような情報が保存されています。
  1. IME内のメニュを表示するための一般的な「漢字↔よみがな」の対応情報
  2. ユーザーが選択した変換の履歴に基づいた「漢字↔よみがな」の対応情報
上記の 1 と 2 の情報は、IME のデータベースで管理されます。このデータベースは、ユーザーの入力や IME の人工知能によって、改善されてゆきます。

つまり IME は「推測しにくい」よみがなを、ユーザーの文字入力によって、簡単に取得することができるように作られているのです。また、そのよみがなは、漢字の情報とともにデバイス上でデータベース化されます。

Q:その貴重なデータをアプリケーションと提供する方法はないのでしょうか?
A:はい!この記事を書いているのは日本語を利用する Android アプリ開発者の皆様にそれをご紹介するためです。Gboard で漢字の変換データをアプリと共有する API が利用可能になりました。

Figure 2: 漢字入力とともに、よみがな情報を提供するIME



この Gboard(v10.1 以降)に追加された API は、IME のテキスト出力に、よみがなを span として追加します。この API を利用するアプリケーションは、その出力を拾うことで、高い精度でユーザーの入力した文章に該当するよみがなを推測できるようになります。

よみがな情報を取得するためには、漢字入力の対象となる TextEdit が下記の条件を満たす必要があります:

  • ア)inputType プロパティの値を textPersonName にする。
  • イ)privateImeOptions プロパティの値を com.google.android.inputmethod.latin.requestPhoneticOutput にする。
Android Studio の Layout エディターで見た場合、設定は下記のようになります。

Figure 3: Android Studioのプロパティ設定

次に、TextWatcher を宣言し、入力通知の際、CharSequence から、getSpans() によって該当する TtsSpan を取得します。
public class MainActivity extends AppCompatActivity {

    private class PhoneticRetriever implements TextWatcher {
        // Extracts phonetic metadata from an incoming text blob
        private String getPhoneticMetadata(CharSequence s) {
            String phonetic = null;
            if (s instanceof SpannableStringBuilder) {
                SpannableStringBuilder textAsSpan = (SpannableStringBuilder) s;
                TtsSpan[] allSpans = textAsSpan.getSpans(0, s.length(), TtsSpan.class);
                if (allSpans.length == 1 && allSpans[0].getType().equals(TtsSpan.TYPE_TEXT)) {
                    // log shows where the span is in the text
                    Log.v("PHON",
                            s.toString() + " [" + textAsSpan.length() + "] start:" +
                            textAsSpan.getSpanStart(allSpans[0]) + " end:" +
                            textAsSpan.getSpanEnd(allSpans[0]));
                    phonetic =  allSpans[0].getArgs().getString(TtsSpan.ARG_TEXT);
                    textAsSpan.removeSpan(allSpans[0]); // avoid consuming again
                }
            }
            return phonetic;
        }
        @Override
        public void afterTextChanged(Editable s) {
            String sphonetic = getPhoneticMetadata(s);
            if (sphonetic == null) {
          // no phonetic data
            } else {
                // phonetic data is in sphonetic - use it as you like
            }
        }

        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {}

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {}
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        EditText etUserInput = (EditText)findViewById(R.id.editTextPhoneticWithOptions );
        etUserInput.addTextChangedListener(new PhoneticRetriever());
    }
}

このサンプルコードでは、afterTextChanged() のコールバックで TtsSpan を取得しています。TtsSpan.ARG_TEXT がよみがなで、getSpanStart() と getSpanEnd() を利用すれば、入力された CharSequence の中で取得したよみがなの位置を取得することができます。

実行可能なコードはこちらのレポジトリをご参照ください。
 → https://github.com/googlesamples/gboard-dev-samples/tree/main/GetIMEPhoneticName

同じコードを使ったサンプルアプリケーションを Play Store からインストールすることも可能です。
 → https://play.google.com/store/apps/details?id=com.ragingsamples.getimephoneticname

Google コンタクト アプリ(v3.37 以降)では、連絡先を編集する際、この API を使って漢字名に該当するよみがなが自動入力されます。自動入力の結果が誤っている場合でも、ユーザーは手動でよみがなを直すことができます。

Figure 4: Google コンタクト アプリのよみがな推測

この API を利用して取得したよみがなの使い道はみなさんの作るアプリ次第ですが、いくつか参考になるユースケースをご紹介します:
  • ユーザーの入力した名前から自動的によみがなを推測する。2021 å¹´ 5 月現在、この API は 名前にのみ対応しているため、ショッピング アプリや銀行アプリ、カスタム CRM アプリ、 SNS アプリなどで利用することができます。
  • 他の IME が Gboard と同じ API を提供することで、より多くのユーザーによみがなのサポートを提供する。

ほかのユースケースのご提案、名前以外に将来的にサポートして欲しい入力タイプ、改善点などありましたら、ぜひフィードバックをお願いします。


Posted by Alex Gimenez - Technical Solutions Consultant

システムの概要図


機械学習モデルの構築


訓練データの収集


今回私たちが立ち向かうタスクは、入力されたキーのシーケンスからユーザーが意図する文字を予測することです。オンライン手書き文字認識に似たタスクではあるものの、その多くはペンタブレットやタッチスクリーンでの入力を前提としています。そのため、キーボードのように低解像度の入力に対応した機械学習モデルを構築するためには、一から訓練データを収集する必要がありました。

データを収集するため、私たちはまずデータ収集用のウェブアプリをつくることから始めました。入力する文字を指定してキーボード上にその文字を書くことで、入力されたキーのシーケンスが保存されるシンプルなアプリです。効率的なデータ収集のために、慣れた方には画面をタップすることで入力終了を判定する待ち時間をスキップする機能も搭載しました。その結果、プロ級の方になると 1 分間あたり約 60 個のデータを入力することが可能になり、高速なデータ収集が可能になりました。



人手によるデータ入力にはミスがつきものです。異常なデータをすばやく見つけて取り除けるよう、入力シーケンスの可視化インターフェイスも実装しました。他にも入力データ数による Contributor ランキングや各文字の累計データ数の表示など、データの入力をモチベートする細かな仕組みを搭載しています。



リリース前には社内で筆跡データ収集会を実施し、有志の Google 社員にデータ入力を手伝ってもらいました。その結果、約 4 万 6000 件の訓練データを収集することができました。収集されたデータは GitHub にて公開しています。データ構造の詳細については README をご覧ください。



データの前処理


訓練に使うデータは、以下のような入力されたキーのシーケンスによって表現されます。以下はひらがなの「い」が入力された一例で、各タプルは keydown イベントが発生したキーと発生した時間をミリ秒で表しています。ここでは複数のキーが同時に押された際の keydown イベントと keyup イベントの対応を特定する煩雑さを避けるため、ここでは keydown イベントのみを使っています。

[("e", 0), ("d", 55), ("c", 102), ("u", 428), ("j", 507)]

私たちはこのシーケンスデータから高精度に文字を予測する機械学習モデルを模索しました。その過程でキーボード上でのキーの位置がキーだと気づき、シーケンスデータを画像情報に変換して畳み込みニューラルネットワーク(Convolutional Neural Network)を適用することにしました。以下では、シーケンスデータから画像情報を抽出する具体的な方法について説明します。

まず、シーケンスデータに含まれる各キー情報を JIS キーボードの QWERTY 配列を前提として x, y 座標情報に変換します。

QWERTY キー配列と x, y 座標系の対応


先ほどの「い」の例では、シーケンスが以下のように変換されます。

[((2.5, 1), 0), ((3.0, 2), 55), ((3.5, 3), 102), ((6.5, 1), 428), ((7.0, 2), 507)]

次に、x, y 座標に対して min-max 正規化を施し、各キーの x, y 座標を 0 から 1 の間に収めます。その結果、シーケンスデータは以下のように変換されます。

[((0.00, 0.00), 0), ((0.11, 0.50), 55), ((0.22, 1.00), 102), ((0.89, 0.00), 428), ((1.00, 0.50), 507)]

最後に、シーケンス内の各点を順々につなげるようにして、16px 四方の正方形のキャンバスの上に描写します。


「い」を描画した例


指がキーボードから離れた区間については、ユーザーの指の動きを特徴量に組み込むために半分の濃さの線で描画しています [1]。物理手書き入力では、指がキーボードから離れたことを keydown イベントから特定する必要があります。ここでは keydown イベントが発生した間隔に着目し、あるキー間の keydown イベントの間隔が他の間隔よりも有意に大きい場合には指が離れたものと判定しています。

MobileNet によるモデルの軽量化


ニューラルネットワークモデルによる認識精度を向上するためには、ニューラルネットワークを多層にして表現力を向上することが有効です。一方で、ニューラルネットワークを多層にすると学習すべきパラメーターの数が増えてしまい、モデルのファイルサイズも大きくなってしまいます。学習済みモデルを Raspberry Pi Zero のような組み込み向けモジュールで使うことや、ブラウザにダウンロードして使うことを考えると、モデルのファイルサイズが大きくなってしまうことは好ましくありません。

そこで私たちは Google が提唱する軽量なニューラルネットワークモデル、MobileNet に用いられている分離式畳み込み層 (separable convolutional layer) を応用することでモデルの軽量化を図りました。分離式畳み込み層は、畳み込み演算を空間方向の畳み込み (depth-wise convolution) とチャンネル方向の畳み込み (point-wise convolution) に分けることによって、学習するパラメーターの数を減らします [2]。分離式畳み込み層を使うことによって、通常の畳み込み層による実装では数 MB になってしまうモデルのファイルサイズを、同程度の精度を維持しながらも 150 キロバイト程度にまで減らすことができました。これにより、後述するハードウェアとブラウザでの高速なモデルの読み込みが可能になりました。

ドメイン知識の活用


上記の方法だけでも文字を認識する機械学習モデルを構築できたものの、精度の面で課題が残りました。たとえば、単純にストロークを画像情報に変換するだけでは、右から左に書かれた線と、左から右に書かれた線を区別できなくなります。また、キーボードから与えられる位置情報は低解像度のため、本来は異なる線が重なって描写されてしまうこともしばしばです。こういったデータの前処理で落ちてしまう情報が原因となって、満足する精度を達成することができませんでした。ここでは、これらの課題を解決するために導入した工夫を紹介します。

まず、私たちは文字を構成する線を 8 方向に分解する方向分解特徴 (directional feature) を導入しました。方向分解特徴は手書き文字認識における重要なドメイン知識のひとつであり、入力データをスパースにすることで小さな画像の中にも効率的に情報を保持できることが知られています [1]。私たちは、単一のチャンネルに画像情報を変換するのではなく、方向分解を施すことで 8 チャンネルの画像に変換するアプローチを採用しました。


「あ」と書かれたストロークの方向分解特徴


方向分解特徴を採用しても、低解像度のために重なってしまう線については課題が残りました。たとえば「は」と「ほ」は画数が異なるものの、「ほ」の 3 画目が 2 画目と重なってしまうことが多く観測されました。このような課題を解決するためには、書き始めから書き終わりまでのどのタイミングで線が書かれるのか、という情報が有効に働くと考えました。

そこで導入したのが時間的特徴(temporal feature)です。方向分解特徴を表す 8 チャンネルに加えて、だんだん線が薄くなる画像とだんだん線が濃くなる画像を加えることで、全体的な時間変化を表した特徴を導入しました。既存の 8 チャンネルに加えてこの 2 チャンネルを加えることで、さらなる精度向上を実現しました。

「あ」と書かれたストロークの時間的特徴


下図に、各特徴量を利用したときの正確度の比較を示します。方向分解特徴、時間的特徴ともに文字認識精度の向上に効果があることが確かめられました。方向分解特徴が筆の動きを表すことで局所的な時間情報を組み込み、時間的特徴が書き始めから書き終わりまでの変化を表すことで大域的な時間情報を組み込むことで、精度向上に貢献していると考えられます。

確認用データセットにおける各特徴量の有無による正確度の比較


以上の特徴量を導入した結果、最終的な機械学習モデルは 16px 四方の正方形のキャンバスに対して 8 つの方向分解特徴と 2 つの時間的特徴を描画した計 10 チャンネルからなる画像を入力として受け取るものになりました。MobileNet のネットワーク構成を元にして、できるだけ少ない層の数で高い精度を実現する構成を模索した結果、以下のネットワーク構成に至りました。
  • 畳み込み層(カーネル: 3x3、フィルタ数: 32、ストライド: 2、活性化関数: Relu) 
  • 分離式畳み込み層(カーネル: 3x3、フィルタ数: 64、ストライド: 1、活性化関数: Relu) 
  • 分離式畳み込み層(カーネル: 3x3、フィルタ数: 128、ストライド: 2、活性化関数: Relu) 
  • 分離式畳み込み層(カーネル: 3x3、フィルタ数: 128、ストライド: 1、活性化関数: Relu) 
  • 全結合層


今回の訓練用プログラムは nazoru-input パッケージとして PyPi で公開されています。以下のように nazoru-input パッケージをインストールし、訓練データを指定して nazoru-training コマンドを実行することで、お手元の環境でも機械学習モデルを構築できます。今回用いた訓練データは https://github.com/google/mozc-devices/raw/master/mozc-nazoru/data/strokes.zip からダウンロードできます。

$ pip install nazoru-input
$ nazoru-training ./data/strokes.zip

この nazoru-training は標準的な TensorFlow の API を利用して実装されていますので、簡単にカスタマイズして利用していただけます。ハイパーパラメーターや入力データセットをコマンドラインオプションで変更することも可能です。詳しくは nazoru-training --help コマンドを参照してください。

ハードウェアでの推論


このようにして構築した機械学習モデルをユーザーが手軽に使えるようにするにはどうすればいいか、チーム内で活発な議論がなされました。パソコンやスマートフォンに特別なソフトウェアをインストールすることなくお気に入りのキーボードですぐに物理手書き入力を使えるようにするため、私たちは学習済みモデルを組み込んだハードウェア「物理手書きコンバーター」を製作することにしました。この物理手書きコンバーターは、訓練済みモデルを使って推論するプログラムを Raspberry Pi Zero にインストールしたものです。ここでは、物理手書きコンバーターを構築する手順を説明します。

推論用プログラムは訓練用プログラムと同様に nazoru-input パッケージ に含まれています。nazoru-input パッケージをインストールし nazoru-input コマンドを実行することで、お手持ちのコンピュータをつかって簡単に物理手書き入力を試すことができます。

$ pip install nazoru-input
$ nazoru-input


推論の機能を手軽に拡張して遊んでいただけるように、API もシンプルにしてあります。以下のようなコードを書くだけで学習済みモデルを利用した推論ができます。詳しくは nazoru-input のコードを参考にしてください。

from nazoru import get_default_graph_path
from nazoru.core import NazoruPredictor

predictor = NazoruPredictor(get_default_graph_path())

# [(key, ms_from_first_input)]
sample_input = [('e', 0), ('d', 100), ('c', 200), ('v', 300),
               ('u', 800), ('j', 900)]
results = predictor.predict_top_n(sample_input, 5)
for result in results:
 print('kana: %s, key: %s, probability: %.3f' % result)


物理手書きコンバーターは Raspberry Pi Zero に nazoru-input パッケージをインストールし、USB で接続されたキーボードから受け取ったキー情報を推論プログラムによって文字に変換、Bluetooth で出力することによって実装されています。このうち、キー入力から文字に変換する部分は基板がなくてもお試しいただけるので、ご家庭にある Raspberry Pi に nazoru-input ãƒ‘ッケージを入れるだけで手軽に手書き機能を利用したプログラムを実装することができます。

また、今回は物理手書きコンバーターのために製作した基板の設計図の CAD データを公開しました。この基板には Bluetooth 通信のためのモジュールやステータス表示のための LED が搭載されており、Raspberry Pi に接続することでパソコンやスマートフォンに Bluetooth を通じて文字を送信することが可能になります。利用する部品は Raspberry Pi Zero や RN42 といった通販や店頭で購入できるものに限定しました。Raspberry Pi Zero を利用することで大きさの制約も小さくしましたので、さまざまな形状のデバイスに組み込むことが可能です。CAD データを編集することで、自由に機能を変更、追加した基板も作製可能です。

さらに、公開されている基板には拡張性をもたせるために未接続のパッド、SPI・I2C 接続用のコネクタ、UART のための切断可能なパターンを用意してあります。これにより、モード切り替えスイッチを実装したり、SPI・I2C 接続のディスプレイによって軌跡を表示するなどの拡張が可能です。

物理手書きコンバーターのプリント基板図面


ブラウザでの推論


お手元に物理手書きコンバーターがない皆さまにもお手軽に物理手書き入力を楽しんでいただくため、私たちはブラウザ上で機械学習モデルが動作するデモを構築しました。TensorFlow で学習したモデルを TensorFlow.js に移植することでブラウザ上での推論を実現しています。

訓練用プログラム nazoru-training は訓練済みモデルを SavedModel 形式で出力します。SavedModel 形式で保存された訓練済みモデルを TensorFlow.js で読み込むには、TensorFlow.js converter を使って Web-friendly format に変換する必要があります。TensorFlow.js converter は変換のためのスクリプトと、変換済みモデルを読み込む Javascript API を提供するライブラリです。以下のコマンドのように tensorflowjs パッケージをインストールして tensorflowjs_converter コマンドを実行することで、訓練済みモデルを変換します。その際、変換するモデルの形式(この場合は SavedModel なので tf_saved_model)、出力層に相当するノードの名前(nazoru-training のデフォルト設定では Nazorunet/Predictions/Reshape_1)を指定します。

$ pip install tensorflowjs
$ tensorflowjs_converter \
   --input_format=tf_saved_model \
   --output_node_names='Nazorunet/Predictions/Reshape_1' \
   /nazorunet/saved_model \
   /nazorunet/web_model


変換が成功すると、tensorflowjs_converter は以下の 3 種類のファイルを出力します。
  • web_model.pb (the dataflow graph) 
  • weights_manifest.json (weight manifest file) 
  • group1-shard\*of\* (collection of binary weight files) 


ネットワークを構成する各パラメーターの重みは group1-shard\*of\* に記述され、モデルの大きさに応じて各ファイルが 4MB に収まるようにシャーディングされます。この工夫により、ブラウザにネットワークファイルがキャッシュされます。

あとは tfjs-converter npm パッケージをインストールし、以下のようにモデルを読み込めばブラウザでの推論ができます。

import * as tfc from '@tensorflow/tfjs-core';
import {loadFrozenModel} from '@tensorflow/tfjs-converter';
const model = await loadFrozenModel(
     '/nazorunet/web_model.pb',
     '/nazorunet/weights_manifest.json');
// Gets a canvas or image element.
const inputImage = document.getElementById('input-image');
const result = model.execute({input: tfc.fromPixels(inputImage)});
// Returns predicted probabilities for each class in a float array.
const probs = result.dataSync();

console.log(probs)  // Float32Array(88) [...]

アプリケーションによってはブラウザ上で高頻度で推論を繰り返すことも考えられるため、TensorFlow.js は効率的にメモリを管理するための便利なメソッドも用意しています。詳しくは Core Concepts in TensorFlow.js をご参照ください。

まとめ


以上、Gboard 物理手書き入力バージョンを支える機械学習技術を紹介しました。

「機械学習モデルの構築」では、効率的に訓練データを収集する仕組みの重要性について述べました。これまでにないタスクに取り組む場合、そもそも学習のためのデータがない場面も珍しくありません。そのような場合、データ収集を効率的に行うための仕組みづくり、インターフェイスの構築が重要になります。

ニューラルネットワークを多層にして表現力を豊かにすることがさまざまなタスクを解く上で有力である一方、機械学習応用の場が Raspberry Pi をはじめとする小型計算機やブラウザなどに広がっていることを考えると、高い精度を保持しながらもできるだけ軽量でシンプルな機械学習モデルを構築することは無視できない課題です。MobileNet をはじめとする軽量のネットワークモデルを採用すること、データの前処理に適切なドメイン知識を活用してモデルのサイズを保持しながらも精度を向上することが、このような課題に応える上で有効であることを示しました。

「ハードウェアでの推論」では、今回構築した機械学習モデルによる推論の方法を説明しました。Raspberry Pi を使ったデバイスの拡張方法についても述べ、推論プログラムに合わせてデバイスの振る舞いをカスタマイズする方法を紹介しました。

「ブラウザでの推論」では、TensorFlow.js を使ってブラウザで推論する方法を説明しました。TensorFlow.js Converter を使うことで簡単に訓練済みモデルの移植ができること、具体的な Javascript API の使用方法について述べました。

今回の記事が、さまざまなプラットフォームをサポートする機械学習システム構築の参考となれば幸いです。そして、Gboard 物理手書きバージョンが今後の文字入力のデファクト・スタンダードになる日がくることを願います。

参考文献


[1] Zhang, Xu-Yao, Yoshua Bengio, and Cheng-Lin Liu. "Online and offline handwritten chinese character recognition: A comprehensive study and new benchmark." Pattern Recognition 61 (2017): 348-360.
[2] Howard, Andrew G., et al. "Mobilenets: Efficient convolutional neural networks for mobile vision applications." arXiv preprint arXiv:1704.04861 (2017).


Written by:
- Shuhei Iitsuka - UX Engineer(Machine learning)
- Makoto Shimazu - Software Engineer(Hardware, System design)