関口宏司のLuceneブログ

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

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

| スポンサードリンク | - | | - | - |
Luke 0.7のリリース
最近Luceneが2.1になったことでLuceneの周辺がいろいろにぎやかだ。

先日Luke 0.6でLucene 2.1のインデックスを参照する方法を書いたばかりだが、本日Lucene 2.1に対応したLuke 0.7が公開された:

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


単にLucene 2.1のインデックスが読めるようになっただけでなく、ドキュメントの追加機能や検索時の検索結果のページング機能などが追加され、機能強化がなされている(他にも追加機能がある)。

LukeはLuceneユーザ(=プログラマ)から支持されているインデックスブラウザであるが、私は気になっている点がある。それはバージョン番号だ。今回ようやく0.6から0.7になった。Luceneが1.9から2.0になったときはLukeは0.6のまま沈黙を続けた。今回、Luceneが2.0から2.1になったとき、それに合わせて0.7がリリースされた格好だ。

これほど支持を集めているLukeであるが、なぜ1.0未満なのだろう。四捨五入すれば1.0になるが、そういう問題ではないだろう。何か作者に不満があり、1.0にしないのかもしれない。

もうひとつ考えられる理由があるとすればそれはWeb 2.0だ。

先日も書いたが、弊社はWeb 2.0企業への変身を模索している。そのため、Web 2.0と聞くと過剰反応してしまうまでになっている。

なぜLuke 0.7がWeb 2.0かというとそれは「永遠のベータ」という例のアレだ。なんとWeb 2.0企業がリリースするものはずーっとベータバージョンのままなのであった。そんなことが許されるのだろうか。しかし大丈夫だ。なぜなら彼らはWeb 2.0企業だからである。

そういうわけでLukeもWeb 2.0を狙っていると私は疑っている。しかしそうなると、別の心配も持ち上がってくるのであった。それはこのままLukeのリリースが続けられるといつかはきっと1.0になってしまうということだ。Lukeの過去のリリースのバージョン番号を見てみると、0.2、0.3、0.4、0.45、0.5、0.6、0.7という具合でこのままいけば1.0になるのは目に見えている。したがって私は作者のAndrzej Bialecki氏に次回から0.01ずつ番号をあげることを提案したい。そうすれば永遠とはいわないまでも、当分もつと考えられるからだ。

ところでリリース0.4と0.5の間に0.45というのが挟まれているが、このときAndrzej Bialecki氏にいったい何があったのだろう。これはこれでとても気になる。

Web 2.0のことを考えると、いろいろ心配が尽きないのであった。




私がWeb 2.0への道を模索しているところへ、次のような衝撃的なタイトルのニュースが届いた:

なぜGoogleは「百度」に負けたのか?
http://www.nikkeibp.co.jp/news/it07q1/526177/


Googleは負けたのか。それは知りませんでした。もしそうだとすると、Googleのまねは止めてすぐに百度に切り替えなければならない。しかしなんか抵抗がある。なにしろ百度だ。三十度くらいにならないものか。ちょっと熱すぎると思う。しかしもしかしたら私の考え違いで「度」といってもこれは温度ではないのかもしれない。考えられるとすればそれは角度だ。しかしそうだとしてもこれは中途半端ではないだろうか。角度だとすると、適当な命名としては九十度や百八十度あたりだろう。あとは四十五度くらいでもいい。いずれにしても百度はどうかと思うのだ。

Web 2.0への道のりは険しい。
| 関口宏司 | Luceneツール | 12:28 | comments(2) | trackbacks(0) |
Lucene 2.1のインデックスをLuke 0.6でブラウズする
Lucene 2.1で作成したインデックスはそれ以前のバージョンのLuceneでは読めないので、Lucene 1.9のJARを含む現在のLuke 0.6ではLucene 2.1のインデックスは読めない。

ただし、Lucene 1.9を含まないLukeのJARとLucene 2.1のJARを使えば読めるようである。

LukeのJARは下記よりluke.jarファイルをダウンロードする(lukeall.jarやlukemin.jarではないので注意):

http://www.getopt.org/luke/
| 関口宏司 | Luceneツール | 01:41 | comments(0) | trackbacks(0) |
Lucene 2.1.0のリリース
Lucene 2.1.0が公開された。

Lucene 2.1.0ではインデックスフォーマットが変更になったので、2.1.0で作成されたインデックスはそれ以前のバージョンのLuceneでは扱えない。Lucene 2.1.0はそれ以前のバージョンのインデックスを読み書きできるが、ドキュメントを追加するとフォーマットが新しくなるので注意が必要である。

