「日本語テキストを分類するベイジアンフィルタ」を簡単につくるyo

数週間前の話になりますが、「はてブのリニューアル会見」の記事を読んでいたところ、はてブにも「自動カテゴライズによる記事分類」の機能が搭載されるとか。。。

同じようなタイミングで「似たようなモノ」というか「ほぼ同じようなモノ」を作っていたので、すごーくインスパイアされてしまいました。ジュワ〜。(アドレナリンの放出音)

数週間たってもいまだ興奮冷めやらぬ状態なので、今日はその件について書いてみようと思います。

Lingua::JA::Categorize - a Naive Bayes Classifier for Japanese document.
http://search.cpan.org/~miki/Lingua-JA-Categorize-0.00001/


はてブのパクリ」ではありません。「ベイジアンによる日本語テキスト分類器」を「簡単に作る」ことを目的としたモジュールです。

もう少し平たく言うと、「任意のカテゴリのセット」で「さくっと簡単に分類器を作る」ためのものであります。「手軽に分類器を作って試してみたいよー」という人向けのモジュールです。


なおリリースはしたものの、内部構造は中途半端な状態なので、APIはだいぶ変わるかもしれません。使っていただける方はその点にご注意ください。

なにが出来るの?

実はCPANにはAlgorithm::NaiveBayesというモジュールがありまして、これを使えば分類器の学習や判定部分については「おどろくほど簡単に」実装できてしまいます。

しかしながら、学習や判定部分は簡単に実装できたとしても、それ以外の部分、特に初期のベイズ学習の材料とすべき「適切な単語のデータセット」を準備するのは意外と面倒だし難しいタスクです。

例えば「野球」というカテゴリを作りたい場合、このカテゴリに分類するための学習データはどうやって用意しましょうか?

「野球に関する文書をどこかからガーっと集めてきて、この中から野球に関する単語をバーっと抽出して!」なんて長島さん的イメージで作業できてしまえば良いのですが、実際に手作業でこれらの単語の収集作業をやっていくのは、さすがにダルビッシュなわけです。

Lingua::JA::Categorizeはこの面倒な「初期学習」に関する作業を、generate() と唱えるだけで、あとは勝手にヨシナにやってくれます。(カテゴリに関する設定ファイルは必要だけどね)

またベイジアンフィルタは一般的に初期学習が終わった段階ではまだまだ精度は低い状態で、これを少しずつ鍛えてやる必要がありますが、この作業をスムーズに行うためのGUI画面(簡易なwebサーバ)を同梱しています。

この画面から「実際にどのような単語が特徴語として選ばれたか」「その結果どのカテゴリが候補としてあがってきたか」が視覚的に把握できます。

またこの分類結果が間違っていた場合、その画面から「補正的な学習」(正解のカテゴリを人間が教え直すこと)をボタン一発で実行することもできます。

(簡易GUIは、配布モジュール直下のegディレクトリの下にサンプル的に作って置いてあります。起動するとport8080でサーバが待っているのでブラウザでアクセスしてみてください。またegの下にはgenerate()のサンプルもあるので自分でカテゴリを決めて分類器を作ってみたい人は参考にしてください。)

作り方(初期学習)

初期学習はこれだけです。

use Lingua::JA::Categorize;

my $category_config = YAML::Load( join "", <DATA>);

my $c = Lingua::JA::Categorize->new;
$c->generate($category_config);
$c->save('save_file_name');

__DATA__
---
旅行・レジャー・生活 / 国内 / 中国・四国:
  keyword:
    - 旅行 中国 四国
  weight: 1
旅行・レジャー・生活 / 国内 / 九州・沖縄:
  keyword:
    - 旅行 九州 沖縄
  weight: 1
旅行・レジャー・生活 / 国内 / 北海道:
  keyword:
    - 旅行 北海道
  weight: 1
旅行・レジャー・生活 / 国内 / 国内:
  keyword:
    - 旅行 国内
  weight: 3

(以下省略)

generate()に渡しているcategory_configですが、これだけは個々に用意する必要があります。
この例だと「旅行・レジャー・生活 / 国内 / 中国・四国」まどの文字列が各「カテゴリ名」を表します。これらのカテゴリ名に対して「keyword」と「weight」が各々設定されているデータ構造だと思ってください。keywordはそのカテゴリに関する「シードキーワード」です。これをベースに関連語を膨らませます。またweightはデフォルト1で、この値を大きくするとそのカテゴリが選ばれやすくなります。

使い方(文書の分類)

これは本当に簡単です。これだけ。

use Lingua::JA::Categorize;

my $c = Lingua::JA::Categorize->new;
my $result = $c->categorize($text);
print Dumper $result->word_set; # 特徴語一覧
print Dumper $result->score; # 分類結果(1位から3位)

仕組み

分類部分のキモであるベイジアンフィルタについてはAlgorithm::NaiveBayesを丸々利用させてもらっています。

それ意外の部分、主に「任意のカテゴリで分類器をつくる部分」や「特徴的キーワードを抽出する部分」には拙作のLingua::JA::TFIDFLingua::JA::Expandを使っています。

これらは簡単に表現すると以下のようになります。

 * Lingua::JA::TFIDF
     ドキュメントから特徴語を抽出するモジュール
 * Lingua::JA::Expand
     検索エンジンのスニペットとL::J::TFIDFを使った関連語抽出モジュール

基本的にはこれらを使って初期学習用の単語データセットを生成して、Algorithm::NaiveBayesで学習させる、といった流れになります。

そう言った意味ではこのLingua::JA::Categorizeはそれ自体が「あたらしい何か」を提供するものではなく、ある意味「組み合わせの工夫」により「こんなに簡単にベイジアン分類器ができちゃうヨ」といった「組み合わせウェア」的なモノだと考えてください。

精度

精度については「どんなカテゴリの分類器をつくるのか」で大きく左右されてしまうので、一概に「良い」とか「悪い」とかは言えませんが、id:naoya氏もはてブリニューアルの会見で「ニュース記事などはだいぶ当たる」という主旨の発言をしていたと思いますが、うん、実際の感想として私も同感ですねぇ。

要するに「ある程度のボリューム」があり「まともな日本語」で書かれていて、かつ「論旨があっち行ったりこっち行ったりしない」ドキュメントであれば、かなりの確率で正解分野を判定できます。カテゴリの作り方によりますが、うまくすれば初期学習をしただけの状態でも8割〜9割ぐらいの精度が出ることも、ごく稀にですが、あります。

逆に言うと人間が読んでも「何書いてあるのかさっぱりわかんねぇー」的なブログなどは正直キツイです。こういったモノは当然精度悪いですね。


というわけで、まだ荒削りではありますが、興味のある方は使ってみてください。

感想とかあったらぜひお願いします。