関口宏司のLuceneブログ

OSS検索ライブラリのLuceneおよびそのサブプロジェクト(Solr/Tika/Mahoutなど)について
<< 記事から名詞だけを取り出す | main | Lucene 2.2のPre-analyzed Fields >>
人名がヒットしたときはスコアを上げる
前回紹介したペイロードのプログラムではSenの出力である品詞情報をペイロード領域に登録し、TermEnumとTermPositionsで全Termのペイロードを取得するというものであった。これにより、名詞など指定した品詞の単語をカウントして多く出現する単語上位10個を表示した。

これはこれで今までのLuceneでは実現が面倒なプログラムが比較的簡単に書けて面白いが、「品詞(ペイロード情報)で検索できないのか」という疑問が当然のことながら湧くだろう。

ペイロードに関する計画には将来はペイロードで検索できるようなことが書かれている。しかしながら現在は、ペイロードで検索はできない。

それは、Luceneのファイルフォーマット情報を見れば、ペイロードは.prxファイルに格納されており(そのためTermPositionsインタフェースでアクセスできるようになっている)、転置索引に登録されている単語とは異なるため、検索に適した状態になっていないことからもうなずける。

ただし、ペイロード情報をスコア計算に使用することは可能である。今回はそのためのQueryクラスであるBoostingTermQueryを使って、検索語が人名のときは他の単語よりもスコアを上げる、というプログラムを作成してみる。

検索に使用するデータは次のとおりである:


private static final String[] CONTENTS = {
"政調会長は中川さんです。",
"総理大臣は安倍さんです。"
};


これに対し、検索語に次の2つを使用する:


private static final String T1 = "安倍";
private static final String T2 = "政調";


さて、これらの検索語のうち人名である「安倍」がヒットしたときだけ、そのドキュメントのスコアが他のドキュメントのスコアよりも上がるようにする、というのが今回作成するプログラムだが、その前にまずは通常のBooleanQueryでOR検索をしてみよう。プログラムは次のようになる:


IndexSearcher searcher = new IndexSearcher( dir );
BooleanQuery query = new BooleanQuery();
Query tq1 = new TermQuery( new Term( F, T1 ) );
Query tq2 = new TermQuery( new Term( F, T2 ) );
query.add( tq1, Occur.SHOULD );
query.add( tq2, Occur.SHOULD );
Hits hits = searcher.search( query );
for( int i = 0; i < hits.length(); i++ ){
Document doc = hits.doc( i );
int id = hits.id( i );
float score = hits.score( i );
System.out.println( score + " : " + doc.get( F ) );
//Explanation exp = searcher.explain( query, id );
//System.out.println( exp.toString() );
}
searcher.close();


実行結果は次のようになり、同点スコアなので登録順にドキュメントが表示される:


0.35355338 : 政調会長は中川さんです。
0.35355338 : 総理大臣は安倍さんです。


BoostingTermQueryを使う

では上記のQueryにBoostingTermQueryの項を加えて品詞が「人名」のとき(品詞は名詞だが、Senの品詞情報に「人名」が含まれる)にスコアが上がるようにする。BoostingTermQueryの項を加えると、次のようになる:


:
BooleanQuery query = new BooleanQuery();
Query tq1 = new TermQuery( new Term( F, T1 ) );
Query tq2 = new TermQuery( new Term( F, T2 ) );
query.add( tq1, Occur.SHOULD );
query.add( tq2, Occur.SHOULD );
Query btq1 = new BoostingTermQuery( new Term( F, T1 ) );
Query btq2 = new BoostingTermQuery( new Term( F, T2 ) );
query.add( btq1, Occur.SHOULD );
query.add( btq2, Occur.SHOULD );

Hits hits = searcher.search( query );
:


BoostingTermQueryとしてはコードは以上だが、この状態ではまだ「人名のときにスコアを上げる」というコードがどこにも入っていない。ではどうするかというと、Lucene 2.2からBoostingTermQueryがペイロードからスコアを計算するためのSimilarityの新しいメソッドscorePayload()というのが追加された。DefaultSimilarityではこのメソッドが常に1を返すようになっているが、BoostingTermQueryをきちんと動作させるには、ペイロードの値を見て適当なfloat値を返すscorePayload()を実装する必要があるのだ。