変更点:
http://svn.apache.org/repos/asf/lucene/java/tags/lucene_2_1_0/CHANGES.txt
| 関口宏司 | Luceneとは? | 01:31 | comments(0) | trackbacks(0) |
BooleanQueryとDisjunctionMaxQuery
titleとcontentとimageからなる次のようなドキュメントshop1とshop2があるとする:

title^2.0contentimage
shop1appleappleりんご
shop2computerappleMac


ここでtitleフィールドにはsetBoost(2.0f)を行う。

上記のドキュメントをLuceneのインデックスに登録し(検索にはimageは関係ないので無視する。ちなみに登録したければbinaryとして登録することもできる)「computer apple」で検索することを考える。このとき、QueryParserを使用せず、検索窓に入力された文字列(ここでは「computer apple」)から単語を取り出してすべてのフィールドでOR検索を行うこととする。

人間が検索エンジンの代わりを務めるとすれば、この場合はまちがいなくshop2を提示するだろう。

Luceneの場合はどうなるだろう。期待する結果としては当然shop2を提示してもらいたい。少なくともshop2>shop1の順で検索結果を表示して欲しいところだ。検索窓に入力された各単語はOR検索としているので、アップルコンピュータを探しているのに果物屋が出てきてしまうのは、まあ予想できることだ。そのぐらいは許してやって欲しい。

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



private static Analyzer analyzer = new WhitespaceAnalyzer();
private static final String FS = "shop";
private static final String FT = "title";
private static final String FC = "content";

public static void main(String[] args) throws IOException {
Directory dir = makeIndex();
Query query = null;

query = getBooleanQuery();
searchIndex( dir, query );

dir.close();
}

private static Directory makeIndex() throws IOException{
Directory dir = new RAMDirectory();
IndexWriter writer = new IndexWriter( dir, analyzer, true );
writer.addDocument( getDocument( "shop1", "apple", "apple" ) );
writer.addDocument( getDocument( "shop2", "computer", "apple" ) );
writer.close();
return dir;
}

private static Document getDocument( String shop, String title, String content ){
Document doc = new Document();
doc.add( new Field( FS, shop, Store.YES, Index.NO ) );
Field ft = new Field( FT, title, Store.YES, Index.TOKENIZED );
ft.setBoost( 2.0f );
doc.add( ft );
doc.add( new Field( FC, content, Store.YES, Index.TOKENIZED ) );
return doc;
}

private static Query getBooleanQuery(){

BooleanQuery bq1 = new BooleanQuery();
bq1.add( getTermQuery( FT, "computer" ), Occur.SHOULD );
bq1.add( getTermQuery( FC, "computer" ), Occur.SHOULD );

BooleanQuery bq2 = new BooleanQuery();
bq2.add( getTermQuery( FT, "apple" ), Occur.SHOULD );
bq2.add( getTermQuery( FC, "apple" ), Occur.SHOULD );

BooleanQuery bq = new BooleanQuery();
bq.add( bq1, Occur.SHOULD );
bq.add( bq2, Occur.SHOULD );

return bq;
}

private static void searchIndex( Directory dir, Query query ) throws IOException{
System.out.println( "query: " + query.toString() );
IndexSearcher searcher = new IndexSearcher( dir );
Hits hits = searcher.search( query );
for( int i = 0; i < hits.length(); i++ ){
Document doc = hits.doc( i );
System.out.println( hits.score( i ) + "¥t" + doc.get( FS ) + " ("
+ FT + ":" + doc.get( FT ) + ", "
+ FC + ":" + doc.get( FC ) + ")" );
//Explanation explanation = searcher.explain( query, hits.id( i ) );
//System.out.println( explanation.toString() );

}
searcher.close();
}

private static Query getTermQuery( String field, String word ){
return new TermQuery( new Term( field, word ) );
}



このプログラムを実行すると、次のようにshop1>shop2の順番で表示されてしまう:



query: (title:computer content:computer) (title:apple content:apple)
0.51503253 shop1 (title:apple, content:apple)
0.51503253 shop2 (title:computer, content:apple)



上記で各ドキュメントの左端に表示されている数値はスコアであるが、shop1とshop2でスコアは同点になっている。そのため、表示順はドキュメントのid順(登録順)となってしまうのだ。

