Zinnia: 機械学習ベースのポータブルなオンライン手書き文字認識エンジン

[日本語][英語]

Zinniaは機械学習アルゴリズム SVM を用いたポータブルで汎用的な オンライン手書き文字認識エンジンです。Zinniaは組み込みの容易さと汎用性を高めるために、 文字のレンダリング機能は持っていません。Zinniaは文字のストローク情報を座標の連続として受け取り、 確からしい順にスコア付きでN文字の認識結果を返すだけに機能を限定しています。 また、認識エンジンは完全に機械学習ベースであるために、文字のみならずユーザの任意のマウス・ペンストロークに対して任意の文字列をマッピングするような認識エンジンを小コスト作成することができます。

主な特徴

ダウンロード

インストール

UNIX

一般的なフリーソフトウェアと同じ手順でインストールできます.

 % tar zxfv zinnia-X.X.tar.gz
 % cd zinnia-X.X
 % ./configure
 % make
 % su
 # make install

認識モデルのインストール

% tar zxfv zinnia-tomoe-XXXX.tar.gz
% zinnia-tomoe-XXXX
% ./configure
% make
% su
# make install

Windows

コンパイル済みのDLLと実行型式がzipファイルの中に含まれています。 認識用モデルは含まれていません。認識モデルをダウンロードし、 テキストモデルファイルをzinnia本体と同じディレクトリに展開した後、以下のコマンドを実行しモデルを生成してください。

bin\zinnia_convert.exe handwriting-ja.model.txt handwriting-ja.model
bin\zinnia_convert.exe handwriting-zh_CN.model.txt handwriting-zh_CN.model

使い方 (認識)

ZinniaはC/C++のライブラリを提供しています。また, SWIGを通して Perl/Ruby/Python から利用することも可能です。

C言語によるサンプル

#include <stdio.h>
#include "zinnia.h"

int main(int argc, char **argv) {
  size_t i;
  zinnia_recognizer_t *recognizer;
  zinnia_character_t *character;
  zinnia_result_t *result;

  recognizer = zinnia_recognizer_new();

  if (!zinnia_recognizer_open(recognizer, "/usr/local/lib/zinnia/model/tomoe/handwriting-ja.model")) {
    fprintf(stderr, "ERROR: %s\n", zinnia_recognizer_strerror(recognizer));
    return -1;
  }

  zinnia_character_t  *character  = zinnia_character_new();
  zinnia_character_clear(character);
  zinnia_character_set_width(character, 300);
  zinnia_character_set_height(character, 300);
  zinnia_character_add(character, 0, 51, 29);
  zinnia_character_add(character, 0, 117, 41);
  zinnia_character_add(character, 1, 99, 65);
  zinnia_character_add(character, 1, 219, 77);
  zinnia_character_add(character, 2, 27, 131);
  zinnia_character_add(character, 2, 261, 131);
  zinnia_character_add(character, 3, 129, 17);
  zinnia_character_add(character, 3, 57, 203);
  zinnia_character_add(character, 4, 111, 71);
  zinnia_character_add(character, 4, 219, 173);
  zinnia_character_add(character, 5, 81, 161);
  zinnia_character_add(character, 5, 93, 281);
  zinnia_character_add(character, 6, 99, 167);
  zinnia_character_add(character, 6, 207, 167);
  zinnia_character_add(character, 6, 189, 245);
  zinnia_character_add(character, 7, 99, 227);
  zinnia_character_add(character, 7, 189, 227);
  zinnia_character_add(character, 8, 111, 257);
  zinnia_character_add(character, 8, 189, 245);

  result = zinnia_recognizer_classify(recognizer, character, 10);
  if (result == NULL) {
    fprintf(stderr, "%s\n", zinnia_recognizer_strerror(recognizer));
    return -1;
  }
  
  for (i = 0; i < zinnia_result_size(result); ++i) {
    fprintf(stdout, "%s\t%f\n",
            zinnia_result_value(result, i),
            zinnia_result_score(result, i));
  }

  zinnia_result_destroy(result);
  zinnia_character_destroy(character);
  zinnia_recognizer_destroy(recognizer);

  return 0;
}

% gcc example.c -lzinnia -o zinnia_sample
% ./zinnia_sample

簡単な解説

C++によるサンプル

#include <iostream>
#include "zinnia.h"

int main(int argc, char **argv) {
  zinnia::Recognizer *recognizer = zinnia::Recognizer::create();
  if (!recognizer->open("/usr/local/lib/zinnia/model/tomoe/handwriting-ja.model")) {
    std::cerr << recognizer->what() << std::endl;
    return -1;
  }

  zinnia::Character *character = zinnia::Character::create();
  character->clear();
  character->set_width(300);
  character->set_height(300);
  character->add(0, 51, 29);
  character->add(0, 117, 41);
  character->add(1, 99, 65);
  character->add(1, 219, 77);
  character->add(2, 27, 131);
  character->add(2, 261, 131);
  character->add(3, 129, 17);
  character->add(3, 57, 203);
  character->add(4, 111, 71);
  character->add(4, 219, 173);
  character->add(5, 81, 161);
  character->add(5, 93, 281);
  character->add(6, 99, 167);
  character->add(6, 207, 167);
  character->add(6, 189, 245);
  character->add(7, 99, 227);
  character->add(7, 189, 227);
  character->add(8, 111, 257);
  character->add(8, 189, 245);

  zinnia::Result *result = recognizer->classify(*character, 10);
  if (!result) {
     std::cerr << recognizer->what() << std::endl;
     return -1;
  }
  for (size_t i = 0; i < result->size(); ++i) {
    std::cout << result->value(i) << "\t" << result->score(i) << std::endl;
  }
  delete result;

  delete character;
  delete recognizer;

  return 0;
}