Similarityを実装する

ではSimilarityを実装しよう。オーバーライドするのはscorePayload()だけなので、DefaultSimilarityを継承して次のようにすればよい:


class PayloadSimilarity extends DefaultSimilarity {
private static final String POS_NAME = "人名";
public float scorePayload(byte[] payload, int offset, int length){
return isName( payload, offset, length ) ? 2.0f : 1.0f;
}


private static boolean isName( byte[] payload, int offset, int length ){
String paystr = new String( payload, offset, length );
return paystr.indexOf( POS_NAME ) >= 0 ? true : false;
}
}


そして実装したPayloadSimilarityクラスを検索する前にIndexSearcherに次のように設定する:


IndexSearcher searcher = new IndexSearcher( dir );
searcher.setSimilarity( new PayloadSimilarity() );
:


これでプログラムを再度実行してみると、次のように「安倍」が含まれているほうが「政調」を含むドキュメントよりも高いスコアを獲得し、上位に表示される:


0.6035534 : 総理大臣は安倍さんです。
0.4267767 : 政調会長は中川さんです。


最終的なプログラムは次のようになる:


public class TestBoostingTermQuery {

private static final String[] CONTENTS = {
"政調会長は中川さんです。",
"総理大臣は安倍さんです。"
};
private static final String F = "f";
private static final String T1 = "安倍";
private static final String T2 = "政調";
private static Directory dir;
private static Analyzer analyzer = new JapaneseAnalyzer();

public static void main(String[] args) throws IOException {
dir = new RAMDirectory();
makeIndex();
searchIndex();
}

private static void makeIndex() throws IOException{
IndexWriter writer = new IndexWriter( dir, analyzer );
for( String content : CONTENTS ){
Document doc = new Document();
Field f = new Field( F, content, Store.YES, Index.TOKENIZED );
f.setOmitNorms( true );
doc.add( f );
writer.addDocument( doc );
}
writer.close();
}

private static void searchIndex() throws IOException{
IndexSearcher searcher = new IndexSearcher( dir );
searcher.setSimilarity( new PayloadSimilarity() );
BooleanQuery query = new BooleanQuery();
Query tq1 = new TermQuery( new Term( F, T1 ) );
Query tq2 = new TermQuery( new Term( F, T2 ) );
query.add( tq1, Occur.SHOULD );
query.add( tq2, Occur.SHOULD );
Query btq1 = new BoostingTermQuery( new Term( F, T1 ) );
Query btq2 = new BoostingTermQuery( new Term( F, T2 ) );
query.add( btq1, Occur.SHOULD );
query.add( btq2, Occur.SHOULD );
Hits hits = searcher.search( query );
for( int i = 0; i < hits.length(); i++ ){
Document doc = hits.doc( i );
int id = hits.id( i );
float score = hits.score( i );
System.out.println( score + " : " + doc.get( F ) );
//Explanation exp = searcher.explain( query, id );
//System.out.println( exp.toString() );
}
searcher.close();
}

static class PayloadSimilarity extends DefaultSimilarity {
private static final String POS_NAME = "人名";
public float scorePayload(byte[] payload, int offset, int length){
return isName( payload, offset, length ) ? 2.0f : 1.0f;
}

private static boolean isName( byte[] payload, int offset, int length ){
String paystr = new String( payload, offset, length );
return paystr.indexOf( POS_NAME ) >= 0 ? true : false;
}
}
}


Senからは品詞情報の他にも単語の読みがなも出力されるので、ペイロードの使い道として検討してみても面白いかもしれない。
| 関口宏司 | Luceneスコアリング | 09:29 | comments(0) | trackbacks(0) |









http://lucene.jugem.jp/trackback/134
+ Solrによるブログ内検索
+ PROFILE
      1
2345678
9101112131415
16171819202122
23242526272829
30      
<< September 2018 >>
+ 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