なぜ同スコアになるかというと、4つのTerm(フィールドがtitleとcontentの2つ、値がappleとcomputerの2つの組み合わせで2*2の4つのTerm)でOR検索しているうちshop1もshop2もそれぞれ2つのTermがヒットしているからである。

ここでtitleには2.0fの重みが設定されているが、shop1もshop2もtitleがひとつずつヒットしているのでその点の違いはない。

上記プログラムのコメントアウト部分を生かして実行すると、和と積の順序は違うがshop1とshop2のスコアが同じになる様子がわかる。

これを表で示すと、次のようになる:

shop1shop2
BQBQTQ(title,computer)×
TQ(content,computer)××
BQTQ(title,apple)×
TQ(content,apple)


ここでBQはBooleanQuery、TQはTermQueryを表すものとする。上の表の○印のところがヒットしてスコアを獲得した部分でBooleanQueryの場合その和を求めるので、shop1とshop2が同スコアとなったものである。

同スコアは困る。「computer apple」と検索したのだから、ここはやはりshop2>shop1の順で表示して欲しい。どうすればいいだろう。

よく使われる手法としては、検索専用フィールドというものをひとつ作成し、他の検索したいフィールド(ここではtitleとcontent)からその検索専用フィールドにコピーする、というものがある。具体的には、shop1の検索対象フィールドには"apple apple"が、shop2の検索対象フィールドには"computer apple"を登録する。そうしておいて検索対象フィールドを「computer apple」で同じくOR検索するとshop2(スコア:0.72711754)>shop1(スコア:0.13427499)という望ましい順番が得られるのだ。

しかしこの場合、titleとcontentを一緒にしてひとつの検索対象フィールドを設定することになるので、titleに設定したい重みが使えなくなってしまう。そのため、検索専用フィールドを設ける方法はフィールドごとに重要度を変えたい場合は採用できない。

DisjunctionMaxQueryを使う

このようなとき、BooleanQueryの代わりにDisjunctionMaxQueryを使って、すべての検索対象フィールドをTermQueryで統合するようにすると問題が解決できる。前掲の表のTermQueryを統合している部分のBooleanQueryをDisjunctionMaxQueryを使って置き換えると、次のようになる:

shop1shop2
BQDMQTQ(title,computer)×
TQ(content,computer)××
DMQTQ(title,apple)×
TQ(content,apple)


ここで、DMQはDisjunctionMaxQueryを表す。DisjunctionMaxQueryは内包するサブQueryのうち、最高スコアとなるものをDisjunctionMaxQueryのスコアとして採用する。そうすると、BooleanQueryの代わりにDisjunctionMaxQueryを使ったプログラムでは、上記のうち黄色の部分だけがそのドキュメントのスコアとして採用されるので、この場合検索結果はshop2>shop1の順番になる。

DisjunctionMaxQueryを使ったプログラムは次の通りである:



public static void main(String[] args) throws IOException {
Directory dir = makeIndex();
Query query = null;

query = getDisMaxQuery();
searchIndex( dir, query );

dir.close();
}

private static Directory makeIndex() throws IOException{
// 前のプログラムと同じ
}

private static Query getDisMaxQuery(){

DisjunctionMaxQuery dmq1 = new DisjunctionMaxQuery( 0.0f );
dmq1.add( getTermQuery( FT, "computer" ) );
dmq1.add( getTermQuery( FC, "computer" ) );

DisjunctionMaxQuery dmq2 = new DisjunctionMaxQuery( 0.0f );
dmq2.add( getTermQuery( FT, "apple" ) );
dmq2.add( getTermQuery( FC, "apple" ) );

BooleanQuery bq = new BooleanQuery();
bq.add( dmq1, Occur.SHOULD );
bq.add( dmq2, Occur.SHOULD );

return bq;
}


private static void searchIndex( Directory dir, Query query ) throws IOException{
// 前のプログラムと同じ
}

private static Query getTermQuery( String field, String word ){
// 前のプログラムと同じ
}



そして実行結果は次の通りとなり、shop2>shop1の順番で表示される:



query: (title:computer | content:computer) (title:apple | content:apple)
1.0 shop2 (title:computer, content:apple)
0.4249042 shop1 (title:apple, content:apple)



次に、title:apple, content:"computer apple"という値を持つドキュメントshop3を登録して再度DisjunctionMaxQueryを使ったプログラムで検索してみる。ただしその際には、tf*idfやlengthNormの影響を受けないよう、Similarityを次のように置き換えておく:




