ちくわ君と学ぶナイーブベイズ

よく晴れた朝、森の研究所では、ちくわ君が赤い木の実をかじりながら、届いたメッセージにのんびり目を通していた。

「ちくわさん!大変です!」勢いよく扉が開いて、はんぺん君がしょんぼりした顔で駆け込んできた。

「やあ、はんぺん君。今日はいつもの元気がないね。どうしたんだい?」

「僕の森メッセージ、最近こんなのばっかり届くんです。『無料でドングリ詰め放題!今すぐクリック!』とか、『あなたが当選しました!』とか……。大事な友達からのメッセージが、この迷惑メッセージの山に埋もれて見つからないんです」はんぺん君はがっくりと肩を落とした。

ちくわ君は嬉しそうに目を細めて、椅子ごとはんぺん君のほうへ向き直った。「なるほど、それは困ったね。でも安心していいよ。それこそ、コンピューターが得意な仕事なんだ。『ナイーブベイズ』という分類器を使えば、迷惑メッセージを自動でより分けられる。実際、世界中のメールソフトが、似たような仕組みで迷惑メールを弾いているんだよ」

「ないーぶ・べいず……なんだか強そうな名前ですね」

「ふふ、やっていることは案外シンプルだよ。一言でいうと、『どんな言葉が入っているかを手がかりに、迷惑メッセージらしさを確率で見積もる』仕組みなんだ。まずは、はんぺん君が今まで受け取ったメッセージを、迷惑なものと普通のものに仕分けして、コンピューターに見せてあげよう」

二人は、これまでのメッセージを十通選んで、迷惑メッセージ(スパム)と普通のメッセージに分けた。迷惑なものが四通、普通のものが六通あった。

「まず、何も中身を見ないうちから言えることがある。十通のうち四通が迷惑メッセージだったよね。だから、新しいメッセージが届いたとき『これはたぶん四割くらいの確率で迷惑かな』と最初に当たりをつけられる。これを『事前確率』と呼ぶんだ」

「なるほど。中身を読む前の、第一印象みたいなものですね」

「うまいこと言うね、まさにそれだよ。さて、ここからが本番。迷惑メッセージと普通のメッセージで、どんな言葉がよく使われていたか、数えてみよう」ちくわ君は楽しそうにキーボードを叩き始めた。

集計はあっという間に終わった。迷惑メッセージには「無料」「今すぐ」「クリック」「当選」といった言葉が何度も出てくる。一方、普通のメッセージには「明日」「遊ぼう」「公園」「ありがとう」といった言葉が並んでいた。

「面白いですね!迷惑メッセージって、使う言葉にクセがあるんだ」

「そう、そのクセこそが手がかりなんだ。じゃあ、さっそく一通、判定してみよう。『無料 ドングリ クリック』というメッセージが届いたとする。この三つの言葉が、迷惑メッセージと普通のメッセージで、それぞれどのくらいの割合で登場していたかを調べるんだ」

ちくわ君はホワイトボードに、三つの言葉のスパムらしさを書き出した。

単語 迷惑メッセージでの出やすさ 普通メッセージでの出やすさ 無料 0.148 0.033 ドングリ 0.111 0.100 クリック 0.148 0.033 「無料」と「クリック」は迷惑メッセージに4倍以上出やすい。「ドングリ」はどちらでも変わらない
図:各単語の「出やすさ」。迷惑メッセージらしい言葉ほど、左の棒が長くなる

「見てごらん、はんぺん君。『無料』と『クリック』は、迷惑メッセージのほうに四倍以上も出やすい。一方で『ドングリ』は、どちらのメッセージにも同じくらい出てくる。つまり『ドングリ』は、迷惑かどうかを見分ける手がかりにはほとんどならないんだ」

「あっ、確かに!『ドングリ』は迷惑メッセージにも普通のメッセージにも出てきますもんね」

「そういうことだね。さて、最後の仕上げだ。最初の第一印象、つまり『四割は迷惑』という事前確率に、三つの言葉の出やすさを順番にかけ算していく。普通のメッセージのほうも同じように計算して、二つのスコアを比べるんだ」

ちくわ君が計算を走らせると、二つの数字が画面に並んだ。迷惑メッセージらしさのスコアが、普通のメッセージらしさのスコアを、大きく上回っている。割合にすると、こうなった。

「無料 ドングリ クリック」の判定 迷惑 93.6% 普通 6.4% → 迷惑メッセージと判定!
図:計算の結果、迷惑メッセージである確率は約94パーセント

