higuchi.com blog

The means justifies the ends

私は如何にして心配するのをやめてスパムを愛するようになったか [CRM114の日本語対応]

さて、先日のインストール編に続いて、CRM114の日本語対応改造です。
CRM114のFAQには“BUT if you use a unicode-based or other wide-character language, you'll need to port up CRM114 to use wchar instead of char, as well as getting unicode-clean regex libraries.(Unicodeなど、マルチバイト文字の言語を使う場合にはCRM114をwcharを使うように改造して、Unicode対応の正規表現ライブラリを使わないとダメ)”と書いてありますが、そんな根性も時間もないので、CRM114本体には手を入れずに、スクリプトの工夫だけで日本語対応してしまいます。
幸い、マルチバイト文字対応はしていないとはいえ8-bit cleanだとのことですので、昨日書いたとおり、メールのテキストをnkfでEUC-JPに変換して文字コードを統一し、kakasiで単語の切れ目に“半角スペース”を入れてからCRM114に引き渡すように手を加えるというQuick Hackで日本語のメールもきちんと処理してくれます。
CRM114は5つまでの単語のつながりをひとかたまりとして頻度をチェックする仕組みになっていますから、kakasiがない環境の場合、“全角”文字1文字を1単語として区切ってN-gram風に使ってもそれなりの効果が期待できるのではないかと思います。
なにはともあれ、具体的な改造方法を。 ■mailfilter.crmの改造
スパムフィルターのスクリプトであるmailfilter.crmを、メールの文字列を解析する前に外部プログラムでフィルターをかけられるように改造します。

mailfilter.crmの150行目近辺
isolate (:m_text:) // isolate (:b_text:) /:*:_dw:/ isolate (:i_text:) /:*:_dw:/
        ↓
isolate (:m_text:) /:*:_dw:/ { match [:text_preprocessor:] /./ syscall (:*:_dw:) (:m_text:) /:*:text_preprocessor:/ } isolate (:b_text:) /:*:m_text:/ isolate (:i_text:) /:*:m_text:/

190行目近辺
syscall (:*:c:) (:exp_text:) /:*:mime_decoder:/
        ↓
syscall (:*:c:) (:exp_text:) /:*:mime_decoder: | :*:text_preprocessor:/

250行目近辺
alter (:m_text:) /:*:_dw: :*:_nl: :*:b_text: :*:_nl: :*:i_text: :*:_nl:/
        ↓
alter (:m_text:) /:*:m_text: :*:_nl: :*:b_text: :*:_nl: :*:i_text: :*:_nl:/
これで、入力されたメールのテキストは、すべてtext_preprocessorという変数に書いてある外部フィルターを通ってからスパム診断されるようになります。

追記:mailfilter.crmは少しずつ変わってきていますので、行数や変更前の中味が微妙に違っていると思います。適宜読み替えてください。
2005年11月の最新バージョンの場合は
- 210行目
match (:m_text:) [:_dw: 0 :*:decision_length:] /.*/
isolate (:m_text:)

isolate (:m_text:) /:*:_dw:/
{
match [:text_preprocessor:] /./
syscall (:*:_dw:) (:m_text:) /:*:text_preprocessor:/
}
match (:m_text:) [:m_text: 0 :*:decision_length:] /.*/

- 256行目
syscall (:*:c:) (:exp_text:) /:*:mime_decoder: /
syscall (:*:c:) (:exp_text:) /:*:mime_decoder: | :*:text_preprocessor: / という変更でOKのはず。

次に、text_preprocessor変数を定義します。この種の変数はmailfilter.cfという設定ファイルにまとめて書いてありますので、それにならいます。

■mailfilter.cfファイルの変更
mailfilter.cfの中のどこか好きな場所に次の1行を追加します。

:text_preprocessor: /nkf -em | kakasi -wc/
これは、text_preprocessorという名前の変数に nkf -em | kakasi -wc という文字列を入れるという命令です。
nkfは文字コード変換のプログラム。-eオプションはEUC-JPへの変換を意味し、-mはメールのSubjectのMIME Encodingされた文字も変換するオプションです。
kakasiの-wオプションが分かち書き変換をするためのオプションで-cは途中に改行が入っている漢字の単語をひとつながりにするオプションです。メールの文章は1行の文字数の制限のために単語の途中で強制的に改行が入っていることがあるので、これをつけておきます。