// 登録するとき
writer.setSimilarity( getSimilarity() );
:

// 検索するとき
searcher.setSimilarity( getSimilarity() );
:

private static Similarity getSimilarity(){
return new TestSimilarity();
//return new DefaultSimilarity();
}

private static class TestSimilarity extends DefaultSimilarity {

public TestSimilarity() {
}
public float tf(float freq) {
if (freq > 0.0f) return 1.0f;
else return 0.0f;
}
public float lengthNorm(String fieldName, int numTerms) {
return 1.0f;
}
public float idf(int docFreq, int numDocs) {
return 1.0f;
}
}



すると結果は次のようになる:



query: (title:computer | content:computer) (title:apple | content:apple)
1.0 shop2 (title:computer, content:apple)
1.0 shop3 (title:apple, content:computer apple)
0.33333334 shop1 (title:apple, content:apple)



shop2とshop3が同スコアで並んでいる。そのため、表示順序はshop2>shop3>shop1となっている。

shop3のcontentフィールドには検索に合致する2つのTermがあるのになぜshop2と同スコアなのだろうか。それは、次の表のように、黄色で示した部分のみのスコアがDisjunctionMaxQueryによって採用されるためで、検索にヒットした薄い緑色の部分はスコアに反映されないからである。

shop1shop2shop3
BQDMQTQ(title,computer)××
TQ(content,computer)××
DMQTQ(title,apple)×
TQ(content,apple)


tie-breakerを指定する

このようなとき、DisjunctionMaxQueryの機能としてshop2とshop3で「同点決勝ゲーム(tie breaker)」を行って優劣をつけさせることができる。次のようにDisjunctionMaxQueryのコンストラクタの引数にtie-breaker multiplifierを指定するのである:



private static Query getDisMaxQuery(){

DisjunctionMaxQuery dmq1 = new DisjunctionMaxQuery( 0.1f );
dmq1.add( getTermQuery( FT, "computer" ) );
dmq1.add( getTermQuery( FC, "computer" ) );

DisjunctionMaxQuery dmq2 = new DisjunctionMaxQuery( 0.1f );
dmq2.add( getTermQuery( FT, "apple" ) );
dmq2.add( getTermQuery( FC, "apple" ) );

BooleanQuery bq = new BooleanQuery();
bq.add( dmq1, Occur.SHOULD );
bq.add( dmq2, Occur.SHOULD );

return bq;
}



この定数は、DisjunctionMaxQueryのスコアを求めるときに、次の式にあてはめて使用される:



スコア = max + ( sum - max ) * tie-breaker multiplifier
ただし、maxはサブQueryの最大スコア、sumはサブQueryの合計スコア



そうすると、前の表の薄い緑で示した部分のスコアが(このプログラムの場合)10分の1だけ反映されることになり、次のようにshop3がshop2よりも高いスコアを獲得するようになる:



query: (title:computer | content:computer)~0.1 (title:apple | content:apple)~0.1
1.0 shop3 (title:apple, content:computer apple)
0.9677419 shop2 (title:computer, content:apple)
0.33870965 shop1 (title:apple, content:apple)



以上のようにDisjunctionMaxQueryは、検索語を複数のフィールドにまたがって検索する場合でフィールドごとに重要度を設定しているときにBooleanQueryの代わりに使用することを検討するとよい。
| 関口宏司 | Lucene自由自在 | 22:53 | comments(0) | trackbacks(0) |
スコア計算デモのソースコードの公開
Luceneのスコア計算のデモのソースコードを弊社のホームページからダウンロードできるようにしたので、活用していただきたい。

RONDHUITのデモページ
http://www.rondhuit.com/demonstration.html
| 関口宏司 | Luceneスコアリング | 22:08 | comments(0) | trackbacks(0) |
FunctionQueryの実用的なサンプル
まず結論を先に言おう。

なぜ結論を先に言うかというと、その方が書いたものの内容が締まって見えると世間的にしばしば思われているからだ。「結論を先に言いますよ」とまず予告する。そうすると読者にも一定の緊張感を持たせることができるのだ。また「この書き手はできるな」と思わせる効果もある。

その反対に結論を後回しにし、まず結論に至る理由を述べる手法もある。しかしその場合、悪くするとついつい理由を長々と述べてしまう。そうすると結論にたどり着く前に読者に飽きられてしまい、最後まで読んでもらえないことが増えてくる。それは書き手として最も避けたいことのひとつだ。

