いまさらNBSVMを調べてみた

kaggleをやっていてテキスト分類のコンペのkernelでNBSVMが使われていたので、勉強のために今更ながらNBSVMに関して調べてみた。

NBSVMはテキストデータのトピック分類や感情分類で比較的に精度が良いことを確認されているアルゴリズムで、このようなジャンルの機械学習モデルの精度基準に使われたりするらしい。kaggleコンペのベースラインkernelでも使われていたりする

NBSVMとは

Naive Bayes素性を利用したSVM(Support Vector Machine)のことである。と聞いてもあまりピンとこないが、ベイズの定理を使ってある分類に分類されやすいかどうかということを計算に変換してから、SVMのような分類器で学習させましょうということらしい。 例えば、機会学習アルゴリズムを用いて、投稿されたニュース記事を「経済」、「エンタメ」、「スポーツ」いずれかのニューストピックに分類したいときにNBSVMを使うことができる。

これからあるニュース記事のトピックを「経済」か「経済」以外かに分類するような機械学習モデルを作成することを想定し、既存の以下の記事(エンタメ記事 2件、経済記事 2件)を学習させる例を考えてみる。

・記事1(トピック:エンタメ)
ローラがファッション分野で引っ張りだこで、雑誌での露出が増えている。 
 
・記事2(トピック:経済)
新卒は電話対応が苦手らしい。  

・記事3(トピック:経済)
電話対応は新卒の仕事だ。  

・記事4(トピック:エンタメ)
新海誠が監督、脚本を務めた映画「君の名は。」が国内興行収入250億円を突破した。

ここで文章を以下のような単語の出現回数のベクトルに変換する。 (説明をわかりやすくするために助詞などトピックの種類とは関連がない単語を省いている。)

・記事1(トピック:エンタメ)
{ファッション: 1、雑誌: 1、露出: 1、新卒: 0、電話対応: 0、苦手: 0、
 監督: 0、映画: 0、興行収入: 0}  

・記事2(トピック:経済)
{ファッション: 0、雑誌: 0、露出: 0、新卒: 1、電話対応: 1、苦手: 1、
 監督: 0、映画: 0、興行収入: 0}  

・記事3(トピック:経済)
{ファッション: 0、雑誌: 0、露出: 0、新卒: 1、電話対応: 1、苦手: 0、
 監督: 0、映画: 0、興行収入: 0}    

・記事4(トピック:エンタメ)
{ファッション: 0、雑誌: 0、露出: 0、新卒: 0、電話対応: 0、苦手: 0、
 監督: 1、映画: 1、興行収入: 1}

次に、条件付確率を考慮して、このベクトルを変換する。(この変換がNBSVMという名前のNB(Naive Bayes)の部分)

記事が「経済記事」かどうかを判別したいこのケースで考慮に入れたいのは、「「経済」記事内のその単語の出現割合」と「「経済」ではない記事内のその単語の出現割合」なので、文章毎の単語の出現回数に  \displaystyle \frac{「経済」記事内のその単語の出現割合}{「経済」ではない記事内のその単語の出現割合} をかけてあげれば、「経済」記事の単語の重みが大きくなり、他のトピック記事に頻出する単語の重みを小さくすることができる。(ベイズの定理との関係は補足に記載)

しかし、ここで「「経済」ではない記事内のその単語の出現割合」が0の場合に割り算ができなくなるという不都合が起こる。なので、分母・分子それぞれに1を足して計算を行い、0割りを回避する。

全単語の出現割合を算出する必要があるが、ここでは「新卒」という単語の出現割合の計算式を確認する。
 「経済」記事内のその単語の出現割合  = \displaystyle \frac{「経済」記事にその単語が出現する回数 + 1( 2 + 1 =3)}{「経済」のドキュメントの数 + 1(2 + 1 =3)} = 1

 「経済」ではない記事内のその単語の出現割合  =\displaystyle \frac{「経済」ではない記事にその単語が出現回数 + 1( 0 + 1 =1)}{「経済」ではないドキュメントの数 + 1(2 + 1 =3)} = 1/3

上の2式より  \displaystyle \frac{「経済」記事内のその単語の出現割合}{「経済」ではない記事内のその単語の出現割合} = \displaystyle \frac{1}{1/3} = 3

「新卒」という単語以外でも同様にして計算を行い、文章毎の単語の出現回数に掛けると

・記事1(トピック:エンタメ)
{ファッション: 0.5、雑誌: 0.5、露出: 0.5、新卒: 0、電話対応: 0、苦手: 0、
 監督: 0、映画: 0、興行収入: 0}  

・記事2(トピック:経済)
{ファッション: 0、雑誌: 0、露出: 0、新卒: 3、電話対応: 3、苦手: 2、
 監督: 0、映画: 0、興行収入: 0}  

・記事3(トピック:経済)
{ファッション: 0、雑誌: 0、露出: 0、新卒: 3、電話対応: 3、苦手: 0、
 監督: 0、映画: 0、興行収入: 0}    

・記事4(トピック:エンタメ)
{ファッション: 0、雑誌: 0、露出: 0、新卒: 0、電話対応: 0、苦手: 0、
 監督: 0.5、映画: 0.5、興行収入: 0.5}

となり、「経済」の記事に出現する単語の重みが大きくなり、その他の記事の単語の重みが小さくなっていることが分かる。

このベクトルをSVNのような分類器で学習させると、新しい記事がどのトピックの記事であるかどうかを判別するモデルができる。 同様に他のトピックに関しても計算をしてトピック毎に分類器のモデルを作れば、新たな記事がどのトピックの記事かを判別するモデルができる。

このように単語の出現回数だけではなくて、特定のトピックであることを前提とした単語の出現割合を考慮することで、機械学習の判別の精度をあげることができる。

実装に関しては、SVMはscikit-learnにあるから良いとして、NBの部分は前述のkernelを見ると、

 r = np.log(pr(1,y) / pr(0,y))

となり、Pythonで結構シンプルに書けることが分かる。

注意点

①アンダーフロー回避

単語の出現回数に、 \displaystyle \frac{「経済」記事内のその単語の出現割合}{「経済」ではない記事内のその単語の出現割合}を掛けたが、この方法ではアンダーフローが発生する可能性があるので、実際にはこれを対数化したものを掛ける。

アンダーフロー: 0.0001 * 0.0001の計算結果が0.0000001のように有効桁が下がり、コンピュータで例えば小数点第4桁までしか計算できない場合には結果は0になってしまい精度が下がることをいう。

 \log 0.0001 * 0.0001 = \log 0.0001 * \log 0.0001
 = \log 10^{-4} +  \log 10^{-4} = (-4) + (-4) = -8
のように対数化すれば、掛け算・割り算を足し算・引き算に変換できるので、有効桁が下がらない。

②トピックに共通する単語の除外

上記の例では、恣意的に共通する単語を除外したが、TfidfVectorizerのようなアルゴリズムを使って頻出の単語の重要度を下げることによって機械的に除外できる。

補足

ベイズの定理出てこなかったので補足。こんな感じで分母と分子のそれぞれで使われているらしい。
 \displaystyle \frac{P(経 | 単)}{P(not経 | 単)} = \displaystyle \frac{P(単) P(単 | 経)}{P(単) P(単 | not経)} = \displaystyle \frac{P(単 | 経)}{P(単 | not経)}

参考文献