2017.12.15 Friday
スポンサーサイト
一定期間更新がないため広告を表示しています
| スポンサードリンク | - | | - | - |
関口宏司のLuceneブログOSS検索ライブラリのLuceneおよびそのサブプロジェクト(Solr/Tika/Mahoutなど)について
2007.02.22 Thursday
Luke 0.7のリリース
最近Luceneが2.1になったことでLuceneの周辺がいろいろにぎやかだ。
先日Luke 0.6でLucene 2.1のインデックスを参照する方法を書いたばかりだが、本日Lucene 2.1に対応した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への道のりは険しい。 2007.02.20 Tuesday
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/ 2007.02.19 Monday
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 2007.02.17 Saturday
BooleanQueryとDisjunctionMaxQuery
titleとcontentとimageからなる次のようなドキュメントshop1とshop2があるとする:
ここでtitleフィールドにはsetBoost(2.0f)を行う。 上記のドキュメントをLuceneのインデックスに登録し(検索にはimageは関係ないので無視する。ちなみに登録したければbinaryとして登録することもできる)「computer apple」で検索することを考える。このとき、QueryParserを使用せず、検索窓に入力された文字列(ここでは「computer apple」)から単語を取り出してすべてのフィールドでOR検索を行うこととする。 人間が検索エンジンの代わりを務めるとすれば、この場合はまちがいなくshop2を提示するだろう。 Luceneの場合はどうなるだろう。期待する結果としては当然shop2を提示してもらいたい。少なくともshop2>shop1の順で検索結果を表示して欲しいところだ。検索窓に入力された各単語はOR検索としているので、アップルコンピュータを探しているのに果物屋が出てきてしまうのは、まあ予想できることだ。そのぐらいは許してやって欲しい。 プログラムは次の通りである:
このプログラムを実行すると、次のようにshop1>shop2の順番で表示されてしまう:
上記で各ドキュメントの左端に表示されている数値はスコアであるが、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のスコアが同じになる様子がわかる。 これを表で示すと、次のようになる:
ここで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を使って置き換えると、次のようになる:
ここで、DMQはDisjunctionMaxQueryを表す。DisjunctionMaxQueryは内包するサブQueryのうち、最高スコアとなるものをDisjunctionMaxQueryのスコアとして採用する。そうすると、BooleanQueryの代わりにDisjunctionMaxQueryを使ったプログラムでは、上記のうち黄色の部分だけがそのドキュメントのスコアとして採用されるので、この場合検索結果はshop2>shop1の順番になる。 DisjunctionMaxQueryを使ったプログラムは次の通りである:
そして実行結果は次の通りとなり、shop2>shop1の順番で表示される:
次に、title:apple, content:"computer apple"という値を持つドキュメントshop3を登録して再度DisjunctionMaxQueryを使ったプログラムで検索してみる。ただしその際には、tf*idfやlengthNormの影響を受けないよう、Similarityを次のように置き換えておく:
すると結果は次のようになる:
shop2とshop3が同スコアで並んでいる。そのため、表示順序はshop2>shop3>shop1となっている。 shop3のcontentフィールドには検索に合致する2つのTermがあるのになぜshop2と同スコアなのだろうか。それは、次の表のように、黄色で示した部分のみのスコアがDisjunctionMaxQueryによって採用されるためで、検索にヒットした薄い緑色の部分はスコアに反映されないからである。
tie-breakerを指定する このようなとき、DisjunctionMaxQueryの機能としてshop2とshop3で「同点決勝ゲーム(tie breaker)」を行って優劣をつけさせることができる。次のようにDisjunctionMaxQueryのコンストラクタの引数にtie-breaker multiplifierを指定するのである:
この定数は、DisjunctionMaxQueryのスコアを求めるときに、次の式にあてはめて使用される:
そうすると、前の表の薄い緑で示した部分のスコアが(このプログラムの場合)10分の1だけ反映されることになり、次のようにshop3がshop2よりも高いスコアを獲得するようになる:
以上のようにDisjunctionMaxQueryは、検索語を複数のフィールドにまたがって検索する場合でフィールドごとに重要度を設定しているときにBooleanQueryの代わりに使用することを検討するとよい。 2007.02.16 Friday
スコア計算デモのソースコードの公開
Luceneのスコア計算のデモのソースコードを弊社のホームページからダウンロードできるようにしたので、活用していただきたい。
RONDHUITのデモページ http://www.rondhuit.com/demonstration.html 2007.02.12 Monday
FunctionQueryの実用的なサンプル
まず結論を先に言おう。
なぜ結論を先に言うかというと、その方が書いたものの内容が締まって見えると世間的にしばしば思われているからだ。「結論を先に言いますよ」とまず予告する。そうすると読者にも一定の緊張感を持たせることができるのだ。また「この書き手はできるな」と思わせる効果もある。 その反対に結論を後回しにし、まず結論に至る理由を述べる手法もある。しかしその場合、悪くするとついつい理由を長々と述べてしまう。そうすると結論にたどり着く前に読者に飽きられてしまい、最後まで読んでもらえないことが増えてくる。それは書き手として最も避けたいことのひとつだ。 したがってまず結論だ。 しかしそれも書き手と読み手の双方でテーマが共通認識としてある場合に限られる。このブログ記事の場合はどうかというと、読者はこれから何が話されようとしているのかわかっていないと思われる。したがって冷静に考えると、まず結論を述べると宣言されても読者はとまどうばかりだろう。最悪の場合、「こいつは実際なにがいいたいんだ」と首を傾げられかねない。 しかも結論結論といいつつ、内容のない文章が続くこの記事はまるでだめだ。 やっぱり結論は最後に言うことにしよう。 取引先と会話をしているとしばしば問題解決のヒントを得られることがある。 つい先日、ある国産検索エンジンベンダーの幹部にLuceneのスコア計算のデモを見せたときの話だ。その幹部から「Luceneでは単語の出現位置でスコアを変えられますか」とたずねられた。少なくともLuceneではそのような機能はない。しかし、数ヶ月の間わからなかった問題がその瞬間に解けたのだった。 それはFunctionQueryのことである。 FunctionQueryというのはFieldの値を計算してスコアに反映するQueryの一種であり、現在はSubversionのtrunkブランチに存在する。昨年のことだが、FunctionQueryの話を顧客としていたとき、適当な使用例を説明したかったのだが思いつかないことがあった。その後も何度かFunctionQueryについて話題に上ることがあったが、動作は説明できても使用例だけは適当なものが思いつかなかった。 「単語の出現位置でスコアを変える」これはまさにFieldの値を計算してスコアに反映することができるFunctionQueryにぴったりの使用例なのだった。 単語の出現位置でスコアを変えるプログラム サンプルプログラムに用いるのは、次のような素敵なコンテンツである:
上記のString配列のひとつひとつの要素が検索対象となるDocumentとする。そしてDocumentをこの順番でインデックスに追加する。 ここで「結論」という単語で検索すると、3つのDocumentがヒットし、スコアはすべて等しく、したがってHits内のDocumentの順番はインデックスに追加された順番となる。Hitsを表示すると、つぎのようになる:
ここで、文章の前の数字はそれぞれのDocumentのスコアである。 ここで「結論」が文章中の先頭に近い位置で出現するDocumentほど価値があるとみなし、高いスコアを獲得させるようにしたい。 これを実現するために、TermFreqVectorのサブクラスであるTermPositionVectorを用いる。そして、FunctionQueryのコンストラクタに渡すValueSourceのサブクラスを次のように実装する:
このサンプルのプログラムでは、「結論」という単語(コンストラクタにてterm引数で渡される)が最初に出現する位置posを固定値10から引いてfloatVal()の戻り値としている。 FunctionQueryはこの戻り値とqueryWeightを掛けた値をスコアとしているため、「結論」という単語が文章中の先頭にあるものほど高スコアが得られやすくなる。 FunctionQueryを使うときは、他のQueryオブジェクトとBooleanQueryで組み合わせて、次のように用いる:
このプログラムではTermPositionVectorを使うので、Fieldを次のようにTermVector.WITH_POSITIONSで作成しなければならない:
そして、プログラムの全体は次のようである:
そして実行結果は次のようになり、「結論」の出現位置が早いものほど高いスコアを獲得するようになる:
さて最初の話に戻ってお約束の結論であるが、いざ書こうとすると、書いている本人もこの記事の結論はなんだかよくわからないのであった。強いて言えば、上記のサンプルプログラムが結論と言えなくもないが、プログラムが結論ではなんとも締まらない。 そこで検索エンジンベンダーをはじめ、いろいろな人によく訊かれる「Luceneで何々の機能はできますか」という質問への私なりの答えをこの記事の結論としておこう。 それは「Luceneではプログラムを書けば何とかなる」ということだ。 |
+ Solrによるブログ内検索
+ PROFILE
+ LINKS
+ Lucene&Solrデモ
+ ThinkIT記事
+ RECOMMEND
+ RECOMMEND
Lucene in Action (JUGEMレビュー »)
Erik Hatcher,Otis Gospodnetic,Mike McCandless FastVectorHighlighterについて解説記事を寄稿しました。
+ RECOMMEND
+ SELECTED ENTRIES
+ RECENT COMMENTS
+ RECENT TRACKBACK
+ CATEGORIES
+ ARCHIVES
+ MOBILE
+ SPONSORED LINKS
|
(C) 2024 ブログ JUGEM Some Rights Reserved.
|
PAGE TOP |