関口宏司のLuceneブログ

OSS検索ライブラリのLuceneおよびそのサブプロジェクト(Solr/Tika/Mahoutなど)について
スポンサーサイト

一定期間更新がないため広告を表示しています

| スポンサードリンク | - | | - | - |
BitSetからDocIdSetへの変更(2.4)
まだ先の話だが、Lucene 2.4からFilterクラスのBitSetオブジェクトを返すbits()メソッドがdeprecatedとなり、代わりにDocIdSetオブジェクトを返すgetDocIdSet()メソッドが導入されることとなった。

Filterは検索結果をフィルタリングするクラスである。これまではフィルタリングのために、インデックス中の全文書数をビット数(長)に持つBitSetをフィルタとして使用していた。しかしこのような実装の縛りがあると、メモリ効率が非常に悪くなるときがある。たとえば1,000万件の文書があるときは(単純に8で割って)およそ1MBのメモリをBitSetのために消費することになる。このとき、「立っている(trueになっている)」ビットがたとえ1ビットであっても1MB消費してしまうので大変効率が悪い。

そもそも文書IDのセットを表現できればよいので、BitSetを使うとしている現在のインタフェースはよろしくない。そこで「文書IDのセット」を表現するDocIdSetが新たに導入された。

DocIdSetの実装クラスとしては、従来のBitSetを内包して使用するDocIdBitSetの他、OpenBitSetおよびSortedVIntListが提供されている。

OpenBitSetはすでにSolrで使用されていたものがLuceneに「逆輸入」されたものである。OpenBitSetはBitSetよりもほとんどの演算が高速である(処理性能比較はOpenBitSetのJavadocを参照のこと)。名前の"open"の由来は、「ビット」を保持する配列が、BitSetではprivateであるのに対し、OpenBitSetではprotectedとなっており、子クラスを実装して「より効率のよいアルゴリズム」を組む余地があること、という理由らしい。

SortedVIntListは、名前から想像できるとおり、Luceneのインデックスでも使われているVIntを使って文書IDのリストを保持するクラスである。このクラスを使えば冒頭に説明したメモリ効率の点でBitSetよりも大変有利になる。
| 関口宏司 | Luceneクラス解説 | 07:49 | comments(0) | trackbacks(0) |
Lucene 2.3.1のリリース
Lucene 2.3.0のバグフィックスバージョンであるLucene 2.3.1がリリースされた。修正された不具合は下記に記されている:

http://svn.apache.org/repos/asf/lucene/java/tags/lucene_2_3_1/CHANGES.txt

Lucene 2.3.0からはインデックスフォーマットの変更はないので、Lucene 2.3.0を使用しているプロジェクトは直ちにLucene 2.3.1に切り替えることをお勧めする。
| 関口宏司 | Luceneリリース | 07:48 | comments(1) | trackbacks(0) |
To be, or not to be.
"To be, or not to be"はハムレットでの有名なセリフである(と偉そうに書きながら読んだことない)が、検索エンジンの側から見ると「ストップワードだけからなる文章」として有名な文字列である。したがって通常はトークンが生成されない。したがって検索できない。

2年位前まではたしかGoogleでも検索できなかったと思う。検索にはヒットするが、かならずしも「To be, or not to be」をきちんと含むHTMLがヒットしていなかった記憶がある。

今日たまたまお客さんにストップワードの説明をしていてGoogleの例を引き合いに出し「To be, or not to be」を実際にGoogleで検索したところ、なんときちんと「To be, or not to be」が含まれるHTMLがヒットするではないか!

普通のユーザはこんなところはどうでもいい話かもしれないが、感動して思わず書いてしまったのだった。
| 関口宏司 | その他(分類不能) | 22:30 | comments(1) | trackbacks(0) |
Microsoftがバイナリ形式のOffice文書仕様を公開
マイクロソフトがOffice文書のバイナリフォーマットの仕様を公開したらしい。

米Microsoftがついに,バイナリ形式のOffice文書仕様を公開
http://itpro.nikkeibp.co.jp/article/NEWS/20080218/294085/

Microsoft Office Binary (doc, xls, ppt) File Formats
http://www.microsoft.com/interop/docs/OfficeBinaryFormats.mspx

