5章 ベイズとベイジアン・フィルター


以前、ベイズの事例として沈没した潜水艦を探し出した例を挙げました。

海域を分割して確率を当てはめ、捜索結果からまた確率を更新して絞り込み見つけ出した例です。


ベイズの特徴として、主観で確率を当てはめて確率分布を作り、実際の観測結果から、確率分布を更新して精度を高めていくやり方がとれることが挙げられます。


統計や確率で著名な方のベイス統計の入門書に、このベイズの主観確率についてわかりやすい例が載っていたました。


ところで、数学の問題では著作権はどうなんでしょうね。


著作権の定義2条の1ではこうなっています。

「・著作物 思想又は感情を創作的に表現したものであつて、文芸、学術、美術又は音楽の範囲に属するものをいう。」

これなら抵触しないで済みそうですかね。


同じ定義2条の10の2では、こんな文になっています。

「・プログラム 電子計算機を機能させて一の結果を得ることができるようにこれに対する指令を組み合わせたものとして表現したものをいう。」

printf("Hello world!");は、程度問題として許容範囲なんでしょうか。 


パッっと著作権法を見ていたのですが、やたらにレコードという単語が目に付きます。

これでもHPにアップする前に一通り見直したりするのですが、

この章のベイジアンフィルターで著作権法を例題にすればよかったかな、と思うぐらいです。


ダウンロードの問題で平成24年に改正があったみたいですね。


全くの丸写しではありませんが引用元を明示していないので、ご指摘を頂ければ修正します。

興味もあるのでその際は、明確なボーダーラインも示して頂ければ大変有難く思います。


また話がズレましたので、ベイズの主観主義に戻します。


ある標的が撃たれました。

AさんかBさんが撃ったのだろうと思われますが、どちらか分りません。

Aさんが標的に当てる腕前は80%で

Bさんが標的に当てる腕前は30%とします。

動機はどちらも同じで半々とします。


これを計算すると、標的が撃たれる確率はInftyProject×0.8+InftyProject×0.3で0.55ですね。

外れる確率は関係ありませんが、InftyProject×0.2+InftyProject×0.7で0.45になります。

Aさんが撃った確率はInftyProjectで、約0.73です。

Bさんが撃った確率はInftyProjectで、約0.27になります。


ここで、Aさんには標的を撃つ動機が見当たらず、Bさんには動機があり、

Aさんの動機0.25、Bさんの動機0.75と、重み付けをしてみます。すると

Aさんが撃った確率はInftyProjectで、約0.47で

Bさんが撃った確率はInftyProjectで、約0.53になります。


この例だと、どっちも50%ぐらいで結局どっちか分らないという顛末なのですが、

動機を確率として割り当てるという、主観を計算に取り入れてます。

的を撃つ能力だけでみると7割り方Aさん、という事になってしまいますが、

動機が薄いという事でなんとか、振り出しに戻っています。


この例には続きでは、的は2発撃たれてますが、あえて3発に増やしてみます。

すると、AさんはInftyProjectは、約0.86になり


すると、BさんはInftyProjectは、約0.14になります。

3発も当てたとするとAさんの確率がグッと上がってきますね。

何発か外して当たったの3発であれば全然違う結果になりますが、この例題では外れは無しです。




さて、次の例です。

3つ壷があり、次のように赤玉と白玉が入っています。

Aの壷には{赤玉3つ、白玉1つ}

Bの壷には{赤玉1つ、白玉1つ}

Cの壷には{赤玉1つ、白玉2つ}


壷が選ばれる確率は、P(壷A)=P(壷B)=P(壷C)=InftyProjectで全部同じです。

それぞれの壷から赤玉が取り出される確率は、P(赤玉|壷A)=InftyProject、P(赤玉|壷B)=InftyProject、P(赤玉|壷C)=InftyProjectとなります。

壷Aが選ばれたなら赤玉が取り出される確率はInftyProject、という当たり前の事を記号化しているだけです。


逆に赤玉だけがポツンとあって、それがどの壷から取り出されたのかは、

P(壷A|赤玉)=InftyProject=約0.47

P(壷B|赤玉)=InftyProject=約0.32

P(壷C|赤玉)=InftyProject=約0.21

と表せます。


これは赤玉という結果が、どこから来たのか、つまり原因がどこであるかを求める確率計算です。

これを逆確率といいます。



検索エンジンやプロバイダなどでテキストに解析やスパムメール判別に使われて成果を上げているナイーブベイズ分類器、ベイジアンフィルタと呼ばれる仕組みの元になるのは、この逆確率です。