したがってまず結論だ。

しかしそれも書き手と読み手の双方でテーマが共通認識としてある場合に限られる。このブログ記事の場合はどうかというと、読者はこれから何が話されようとしているのかわかっていないと思われる。したがって冷静に考えると、まず結論を述べると宣言されても読者はとまどうばかりだろう。最悪の場合、「こいつは実際なにがいいたいんだ」と首を傾げられかねない。

しかも結論結論といいつつ、内容のない文章が続くこの記事はまるでだめだ。

やっぱり結論は最後に言うことにしよう。




取引先と会話をしているとしばしば問題解決のヒントを得られることがある。

つい先日、ある国産検索エンジンベンダーの幹部にLuceneのスコア計算のデモを見せたときの話だ。その幹部から「Luceneでは単語の出現位置でスコアを変えられますか」とたずねられた。少なくともLuceneではそのような機能はない。しかし、数ヶ月の間わからなかった問題がその瞬間に解けたのだった。

それはFunctionQueryのことである。

FunctionQueryというのはFieldの値を計算してスコアに反映するQueryの一種であり、現在はSubversionのtrunkブランチに存在する。昨年のことだが、FunctionQueryの話を顧客としていたとき、適当な使用例を説明したかったのだが思いつかないことがあった。その後も何度かFunctionQueryについて話題に上ることがあったが、動作は説明できても使用例だけは適当なものが思いつかなかった。

「単語の出現位置でスコアを変える」これはまさにFieldの値を計算してスコアに反映することができるFunctionQueryにぴったりの使用例なのだった。

単語の出現位置でスコアを変えるプログラム

サンプルプログラムに用いるのは、次のような素敵なコンテンツである:



private static final String[] contents = {
"最後に述べるのは結論です。",
"途中で結論を述べます。",
"結論を最初に述べます。"
};



上記のString配列のひとつひとつの要素が検索対象となるDocumentとする。そしてDocumentをこの順番でインデックスに追加する。

ここで「結論」という単語で検索すると、3つのDocumentがヒットし、スコアはすべて等しく、したがってHits内のDocumentの順番はインデックスに追加された順番となる。Hitsを表示すると、つぎのようになる:



0.26711923 最後に述べるのは結論です。
0.26711923 途中で結論を述べます。
0.26711923 結論を最初に述べます。



ここで、文章の前の数字はそれぞれのDocumentのスコアである。

ここで「結論」が文章中の先頭に近い位置で出現するDocumentほど価値があるとみなし、高いスコアを獲得させるようにしたい。

これを実現するために、TermFreqVectorのサブクラスであるTermPositionVectorを用いる。そして、FunctionQueryのコンストラクタに渡すValueSourceのサブクラスを次のように実装する:



static class TermPositionFunction extends ValueSource {

private String field;
private String term;

public TermPositionFunction( String field, String term ){
this.field = field;
this.term = term;
}

public DocValues getValues(final IndexReader reader) throws IOException {
return new DocValues(){
public float floatVal(int id) {
TermPositionVector tpv = null;
try {
tpv = (TermPositionVector)reader
.getTermFreqVector( id, field );
} catch (IOException e) {
return 0;
}
int index = tpv.indexOf( term );
int pos = tpv.getTermPositions( index )[0];
return 10 - pos;
}
public int intVal(int id) {
return (int)floatVal(id);
}
public long longVal(int id) {
return (long)floatVal(id);
}
public double doubleVal(int id) {
return (double)floatVal(id);
}
public String strVal(int id) {
return Float.toString(floatVal(id));
}
public String toString(int id) {
return "TermPositionFunction(" +
field + ":" + term + " in " + id + ")";
}
};
}
:
}



このサンプルのプログラムでは、「結論」という単語(コンストラクタにてterm引数で渡される)が最初に出現する位置posを固定値10から引いてfloatVal()の戻り値としている。

FunctionQueryはこの戻り値とqueryWeightを掛けた値をスコアとしているため、「結論」という単語が文章中の先頭にあるものほど高スコアが得られやすくなる。

FunctionQueryを使うときは、他のQueryオブジェクトとBooleanQueryで組み合わせて、次のように用いる:



private static Query getQuery(){
Query query = new TermQuery( new Term( F, "結論" ) );
BooleanQuery bq = new BooleanQuery();
bq.add( query, Occur.MUST );
Query funcQuery =
new FunctionQuery( new TermPositionFunction( F, "結論" ) );
bq.add( funcQuery, Occur.MUST );
return bq;
}