公開した理由は上記記事によれば「Open XMLをISO(国際標準化機構)標準にするうえで,複数の国の標準化団体から既存のバイナリ形式についても開示するよう求められたこと」とのことであるが、検索エンジン業界から見てもうれしいニュースである。

Office文書を検索対象にするにはOffice文書ファイルから生のテキスト文字列を取り出す必要があるが(このプログラムを一般にフィルタと呼ぶ)、これまではこの種の情報は非公開であったためフィルタの開発は「手探り」で行われていた。しかし今後は、バイナリフォーマットが公開されたことで、フィルタの質の向上やフィルタプログラムの選択肢が増えていくことが期待できる。

OSSのフィルタ:
http://poi.apache.org/index.html
http://textmining.org/
http://www.foolabs.com/xpdf/home.html
http://b2xtranslator.sourceforge.net/

フリー:
http://www31.ocn.ne.jp/~h_ishida/xdoc2txt.html

有償製品:
http://www.dehenken.co.jp/products/products-01/products-01.html
| 関口宏司 | Luceneツール | 14:37 | comments(0) | trackbacks(0) |
IndexWriterのJavadocの日本語訳
Lucene 2.2からLucene 2.3にかけて、効率的なインデクシングを実現するためにIndexWriterは大変貌を遂げた。

IndexWriterの現在の動作は同クラスのJavadocに詳細に記述されている。Javadocを英語で読むのはなかなか大変なので、現時点のIndexWriterのJavadocの日本語訳をここに掲載する。なお翻訳は一部かなりの意訳が入っているので、正確に読みたい方はオリジナルのJavadocを合わせて参照していただきたい:

IndexWriter
http://hudson.zones.apache.org/hudson/job/Lucene-trunk/javadoc/org/apache/lucene/index/IndexWriter.html




IndexWriterはインデックスを作成・保守します。

コンストラクタのcreate引数は新しいインデックスを作成するか、既存のインデックスをオープンするかを決定します。他のreaderたちがインデックスを使用中でもcreate=trueでインデックスをオープンできます。古いreaderたちがスナップショットをオープンした時点のインデックスで検索を続行し、再オープンするまで新しく作成されたインデックスは参照されません。create引数をとらないコンストラクタもあり、こちらはインデックスがなければ作成し、あれば既存のインデックスをオープンします。

いずれにせよ、文書はaddDocument()で追加され、deleteDocuments()で削除されます。updateDocument()を使うと文書を更新することができます(これは当該文書を削除して追加するだけです)。追加・更新・削除を行った場合は、closeを呼び出す必要があります。

これらの変更はメモリ中にバッファされ定期的にDirectoryにフラッシュされます(前述のメソッド呼び出し中に)。バッファされた「削除数の上限」(setMaxBufferedDeleteTerms(int)を参照のこと)か、最後のフラッシュ以降に追加された「文書の上限」のどちらかに先に達した場合にフラッシュが実行されます。「文書の上限」に達したときにフラッシュが行われる場合、「文書の上限」とはRAMの使用量(setRAMBufferSizeMB(double)を参照のこと)か、または、文書数を基準とします。デフォルトはRAMの使用量が16MBに達したときにフラッシュが行われます。最適なインデクシングスピードを得るには十分なRAMバッファ使用量を設定してください。flush()メソッドを呼ぶことで強制的にフラッシュすることもできます。フラッシュが行われると、ペンディングされた文書の削除と追加がインデックスに書き込まれます。フラッシュにより1つ以上のセグメントのマージが発生することもあります。セグメントのマージはデフォルトでバックグラウンドスレッドで行われるので、addDocument()の呼び出しはブロックされません(下記のMergeSchedulerの変更も参照してください)。

コンストラクタのオプション引数autoCommitは、同じインデックスを読んでいるIndexReaderインスタンスたちへの変更の可視性を制御します。これがfalseのとき、インデックスの変更はclose()メソッドが呼ばれるまで知ることができません。つまり変更はDirectoryに対して新しいファイルにフラッシュされますが、コミットされないということです(新しいsegments_Nファイルが作成されません)。もし何かよくないことがclose()前におきると(たとえばJVMのクラッシュなど)、インデックスは変更を反映していない状態のままとなります(最初の状態のままです)。abort()を呼ぶと、変更をコミットせずにIndexWriterをクローズします。そしてフラッシュされたインデックスファイルを削除します。このモードは都合の悪いときにreaderたちのリフレッシュを防止するのに役立ちます(たとえば、すべての削除を完了したが、追加を完了していないときなど)。これは単純な単一writerトランザクションセマンティクスを実装するのに利用できます(「すべてかゼロか」)。

