関口宏司のLuceneブログ

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

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

| スポンサードリンク | - | | - | - |
MemoryIndexを使う(初心者向き)
MemoryIndexは文字通り、メモリー上に持つインデックスである。おなじみのRAMDirectoryとの違いは、RAMDirectoryはLuceneのインデックスディレクトリであるDirectory抽象クラスのサブクラスであるのに対し、MemoryIndexはDirectoryファミリーとはまったく関係ない独立したクラスである。RAMDirectoryはFSDirectoryの代わりにIndexWriterやIndexReaderの引数に使用することができるが、MemoryIndexはそのような使い方ができない(ただし、MemoryIndexにはcreateSearcher()というメソッドがあり、それを使ってIndexSearcherを取得することができる)。

MemoryIndexが特別なのはそれだけではない。MemoryIndexはなんと、Documentがひとつだけしか入れられないのだ。Documentがひとつしか入れられないので、addDocument()というメソッドを持たず、addField()というメソッドを提供している。そしてsearch(Query)というメソッドを使ってfloatのスコアを返す。スコアが0以上であればクエリがヒットしたことがわかる。

MemoryIndexはどういう場面で使われるかというと、たとえば、ニュースサイトなどでユーザが好みのキーワードを登録しておき、そのキーワードがヒットする記事がアップされたことを通知するサービスを提供したいときなどが考えられる。

使い方を次に示す:



public class TestSimpleMemoryIndex {

static final String F = "f";
// Infoseekのニュースサイトより記事の一部を拝借・・・
static final String[] ARTICLES = {
"男子ゴルフのカシオワールドで獲得賞金1億円を突破し、パネルを掲げて喜ぶ石川遼。",
"オバマ次期大統領が12月1日に記者会見を開き、ヒラリー・クリントン上院議員(61)を国務長官に指名すると報じた。",
"米シティグループが、傘下の日興シティ信託銀行を売却する方針を決めたことが29日、明らかになった。"
};
static final String[] USER_KEYWORDS = {
"石川遼", "オバマ"
};
static Analyzer analyzer = new JapaneseAnalyzer();

public static void main(String[] args) throws ParseException {
QueryParser parser = new QueryParser( F, analyzer );
for( String article : ARTICLES ){
MemoryIndex index = new MemoryIndex();
index.addField( F, article, analyzer );
for( String keyword : USER_KEYWORDS ){
Query query = parser.parse( keyword );
float score;
if( ( score = index.search( query ) ) > 0 ){
System.out.println( "¥n[" + keyword + "] の記事が追加されました!(スコア:" + score + ")" );
System.out.println( "記事:" + article );
}
}
}
}
}



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



[石川遼] の記事が追加されました!(スコア:0.13424811)
記事:男子ゴルフのカシオワールドで獲得賞金1億円を突破し、パネルを掲げて喜ぶ石川遼。

[オバマ] の記事が追加されました!(スコア:0.057534903)
記事:オバマ次期大統領が12月1日に記者会見を開き、ヒラリー・クリントン上院議員(61)を国務長官に指名すると報じた。



| 関口宏司 | Luceneクラス解説 | 20:13 | comments(0) | trackbacks(0) |
Tokenがdeprecatedに!(2.9)
Lucene 2.9ではTokenクラスがdeprecatedとなる予定である。Tokenクラスには直前の単語からの位置増分、その単語の開始と終了位置、単語のタイプおよびPayloadという情報が入っている。TokenはReaderを入力とするTokenizerからTokenStreamが出力され、TokenStreamのnext()メソッドの呼び出しで取り出せるものである。

この状態だとLucene 3.0で目指しているFlexible Indexingと相性が良くない。もっと軽量のインデックスにしようとして単語の位置情報などを不要としても、AnalyzerのほうでTokenを出力している限り、不要な情報を出力するためにCPUが食われてしまうことになる。

そこでLucene 2.9ではTokenをdeprecatedとし、Tokenは単語のテキスト情報まで含めて属性の集合のような扱いにしよう、ということになった。

これまでの次のようなプログラムは:



final String str = "aaa bbb ccc ddd";
Tokenizer tokenizer = new WhitespaceTokenizer( new StringReader( str ) );
while( true ){
Token token = new Token();
token = tokenizer.next( token );
if( token == null ) break;
System.out.println( token );
}
tokenizer.close();