zinniaはオンライン手書き文字認識が行えるため、 ユーザの不完全な入力(入力途中の文字)についてもある程度認識可能です。

S式による文字表現

文字クラス(zinnia_character_t / zinnia::Character)の座標情報はS式形式の文字列として設定することができます

(character
 (width キャンバス幅)
 (height キャンバス高さ)
 (strokes
   ((0画目x 0画目y) ... (0画目x 0画目y))
   ((1画目x 0画目y) ... (1画目x 1画目y))
   ((2画目x 2画目y) ... (2画目x 2画目y))
   ...))

static const char sexp[] = "(character (width 1000)(height 1000)(strokes ((243 273)(393 450))((700 253)(343 486)(280 716)(393 866)(710 880))))";
zinnia_character_t *character = zinnia_character_new();
zinnia_character_parse(character, sexp);
static const char sexp[] = "(character (width 1000)(height 1000)(strokes ((243 273)(393 450))((700 253)(343 486)(280 716)(393 866)(710 880))))";
zinnia::Character *character = zinnia::Character::create();
character->parse(sexp);

文字の学習

学習には、学習させたい文字とそれに対応するストローク情報が必要です。 学習データは以下のようなS式で記述します。1つの文字に複数のストローク情報 があっても問題ありません。一般的にデータが増えれば増えるほど認識精度が向 上します。Zinniaはバッチ学習のみをサポートします。

S式フォーマット

(character
 (value 認識したい文字)
 (width キャンバス幅)
 (height キャンバス高さ)
 (strokes
   ((0画目x 0画目y) ... (0画目x 0画目y))
   ((1画目x 0画目y) ... (1画目x 1画目y))
   ((2画目x 2画目y) ... (2画目x 2画目y))
   ...))

(character (value あ) (width 300) (height 300) (strokes ((54 58)(249 68)) ((147 10)(145 201)(182 252)) ((224 103)(149 230)(82 240)(53 204)(86 149)(182 139)(240 172)(248 224)(228 250))))
(character (value い) (width 300) (height 300) (strokes ((56 63)(43 213)(67 259)(94 243)) ((213 66)(231 171)(208 217))))
(character (value う) (width 300) (height 300) (strokes ((102 35)(187 45)) ((73 121)(167 105)(206 139)(198 211)(135 275))))
(character (value え) (width 300) (height 300) (strokes ((140 19)(162 38)) ((62 105)(208 100)(51 263)(128 205)(188 268)(260 252))))
(character (value お) (width 300) (height 300) (strokes ((64 94)(240 98)) ((148 35)(159 129)(140 199)(105 247)(64 228)(101 161)(192 161)(222 223)(189 257)) ((223 49)(253 89))))

例として zinnia-tomoe の中にある *.s ファイルをご覧ください。

ファイルが準備できたら、zinnia_learn コマンドを使い、学習を行います。

% zinnia_learn train-file model-file
...
learning: (8/3033) 8 ....
learning: (9/3033) 9 ...
learning: (10/3033) 々 ............
learning: (11/3033) 〆 ....
learning: (12/3033) あ ....
learning: (13/3033) い .......
learning: (14/3033) う ...............
learning: (15/3033) え ......................................................................................
learning: (16/3033) お ......
learning: (17/3033) か .......
learning: (18/3033) き .......
learning: (19/3033) く .......

train-file が学習データ、model-file がモデルファイルです。

学習が正常かどうかテストを行います

% zinnia -m model-file < test-file

model-file は学習後にできあがったモデル、test-file はS式で書かれたテスト ファイルです。学習データそのものをテストしたい場合は、test-file を学習デー タにします。

学習後,model-file.txt (テキストファイル)と model-file (バイナリファイル)が作成 されます。テキストファイルはアーキテクチャに非依存の形式、model-fileは アーキテクチャに依存したモデルファイルです。zinniaの認識エンジン (Zinnia::Recognizer)はバイナリ形式のみを受け付けます。別のプラットホーム 上にバイナリモデルをポーティングする場合は、ターゲットのマシンで txt ファイルからバイナリファイルに変換を行います。

% zinnia_convert model-file.txt model-file

また、テキストモデルをCのヘッダに変更することも可能です

% zinnia_convert --name=my_model --make_header model-file.txt my_model.h

ヘッダファイルを使う場合認識エンジン側では、my_model.h をincludeし、 zinnia_recognizer_open_from_ptr() を使ってモデルをロードします。 my_model(モデルの実態) と my_model_size(モデルのサイズ)が my_model.h に 定義されています)

#include "my_model.h"
if (!zinnia_recognizer_open_from_ptr(recognizer, my_model, my_model_size)) {
  /* error */
}

モデルの圧縮

絶対値の小さい重みを省略することで、認識精度を犠牲にしながら、モデルを不可逆圧縮することができます。

% zinnia_convert -c 0.01 model-file.txt model1
% zinnia_convert -c 0.001 model-file.txt model2
% ls -h -l model*
-rw-r--r-- 1 taku taku 3.1M 2008-07-14 12:50 model1
-rw-r--r-- 1 taku taku  25M 2008-07-14 12:50 model2

c は重みの閾値です。デフォルトは 0.001 です。 大きく設定するとよりアグレッシブに圧縮を行います。実行環境に応じて圧縮率を選択してください。

全APIのリスト