このプログラムではTermPositionVectorを使うので、Fieldを次のようにTermVector.WITH_POSITIONSで作成しなければならない:



Document doc = new Document();
doc.add( new Field( F, content,
Store.YES, Index.TOKENIZED, TermVector.WITH_POSITIONS ) );
writer.addDocument( doc );



そして、プログラムの全体は次のようである:



public class ScoreByTermPosition {

private static final String[] contents = {
"最後に述べるのは結論です。",
"途中で結論を述べます。",
"結論を最初に述べます。"
};
private static final String QUERY_TERM = "結論";
private static final String F = "content";
private static Analyzer analyzer = new JapaneseAnalyzer();

public static void main(String[] args) throws IOException {
Directory dir = makeIndex();
searchIndex( dir );
dir.close();
}

private static Directory makeIndex() throws IOException {
Directory dir = new RAMDirectory();
IndexWriter writer = new IndexWriter( dir, analyzer, true );
for( String content : contents ){
Document doc = new Document();
doc.add( new Field( F, content,
Store.YES, Index.TOKENIZED, TermVector.WITH_POSITIONS ) );
writer.addDocument( doc );
}
writer.close();
return dir;
}

private static void searchIndex( Directory dir ) throws IOException {
IndexSearcher searcher = new IndexSearcher( dir );
Query query = getQuery();
Hits hits = searcher.search( query );
for( int i = 0; i < hits.length(); i++ ){
System.out.println( hits.score( i ) + "¥t" + hits.doc( i ).get( F ) );
}
searcher.close();
}

private static Query getQuery(){
Query query = new TermQuery( new Term( F, QUERY_TERM ) );
BooleanQuery bq = new BooleanQuery();
bq.add( query, Occur.MUST );
Query funcQuery =
new FunctionQuery( new TermPositionFunction( F, QUERY_TERM ) );
bq.add( funcQuery, Occur.MUST );
return bq;
}

static class TermPositionFunction extends ValueSource {

private String field;
private String term;

public TermPositionFunction( String field, String term ){
this.field = field;
this.term = term;
}

public DocValues getValues(final IndexReader reader) throws IOException {
return new DocValues(){
public float floatVal(int id) {
TermPositionVector tpv = null;
try {
tpv = (TermPositionVector)reader
.getTermFreqVector( id, field );
} catch (IOException e) {
return 0;
}
int index = tpv.indexOf( term );
int pos = tpv.getTermPositions( index )[0];
return 10 - pos;
}
public int intVal(int id) {
return (int)floatVal(id);
}
public long longVal(int id) {
return (long)floatVal(id);
}
public double doubleVal(int id) {
return (double)floatVal(id);
}
public String strVal(int id) {
return Float.toString(floatVal(id));
}
public String toString(int id) {
return "TermPositionFunction(" +
field + ":" + term + " in " + id + ")";
}
};
}

public String description() {
return "TermPositionFunction(" + field + ":" + term + ")";
}

public boolean equals(Object o){
if( !( o instanceof TermPositionFunction ) )
return false;
if( this == o )
return true;
TermPositionFunction tpf = (TermPositionFunction)o;
return new EqualsBuilder()
.append( field, tpf.field )
.append( term, tpf.term )
.isEquals();
}

public int hashCode() {
return new HashCodeBuilder( 17, 37 )
.append( field )
.append( term )
.toHashCode();
}
}
}



そして実行結果は次のようになり、「結論」の出現位置が早いものほど高いスコアを獲得するようになる:



1.0 結論を最初に述べます。
0.8037345 途中で結論を述べます。
0.5093361 最後に述べるのは結論です。






さて最初の話に戻ってお約束の結論であるが、いざ書こうとすると、書いている本人もこの記事の結論はなんだかよくわからないのであった。強いて言えば、上記のサンプルプログラムが結論と言えなくもないが、プログラムが結論ではなんとも締まらない。

そこで検索エンジンベンダーをはじめ、いろいろな人によく訊かれる「Luceneで何々の機能はできますか」という質問への私なりの答えをこの記事の結論としておこう。

それは「Luceneではプログラムを書けば何とかなる」ということだ。
| 関口宏司 | Lucene自由自在 | 09:55 | comments(0) | trackbacks(0) |
+ Solrによるブログ内検索
+ PROFILE
    123
45678910
11121314151617
18192021222324
25262728   
<< February 2007 >>
+ 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