名古屋で数学するプログラマ(仮)

@antimon2 が趣味兼一部本職の数学で何かするときのブログ。

ナイーブベイズフィルターの実装と考察

やっぱ実家はいろいろ捗る。洗濯とか料理とか自分では何もやらなくていいから。
というわけで、前回の勉強の続き、予告通りナイーブベイズフィルターを実装してみました。

実装

と言っても、実装に関しては手抜きです。
gihyo.jpの連載機械学習 はじめようの、第3回「ベイジアンフィルタを実装してみよう」のサンプルコードをほぼそのまま流用しました。
ただし。以下のような手を加えています。

  • せっかくなので別言語(=Ruby)に翻訳することで、一応自分でコードを写経して覚えるという行動を取る。
  • せっかくなので先日インストールした MeCab を利用するようにコードを修正する。

どちらもすんなりうまくいったのですが、後者でちょっと躓きました。それは追々触れていきます。

まずは翻訳(写経)*1

あ、もちろん morphological.rb の動作には MeCabMeCab Ruby のインストールが前提です。

さて、これをそのまま動かしてみたのですが…。

$ ruby naivebayes.rb
ヴァンロッサム氏によって開発されました. => 推定カテゴリ: Ruby
豊富なドキュメントや豊富なライブラリがあります. => 推定カテゴリ: Python
純粋なオブジェクト指向言語です. => 推定カテゴリ: Ruby
Rubyはまつもとゆきひろ氏(通称Matz)により開発されました. => 推定カテゴリ: Ruby
「機械学習 はじめよう」が始まりました. => 推定カテゴリ: 機械学習
検索エンジンや画像認識に利用されています. => 推定カテゴリ: 機械学習

最初の文章が、期待通り「Python」に分類されず「Ruby」と分類されてしまっています。
他の「『機械学習 はじめよう』のサンプルを試してみた」サイトをいくつか見てみても、きちんと Python と分類されているところしか見付からないのに、なぜだろう。ロジックは間違っていないし。
ということで、色々見てみました。

MeCab形態素解析の精度、あるいは、クセ?

色々調べた結果、以下のことが分かりました。

まず、PythonRuby に翻訳したコードには問題なし(ロジック的に)。
主な原因は全て、MeCab 側にある、と。
その問題点は、いくつか出てきたのですが大きなのは以下の2つ。

  • 人名「グイド・ヴァンロッサム」が、この塊で一般名詞として認識されている。
    • 特に「ヴァンロッサム」が単語として認識されていないのでカウントに引っかからない。
  • 日本語的には助動詞の「れ(る)」「られ(る)」が、動詞(接尾)として認識されている。
    • 動詞は検索対象なので、これが含まれる量がカテゴリ推定に大きく左右されてしまっている。

つまり簡単に言うと、

  • 「ヴァンロッサム」という単語が出てきたがこれは学習データに存在しない → 推定材料にならない。
  • 「開発された」の「れ(動詞)」が、Ruby カテゴリの学習データに一番多く存在している → 推定材料。

ということで結果として Ruby に推定分類されてしまった、というわけ。

MeCab 側の解決方法模索

まず、MeCab の解析精度にも問題があることは確かです。
考えてみれば先日インストールしてから全然触ってないですし、辞書も mecab-ipadic をデフォルトのままいじってないですから。
あと morphological.rb での品詞分類も MeCab デフォルトの品詞分類に基づいたモノになっています。

このあたりの、設定を弄るなり、学習データを用意して学習させるなり、mecab-dic-overdrive等のツールを使って辞書へのパッチ適用・ノーマライズ、ユーザ辞書の拡充、などを行っていくべきでしょう。行く行くは。

ま、それはそれとして、課題として残しておいて、今回は別のアプローチを考えます。

学習

MeCab にも「学習課題」が出てきたわけですが、そっちのせいにしないで、自分自身にもっと何かできることを探す、というのがやっぱ筋でしょうね。
じゃ、今回実装したフィルターに、「学習」させれば良い。
そもそも現在の学習データが各カテゴリ1件ずつというのは少なすぎますからね。
連載記事の最後の方にもきちんと書いてあります。

訓練データが増えることによって,より正確な分類ができるようになるので興味のある方はご自身で試してみてください

ということで、訓練データを追加してみましょう。
というか、naivebayes.rb には追加訓練データがコメントアウトしてあります。

  #nb.train("ヴァンロッサム氏によって開発されました。", "Python")

そう、正しく分類されなかったら、その正しい分類先を教えてやる。これが機械学習の「学習」の基本。

ということでこのコメントアウトを外して実行してみました。

$ ruby naivebayes.rb
ヴァンロッサム氏によって開発されました. => 推定カテゴリ: Python
豊富なドキュメントや豊富なライブラリがあります. => 推定カテゴリ: Python
純粋なオブジェクト指向言語です. => 推定カテゴリ: Ruby
Rubyはまつもとゆきひろ氏(通称Matz)により開発されました. => 推定カテゴリ: Ruby
「機械学習 はじめよう」が始まりました. => 推定カテゴリ: 機械学習
検索エンジンや画像認識に利用されています. => 推定カテゴリ: 機械学習

無事、同文章が Python に分類されました(^-^)

でも、これで「めでたしめでたし」じゃないんですけどね。「れ(る)」「られ(る)」問題はまだ残ってます。
例えば「必要に迫られて開発された発明品。」という文章*2を分類してみると、この時点での結果は「Python」となります。
同じ文章を、先ほどコメントアウトを外した追加訓練データをもう一度コメントアウトして実行すると、今度は「Ruby」になります。

ここまで来ると、やっぱり MeCab 側の調整か、プログラム側の調整が必要になってくるでしょうね。
元々連載記事の方にも、推定フェーズの出現頻度のところで「ゼロ頻度問題」の解決策とその精度問題のことが取り上げられています。今回は同連載のコードをそのまま翻訳したので、決して精度の高くない「加算スムージング」という補正方法を用いています。これをもっと精度の良いモノに置き換えることで解決する問題とかもきっと出てくるでしょう。

まとめ

勉強と言っても今回はほぼ写経で終わりましたが、これで大体「どういうことをすれば単純分類器が作れるか」ということは掴めたと思います。
もっと実用的な or 個人的ニーズのある文例の分類を試しながら、色々調整していこうと思います。

次回

次…どうしよう。
連載機械学習 はじめようの流れに沿って、次の記事、理論編の分布の話を掘り下げてみるか。
それとも、ここでもうちょっと統計よりに方向転換を図るか…。

*1:gist に上げてありますので、ご自由に Fork してご使用ください。もっとこうしたら良いよとかあればそれも大歓迎。

*2:この例文には意味はありません。主に「れ(る)」「られ(る)」によって分類先の推定が左右されることを見るためだけの例文です。