spamメールはここで改めて述べるまでもなく、鬱陶しいものです。 特に連絡先をWebなどで知らせればならない立場の人にとっては、 botsによる自動収集でいやがおうにもspamメールは増えていく一方です。 また、メーリングリスト管理者にとっても、 spamメールでメーリングリストのSN比が下がることはユーザの利便性を下げてしまうため、 頭の痛い問題です。
一般的なspamメールフィルタで利用されている 単語(blacklist)を登録する方式のフィルタは、 「お金をもうける」などの確実にはじける対象に対しては有効ですが、 間に*などの文字を入れて別の単語に見せかけるなど、 イタチゴッコになりやすいという側面を持っています。 また、特定のサイトだけを弾いたり、 既知のサイトからのメールだけを受け入れる方法も、 正当なサイト利用者からの正当なメールを弾いてしまう可能性もありなかなか 悩ましいものがあります。
登録する単語が分かってしまい、 それを避けるようにspammerが単語を選ぶのであれば、 これを逐次学習することで、spammerへの対処が可能と考えられるでしょう。 もちろん、明示的な単語リストを逐次更新していくのでもいいですが、 例えばボタン一つでこれはspamメール/広告メールなどと印を付けるだけで、 不要なメールが来なくなれば、メール分類の手間も減らせるでしょう。
今回、ここで紹介するメールフィルタは、従来のルールベースのものではなく、 Naive Bayes Classificationという統計的な学習手法を基礎とした、 逐次学習型のメールフィルタです。
Naive Bayes Classification(NBC)を利用したメール分類手法に関しては Paul Graham氏の論文 「A Plan for SPAM (http://www.paulgraham.com/spam.html)」 に詳しく説明されています (河合さんによる日本語訳が http://www.shiro.dreamhost.com/scheme/trans/spam-j.htmlで 提供されています。)。 この論文以外にも氏のページからは、 spam対策に関する有用な情報をたくさん手にいれることができます。
NBCを利用したフィルタリングには以下のような特長があります。
学習するフィルタである。
良いメールとspamメールの分類さえできていれば、 これらに良くあらわれる単語の出現頻度から自動的に spamメールらしさおよび良いメールらしさが計算可能である。
spamメールの進化に対応している。
spamらしさの学習は逐次的に可能なため、 spamメールで用いられる単語が変化しても、 自動的に学習して対応することが可能である。
自分の興味に応じたフィルタリングが可能。
多くの人にとってspamメールであるものでも、 ある人にとっては興味のあるメールになる可能性があります。 NBCではspamメールの分類は利用者が行うため、 そのようなメールに対しても適切に学習します。
学習が高速である。
厳密な意味での統計学習をしたければ、 (Naiveでない)Baysian Classifierを使えばいいのですが、 計算時間がかかるという問題点があります。 NBCでは単語の出現回数とどのクラスに分類にされたかの回数だけ わかれば計算できますので、高速に計算可能です。
詳細は論文に当たってもらうとして、ここではこの手法の概略に関して 説明します。
ここでは論文の概略についてのみ説明します。 また、数式などに用いる表記は変更しています。 詳細を知りたい人は論文を実際に読んでみてください。
はじめに、spamであることが分かっているメール群と そうではないメール群を用意します。 ここでは、これらをそれぞれspamメールと良いメールと呼びます。 spamメールと良いメールの数をそれぞれ、 nspamとngood とします。 ここで、ある単語wordの 全てのspamメールと良いメールでの出現回数を、 それぞれ b(word),g(word) とします。 出現回数なので、場合によっては各メールの数よりも大きくなることには 注意をしてください。 これらの値を使うと、ある単語がspamメールに含まれている 確率のようなもの [1] は、
b(word)
-------
nspam (編集注:インラインで組版してください。)
で得られます。
この値が1を越える場合は1とします。
同じようにある単語が良いメールに含まれている確率のようなものは
g(word)
-------
ngood (編集注:インラインで組版してください。)
で計算できます。
これらを使って、以下の式の値を求めます。
b(word) ------- nspam p(word)= ----------------------- b(word) 2*g(word) ------- + --------- nspam ngoodこの値がspamらしさを表す指標となり、 1に近いほどその単語がspamで利用される単語であることを示します。 ここで、良いメール側に2を掛けているのは、 経験的に誤検出( false posivive [2] )を避けるためです。
単語が初めて現れたときにspamらしさをいくつにすべきかという問題が出てきます。 これに関しても、経験的に0.4という中立的な値が割り振られています。
新しいメールが到着したときには、メールを単語トークンに分割し、 もっとも特徴的な15トークンを抽出します。 ここでの特徴的というのは、0.5からどれぐらい離れているか、 つまりどれだけどっちからしいかで評価されます。
これらのトークンから以下の式の値を計算します。
prod = Πp(word)
pspam =
prod
------------------------------
prod + (1-Πp(word))
(編集注:ΠはΠfor all wordで組版
この値が文章全体としてのspamらしさを表すことになります。
もう一つ重要な論文に 「Gary Robinson's Rants」 (http://radio.weblogs.com/0101454/stories/2002/09/16/spamDetection.html) があります。 後述のBogofilterや spambayes(http://spambayes.sourceforge.net/)など ではこの改良方式が既に実装され、 コマンドラインオプションで利用可能になっています。 ここでは、「Plan for SPAM」の問題点について概観し、 ad-hocに決められていたパラメータを意味のある値として 決定する方法について述べられています。 今回は、この論文については詳しく述べませんが、 より確率的に妥当な指標を得たい場合はこちらも読んだ方が良いでしょう。
これまで説明した方法は基本的なものですが、 もう少し処理を加えると改良されそうだなと考えられる方法があります。 これらは既にフィルタアプリケーションで実装されているものもありますが、 代表的なものをここで紹介します。
複数語を単語とみなす。
慣用句などのように、複数の単語が集まって、 初めて一つの意味を持つ場合が存在します。 spamメールの例でいくと、 「未承諾広告」は3単語ではじめてspamらしさを表現すると言ってもいいでしょう。 このような単語を扱うために、 2語語句などの出現確率を利用する方法が考えられます。
メールヘッダの一部だけを利用する。
メールヘッダには
確率バイアスがかからないように、メールのユニークさを保証する。
spamメールは一般に同じ内容が様々なアドレスに対して送られます。 みなさんも各種メーリングリストから同じ内容のspamメールが 次々に送られてきて、辟易したご経験があることでしょう。 これらを単純にspamとして処理すると、 確率的にバイアスがかかってしまいます [3]。 そこで、同じ内容のメールは1度と数えるような改良が考えられます。 このためには、メッセージIDを利用したり、 本文部分のMD5を利用することができます。 spamメールが複数のSMTPサーバから送られることが多いことを考えると、 後者の方が有効だと思われます。
分類項目をspamメールと良いメールだけにせず、 もっと複数の分類を扱う。
後述のifileがまさにこのためのメールフィルタです。 一般にメーリングリストなどはメールヘッダをうまく利用することで 分類可能ですが、 例えば 「メーリングリストを問わず、Lispに関する記事を一つのフォルダに集めたい」とか、 「緊急で処理する必要のあるものだけ集めたい」という要望には このような複数クラスでの分類が可能であった方が助かります。
ここでは、論文の筆者のサイトの一覧 (http://www.paulgraham.com/filters.html) から、主にports collectionに含まれているソフトウエアに関して説明します。
表1に主にportsに含まれているNBCを用いたspamメールフィルタを示します。 かなりの数がありますが、最初から日本語が利用できる状態になっているものは ありません。
表 1. portsに含まれているNBC spamフィルタの一覧
名称 | バージョン | 開発サイト | portsの場所 | 記述言語 | 備考 |
---|---|---|---|---|---|
Bayespam | 0.9.2 | http://www.garyarnold.com/projects.php#bayespam | mail/bayespam | Perlスクリプト | |
SpamOracle | 1.2 | http://cristal.inria.fr/~xleroy/software.html | mail/spamoracle | Ocaml | |
SpamProbe | 0.7c | http://sourceforge.net/projects/spamprobe/ | mail/spamprobe | C++ | |
ifile | 1.1.4 | http://www.nongnu.org/ifile/ | mail/ifile | C(+Tcl,) | spamかそうでないかだけではなく、複数のクラスに対応。 |
Bogofilter | 0.8.0_2 | http://bogofilter.sourceforge.net/ | mail/bogofilter | C | |
Mozilla | 1.3a | http://www.mozilla.org/ | www/mozilla-devel(予定) | C++ | 1.3a以降でspamフィルタを組み込み。 |
Gauche:SpamFilter | http://www.shiro.dreamhost.com/scheme/wiliki/wiliki.cgi?Gauche%3aSpamFilter&l=jp | なし | Gauche | 将来的にはscmailとの連携も予定。 |
表2に各フィルタの機能比較を示します。 MUAで対応しているMozillaを除いては、 基本的にprocmailなどのMDAから利用するように設計されています。 ifileはデータベースがテキストファイルなので、 単語分布が簡単に確認でき、面白いです。
表 2. 各フィルタの機能比較
ソフトウエア | ドキュメント [a] | データベースファイル/ディレクトリ(default) | データベース形式 | procmail対応 [b] | 備考 |
---|---|---|---|---|---|
Bayespam | README | 指定が必須(-oオプション) | BerkleyDB v.3 | o | |
SpamOracle | README | ~/.spamoracle.db | 独自バイナリ | o | |
SpamProbe | man | ~/.spamprobe/ | BerkleyDB | o | |
ifile | man | ~/.idata | PlainText | - | |
Bogofilter | man,READMEなど多数 | ~/.bogofilter/ | BerkleyDB | o | |
Mozilla | http://www.mozilla.org/mailnews/spam.html | x | MUA組み込みの独自インターフェース。 | ||
注意: a. manはmanpage, 他はその名称のファイル。 b. o:レシピがドキュメントに存在。 -:レシピの記述無し。 |
ここでは、詳細な利用法は述べませんが、 基本的な操作に必要なコマンドラインオプション一覧を 表3に示します。 詳細は各フィルタ付属のドキュメントを参照してください。
表 3. コマンドラインオプション一覧
ソフトウエア | データベースの指定 | spamメール/フォルダの指定 | 良いメール/フォルダの指定 | 学習 | 分類テスト | 備考 |
---|---|---|---|---|---|---|
Bayspam | -o database.db(必須) | --spam file_or_directory | --good file_or_directory | bayes_process_email.plを利用 | ||
SpamOracle | -spam spammails | -good goodmails | spamoracle add -good goodmails -spam spammails | spamoracle test testmails | ||
SpamProbe | -d database_directory | spam spammails | good goodmails | spamとgoodで個別にspamprobeを実行して学習。 | spamprobe score testmails | |
ifile | -b database_file | -i spam_folder | -i good_folder | 各カテゴリ毎に、ifile -i category_folder | ifile -c -q | |
Bogofilter | -d database_directory | -s | -n | 各メール毎にcat spammail|bogofilter -s [or -n ] | cat testmail|bogofilter -v | メールの入力は標準入力から。 |
今回紹介したソフトウエアはまだ日本語への対応などがされていないもので、 すぐに利用できるようなものではありません。 しかし、筆者が受け取るspamメールの大多数は英語のもので、 ついで韓国語や中国語の順ですので、一見spamメールの分類を作るには 問題が無さそうに思えます。 しかし、分類をする以上はspamメールで無い例も必須になりますので、 単語が空白で区切られていることを仮定している [4] これらのソフトウエアを利用しても、ヘッダの情報が有効に利用される程度で 効果が半減してしまいます。
日本語で分かち書きが可能なソフトウエアとしては、 kakasi (japanese/kakasi, http://kakasi.namazu.org/) やChasen (japanese/chasen, http://chasen.aist-nara.ac.jp/: 本来は形態素解析のためのソフトウエア) があります。 ここでは、kakasiを使って単語を分割して、 学習する実験をしてみました。 ここでは、改造の容易さを優先して、 PerlスクリプトであるBayespamを利用します。
図1に学習用プログラムの変更箇所を示します。 本来であればkakasiのPerlモジュール版である Text::Kakasi (japanese/p5-Text-Kakasi)を利用した方が 速度面でも有利なのですが、 ここでは実験と言うことで簡単な実装にとどめています。 「単語」の意味もオリジナルと変っているのには注意してください。
図 1. bayes_process_email.plへの変更部分
--- /usr/local/bin/bayes_process_email.pl Thu Jan 2 00:11:55 2003 +++ bayes_process_email.pl Thu Jan 9 01:57:01 2003 @@ -232,7 +232,7 @@ $mailcount++; print '.' if ( $mailcount % 100 ) == 0; - if ( ( $file eq "-" ) || open(F, "< $file") ) + if ( ( $file eq "-" ) || open(F, "/usr/local/bin/nkf -e $file|/usr/local/bin/kakasi -w|") ) { my $data; $data = $parser->parse(\*F) if $file ne "-"; @@ -242,12 +242,15 @@ # Rip out HTML comments $message =~ s/\<\!\-.*\-\>//gs; - $message = lc $message if not $case_sensitive; - foreach (split( /[^\w'$-]+/, $message ) ) + foreach (split( /\s+/, $message ) ) { # Ignore all-numeric tokens - if ( !( $_ =~ /^[0-9\-]+$/g ) ) { if (length($_) > 0){$token_occurrences{ $_ } += 1.0;} } + if ( !( $_ =~ /^[0-9\-]+$/g ) ) { + if (length($_) > 0){ + $token_occurrences{ $_ } += 1.0; + } + } } } }
実験対象のデータは、spamメール626通、良いメール802通となっています。 良いメールは本誌記者用メーリングリストを利用しましたので、 出現単語などにはかなり偏りがあります。 筆者が目でみて判断したspamメール(555通)にはほとんど日本語のspamが含まれなかったため、 無料プロバイダなどのサービスを利用するときに自動的に購読しなくてはならない 宣伝が中心のものを選択し、71通追加してあります。
このプログラムで単語と認識されたものは全部で14572になります。 学習したデータベースを 図2 のプログラムで調べると、以下のような傾向が見られます。
spam度が高い場合(0.99で9356単語)。
HTMLタグやスタイルシートの断片などが多く見られます。 2つ以上の文字からなる日本語単語 [5] では、 「商品」(spamメールで373回出現)、「販売価格」(247)、「本数」(225)、 「キャンペーン」(213)、「マイル」(185)の順になります。
spam度が中ぐらいの場合(0.5で27単語)。
英語ではtheやfrom,withなど、日本語では句読点などの どんな文章でも必須で利用されるものが含まれています。 また、Received:などのメールヘッダの項目名もここに含まれています。
spam度が低い場合(0.01で47単語)。
英語ではWanderlustのバージョンに含まれる文字列が多かったです。 これは筆者がWanderlustを常用しているためだと考えられます。 もちろん、「FreeBSD」(良いメールで429回出現)が多く、 ついで「Mew」(286)、「Emacs」(299)、「Mule」(286)の順になっています。 筆者の名前のローマ字表記(MUTOH 125,Takeshi 151)も同じく多かったです。 日本語の単語は「インストール」(340)、「疲れ」(190)、「コマンド」(116)、「次の」(89)、「ユーザ」(86)と なにやら壮絶な雰囲気を醸し出しています。
図 2. データベースから単語一覧を表示するスクリプト
#!/usr/bin/perl -w use strict; use MIME::Parser; use Fcntl; use DB_File; use vars qw($number_of_messages %token_occurrences); use Bayespam::Process; my $number_of_nonspam_messages = 0; my $number_of_spam_messages = 0; my %token_ratings = (); my $outfile = shift; my $parser = new MIME::Parser; $parser->output_to_core(1); $parser->extract_uuencode(1); # Open the database tie( %token_ratings, "DB_File", $outfile, O_CREAT|O_RDWR, 0666, $DB_BTREE) || die "Could not open or create database $outfile."; # Disable STDOUT buffering $| = 1; # Read in saved settings in database $number_of_spam_messages = $token_ratings{ " spam " } || 0; $number_of_nonspam_messages = $token_ratings{ " nonspam " } || 0; # print "SPAM#:$number_of_spam_messages, NON_SPAM#:$number_of_nonspam_messages\n"; foreach (%token_ratings) { next if(!defined $token_ratings{$_}); my(@line)=split(/\s+/,$token_ratings{$_}); print "$_\t@line\n" ; } untie( %token_ratings );
今回は、日本語の対応が完全なソフトウエアの紹介と言うわけではなかったため、 すぐに役に立つという記事にはなりませんでした。 申し訳ありません。 ただし、日本語化などの対応や、 Mozillaでの実装次第ではまもなく このような手法も利用されていくようになってくるのでしょう。 問題は、ここで紹介したソフトウエアはMozillaの実装を除いては、 古き良きUNIXの伝統に従ったフィルタでの実装となっていることです。 単なるユーザにとっては、 MUAからいかに便利に利用できるかというインターフェース回りで 工夫が欲しいところだと思います。 我こそはと思わん方は是非MUAへの組み込みにも挑戦してみてください。
[1] | 厳密な確率にはならないことは、 値が1を越えることから直感的に分かると思います。 |
[2] | 「spamでないものをspamと判断してしまうこと。」 日本語では第一種の誤りともいいます。 ここでは、分かりやすさを優先して河合さんの訳からこれを採用しました。 同じく、false negative、 つまり「spamであるのにspamと判断されなかったもの」は 「逃がすメール」としました。 |
[3] | もっとも、しつこいspamメールはバイアスをかけて 積極的に排除するという方針もあり得ます。 |
[4] | 中にはASCIIコードであることを単語の構成要素として、 しっかりチェックしているソフトウエアもあります。 |
[5] | ひとつの文字では中国語などの他のコードと 区別できない可能性があるためです。 |