autoCommitがtrueのときは、フラッシュ時にコミットも行われます(IndexReaderインスタンスはインデックスの更新を参照可能になります)。これはデフォルトでLucene 2.2以前の動作と同じです。このモードで実行中のときは、最適化中やマージ中にreaderたちがリフレッシュしないように気をつけてください。大量のディスクスペースを消費してしまいます。

autoCommitの指定に関係なく、IndexReaderやIndexSearcherはオープンした時点のインデックスを参照します。readerをオープンした後にインデックスにコミットされた変更は、インデックスを再オープンするまでreaderからは見えません。

もしインデックスにしばらく文書が追加されることがなく最適な検索性能が要求されるときは、クローズする前にoptimize()メソッドを呼ぶとよいでしょう。

IndexWriterをオープンすると使用中のディレクトリにロックファイルが作成されます。このとき同じディレクトリに対してIndexWriterをもうひとつオープンしようとすると、LockObtainFailedExceptionがスローされます。IndexReaderを使って同じディレクトリのインデックスから文書を削除していたときもLockObtainFailedExceptionはスローされます。

高度な使い方: IndexWriterはオプションでIndexDeletionPolicyの実装を指定できます。これにより前回のコミットをインデックスからいつ削除するかを制御できるようになります。デフォルトのポリシーはKeepOnlyLastCommitDeletionPolicyで、新しいコミットが完了するとすぐに前のコミットを削除します(Lucene 2.2以前の動作と同じです)。あなた自身のポリシーを実装することで前のコミット時点のインデックスを明示的に保持することができるようになります。これはNFSのようなファイルシステムで必要になることがあります。NFSではLuceneがあてにしている「最後にcloseした時点で削除される」セマンティクスがサポートされないためです。

高度な使い方: IndexWriterはMergePolicyとMergeSchedulerを別々に変更できます。MergePolicyはインデックス中のセグメントが変更されるときは常に呼ばれます。その役割はマージがある場合にマージを選択し、マージの詳細を記述したMergePolicy.MergeSpecificationを返すというものです。optimize()時のマージも選択します。デフォルトはLogByteSizeMergePolicyです。また、MergeSchedulerはリクエストされたマージと共に呼び出され、マージをいつどのように実行するのか決定します。デフォルトはConcurrentMergeSchedulerです。
| 関口宏司 | Luceneクラス解説 | 10:09 | comments(0) | trackbacks(0) |
IndexWriter.expungeDeletes()メソッドの追加(Lucene 2.4)
現trunkのIndexWriterクラスに、インデックスから削除された文書を完全に取り除くexpungeDeletes()メソッドが追加された。

IndexReaderやIndexWriterを使ってインデックスから削除された文書はいわゆる「削除フラグ」が立てられた状態に過ぎない。いままではoptimize()をする以外にこれを完全に削除する方法がなかったが、expungeDeletes()が追加されたことでoptimize()よりも「軽く」削除文書を取り除くことができるようになった。optimize()はセグメントファイルを1つにまとめるが、expungeDeletes()はそのようなことは行わないので、optimize()よりもコストが低いのだ。

expungeDeletes()のJavadocにあるとおり、expungeDeletes()を行うことで、ディスク消費量と検索実行時のメモリの消費量の削減効果がある。

また、インデックス中に削除済み文書があると「スコア計算」にも影響を与える。なぜそうなってしまうかというと、文書を削除してoptimize()やexpungeDeletes()を行わないと、idf値を計算する際のdocFreqとmaxDocが削除前のままとなっているためだ。

これを確認するプログラムを紹介しよう。このプログラムは最初に"aaa", "aaa", "bbb"という3つの文書を登録し、次の操作を順番に行うものである:


  1. "aaa bbb"を検索して検索結果をスコアと共に表示

  2. docId=1の"aaa"の文書を削除

  3. "aaa bbb"を検索して検索結果をスコアと共に表示

  4. expungeDeletes()を実行

  5. "aaa bbb"を検索して検索結果をスコアと共に表示



