Skip to content

kurgm/gwtegaki

Repository files navigation

グリフウィキ手書き検索

グリフウィキに登録された数十万のグリフを手書きで検索することができます。

検索画面:https://kurgm.github.io/gwtegaki/

gwtegaki

何のため?

(ここでは「字形」は筆画で構成される図形を指し,「グリフ」は 1 字形とその字形にグリフウィキ上で付けられた一意な名前(英数字列)の対を指します。つまり,「…の字形を持つグリフを見つける」とは,グリフの名前(英数字列)を見つけることを意味します。)

グリフウィキには大量のグリフが登録されており,この中からグリフを検索する手段がいくつか用意されています。しかしある特定の字形を持つグリフを探し出したいとき,探している字形によっては見つけ出すことが困難なことがしばしばあります。(詳しくはグリフウィキ部品検索#何のため?を参照してください。)

とくに,部分字形が一般的でない字形や,それ以上分解できないような字形を持つグリフを探したい場合は部品検索が役に立たないことがあります。

この「グリフウィキ手書き検索」は,字形からグリフを検索する一つの手段を提供し,他の検索方法では見つけられない・見つけにくいグリフを探すためのいわば補完的な役割を担います。

技術的な検討

「グリフウィキ手書き検索」を,手書きの字形を入力とし,それに対応するグリフ名を出力するというタスクとみなすことができる。

一般的な「文字認識」や「手書き入力」のタスクと大きく異なる性質がある:

一般的な文字認識 グリフウィキ手書き検索
出力 ある決まった文字集合(例えば JIS 漢字)に属する文字 内容が随時追加・更新されるグリフ集合に属するグリフの名前
字形の包摂 似た字形は同じ文字に包摂されている
= 出力で区別される文字は入力字形が(ある程度)異なっている
よく似た字形に異なるグリフ名が対応しうる(地域コードの有無など)
= 出力で区別されるグリフ名がよく似た入力字形を持ちうる
訓練データ 1 つの文字に対して多数の手書きデータが準備される 1 つのグリフ名に対して 1 つの字形しか準備されない

技術的な詳細

現在の「グリフウィキ手書き検索」を実現している技術について紹介します。

KAGE データ形式の字形データと手書き字形のデータを共通の特徴量ベクトル空間に埋め込み,近似最近傍探索 (ANN) を用いて手書き字形に似たグリフを高速に検索します。

ここで,手書き字形のデータは筆画の列として表され,それぞれの筆画は通過する座標点の列として表されます。

特徴量の抽出

グリフウィキ上に登録される KAGE データ形式の字形は一般的な筆順に従わない順序で記録されていることが少なくありません。加えて,ユーザーが手書き検索に頼るようなケースではその性質上,探している字形について一般的といえる筆順が客観的に明確でないことが多々あるでしょう。したがって,筆画同士の順序に依存しない特徴量を設計する必要があります。

現在のシステムでは,字形に含まれる各筆画から独立に特徴量として 394 次元のベクトルを抽出し,それらのベクトルの総和をとって字形全体の特徴量ベクトルとしています。

筆画から特徴量ベクトルを抽出する処理は,代表点の選出,中間特徴量の計算,ビニングによるヒストグラムの生成から構成されます。

詳細は model/src/model.rs のソースコードを参照してください。

代表点の選出

手書き字形のデータにおいては筆画は通過する座標点の列として表されます。前処理として筆画の座標点列から始点,終点,および中間点を代表点に選出します。

始点と終点は座標点列の最初と最後の点をそのまま選びます。中間点は,始点と終点を結ぶ直線から最も離れた座標点を選びます。中間点は曲線筆画の形状(右払いなのか止めなのか,あるいは右払いなのか折れなのか乙線なのか)を区別するために重要な情報です。直線筆画の場合は手書きのブレの影響を受けやすいため,曲がりの度合いが小さい場合は代わりに始点と終点の幾何学的な中点を中間点とします。

KAGE データ形式の字形データに対しては,曲線に対してはベジエ曲線の通過点を計算し,同様に始点,終点,中間点を選出します。

以降では,始点 → 終点,始点 → 中間点,中間点 → 終点 の 3 つのセグメントに対して特徴量ベクトルを計算し,それらをそれぞれ重み 1.0, 0.4, 0.4 を付けて総和したものを筆画の特徴量ベクトルとします。(中間点の座標は始点と終点に比べて字形のくせの影響を受けやすいと考えられるので,中間点を結ぶセグメントの重みを小さく設定していますが,その妥当性は実験的に確かめられたものではありません。)

中間特徴量の計算

セグメント $(p_x, p_y) \to (q_x, q_y)$ から以下の中間特徴量を計算します。

  • 絶対位置を表す特徴量: 4 次元ベクトル
    • セグメントの始点座標 $(p_x, p_y)$(2 次元)
    • セグメントの終点座標 $(q_x, q_y)$(2 次元)
  • 相対位置を表す特徴量: 4 次元ベクトル
    • セグメントの中点座標 $(\frac{p_x + q_x}{2}, \frac{p_y + q_y}{2})$(2 次元)
    • セグメントの長さ $(\sqrt{(q_x - p_x)^2 + (q_y - p_y)^2})$(1 次元)
    • セグメントの向き $(\mathrm{atan2}(q_x - p_x, q_y - p_y))$(1 次元)
      • 一般的な定義と異なり,下向きが 0°,右向きが 90°,左向きが −90°,上向きが ±180° となるようにしています。漢字の字形で通常現れる向きは右上〜右〜下〜左下であることを利用し,±180° に通常現れない上向きを割り当てることで,±180° の不連続性の問題を回避しています。

特徴量のビニング

中間特徴量は 1 つ 1 つのセグメントを表現するベクトルではありますが,そのままでは足し合わせて字形全体の 1 つの固定長ベクトルに要約することができません。 $n$ 次元ベクトルの中間特徴量を $n$ 次元空間における座標値とみなして,この $n$ 次元空間にある格子点(ビン)に割り振ることで $n$ 次元空間内のヒストグラムを生成します。ヒストグラム同士は足し合わせることができるので,字形全体の特徴を 1 つの特徴量に要約することができます。

ビンが配置される格子点の座標は次元ごとに定められた区間内の整数値の組になります。そのため,まず中間特徴量の各次元の値を,この区間に収まるように次元ごとにあらかじめ定められた固定値による除算とクランプ処理により正規化します。

  • 絶対位置を表す特徴量については,各次元で座標成分が {0, 1} を取る格子点を 4 次元空間内に $2^4 = 16$ 個配置します。
    • 字面の左上を (0, 0),右下を (1, 1) とする座標系にセグメントの始点と終点の座標をそれぞれ正規化します。
  • 相対位置を表す特徴量については,格子点の座標成分が取り得る整数値は以下の通りで,4 次元空間内に格子点を $3^2 \times 6 \times 7 = 378$ 個配置します。
    • セグメントの中点座標: 各次元で {0, 1, 2}
      • 字面の左上を (0, 0),右下を (2, 2) とする座標系にセグメントの中点の座標を正規化します。
    • セグメントの長さ: {0, 1, 2, 3, 4, 5}
      • 字面の対角線が長さ 5.5 に相当するように正規化します。
    • セグメントの向き: {0, 1, 2, 3, 4, 5, 6}
      • 左向きが 0,下向きが 2,右向きが 4,上向きが 6 となるように正規化します。

中間特徴量とそれぞれの格子点の距離を計算し,距離が小さいほど大きな重みを付けてその格子点に対応するビンに割り振ることで,平滑化されたヒストグラムを生成します。こうすることで,手書きのブレや位置や角度の違いに対して頑健な特徴量を得ることができます。

距離に応じた重みの計算に RBF(ガウシアン)カーネルを用いることでビンへのソフトな割り振りを実現しています。RBF カーネルのハイパーパラメーター $\gamma$ は現状 1 に固定されています。(パラメーターの調整による精度改善の余地があります。軸ごとに変えることもできそうです。)

なお,相対位置を表す特徴量は,そのセグメントの長さに応じてビンに加算される重み付けの係数を変えることで,長いセグメントが短いセグメントよりも大きく影響するように設計されています。これは,長いセグメントは字形の大きな構造を表すことが多く,短いセグメントは字形の細かい部分を表すことが多いためです。(係数計算のパラメーターは実験的に確かめられた最適値ではありません。)

こうして得られた 4 次元空間内のヒストグラムを (格子点の個数) 次元のベクトルに平坦化して(絶対位置と相対位置のベクトルを)連結し,そのセグメントを表す 16 + 378 = 394 次元特徴量ベクトルを得ます。重み付けをしてセグメントの特徴量ベクトルを足し合わせることで,最終的に字形全体の特徴量ベクトルが得られます。

格子点の個数の設計は,最終的な特徴量ベクトル空間における距離に,中間特徴量のどの次元の違いが大きく影響するかに直接的に関わります。現状の設計では相対位置の違いが絶対位置の違いよりも大きく影響するようになっています。(これについても実験的に確かめられた最適値というわけではなく,パラメーターの調整による精度改善や次元数削減の余地があります。)

技術スタック

  • 特徴量抽出 (model/) は Rust で実装されており,ブラウザ上で手書き入力された字形に同等の特徴量抽出を実行できるように,WebAssembly にもコンパイルされています。
  • インデックス構築 (build_index/) については,GlyphWiki のダンプデータから各グリフの特徴量ベクトルを計算する処理が Rust で実装されており,計算された特徴量ベクトルのストリームから HNSW インデックスファイルを構築する処理が Node.js で実装されています。
  • 検索バックエンド (backend/) は Node.js で実装されたウェブサーバーで,HNSW インデックスファイルをメモリ上に読み込み,入力された特徴量ベクトルとの近似最近傍検索を行うサービスを提供します。
    • Google Cloud の Cloud Run 上で動作し,ボリュームとしてマウントされた Cloud Storage バケットからインデックスファイルを読み込むように設計されています。
  • 検索フロントエンド (frontend/) は Astro + React で実装された静的なウェブページで,手書き入力された字形の特徴量ベクトルを検索バックエンドに送信し,検索結果を表示します。
    • GitHub Pages にデプロイされています。

ライセンス

MIT License (See LICENSE)

About

Hand-writing search for GlyphWiki

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages