Shoeisha Technology Media

CodeZine(コードジン)

特集ページ一覧

JavaMailとClassifier4jでインテリジェントな電子メールフィルタを作成する

オープンソースによるスパムフィルタ

  • ブックマーク
  • LINEで送る
  • このエントリーをはてなブックマークに追加
2005/04/01 00:00

スパムフィルタ製品を使っていて、機能上の限界や誤判定に悩まされたことはないだろうか。Classifier4Jは、電子メールや各種のテキストドキュメントを読み取って必要なものだけを正確に選別するカスタムアプリケーションを作成するためのオープンソースJavaライブラリである。

はじめに

 スパムフィルタ製品を使っていて、機能上の限界や誤判定(false positive)に悩まされたことはないだろうか。Classifier4Jは、電子メールや各種のテキストドキュメントを読み取って必要なものだけを正確に選別するカスタムアプリケーションを作成するためのオープンソースJavaライブラリである。

インテリジェントフィルタとは

 日々の生活が情報で埋め尽くされ、電子メールクライアントの受信ボックスが散らかり出すと、そうした情報を効率的に読み取り、選別、処理することが手作業では難しくなる。そもそも、そんなことに使う時間はないのだ。さまざまな人や企業が努力しているにもかかわらず、この状況はいっこうに改善されていない。むしろ、悪くなる一方である。

 こうした問題の対策の1つとして、インテリジェントフィルタの導入がある。インテリジェントフィルタは特定のキーワードを探すだけでなく、テキストの意味も判別しようとする。つまり、電子メールのテキストを読み取って定義済みの一連のパラメータを基に、その内容がユーザーに必要な情報かどうかを統計的に判断するのだ。最近のスパムフィルタの多くにはこの機能があり、ユーザーがそのメールがスパムかどうかを選別するたびに学習していく。このようなツールは時間の経過とともに賢くなっていくが、それでも間違いが完全になくなるわけではない。例えば、よく見られるのが誤判定の問題である。

Classifier4JとJavaMail APIを使った電子メールクライアント

 Classifier4Jは、テキストの分類処理を行うためのオープンソースJavaライブラリである(Sourceforgeから入手可能)。このライブラリは「ベイジアン分類法」を実装している。ベイジアン分類法とは、特定の仮説が成り立つ蓋然性をベイズ定理に基づいて計算する統計手法である(適切な実装の概要についてはhttp://www.paulgraham.com/better.htmlを参照)。これは、通常テキストの内容が特定の主題に合致しているかどうかを評価するために使用され、代表的な使用例は電子メールがスパムかどうかを判別することである。

 本稿では、JavaMail APIを使用して、非常に優れた機能を持つ単純なPOP3クライアントを作成する。これを応用して、IMAP、POP3、SMTPを使った独自のメールアプリケーションを開発することができる(JavaMailの詳細については、Sunのドキュメントを参照のこと)。このPOP3クライアントを、POP3ボックスから電子メールを取り出し、Classifier4Jライブラリを通じて、内容の分類、スパム判定、さらには内容の自動要約を行うようにする。

 まずはJavaMail APIを入手し、使用できる状態にする。JavaMailはSunから入手できる(本稿のソースコードではバージョン1.3.1を使用)。さらに、JavaMailに従属するJavaBeans Activation Framework(JAF)も必要である。

電子メールクライアントの作成

 上記のパッケージをダウンロードしてインストールしたら、次は電子メールクライアントを作成する。ここでは、POP3電子メールアカウントと、そのアカウントに関連するユーザー名、ログイン、サーバー名の情報が必要になる。

 ここで作成するのは非常に単純なコンソールアプリケーションだが、必要であれば、後からもっと複雑にすることもできる。ここで興味ぶかいのは、これをサーブレットベースのアプリケーションに組み込めば、スパムフィルタと分類機能を備えた、HotmailやYahoo!のようなホスティング型電子メールクライアントを実現できることだ。

 このアプリケーションのコードは次のようになっている。

package com.devx.jmail;
import javax.mail.*;
import javax.mail.internet.*;
import java.util.*;
import java.io.*;
import net.sf.classifier4J.*;
public class MailReader
{
  public static void main(String[] args)
  {
    try
    {
      String popServer="yourpopserveraddress";
      String popUser="yourpopusername";
      String popPassword="yourpoppassword";
      GetMail(popServer, popUser, popPassword);
    }
    catch (Exception e)
    {
      e.printStackTrace();
    }
    System.exit(0);
  }

 アプリケーションが正常に動作するには、popServerpopUserpopPasswordには、POP3アカウントの正しい値を割り当てる必要がある。見てのとおり、これはごく単純なコンソールアプリケーションで、大した処理は(現時点では)行っていない。実際の処理を受け持っているのはGetMail関数である。

メールの取得

 JavaMail APIは非常に大きなライブラリなので、ここで詳細を解説することはできない。ここで紹介する例では最も単純な処理だけを行ってみる。つまり、ログイン、受信ボックス内のメールのスキャン、受信ボックス内のメールの複製ダウンロードである。ここでは、この関数の核となる部分を紹介しておく。GetMail関数の完全なコードは、JavaMailのダウンロードファイルを参照のこと。

store.connect(popServer, popUser, popPassword);
      folder = store.getDefaultFolder();
      if (folder == null) throw new Exception("No default folder");
      folder = folder.getFolder("INBOX");
      if (folder == null) throw new Exception("No POP3 INBOX");
      folder.open(Folder.READ_ONLY);
      Message[] msgs = folder.getMessages();
      for (int nMsg = 0; nMsg < msgs.length; nMsg++)
      {
        strEmail = buildMessage(msgs[nMsg]); 
      }

 JavaMailのstoreオブジェクトをセットアップしたら、ServerNameUserNamePasswordパラメータを使用して、storeオブジェクトに接続する。接続が成功し、例外が返されなかった場合は(上記のコードはtry..catch句内に書く必要がある)、storeオブジェクトに関連付けられているデフォルトフォルダを取得する。デフォルトフォルダがない場合は、例外が返される。

 それぞれのPOP3アカウントには、着信メールを格納するためのINBOXフォルダがある。このフォルダが存在する場合は、フォルダを開き、そこからMessageオブジェクトの配列を読み取る。これは現在の受信ボックス内の全メールの一覧である。INBOXフォルダは読み取り専用(READ_ONLY)として開かれているので、メールを読み取っても、その受信ボックスからメールが削除されることはない。

 その後、すべてのMessageオブジェクトをループ処理し、buildMessage関数を使用して各Messageの文字列を生成する。buildMessage関数の完全なコードについては、JavaMailのダウンロードファイルを参照してほしい。ここでは、この関数の核となる部分を紹介しておく。

InputStream is = messagePart.getInputStream();
  BufferedReader reader=new BufferedReader(new InputStreamReader(is));
  String thisLine=reader.readLine();
  while (thisLine!=null)
  {
    strReturn +=thisLine;
    thisLine=reader.readLine();
  }

 POP3の電子メールメッセージは、送信者名、送信者アドレス、件名、本文など、いくつかのエンティティからなる。さらに本文はいくつかのパーツに分かれ、添付ファイルを含んでいることもある。buildMessage関数はこれらのエンティティをすべて受け取り、1つの文字列に連結して、呼び出し元に返す。

 電子メールメッセージのさまざまなパーツの中で、今回のサンプルで重要なのは本文の部分である。本文は一般に複数のテキスト行からなるので、messagePartオブジェクト(電子メール本来から作成されるオブジェクト、詳しくは関数の完全なコードを参照)は、本文を1行ずつ読み取るために使われるInputStreamを公開している。これを使用してBufferedReaderを作成し、そのBufferedReaderが電子メールの本文を読み取る。

 これで、今回のサンプルで使用する単純な電子メールクライアントが完成した。このクライアントの機能は、POP3ボックスにログインし、受信ボックスからメールを取得し、メールを1つずつダウンロードし、それをClassifier4Jで分類・要約するための文字列に変換することだけである。

単純なテキスト分類

 Classifier4Jには、テキスト分類のためのライブラリが数多く含まれている。まず、単純な照合を行うSimpleClassifierについて見ていこう。次のコードは、SimpleClassifierを使用して、スパムメールかどうかの蓋然性スコアを設定する方法を示している。この例では、スパムかどうかを単に「Belgium」という単語の有無に基づいて判断している(『Hitch Hikers Guide to the Galaxy』が好きな人ならば、この単語を選んだ理由はおわかりいただけるだろう。詳しい説明については、このページを参照)。

public static double checkSpam(String strEmailBody)

  {
    double dClassification = 0.0;
    try
    {
      SimpleClassifier classifier = new SimpleClassifier();
      classifier.setSearchWord( "Belgium" );
      dClassification = classifier.classify(strEmailBody);
    }
    catch(Exception e)
    {
      e.printStackTrace();
    }
    return dClassification;
  }

 ダウンロードしたコードを見ればわかるが、前出のGetMail関数はこの関数を呼び出している。電子メールをダウンロードして1つの文字列にまとめた後、その文字列をこの関数に渡し、スパムの蓋然性スコアを判定する。これは非常に単純な例なので、蓋然性スコアは0.01.0のどちらかになる。0.0は正当な電子メール、1.0はスパムの可能性が高いメールである。このプログラムでは、蓋然性スコアが0.7より大きいもの(>0.7)をすべてスパムと見なすことにする。なお、ここでは大文字と小文字を区別している。単語「belgium」を含む電子メールのスコアは0.0で、単語「Belgium」を含む電子メールのスコアは1.0になる。大文字と小文字を区別しないようにしたい場合は、strEmailBodyを変換したバージョン(つまり、すべて小文字の「belgium」や、すべて大文字の「BELGIUM」など)についてもチェックする必要がある。

ベイジアン分類

 上記のような単純な分類法は、手始めとしては役に立つだろう。しかし、もっと細かい分類をしたい場合には、ベイジアン分類法を使用する必要がある。幸い、Classifier4jではベイジアン分類法を簡単に利用でき、複雑な統計分析を水面下で実行することができる。

 次に示すのは、ごく単純なベイジアンフィルタの使用方法である。

IWordsDataSource wds = new SimpleWordsDataSource();
      wds.addMatch("Belgium");
      wds.addMatch("Vogon");
      wds.addMatch("Devx");
      IClassifier classifier = new BayesianClassifier(wds);
      dReturn = classifier.classify(strEmailBody); 

 このフィルタはまずSimpleWordsDataSourceで初期化され、キーワードとなる3つの単語が設定される。ただし、この単純なサンプルは、このままではまったく役に立たない。ベイジアンフィルタの判断の基準になるものがほとんどないからだ。ベイジアンフィルタを効果的に使用するためには、コンテキストに合致する単語群とコンテキストに合致しない単語群のデータセットを用意して、フィルタを訓練する必要がある。実世界では、両方のコンテキストに合致する単語が数多く存在する。

 具体的な例で考えてみよう。たとえば「the」という単語を考えてほしい。これは、スパムであってもなくても、ほとんどすべての電子メールに出てくる単語である。しかし、「millionaire(大金持ち)」という単語はスパムメール内に出てくることが多い。高機能のスパムフィルタアプリケーションは、何がスパムで何がそうでないかを絶えず学習しており、その中で「知性」を身につけていく。つまり、着信メールを受け取ったときに、これまでの経験と知識に基づいて、そのメールがスパムかどうかを判断するようになる。

 Classifier4Jのベイジアンフィルタを訓練するには、ITrainableClassifierインターフェイスを使用する。実際のデモは、Classifier4Jのオプションディストリビューションで見ることができる(src/java/net/sf/classifier4J/demo内)。このデモは、フィルタを効果的に訓練するための入力テキストファイルの形になっており、別のファイルの妥当性を判定するときに、この入力ファイルを「刺激」として使用するようベイジアンフィルタを訓練する。ここで紹介したメールアプリケーションで着信メールに常にフィルタを適用し、スパムを排除できるようにするのは、比較的簡単である。

Classifier4Jによる自動要約

 このアプリケーションでは、着信メールを分類するだけでなく、内容を要約することもできる。たとえば、受信ボックスの中身を取り出し、メールの内容を要約し、その要約を新しい電子メールにして携帯電話やその他のモバイルツールなどに送信するようアプリケーションを拡張することも可能である。

 Classifier4Jで要約を行うのは非常に簡単である。必要な作業は、ISummarizerからクラスを作成し、そのクラスに要約対象の文字列と要約後の文の数を渡すことだけだ。残りの処理はすべて自動的に行われ、要約された文字列が返される。

 次に示すのは、ダウンロードファイル内に含まれているgetSummaryメソッドのコードである。

public static String getSummary(String strEmailBody, int nSentences)
  {
    ISummariser summ = new SimpleSummariser();
    String strSumm = summ.summarise(strEmailBody,nSentences);
    return strSumm;
} 

 今回のアプリケーションでは、ISummarizerを呼び出し、それに電子メールの本文を渡し、この電子メールを3つの文に要約するよう要求したいと思う。そのためのコードは次のようになる。

String strSumm = getSummary(strEmail,3); 

 このアプリケーションをテストするために、以前に私が書いたDevXの記事の1ページ目を電子メールの本文に貼り付け、自分のメールアドレスに送信してみた。すると、Javaアプリケーションはこの電子メールをダウンロードし、要求どおりに要約してくれた。結果は次のようになった。

The invention of database driver methodologies such as JDBC 
and ODBC led to applications being loosely coupled with their back end databases, 
allowing best-of-breed databases to be chosen-and swapped
out when necessary-without any ill-effect on the user interface. 
Similarly, the decoupling of data and presentation in HTML-by using XML for the data and 
XSLT for the presentation of data-has led to much innovation and flexibility, 
not least of which is the ability to deliver a document as data in XML and deliver 
custom styling for that document with different XSLTs. 
A runtime engine would be present on the desktop, and servers would 
be able to deliver the GUI to the browser with an XML document.

 これは1001ワードのテキストを3つの文(合計115ワード)に要約したもので、なおかつ元の記事の内容をきちんと表している。なかなか見事な要約ぶりである。

まとめ

 現代では、ますます多くの情報が受信ボックス、インスタントメッセンジャー、電話、テレビなど各種メディアに押し寄せるようになっているので、こうした情報のコンテキストを把握するための技術が今後より一層求められるようになるだろう。わかりやすい利用例はスパムフィルタだが、それ以外にも、さまざまな方法でこの機能が利用されるようになるだろう。たとえば、ニュースプロバイダからの新着ニュースを監視し、関心のある分野のニュースだけを選んで要約し、モバイルツールに転送するインテリジェントエージェントはどうだろうか。あるいは、映画のレビューを読み取り、関心のあるジャンルのレビューだけを選び、あらすじの要約を電子メールで送信するなどのアプリケーションも考えられる。

 可能性は無限である。Classifier4Jオープンソースライブラリは、このようなアプリケーションを開発するための第一歩だ。本稿で紹介したのは、Classifier4jと電子メールインターフェイスを使用して実現できることのほんの一部である。残りは自分で見つけていってほしい。



  • ブックマーク
  • LINEで送る
  • このエントリーをはてなブックマークに追加

著者プロフィール

  • Laurence Moroney(Laurence Moroney)

    ニューヨーク市の大手金融サービス企業のベテラン設計者。カジノ管理システムから企業内チャットシステムにいたるまで、さまざまな分野のソフトウェア開発に携わる。Webサービスセキュリティに関する共著が近日刊行予定。

  • japan.internet.com(ジャパンインターネットコム)

    japan.internet.com は、1999年9月にオープンした、日本初のネットビジネス専門ニュースサイト。月間2億以上のページビューを誇る米国 Jupitermedia Corporation (Nasdaq: JUPM) のニュースサイト internet.com や EarthWeb.c...

バックナンバー

連載:japan.internet.com翻訳記事

もっと読む

All contents copyright © 2005-2020 Shoeisha Co., Ltd. All rights reserved. ver.1.5