Thursdayホワイトペーパー「Excelで学ぶ!Luceneのスコア計算」公開ホワイトペーパー「Excelで学ぶ!Luceneのスコア計算」を公開した。Saturday短いフィールドにはコンスタントなtfが有効な件タイトルのような短いフィールドには、tfを考慮しない方がいい場合がある。doc=1)このようなときはDefaultSimilarityのtf()をオーバライドして常に1が返るようにする。doc=0)サンプルプログラムは次の通り。上記のように「江東区」と「新宿区」ではDocument数が異なるために地域属性のIDFが違ってきてスコアが新宿が高くなってしまう。地域のような属性フィールドではスコアを計算したくないとき、通常はFilterを使うことが考えられるが、Filterは場合によっては使いたくないときもあるだろう。卓郎氏のコラム「役所のマインドコントロールから脱け出せ!」を読んだ。これによると財務省のホームページでは、同省の都合の悪い記述は脚注などで小さい文字で表示されているらしい。そこで思いついたのがタイトルのアイディアだ。私はかねてより官公庁や自治体などのホームページを横断検索するデモを作りたいと思って(2年以上経つがなかなか着手できないでいるのだが、それはこの際どうでもよい)いる。そのサイトで「小さい文字ほど重要である」「階層が深いほど重要である」「被リンクが少ないほど重要である」とみなす、その名も「逆ページランク」というのを実装してみてはどうだろう。官公庁のホームページでは国民に知らせたくない重要な情報ほど文字を小さくしているようなので、この冗談のようなアイディアは案外いいセン行くのではないか。size="-1">がより国民にとって重要になってくるのであり、高いスコアを与えて検索結果の上位にランキングさせるのである。Tuesday人名がヒットしたときはスコアを上げる前回紹介したペイロードのプログラムではSenの出力である品詞情報をペイロード領域に登録し、TermEnumとTermPositionsで全Termのペイロードを取得するというものであった。これにより、名詞など指定した品詞の単語をカウントして多く出現する単語上位10個を表示した。これはこれで今までのLuceneでは実現が面倒なプログラムが比較的簡単に書けて面白いが、「品詞(ペイロード情報)で検索できないのか」という疑問が当然のことながら湧くだろう。ペイロードに関する計画には将来はペイロードで検索できるようなことが書かれている。現在は、ペイロードで検索はできない。それは、Luceneのファイルフォーマット情報を見れば、ペイロードは.prxファイルに格納されており(そのためTermPositionsインタフェースでアクセスできるようになっている)、転置索引に登録されている単語とは異なるため、検索に適した状態になっていないことからもうなずける。ペイロード情報をスコア計算に使用することは可能である。今回はそのためのQueryクラスであるBoostingTermQueryを使って、検索語が人名のときは他の単語よりもスコアを上げる、というプログラムを作成してみる。{"政調会長は中川さんです。","総理大臣は安倍さんです。"政調";さて、これらの検索語のうち人名である「安倍」がヒットしたときだけ、そのドキュメントのスコアが他のドキュメントのスコアよりも上がるようにする、というのが今回作成するプログラムだが、その前にまずは通常のBooleanQueryでOR検索をしてみよう。政調会長は中川さんです。総理大臣は安倍さんです。BoostingTermQueryを使うでは上記のQueryにBoostingTermQueryの項を加えて品詞が「人名」のとき(品詞は名詞だが、Senの品詞情報に「人名」が含まれる)にスコアが上がるようにする。:BoostingTermQueryとしてはコードは以上だが、この状態ではまだ「人名のときにスコアを上げる」というコードがどこにも入っていない。2.2からBoostingTermQueryがペイロードからスコアを計算するためのSimilarityの新しいメソッドscorePayload()というのが追加された。DefaultSimilarityではこのメソッドが常に1を返すようになっているが、BoostingTermQueryをきちんと動作させるには、ペイロードの値を見て適当なfloat値を返すscorePayload()を実装する必要があるのだ。Similarityを実装するではSimilarityを実装しよう。総理大臣は安倍さんです。政調会長は中川さんです。{"政調会長は中川さんです。","総理大臣は安倍さんです。false;}}}Senからは品詞情報の他にも単語の読みがなも出力されるので、ペイロードの使い道として検討してみても面白いかもしれない。EEEこのプログラムでは、単語"AAA"と"BBB"両方を含む3つのドキュメントがすべて同じスコアとなっている。ここで単語"AAA"と"BBB"が検索ユーザから見て相互に関連性の高い単語の場合を考えてみる。Hibernate」と入力して2つの単語が登場する1つの記事を探す、というような場面である。その検索結果一覧をクリックして記述された記事(仮にブログとする)を読んだときに、TomcatとHibernateが同一ページに現れているものの、実は全然関係ない2つの記事にTomcatとHibernateが別々に書かれていてがっかりした、という経験は誰しも一度はあるだろう。このようなとき、2つの単語間の距離をスコアに反映し、近いものほど高いスコアになるようにできれば、ある程度このようなガッカリ感は防ぐことができると考えられる。Luceneでは2つ(以上)の単語の距離をスコアに反映して近いものほど高いスコアを獲得させるようにすることが簡単にできる。これにはPhraseQueryとslopを使用する。slopはPhraseQueryで使用される成句とみなせる2つの単語間の最大距離を示すパラメータである(詳しくはLucene本190ページを参照)。このslop値を適当な範囲までとってスコアに加算するようにプログラムを修正する。EEESolrではこの機能を使って、「ユーザが入力した検索式の全体をPhraseQueryに変換し、単語間相互の距離が近いドキュメントほどスコアを高くする」ということをDisMaxRequestHandlerにて行っている。ThursdayFieldNormModifier「長けりゃいいってものじゃない」この意見には誰もが首肯するところであろう。超大作と宣伝された映画や小説などを見たり読んだりした後に、やたら長いだけで内容的に満足できずひどくがっかりした、というは経験は多くの人にあるだろう。映画や小説のようなものはエンターテイメントなのでまだいいが、たとえばプログラミングの仕事で調べ物をするとき、検索で引っかかった記事がやたら長いだけで開発のヒントにならなかった、というのは仕事の能率に影響してしまうので、そういう記事は検索結果の表示順で下位にランキングして欲しい。このような要求を完全に満たすことは難しいが、Luceneでは現実的な解としてランキングを決めるスコア計算の一部分であるlengthNorm()がこの「長けりゃいいってものじゃない」という人の気持ちを代弁するものとして提供されている。このメソッドは、同じ検索キーワードを同じ個数含む記事があるときに、より短いほうの記事のスコアを上昇させる役割がある。先日、次のような顧客に会った:「長くたっていいじゃないか」だってにんげんだもの・・・思わずそう下の句をつけたした私であった(もちろん心の中で)。私のブログ記事はしばしば技術的なこと以外の内容がだらだらと続き、プロジェクトで忙しいプログラマの不興をかっているのではないかと気になることもしばしばだ。そのため、「長くてもいいんです」というその言葉がこの際どうでもいいことだが、深く心に沁みたものである。それはともかく、このlengthNorm()は当該フィールドのトークン数を引数に取る関数なので、検索実行時に計算する必要がない。そのためLuceneではフィールドのboost値を掛けた結果をfieldNormとして記録してしまっている。そのため、SimilarityをオーバーライドしてlengthNorm()を書き換えた場合、インデックスを再作成する必要があるのであった。ところが最近、Luceneのソースを見ているときに、trunkにFieldNormModifierなるクラスがあることを発見した。このソースを読むと、どうやら既存のインデックスのfieldNorm値を書き換えるコマンドラインツールのようである。...]こうすると、引数で指定したインデックスのフィールドのfieldNorm値が、指定したSimilarityのlengthNorm()を使って再計算されて書き換えられる(ただし、boostは指定できないのでlengthNorm()だけで調整する必要がある)。Similarityクラス名を指定するところに代わりに-nを指定すると、fieldNorm値が1.0と記録されるようになるので単に「固定値にしたい」というときに便利である。2.1と組み合わせては使用できないので注意)。Luceneを使ったアプリケーションの運用を開始したはいいが、記事の長さはスコアに反映したくないということに後から気がついてしまい、巨大なインデックスを前に途方にくれていたプロジェクトメンバーには朗報かもしれない。WednesdayQueryParserを使用した範囲検索ここ数ヶ月ノートPC使用中、いわゆる「ブルースクリーン」の出現で悩まされている。時期的には、もともと購入時の512Mメモリに1G追加したときからの発生なので、まずはメモリを疑い、Memtest86というツールを使ってチェックしてみたのだが、問題ないようだ。次にハードディスクを疑い、OS(Windowsである)付属のディスクチェックツールを使ったりしているのだが、悪いところは見つからない。メーカーに修理に出すという手段があるのかもしれないが、その間持ち運べるPCがなくなるのは困る。Vistaも出たことだし(というのは自分が納得するための言い訳に過ぎないが)DuoCoreのノートを買ったほうがいいだろうか、などと考えているところだ。そんなことで、最近は価格.comによく訪れてノートPCの価格を調べたりしている。いうわけで、本日のネタデータはある日ある機種のノートPC価格のショップ比較を使用することにする。208000上記のようなデータがLuceneのインデックスに索引付けされているとする(価格はpriceというフィールド名とする)。199999]検索式の意味は、価格が20万円以下のものを探す、ということだ。180000ここでショップ名の前に表示されている実数は各ドキュメントのスコアである。スコアが同じなので、ドキュメントIDの順(=ドキュメントの登録順)に結果が表示されている。使用目的から考えれば、価格の安い順に並んだ方が親切であるが、それは単にソートの問題なのでここではおいておく。2.0で実行してみる。2.1の結果と表示順が異なる結果となった。2.1からはConstantScoreRangeQueryに変更した、というものである。そのような変更がなされたかというと、範囲検索のようなものはもともと、スコアを計算してランキングをするようなものじゃないだろう、というような理由があるためだ。RangeQueryは実際の検索を実行する際は、TermQueryをBooleanQueryで組み合わせたQueryに展開される。これはWildcardQueryなどでも同様である。どのように展開されるかは、Query.rewrite()を実行して得られたQueryのtoString()を見てみるとよい。2.0ではスコアが異なる理由がわかるであろう。価格が198000円のものは180000円のものよりありふれているのでIDF値が低くなってしまうのである。理屈はそうかもしれないが、結果はいかにも奇妙なものに見える。2.1でRangeQueryがConstantScoreRangeQueryで置き換えられた理由だ。ConstantScoreRangeQueryはその名のとおり固定スコアを返すQueryであり、検索の際もBooleanQueryに展開しない。そのため、TooManyClauses例外が発生してしまう心配もなくなるのである。今回使用したプログラムを以下に示す。2.1のQueryParserでの範囲検索の検索式はデフォルトでConstantScoreRangeQueryに展開されるようになったが、以前のようにRangeQueryに展開させたいときは、setUseOldRangeQuery(true)を使用すればよい。Fridayスコア計算デモのソースコードの公開Luceneのスコア計算のデモのソースコードを弊社のホームページからダウンロードできるようにしたので、活用していただきたい。DEMOhttp://demo.rondhuit-search.com/etcscoredemo/スコアとは、検索結果を表示するときに文書の表示順位を決めるための数値のことである。スコア計算については(笑点の話などムリヤリ感も多少あるが)Lucene本にやさしく解説してあるのでここでは触れないが、このデモについて簡単に説明する。language.ソース文書は英語であるが、その意図は単語の切れ目がはっきりしていてわかりやすく、スコア計算の学習に適していると判断したためである。ちなみにインデクシング時と検索時にはStandardAnalyzerを使用しており、デフォルトのストップワードがそのまま適用される。文書はtitleとcontentフィールドからなっており、どちらもTOKENIZEDである。使い方は、デモページの下半分に並んでいるボタンをクリックして検索を実行するだけの簡単操作だ。結果はページの上部に表示される。[tfの効果を見る]ボタンをクリックすると、tf項の効果が(割と)出やすい検索を発行して結果を表示する。ここでは"java"という単語をcontentフィールドに対して検索するので、2件の文書がヒットする。Book」より上位にランクされる様子がわかる、という具合だ。ONLYになっており、ボタンをクリックしてそれぞれの項目の効果を簡単に見られるようになっている。最後のフォームの部分は検索窓に自由に検索語を入力し、検索を実行できるようになっている。ここでは検索質問文字列をQueryに変換するのにQueryParserを使用しているので、QueryParserのコマンド(重みづけ、あいまい検索、ワイルドカード検索、・・・)がそのまま使用できる。デモのソースコードはロンウイットのページで公開する予定だが、現在ホームページを整備中なので、今しばらくお待ちいただきたい。比較のポイント商用検索エンジンを購入した企業担当者は読まないでください。McCandlessFastVectorHighlighterについて解説記事を寄稿しました。