実行結果は次のとおりとなる:



** 初期状態
maxDoc = 3, numDocs = 3
score = 0.5725882 : bbb
score = 0.28986934 : aaa
score = 0.28986934 : aaa

** 削除直後
maxDoc = 3, numDocs = 2
score = 0.5725882 : bbb
score = 0.28986934 : aaa

** expungeDeletes()呼出し後
maxDoc = 2, numDocs = 2
score = 0.35355338 : aaa
score = 0.35355338 : bbb



3回の"aaa bbb"という検索実行に対し、初期状態では2つのaaaよりも1つのbbbが高いスコアを獲得してトップに表示されている。そして1つの"aaa"を削除直後では、削除されずに残ったaaaと、bbbは同スコアになるはずが削除前と変わらないスコアとなっている。これは削除によってdocFreqとmaxDocが変わらないことがidf計算に影響を与えてるためだ。これはLuceneの仕様である。

そして、expungeDeletes()実行後はaaaとbbbは同スコアとなっているのがわかる。

プログラムは次のとおりである:



public class TestExpurgeDeletes {

static Directory dir = new RAMDirectory();
static Analyzer analyzer = new WhitespaceAnalyzer();
static final String F = "f";

public static void main(String[] args) throws IOException, ParseException {
makeIndex();
System.out.println( "** 初期状態" );
printSearchResults();
deleteDoc();
System.out.println( "¥n** 削除直後" );
printSearchResults();
expurgeDeletes();
System.out.println( "¥n** expurgeDeletes()呼出し後" );
printSearchResults();
}

static void makeIndex() throws IOException {
IndexWriter writer = new IndexWriter( dir, analyzer );
writer.addDocument( getDoc( "aaa" ) );
writer.addDocument( getDoc( "aaa" ) );
writer.addDocument( getDoc( "bbb" ) );
writer.close();
}

static Document getDoc( String value ){
Document doc = new Document();
doc.add( new Field( F, value, Store.YES, Index.TOKENIZED ) );
return doc;
}

static void printSearchResults() throws IOException, ParseException {
IndexSearcher searcher = new IndexSearcher( dir );
int maxDoc = searcher.getIndexReader().maxDoc();
int numDocs = searcher.getIndexReader().numDocs();
System.out.println( "maxDoc = " + maxDoc + ", numDocs = " + numDocs );
QueryParser parser = new QueryParser( F, analyzer );
Query query = parser.parse( "aaa bbb" );
Hits hits = searcher.search( query );
for( int i = 0; i < hits.length(); i++ ){
System.out.println( "score = " + hits.score( i ) + " : " + hits.doc( i ).get( F ) );
//Explanation exp = searcher.explain( query, hits.id( i ) );
//System.out.println( exp.toString() );
}
searcher.close();
}

static void deleteDoc() throws IOException {
IndexReader reader = IndexReader.open( dir );
reader.deleteDocument( 1 );
reader.close();
}

static void expurgeDeletes() throws IOException {
IndexWriter writer = new IndexWriter( dir, analyzer, false );
writer.expungeDeletes();
writer.close();
}
}



コメントアウトされている部分を有効にすると、それぞれの文書のスコアの詳細が表示されるので、削除前後やexpungeDeletes()実行前後のidf値を比べてみるとよいだろう。
| 関口宏司 | Luceneクラス解説 | 07:28 | comments(0) | trackbacks(0) |
警告: Lucene 2.3.0の不具合
現在、Lucene 2.3にて、autoCommit=falseでIndexWriterを使用すると作成されたインデックスが不整合を起こす不具合が報告されている:

https://issues.apache.org/jira/browse/LUCENE-1173

すでに上記にてパッチが提供されたが、他のパターンでの不具合も報告されており、現在テストケースおよび修正パッチの開発がされている。今週末ごろを目標にLucene 2.3.1がリリースされる予定である。

なお、autoCommit=true(デフォルト)で使用している分には発生しない。
| 関口宏司 | Luceneリリース | 10:03 | comments(0) | trackbacks(1) |
Tokenのタイプをビットで表す(Lucene 2.4)
まだ「実験的」段階であるが、次期Lucene 2.4に相当する現trunkのTokenクラスに、タイプをビットで表す追加コードが入った。