また、同じファイルの中にある
:lcr: /[[:graph:]][-.,:[:alnum:]]*[[:graph:]]?/ という行を
:lcr: /([[:graph:]][-.,:[:alnum:]]*[[:graph:]]?)|([^[:space:][:cntrl:][:graph:]]+)/ に書き換えます。
これはlcrという変数を定義しているのですが、この変数の中にはどういう文字列をスパムの診断のための単語として認識するかが正規表現で書いてあります。正規表現モジュールのマルチバイト文字関数を使うようにプログラム本体を改造すればこのままでも日本語文字の単語を単語として認識してくれるのですが、そうでない場合、この正規表現では日本語の文字は全部切り捨てられてしまいます。つまり、日本語のメールの中のASCII文字だけでスパムかどうかの診断をすることになってしまいます。日本語(この場合EUC-JPの“全角”)文字が連なったものを単語として切り出すように指定しなくてはなりません。古い正規表現モジュールならEUC-JPの文字を[\x8E\xA1-\xFE][\xA1-\xFE])]などと文字コードで表現することもできるのですが、このプログラムで使っているTREという正規表現モジュールはお行儀のいいPOSIX準拠なのでこれは使えません。そこでやや無理やりですが[^[:space:][:cntrl:][:graph:]]で非ASCII文字(つまり“全角”文字)を表現しています。
自分でこの正規表現を書き換えてみて、単語がうまく切り分けられているかどうかを調べたい場合は
crm -T mailfilter.crm < メールのテキストファイル 2> trace.txt などとやると、プログラムがどのように動いたかが逐一 trace.txt というファイルに出力されます。Classify で始まる行に、認識した単語が書かれています。
Paul GrahamのBetter Bayesian Filtering日本語訳)に書かれているような単語の切り分け方の工夫によって認識率がどうなるかを試してみたい場合は、この正規表現をいじってみればよいということになります。お時間のある方はぜひ試してみてください。

これで日本語化に必要な改造は完了です。
あとはオリジナルのドキュメントを参考に、mailfilter.cfの中の変数を設定します。
:spw: /DEFAULT_PASSWORD/ → :spw: /なにか好みのパスワード(半角)/
:mime_decoder: /mewdecode/ → :mime_decoder: /インストールされているMIME Decoderプログラム/
ここまでが必須の設定項目です。そのほか、ログファイルやスパム診断結果の出力方法の指定を好みに合わせて変更します。たとえば次のような変更です(ほかにもあります)。
:spam_flag_subject_string: /ADV:/ → :spam_flag_subject_string: //
:add_verbose_stats: /yes/ → :add_verbose_stats: /no/
:add_extra_stuff: /yes/ → :add_extra_stuff: /no/
:log_to_allmail.txt: /yes/ → :log_to_allmail.txt: /no/
...
■.procmailrc
CRM114は、入力されてきたメールがスパムかどうかを診断して、スパムだったらヘッダにX-CRM114-Status: SPAM ... という文字を追加し、そのまま出力するというプログラムですので、procmailを使って受信したメールをCRM114に食わせて、結果のメールのヘッダのX-CRM-114-Statusフィールドを見てスパムだったらSpamフォルダに分類するという処理を行います。次のようなレシピを使いました。

:0 fw:crm.lock
| /usr/local/bin/crm -u ~/.crm114/ mailfilter.crm
:0
* ^X-CRM114-Status: SPAM.*
| dmail +mail/Spam

この例では、ホームディレクトリの下に.crm114というサブディレクトリを作って、その中にmailfilter.crmや辞書ファイル、設定ファイル一式を入れてあります。また、私はuw-imapのmbx形式のメールフォルダを使っているのでdmailでフォルダに分類していますが、通常のmbox形式なら最後の行はスパムを入れるフォルダのファイル名だけを、MH形式ならフォルダにするディレクトリのパスの最後に/.をつけたものを書けばよいわけです。
フォルダを使わない(使えない)場合は、ヘッダに付加した情報を使って(スパムだったときSubjectにSpamという文字を追加するといったこともできます)メールソフト側でスパムだけを分別したり、スパムメールだけ別のメールアドレスに転送したりといった処理も考えられます。

■トレーニング
ベイジアンフィルターを使ったスパムフィルターは、使い始めにトレーニングと称して手元にあるスパムメールと通常メールを流し込むように指示してあることが多いですが、CRM114はそういう集中トレーニングをすると認識率が悪くなるので避けるようにと書いてあります。その代わりにTOE(Train Only Errors)と言って、実際に使ってみて間違った認識をしたものだけを「違う」と教えるのがよいそうです。
「違う」と教えるには、間違って認識したメールを、ヘッダーをつけたままで自分宛に転送するだけです。転送するとき、そのメールの先頭に
command パスワード spam (これはスパムだ、と教える)
command パスワード nonspam (これはスパムじゃない、と教える)
という行を入れておきます。そうすると、その行から後ろのテキストだけを読み取って“学習”するしくみになっています。転送するとき、CRM114がつけたX-CRM114-Statusなどのフィールドの行は削除しましょう。また、転送するときフッタに自分の名前などが追加されないように気をつけてください。そうしないと、自分の名前やメールアドレスまで“学習”されてしまいます。
スパムは知らない言語の文字コードでエンコードされていることも多いですから、メールの“ソース”を表示できるメールソフト(MozillaならCtrl-U)なら、ソースをコピー&ペーストして先頭にcommand行を付けるという操作が間違いがありません。

この状態で2日ほど使っていますが、500通ぐらいのスパムと100通ぐらいの通常メールを処理して、「違う」とトレーニングしたのは10通ぐらい。ごく最初のうちは間違って分類されていないか調べるためにスパムのフォルダをまめにチェックしていましたが、トレーニングの頻度もどんどん少なくなってきて、手放しでも安心できるレベルになってきました。この先どんな風に変化していくか、興味津々です。

必要な環境が揃っている方は、ぜひお試しくださいませ。

