はじめに数式のご紹介
今回はベイズの定理を用いた、迷惑メールフィルタの作り方を簡単に解説していきます!
作り方、とは書きましたが、今回ソースコードは一切書きません。
概念だけお話ししようと思いますのでプログラマーでは無い方にもお楽しみいただけるかと思います!
ベイズの定理を調べると必ず
P(B|A) = \frac{P(A|B)P(B)}{P(A)}
という数式が見つかると思います。
この式の証明はここでは行いませんが、これが成り立つのだということをまずは押さえておいてください。
この数式の意味をまず解説します。
P(B|A)とは、「Aが成立すると、Bが起こる確率」
P(A|B)とは、「Bが起こったとき、Aが成立している確率」
P(B)は「Bが起こる確率」
P(A)は「Aが成立する確率」
抽象的ですが、とりあえずこういう意味になります!
迷惑メールフィルタに前の式を当てはめてみましょう
AとかBとか言われても非常に抽象的で分かりづらいですね!
では、迷惑メールを例に上げてみましょう!
迷惑メールフィルタはどうやって迷惑メールかどうかをフィルタリングしているのでしょうか?
今回作るフィルタはナイーブベイズと呼ばれるものです。
このナイーブベイズによるフィルタは文章中に含まれる単語をすべて確認し、それぞれの単語が迷惑メールっぽい単語か、それとも普通のメールっぽい単語かを見極め、確率を出すことで求めます。
まずは一単語「無料」でやってみましょう!
とりあえず、先の数式に今回の要件を当てはめてみましょう!
あと、AとかBでは分かりづらいので数式にも実際の単語を入れてみます。
P(B|A)
= P(無料|迷惑)
=> 単語「無料」が含まれるとき、そのメールが迷惑メールである確率
P(A|B)
= P(迷惑|無料)
=> 迷惑メールに、単語「無料」が含まれる確率
P(B)
= P(無料)
=> あらゆるメールを無作為に選んだとき、「無料」という文字列が現れる確率
P(A)
= P(迷惑)
=> あらゆるメールを無作為に選んだとき、そのメールが迷惑メールである確率
P(無料|迷惑) = \frac{ P(迷惑|無料) \times P(迷惑)}{P(無料)}
見て分かる通り、これを計算するには、あらかじめデータが必要です。
迷惑メールに、単語「無料」が含まれる確率
とかを出さないといけません。
もしデータが何も無いのであれば、最初の一回だけは手作業で分類していくことになります。
無作為に取得した色々なメールを見て、手作業で迷惑メールか一般メールかを振り分け、辞書を作成するのです。
その結果、「無料」という単語は以下のような確率で「迷惑メール」、「一般メール」に出現することが分かったと仮定します。
| |迷惑メール|一般メール|出現しない|
|---|---|---|---|---|
|無料|0.4|0.1|0.5|
また、インターネット上で迷惑メールと一般メールは8:2の割合であることも分かったとしましょう。
※上記確率は全部適当です!
迷惑メールである確率↓
P(無料|迷惑) = \frac{ P(迷惑|無料) \times P(迷惑)}{P(無料)}
一般メールである確率↓
P(無料|一般) = \frac{ P(一般|無料) \times P(一般)}{P(無料)}
これを計算すればいいのですが、右辺の分母は同じなので比較するだけなら省略できますね。
P(無料|迷惑) \propto P(迷惑|無料) \times P(迷惑)
一般メールである確率↓
P(無料|一般) \propto P(一般|無料) \times P(一般)
※注 「∝」は「比例する」という意味です。
これを計算してみると
P(無料|迷惑) \propto 0.4 \times 0.8 = 0.32
P(無料|一般) \propto 0.1 \times 0.2 = 0.02
よって、迷惑メールの確率である確率のほうが高いので、このメールは迷惑メールに振り分けることが出来ます。
次は文章でやってみましょう!
ところで、この確率は「無料」だけが本文のメールにのみ適用されます。
実際、こんな一単語だけのメールはまず存在しませんよね?
それでは、2つ以上の単語の場合どう計算するかを考えてみましょう。
2つ以上の単語の場合は掛けあわせればOKです。
「無料で遊べる簡単アプリ!」
という文章を解析してみましょう。
助詞(「が」とか「は」とか「で」とか)は取ります。
記号も取ります。
単語は全て終止形(辞書で引ける形)に直します。「遊べ」→「遊ぶ」
なぜかと言いますと、単純で、「『が』が多いメールは迷惑メールだ!」とは言いづらいと考えたからです。
「『無料』が多いメールは迷惑メール」ならある程度言えそうですが、助詞はどんなメールにでも均等に含まれているだろうという仮説のもと、こういうフィルターをかけています。記号も同様です。
終止形にするのも、「『遊ば』なら迷惑メールで『遊べ』なら一般メールだ」とは言いづらいと思ったからです。また、全ての活用形の辞書が必要になってしまうのも結構キツイです。
この分類はMecabというツールを使うと自動でやってくれます。
P(無料で遊べる...|迷惑) \propto P(無料|迷惑) \times P(遊ぶ|迷惑) \times P(簡単|迷惑) \times P(アプリ|迷惑)
P(無料で遊べる...|一般) \propto P(無料|一般) \times P(遊ぶ|一般) \times P(簡単|一般) \times P(アプリ|一般)
恐らくこれを計算すると
P(無料で遊べる...|迷惑)
= 0.000000051232
P(無料で遊べる...|一般)
= 0.000000000013
とかそれぐらいの小さい確率になります。1より小さい数で何度も掛けているので当然ですね。
すごく小さい数ですが、これでOKです。
0.000000051232
と書くのが面倒くさい人は、5.1232-e8
と書くことも出来ます。頭に付いているゼロの数を数えれば良いだけです!
0.000000051232 = 5.1232 \times 10^{-8} = 5.1232 - \mathrm{e} 8
0.000000000013 = 1.3 \times 10^{-11} = 1.3 - \mathrm{e} 11
ある程度離れているようでしたらこっちの方が比較しやすいですね!
1件もヒットしなかったときの考慮
このベイズの理論ですが、過去のデータを元に統計しています。
ここでもし、「遊ぶ」という単語が過去に一件も出ていなかったらどうしたらよいでしょう?
まず、 P(遊ぶ|迷惑)
にも P(遊ぶ|一般)
にも無いのであれば、それがあってもなくても結果は変わりません(比較しているだけですので)。なので、スルーして良いです。何か数字を入れなければいけないのなら1でも掛けておきましょう。
問題なのが、片方の辞書にだけある場合です。
P(遊ぶ|迷惑) = 0.1
P(遊ぶ|一般) = 0
さて、この場合非常に困りました!
P(遊ぶ|迷惑)
は良いでしょう。0.1なのですから、0.1をかければ良いだけです。
問題は P(遊ぶ|一般)
の方です。まともに0を掛けたらその後なにをしても0になってしまいます。
じゃあさっきみたいに無かったことにしましょうか?
いえ、それではP(遊ぶ|迷惑)
だけ0.1が掛けられて、 P(遊ぶ|一般)
は1が掛けられることになります!
つまり、 減らすべき方の確率が増え、増やすべき方の確率が減ることになります!!
というわけで、0に限りなく近い数字を掛けることを提案します。やってみたところ、「0.00001」ぐらいがちょうど良さそうです(ダメだったら適当に調整してください!)
ベイズが流行らなかったわけ
さて、ここまで来るとそろそろ手計算では厳しいです。P(無料|迷惑)
を計算するのも結構面倒なのに、これを全部手計算したら数分かかってしまうでしょう。
今はコンピューターが一瞬で計算できますが、この理論ができたのは200年ほど前です。もちろんコンピューターなどという便利なものはありませんでした。
というわけで、便利ではあるのですが、計算が辛いので、当時はあまり流行らなかったと言われています。