LuceneにおいてToken(トークン)とは、単語のテキスト文字列と単語の文章中の位置である開始オフセット&終了オフセット、および単語の種別(タイプ)を保持するオブジェクトである。Lucene 2.2からはこれらに加えてPayloadが保持できるようになった。

このうちタイプはStringであり、Tokenizerとして形態素解析を用いる場合、タイプには形態素解析器が識別した単語の品詞情報を入れるのが一般的な使い方である。タイプはStringなので、(Senを使用した場合は)タイプには「名詞-固有名詞-人名」や「名詞-固有名詞-地域-一般」などとTokenizerにより設定される。

Tokenizerの下流であるTokenFilterは上流から送られてきたトークンを加工したり、場合によってはタイプ情報を見ながらフィルタリングしたりする。今回の修正はこのタイプ情報をビットで表現するようにしよう、というものである。タイプをビットで表すようにAnalyzer内で統一すれば、Stringよりもフィルタリングなどの計算が簡単で高速に行えるようになる、というメリットがあるためだ。

このビットは現在のところプリミティブのint型が用いられているが、longに変わる可能性も示唆されている。
| 関口宏司 | Luceneクラス解説 | 01:36 | comments(0) | trackbacks(0) |
[小ネタ] textmining.org
textmining.orgからWordファイルからテキストを抽出するtm-extractorsライブラリが公開された:

http://www.textmining.org/

使い方は次のようにとても簡単だ:



public class TestWordTextExtractor {
public static void main(String[] args) throws Exception {
WordTextExtractorFactory factory = new WordTextExtractorFactory();
// args[0]にはWordファイル名を指定
TextExtractor extractor = factory.textExtractor( new FileInputStream( args[0] ) );
System.out.println( extractor.getText() );
}
}


| 関口宏司 | その他(分類不能) | 10:05 | comments(0) | trackbacks(0) |
Luke 0.8のリリース
昨日Lucene 2.3対応版であるLuke 0.8が公開された:

http://www.getopt.org/luke/

Luke 0.8はLucene 2.3に対応した以外に次の特徴を持つ:


  • Java 5.0が必須となった。

    なお、Lucene 2.3はまだJava 1.4をサポートしている。

  • [Documents]タグに次のボタンが追加された。

    • [TV]選択したフィールドのTerm Vectorをポップアップ画面で表示する(下図)。
      Luke 0.8 term vector

    • [Text]選択したフィールドのテキストをポップアップ画面で表示する。

    • [Hex]選択したフィールドのテキストをポップアップ画面にてHex値で表示する。

    • [Save]選択したフィールドのテキストをファイルに保存する。


  • [File]-[Open Lucene index]メニューにてインデックスをRAMDirectoryにロードするオプションが追加された(下図)。
    Luke 0.8 RAMDirectory

  • 前回使用したAnalyzerとフィールド名を記憶する機能。

  • Boostの表示を廃止し代わりにfieldNorm値を表示するNormを設けた(下図)。
    Luke 0.8 Norm


| 関口宏司 | Luceneツール | 07:17 | comments(0) | trackbacks(0) |
+ Solrによるブログ内検索
+ PROFILE
     12
3456789
10111213141516
17181920212223
242526272829 
<< February 2008 >>
+ LINKS
検索エンジン製品 - 比較のポイント
商用検索エンジンを購入した企業担当者は読まないでください。ショックを受けますから・・・
>>製品比較 10のポイント
+ Lucene&Solrデモ
+ ThinkIT記事
+ RECOMMEND
Apache Solr入門 ―オープンソース全文検索エンジン
Apache Solr入門 ―オープンソース全文検索エンジン (JUGEMレビュー »)
関口 宏司,三部 靖夫,武田 光平,中野 猛,大谷 純
+ RECOMMEND
Lucene in Action
Lucene in Action (JUGEMレビュー »)
Erik Hatcher,Otis Gospodnetic,Mike McCandless
FastVectorHighlighterについて解説記事を寄稿しました。
+ RECOMMEND
+ SELECTED ENTRIES
+ RECENT COMMENTS
+ RECENT TRACKBACK
+ CATEGORIES
+ ARCHIVES
+ MOBILE
qrcode
+ SPONSORED LINKS