「すごい……!ちゃんと迷惑メッセージだって見抜きました!しかも九割以上の自信を持って!」はんぺん君は興奮して尻尾を振った。

「うまくいったね。ところで、さっきから『言葉の出やすさを順番にかけ算する』と言ってきたけれど、ここにこの手法のちょっとずるい仮定が隠れているんだ。それが『ナイーブ』、つまり『単純な』という名前の由来だよ」

「ずるい仮定、ですか?」はんぺん君は首をかしげた。

「うん。本当は、言葉どうしには関係があるよね。『今すぐ』の後ろには『クリック』が続きやすい、とか。でもナイーブベイズは、そういうつながりをぜんぶ無視して、『どの言葉も、てんでばらばらに、独立して現れる』と割り切ってしまうんだ。現実には少し乱暴な仮定だよね」

「えっ、そんなに大ざっぱで、ちゃんと当たるんですか?」

「それがね、不思議とよく当たるんだ」ちくわ君は嬉しそうに新しい木の実を口に放り込んだ。「迷惑メッセージかどうかを当てるだけなら、言葉の細かいつながりまで考えなくても、『どんな言葉が入っているか』だけで十分に見分けがつくことが多い。単純なのに、速くて、よく当たる。だからこの手法は、何十年も現役で使われ続けているんだよ」

「単純なのが、かえって強いんですね……。あれ、でもちくわさん。一つ気になることがあります。もし迷惑メッセージに一度も出てこなかった言葉が、新しいメッセージに入っていたら、どうなるんですか?出やすさがゼロだと、かけ算したら全部ゼロになっちゃいませんか?」

ちくわ君は木の実を持つ手を止めて、嬉しそうに身を乗り出した。「はんぺん君、今日一番いい質問だ。まさにそれが、初心者がいちばんつまずく落とし穴でね。たった一つの未知の言葉のせいで、スコアがまるごとゼロになってしまう。これを防ぐために、賢い人たちが面白い工夫を考えたんだ」

「どんな工夫ですか?」

「『どの言葉も、最初から一回だけは出てきたことにしておく』んだ。実際には数えてゼロだった言葉にも、こっそり一を足しておく。そうすれば、出やすさが完全なゼロになることはなくなる。たった一を足すだけの素朴な工夫だけど、これがよく効くんだよ」

「なるほど!ゼロにさえならなければ、かけ算で消えてしまうこともないんですね」

「そういうことだね。実際の分析では、こうした計算はライブラリがぜんぶ面倒を見てくれる。Pythonなら、数行でこの分類器が作れるよ」ちくわ君は楽しそうにコードを書き足した。

from sklearn.feature_extraction.text import CountVectorizer
from sklearn.naive_bayes import MultinomialNB

# メッセージを単語の出現回数に変換する
vec = CountVectorizer()
X = vec.fit_transform(messages)   # messages: 学習用メッセージ
y = labels                        # labels: 迷惑=1, 普通=0

# ナイーブベイズ分類器を学習させる
model = MultinomialNB().fit(X, y)

# 新しいメッセージを判定する
print(model.predict(vec.transform(["無料 ドングリ クリック"])))
# [1]  → 迷惑メッセージと判定

「たったこれだけ……!さっきの計算が、ぜんぶこの中に入っているんですね」はんぺん君は画面に張りついた。

「そうだよ。仕組みを知ったうえでライブラリを使うと、なぜうまくいくのか、どんなときに間違えそうかが見えてくる。そこが大事なんだ。例えばこの分類器は、学習に使ったメッセージに『ドングリ』ばかり並んでいたら、ドングリを基準に世界を見るようになってしまう。どんな言葉を学ばせるかで、性格が決まるんだよ」

はんぺん君は、ぱっと顔を上げた。「ちくわさん、ありがとうございました!家に帰ったら、さっそく自分のメッセージで試してみます。これで大事な友達からのメッセージを、もう見逃しません!」

「それはいいね。うまく仕分けできたら、ぜひ聞かせてよ。楽しみにしているから」ちくわ君は温かく手を振って見送った。

はんぺん君が朝の光の中へ駆けていったあと、ちくわ君は届いたメッセージの一覧に向き直った。たった一を足すだけの素朴な工夫で、計算が破綻せずに回り出す。そのささやかな知恵を思うと少し心が弾んで、ちくわ君は新しい赤い木の実へ手を伸ばした。

森の研究所には、今日も小さな発見の喜びが満ちていた。

教育 機械学習 確率 テキスト分類 友情