ライブラリーとして使うZend Framework - PHPから日本語全文検索(形態素解析篇)
Posted 6月 30th, 2008 by twk先週末行った第34回PHP勉強会でkaz_29さんがPostgreSQLの日本語全文検索のお話をされていました。
私はメインでMySQLを使っていますが、MySQLだとまたやり方が違ったり、DBにモジュールを追加する必要があるのでサーバーに手を入れられないと使えなかったりするので、PHPだけで全文検索する方法を紹介します。Windowsでも動きます。
あんた前回uni-gram解析器紹介してるじゃん、て声もありそうですが、今回は新たにMecabを呼び出す形態素解析版をつくりました。
と言うことで、拡張モジュールのインストールは不要なんですがMecabコマンドのインストールが必要です。
n-gram版ならMecabコマンドのインストールは不要ですのでこのまま下の方まで見てもらえればと思います。
どのように実現しているかの簡単な説明ですが、
Zend Frameworkでは、Apache Luceneと言うJavaで使われる検索モジュールと互換の索引を作ったり呼び出したりできます。
ただしデフォルトでは日本語に対応していないのでそれを日本語に対応させたというわけです。
Zend Frameworkマニュアル内の記述も良かったらご覧ください。
良いところ
・DB (MySQL, PostgreSQL等) を選ばない
・DBやPHPにパッチを当てたり拡張モジュールを入れる必要がない
・Luceneの豊富な検索機能を使える
・Luceneのインターフェイスを使える他のプログラムからでも使える (例:Java)
・どのPHPフレームワークでも使える
・WindowsでもLinuxでも動く
駄目なところ
・索引追加時、クエリ構築時にMecabコマンドをいちいち起動するので多分ものすごーーく遅い
・ちゃんとテストしていないので実績がない
・DBのデータとは別に索引を管理する必要がある
・Mecabのインストールが必要
・Zend Frameworkのインストールが必要 (フレームワーク自体は使う必要ありません)
・辞書がUTF-8でないと若干処理があやしい
まずは、Zend Frameworkをインストールします。
本家配布サイトからダウンロードして適当な場所に置いてinclude_pathに追加しても良いのですが、
面倒なので非公式pear版を入れます。
# pear channel-discover zend.googlecode.com/svn # pear install zend/zend
次に、Mecabをインストールします。コマンドラインで
# mecab -v
と入力してバージョン情報が出ればすでにインストールされています。
なければMecabの公式サイトで日本語で説明されています。
Windowsの場合には環境変数PATHにbinディレクトリー (c:\program files\mecab\bin) を追加しておいてください。
今回作ったモジュールはcodereposにて公開しています。
関係ないモジュールも多少含まれますが、
# svn co http://svn.coderepos.org/share/lang/php/ZendFramework_ext
で取ってくるのが手っ取り早いと思います。
include_pathに設定しておいてください。
まずはMecabがちゃんと動いているかどうか下記で確認します。それっぽく出ていればOKです。
$text = '私は岩崎です。twkとも言います。東京都出身です。京都出身ではありません。ところで日本でWindows 95が発売されたのはいつでしょう?'; $mecabTagger = new Twk_MeCab_Tagger(); $mecabNodes = $mecabTagger->parseToNodes($text); echo "parseToNodes:
\n"; foreach ($mecabNodes as $node) { /* @var Twk_Mecab_Node $node */ echo "surface:{$node->surface} features:" . implode(', ', $node->features) . " characterType:{$node->characterType} startPosition:{$node->startPosition} endPosition:{$node->endPosition}
\n"; }
次に索引の作成と検索です。Zend_Search_Luceneを使ったことがないと
若干不明なところもあるかも知れませんが、一つの検索メソッドで、タイトルと本文のどちらからも検索できたりしています。
ちなみに、前回紹介したuni-gram版を使う場合は、$analyzerの行を入れかえます。
変更した場合、文脈は関係なく一文字ずつ比較するようになるので、
例えば「京都」で検索した時に「東京都出身です。」のコンテンツもヒットしてしまうようになります。
// 索引作成
$luceneIndexDir = 'c:\\temp\\lucene';
$searchIndex = null;
try {
$searchIndex = Zend_Search_Lucene::open($luceneIndexDir);
} catch (Zend_Search_Exception $e) {
$searchIndex = Zend_Search_Lucene::create($luceneIndexDir);
}
/* @var $searchIndex Zend_Search_Lucene_Interface */
require_once 'Zend/Search/Lucene/Analysis/Analyzer.php';
//$analyzer = new Zend_Search_Lucene_Analysis_Analyzer_Common_Utf8();
//$analyzer = new Twk_Search_Lucene_Analysis_Analyzer_Common_Utf8MbcsUnigram();
$mecabConfig = new Zend_Config(array('path' => 'c:\\program files\\mecab\\bin\\mecab.exe'));
$analyzer = new Twk_Search_Lucene_Analysis_Analyzer_Common_Utf8Mecab($mecabConfig);
Zend_Search_Lucene_Analysis_Analyzer::setDefault($analyzer);
// 追加
$encoding = 'UTF-8';
$data = array(
array('content_id'=>1, 'title'=>'名前', 'content'=>'私は岩崎です。'),
array('content_id'=>2, 'title'=>'岩崎の出身地', 'content'=>'東京都出身です。'),
array('content_id'=>3, 'title'=>'出身地', 'content'=>'京都出身ではありません。'),
array('content_id'=>4, 'title'=>'クイズ', 'content'=>'ところで日本でWindows 95が発売されたのはいつでしょう?'),
);
foreach ($data as $datum)
{
$doc = new Zend_Search_Lucene_Document();
$doc->addField(Zend_Search_Lucene_Field::Keyword('content_id', $datum['content_id'], $encoding));
$doc->addField(Zend_Search_Lucene_Field::Text('title', $datum['title'], $encoding));
$doc->addField(Zend_Search_Lucene_Field::Text('content', $datum['content'], $encoding));
// 古いのがあれば消去してから追加
$term = new Zend_Search_Lucene_Index_Term($datum['content_id'], 'content_id');
$query = new Zend_Search_Lucene_Search_Query_Term($term);
$hits = $searchIndex->find($query);
foreach ($hits as $hit)
$searchIndex->delete($hit->id);
$searchIndex->addDocument($doc);
}
$searchIndex->commit();
// 検索
Zend_Search_Lucene_Search_QueryParser::setDefaultEncoding($encoding);
$phrase = '京都';
echo 'searching with ', $phrase, '
';
$query = Zend_Search_Lucene_Search_QueryParser::parse($phrase);
$hitDocuments = $searchIndex->find($query);
foreach ($hitDocuments as $doc)
{
echo $doc->id, $doc->title, $doc->content, '
';
}
辞書の文字コードに関して、デフォルトではUTF-8になっていません。
各形態素の開始位置、終了位置を保存している部分がある関係で、
本格利用する場合にはUTF-8に変更した方が良いかもしれません。
変更の仕方
ソースを見てもらうとわかりますが、形態素解析のたびにMecabのプロセスが呼び出されます。
開発環境がWindowsのためphp_mecab (旧版の説明) は使ってません。
長い・・・ので色々と説明をはしょっているので通じるのか非常に不安です。説明上手への道は長く険しい。。そして果たしてどこまでちゃんと動くのか。