(実行結果)


(aaa,0,3)
(bbb,4,7)
(ccc,8,11)
(ddd,12,15)



Lucene 2.9では次のようになる:



final String str = "aaa bbb ccc ddd";
WhitespaceTokenizer.setUseNewAPIDefault( true );
Tokenizer tokenizer = new WhitespaceTokenizer( new StringReader( str ) );
while( tokenizer.incrementToken() ){
TermAttribute ta = (TermAttribute)tokenizer.addAttribute( TermAttribute.class );
PositionIncrementAttribute pia = (PositionIncrementAttribute)tokenizer.addAttribute( PositionIncrementAttribute.class );
OffsetAttribute oa = (OffsetAttribute)tokenizer.addAttribute( OffsetAttribute.class );
TypeAttribute tya = (TypeAttribute)tokenizer.addAttribute( TypeAttribute.class );
System.out.println( ta + ", " + pia + ", " + oa + ", " + tya );
}
tokenizer.close();



(実行結果)


term=aaa, positionIncrement=1, start=0,end=3, type=word
term=bbb, positionIncrement=1, start=4,end=7, type=word
term=ccc, positionIncrement=1, start=8,end=11, type=word
term=ddd, positionIncrement=1, start=12,end=15, type=word


| 関口宏司 | Luceneクラス解説 | 11:22 | comments(1) | trackbacks(0) |
boostをゼロにして検索すると・・・
次のようなラーメン店のデータがあり、フィールドshopに登録されるとする:



static final String[] SHOPS = {
"ラーメン 1", "ラーメン 2", "ラーメン 3", "ラーメン 4", "ラーメン 5",
"ラーメン 6", "ラーメン 7", "ラーメン 8", "ラーメン 9", "ラーメン 10",
"ラーメン 11", "ラーメン 12", "ラーメン 13", "ラーメン 14", "ラーメン 15"
};



そして同じ文書の別フィールドwardに次のようにラーメン店の場所が登録されるとする:



static final String[] WARDS = {
"江東区", "江東区", "江東区", "新宿区", "文京区",
"江東区", "江東区", "江東区", "新宿区", "文京区",
"江東区", "江東区", "江東区", "新宿区", "文京区"
};



ここで次のように「江東区または新宿区のラーメン店」を検索するQueryを作成して検索する:



QueryParser parser = new QueryParser( "shop", analyzer );
Query query = parser.parse( "+ラーメン +(ward:江東区 OR ward:新宿区)" );
TopDocs docs = searcher.search( query, 100 );



取得したdocsを表示(スコアとDocument内容を整形して)すると、次のようになる:



1.1294092 : ラーメン 4@新宿区
1.1294092 : ラーメン 9@新宿区
1.1294092 : ラーメン 14@新宿区
0.53457046 : ラーメン 1@江東区
0.53457046 : ラーメン 2@江東区
0.53457046 : ラーメン 3@江東区
0.53457046 : ラーメン 6@江東区
0.53457046 : ラーメン 7@江東区
0.53457046 : ラーメン 8@江東区
0.53457046 : ラーメン 11@江東区
0.53457046 : ラーメン 12@江東区
0.53457046 : ラーメン 13@江東区



上記のように「江東区」と「新宿区」ではDocument数が異なるために地域属性のIDFが違ってきてスコアが新宿が高くなってしまう。

地域のような属性フィールドではスコアを計算したくないとき、通常はFilterを使うことが考えられるが、Filterは場合によっては使いたくないときもあるだろう。

そんなときは、上記の検索式の地域属性の項のBOOSTを次のようにゼロに設定するとよい:



Query query = parser.parse( "+ラーメン +(ward:江東区 OR ward:新宿区)^0" );



これで実行すると、次のように地域間でスコアの違いがなくなる:



0.5846634 : ラーメン 1@江東区
0.5846634 : ラーメン 2@江東区
0.5846634 : ラーメン 3@江東区
0.5846634 : ラーメン 4@新宿区
0.5846634 : ラーメン 6@江東区
0.5846634 : ラーメン 7@江東区
0.5846634 : ラーメン 8@江東区
0.5846634 : ラーメン 9@新宿区
0.5846634 : ラーメン 11@江東区
0.5846634 : ラーメン 12@江東区
0.5846634 : ラーメン 13@江東区
0.5846634 : ラーメン 14@新宿区