メールを単語に分解して1つ1つ調べていって、その元はどこなのか、どこから来た可能性が高いのかを確率計算しようという考え方です。


「ベイジアンフィルタ」などのキーワードを検索エンジンにかけると、プログラムへの実装解説やサンプルプログラムが見つかります。

筆者も幾つかの、サンプルプログラムを動かしてみました

それらは全てpythonという言語で書かれていて、雑誌に掲載された同じ記事を参考に作ったようでした。


プログラム構造は大体同じで、こんな感じです。


train() #訓練関数

for(登録されている作家分ループ){

 作家の基本となる確率計算

  for (読み込ませた文章(分析対象)の単語がなくなるまで、単語1つ1つループ){

    単語毎の確率計算

    確率の集計 

  }

}


肝心の確率計算部分ですが、大元になったであろう記事では、P(cat|doc) = P(doc|cat)P(cat) / P(doc)を計算式としていていますが、プログラムコードは少し違って複雑なものでした。


P(doc)部分は、文章が生起する確率として学習済みの特定カテゴリの文書数を学習させた全文書数で割って求めています。

P(doc|cat)P(cat)部分は、単語毎にP(word|cat)を、特定の単語をカテゴリが使用した回数+1を、特定のカテゴリが使用した単語ののべ回数+(学習させた全単語のユニーク数)で割って求めています。

分子部分の+1は、単純ベイズの確率モデルは本来掛け算ですので仮にゼロが混じると計算結果がおかしくなってしまいます。

分析したい文章から抜き出した単語が学習データに登録されていない場合、プログラム内でゼロになってしまいすので、それを補正しています。


そしてP(doc)を対数変換した値にP(word|cat)を対数変換した値を足し込んだ値をスコアとして、最も値の大きかったカテゴリを、推定結果としています。

いくつかのサンプルプログラムでは、これをさらに変形した計算をしていました。


これらを実行してみると、なるほど作成者の意図通りの結果を出してくれますが、式から作者の意図を斟酌するには最適化され過ぎていて、ナイーブベイズの解説に使うには不向きな面が感じられます。


ナイーブベイズ分類に使われる計算式に単純ベイズの確率モデルというものがあります。

InftyProject

ですのでプログラム構成にサンプルをお借りして、計算は逆確率そのもののシンプルなプログラムをRで組んでみました。

######################################

# 学習フェーズ

#  (学習結果テーブル作成・ダミー)

######################################

train<-function(){

  gt<<-matrix(c(12,7,9,11,0, 1,1, 0,

                 1,2,1, 4,6,11,3,12,

                 4,5,5, 6,7, 6,5, 7),ncol=3)

  colnames(gt)<<-c("スパム","ハム","微妙") # 列をカテゴリ

  rownames(gt)<<-c("無審査","当選","出会い","おめでとう","ランチ","交通","備品","勤怠") #行を単語

}

######################################

# 分析したい文章を形態素へ分解

#  (本来はMeCabなどのツールと連携する)

######################################

morphological<-function(){

  gs<<-c("おめでとう","ござい","ます","。","厳正",  "なる",   "審査","の","結果",

        "、","あなた","様","が","当選","致し","まし","た","。")

}

######################################

# 単語が学習されているかチェック

######################################

rownamechk<-function( word ){

  for( w in rownames(gt)){

    if ( w == word ){return(0)}

  }

  return( -1)

}

######################################

# 単語毎の出現確率

######################################

CatWP<-function(categorie,word){

   if( rownamechk( word ) >=0 ) {

      return(gt[word,categorie])

   }

  return(-1)

}

######################################

# メイン 

######################################

  gt<<-0  #学習データ

  gs<<-0  #分析対象(スパムメールを形態素に分解したデータ)


  #学習フェーズ

  train() 


  #分析対象を形態素に分解

  morphological() 


  bestjudge=0

  bestcat=0


 #どのカテゴリの可能性が高いかをループさせて調べている。

  for( categorie in colnames(gt) ){

    catP=sum(gt[,categorie])/sum(gt)             #注1)P(cat)  カテゴリののべ単語数/全体ののべ単語数


    wPsum=1.0

    dP=1.0     #denominator

   #分析したい文章を単語毎ループさせ調べている。

    for( word in gs ){

      if( (wP=CatWP(categorie,word))<=0) {next}  #注2)学習していない単語は飛ばしている

      wPsum=(wP/sum(gt[,categorie]))*wPsum         #P(F1|cat)×P(F2|cat)...P(Fn|cat)

      dP=(sum(gt[word,])/sum(gt) ) * dP              #P(F1)×P(F2)....(Fn)

    }

    rsltP=(wPsum*catP )/dP                         #(P(cat)×総和(P(fn|cat)))/総和(P(fn))


    if( bestjudge<rsltP || bestjudge==0){         #確率の一番高いカテゴリをメモ

      bestjudge=rsltP

      bestcat=categorie

    }

    cat(sprintf("分析したメールが%sである確率は%fです。\n",categorie,rsltP))

  }

  cat(sprintf("このメールは%sの可能性があります\n",bestcat))