コメント

さとうさんのコメント:

トラックバックさせていただいている
http://d.hatena.ne.jp/steal...
のさとうともうします。

おかげさまで、CRM114を利用してそこそこ良い感じにフィルタリング出来るようになりました。
しかしどうも、学習がいまいちうまくいかないようで、特に日本語の場合に、何度も同じようなメールを学習させてやっても効果が上がらないことがあります。
学習用にだしたコマンドメールの結果を見ると、「LEARN AS NONSPAM UNNECESSARY」とか言われてしまうのですが、まだ学習させてないはずじゃん… で、同じようなメールがくるとまた間違ってSPAMと認識、という感じです。

higuchiさんはこのような状況にはなりませんでしたでしょうか。

樋口 理さんのコメント:

はじめまして。
lcr変数の正規表現を書き間違えていて、日本語の切り出しに失敗していたときに、そういうことになったことがあります。
まずは、きちんと日本語の単語を切り分けているかどうか、-T オプションをつけてログを出してみては?
2004/4/20 21:30

さとうさんのコメント:

アドバイスいただいたように、-T付けて観察してみたのですが、どうもうまく動いているっぽいです。
ただ、mime_decoderの指定をデフォルトのままにしていて、自分のところの環境にはなかったので、そこで失敗していたのかもしれません。
関係ないかもしれませんが、とりあえずそこを修正したので、様子を見てみます。

さとうさんのコメント:

もう少し追ってみてたのですが、自分はSPAM溜めとく用の別アカウントに転送しているため、Return-Path:フィールドが書き変わっているのと、From(From:ではなく最初の行のほう)が無くなっているため、それで先にSPAMと識別されたものが、NONSPAMとして学習させようとすると、すでにNONSPAMとして識別されてますよ、と言われているようです。
X-CRM114-Status:フィールドだけでなく、Return-Path:も削除したもので学習させてみます。

樋口 理さんのコメント:

なるほど。

ヘッダの情報はSpamの判断のかなり大きなウェイトを占めるように「育つ」ようですから、直感的には納得のいく誤動作ですね。
2004/4/23 10:08

さとうさんのコメント:

今回のように、SPAMとNONSPAMの指定のさせかたが一般ユーザには難しいと思うので、POPFileみたいなWebインターフェイスが出てないかな~、と思って探してみていたのですが、最近こんなのが作られたみたいです。

http://www.avtechpulse.com/...
http://www.johnjohnston.org...

imapサーバ上でSPAMとかNONSPAMとかに分類すると、それで学習してくれるようです。

ということで、imapdとSquirrelMailの導入して、POPFileと似たような環境の構築予定です。

樋口 理さんのコメント:

おー。これはいいですね。
IMAP経由で、再仕分けフォルダにドラッグ&ドロップするだけで教育してくれるわけですね。
私はuw-imapdで、Mboxフォーマットなので、John Johnston氏のやつが、まさにぴったりです。さっそく今週末にでも試してみます。
情報、ありがとうございます。
2004/4/23 11:02

コメントを書く

関連するかもしれない記事

How I Leaned to Stop Worrying and Love the Spam [CRM114のインストール]

さて、驚異のスパム分別精度99.98%を誇る、新型ベイジアン=マルコフモデル搭載(笑)スパムフィルターT...

この記事を読む »

Spam

宛て字の利用方法 [涙ぐましいスパム]

英語圏にも宛て字があることは知っていたんです。 "for you" を "4U" とか、"cool" を "kewl" とか...

この記事を読む »

ルールから確率へ [スパム除けベイジアンフィルター]

私のメールアドレスは、昔からWeb上などに掲示していたため、今では1日200通ぐらいのスパムが送られてき...

この記事を読む »

告白 [サーバーを Spam メール送信の踏み台にされました]

実にお恥ずかしい。自分で管理しているサーバーをスパム送信の踏み台にされてしまいました。みだりに公...

この記事を読む »

汎用テキスト分類フィルタの威力 [CRM114 活用事例]

先月導入したベイジアンフィルター式スパムフィルターのCRM114ですが、着々と手になじんで来ました。 ...

この記事を読む »

日本語対応 [Nucleusの使い方]

このサイトで使用しているNucleusというBlogシステムを日本語対応させる備忘録です。 このシステム、...

この記事を読む »

史上最大の攻撃 [コメントスパム]

夜遅く家に帰って、メールをチェックしながらこのブログを開いてみるとコメントスパムの嵐。 わずか...

この記事を読む »

世にスパマーのタネは尽きまじ [迷惑メールっていくら儲かる?]

世にスパマーのタネは尽きまじ [迷惑メールっていくら儲かる?]

3月11日の地震発生以来、各社ともメールマガジンでの営業活動を控えているようで、メールボックスがいつ...

この記事を読む »

くせもの [ビジネス FOMA M1000]

携帯電話を替えました。発表時から気になっていたビジネス FOMA M1000です。 出先でちょっとブラウザ...

この記事を読む »

ついに来たか [コメントスパム]

このブログにコメントを書いていただくと、メールで通知が来るように設定しているのですが、今朝メール...

この記事を読む »