| 関口宏司 | Luceneスコアリング | 00:18 | comments(1) | trackbacks(0) |
Java 1.6.0_10でも直っていないという報告
Java 1.6.0_10で改修されたと見られていた現象がいまだ発生するという報告があった:

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

すでにSunに報告済みでAcceptされているとのこと。非常にまれにしか発生せず、再現が難しいバグのようである。
| 関口宏司 | 不具合関連 | 08:31 | comments(0) | trackbacks(0) |
Lucene 2.4でバイナリデータがロストするバグ
Lucene 2.4でインデックスに登録したバイナリデータがロストするバグが報告され、早速対応された。Lucene 2.4.1と2.9で修正される:

https://issues.apache.org/jira/browse/LUCENE-1452
| 関口宏司 | その他(分類不能) | 19:28 | comments(0) | trackbacks(0) |
過去バージョンの単体テストを実行するAntターゲット(2.9)
現在のLucene trunkではLucene 2.9の開発が行われているが、「過去バージョンの単体テスト」を実行するAntターゲット"test-tag"が追加された。

次のように実行すると、現在のtrunkのディレクトリの下にtags/lucene_2_4_0/というディレクトリが作成されてそこにLucene 2.4の単体テストコードがダウンロードされ、Lucene 2.9のライブラリを使ってLucene 2.4.0のテストが実行される:



$ ant -Dtag=lucene_2_4_0 test-tag


| 関口宏司 | Lucene開発環境 | 16:51 | comments(0) | trackbacks(0) |
(メモ)Katta = Lucene + Zookeeper + Hadoop + and more!?
Kattaプレゼンテーション
http://joa23.files.wordpress.com/2008/09/katta-overview.pdf
| 関口宏司 | その他のOSS検索エンジン | 13:21 | comments(0) | trackbacks(0) |
(メモ)GrantさんによるSolr 1.3の記事
http://www.ibm.com/developerworks/java/library/j-solr-update/?S_TACT=105AGX01&S_CMP=HP
| 関口宏司 | Solr | 23:31 | comments(6) | trackbacks(0) |
コミット時にユーザデータを書き込む(2.9)
Lucene 2.9からIndexWriterのcommit()メソッドにStringの引数が渡せるようになった。この引数は「ユーザデータ」と呼ばれ、コミット(Luceneでコミットといえば、segments_Nファイルを書き込むことである)時にsegments_Nファイルに書き込まれるものである。そのようにして書き込まれたユーザデータはIndexReaderのgetCommitUserData()メソッドで取得できる。

この説明だけで何に使うと便利なのか、ピンと来る人は少ないだろう。

この機能の提案者のやりたかったことは、「2つのインデックスが同じかどうか知りたい」というものであり、メーリングリストの議論によって行き着いたのが、前述のメソッド提供するというものである。

Lucene 2.3より、インデックスのマージはバックグラウンドスレッドで行われることがあり得る(デフォルトでバックグラウンドで実行される)ので、2つのインデックスに対し、同じ順番で同じドキュメントを追加していっても同じファイル構成になっているという保証がない。また、片一方でoptimize()を実行し、もう片一方では行わなかった場合、インデックスの内容が同じかどうかを比較するのは相当に困難で実行時間のかかるプログラムとなってしまう。

結局これを解決するにはLuceneではなくアプリケーションで「インデックスが同じであること」を保証するようにしなければならず、その目印としてコミット時に任意のユーザデータをインデックスに書き込めるようにしよう、となった。optimize()などによりマージが走ってもユーザデータは引き継がれるため、optimize()前後でも同一インデックスかどうかの確認がアプリケーションでとれる仕組みだ。

https://issues.apache.org/jira/browse/LUCENE-1382
| 関口宏司 | Luceneクラス解説 | 02:03 | comments(0) | trackbacks(0) |
+ Solrによるブログ内検索
+ PROFILE
      1
2345678
9101112131415
16171819202122
23242526272829
30      
<< November 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