学習済みデータ

           スパム ハム 微妙

無審査         12    1    4

当選            7    2    5

出会い          9    1    5

おめでとう     11    4    6

ランチ          0    6    7

交通            1   11    6

備品            1    3    5

勤怠            0   12    7


分析するメール本文

"おめでとうございます。厳正なる審査の結果、あなた様が当選致しました。"


このメールを単語に分解してひとつひとつを学習テーブルに照らし合わせて計算するだけです。


少しプログラム解説をしてみます。


学習フェーズ関数train()は、学習テーブルgtに学習結果を反映させています。


学習フェーズでは、本来はカテゴリに合わせて文章を沢山読み込ませます。

たとえば、文の末尾は出現頻度が多くなります。

全く同じ作家の同じ文章のですます調を、である調を変えただけでも、その作家のものでは無いと判定される可能性が高くなります。

同じ作家でもSF物や恋愛物、時代物では、使用される語彙も違ってくるでしょう。


恣意的な学習は分析結果の信頼性を失うので、やってはいけませんが、

実際にベイジアンフィルターを実装する場合は、どんなカテゴリでどのような分析をするのか、

目的に合わせてある程度の方針は必要になります。


単純ベイズ確率モデルを前提にした場合、確率の掛け算が出てきます。

もし学習されていない単語があれば、ゼロになってしまう恐れがあります。

そこで、スムージングといわれる補正が使われる事が一般的です。


プログラム内のコメント注2)とも関連することですが、方言や俗語などが出てきた場合、それはまだ学習されていない単語かも知れません。

しかし、このような単語は分析対象を決定付ける重要なファクターとなる可能性も高い外れ値でもあります。

これを単純にスムージングする方法もあります。

サンプルプログラムではスムージングせずに、単純にスキップしていますが、ここに別ロジックを組み込み特徴的な単語の処理を入れると、さらに精度の高い分析になって行きます。

これは余談でした、プログラム解説に戻します。


分析したい文章を形態素へ分解のフェーズはメール文を予めMeCabに掛けて、分解した結果をベクトルデータ化しています。


次に、2つのループの入れ子の処理で確率の計算をしています。

大きなループでは、tgに学習させたカテゴリ”スパム","ハム","微妙"の3回廻ることになります。

InftyProjectの計算を行いますので、変数catPにP(C)値を代入しています。

小さなループでは、メール文の単語毎に廻していて、このループが終了するとInftyProject)値を得られます。

あとは公式通りの計算ですね。


これを実行した結果です。

分析したメールがスパムである確率は0.804878です。

分析したメールがハムである確率は0.085714です。

分析したメールが微妙である確率は0.285714です。

このメールはスパムの可能性があります。


この結果は当たり前ですね。


壷と赤玉白玉に例えれば、単語は赤玉白玉になります。

壷はカテゴリに相当します。


その単語を含む割合いが高い壷はどれか。

文章全体で見たときに、どの壷から出てきた可能性が一番高いか。

という事をやっているのがベイジアンフィルターです。


ベイジアンフィルターもテキストマイニングのひとつです。

テキストマイニングはベイジアンフィルターだけでなく、日進月歩でいろいろな手法が研究されています。

プログラムロジックはシンプルなものの組み合わせで作り上げないと手を加えられないものになってしまいます。

その方が部分抽出してサンプル公開しやすいですしね。


因みにこのプログラムは著作権法2条10の2を放棄します、このプログラムによるいかなる損害にも責任を負いません。

損害の出ようがないと思いますが、時間の無駄だったとか、そういうのも受付けません。

なお、バグには対応させて頂きます。


今回はかなりプログラムに偏った内容になってしまいました。

プログラムの登場は今回でお終いです。


さて、次回はランダムウォークを取り上げます。



前章へ 次章へ

メインメニュー