2017.12.15 Friday
スポンサーサイト
一定期間更新がないため広告を表示しています
| スポンサードリンク | - | | - | - |
関口宏司のLuceneブログOSS検索ライブラリのLuceneおよびそのサブプロジェクト(Solr/Tika/Mahoutなど)について
2007.11.27 Tuesday
LuceneとSennaの比較:basic API編(sen_records)
引き続きSennaのbasic APIとそれに相当するLuceneのAPIの比較を行う。
今回はSennaのbasic APIのもうひとつの型であるsen_recordsを取り上げる。 sen_recordsはマニュアルによれば「検索結果として返されるレコードの集合をメモリ上に一時的に格納するためのデータ型です」ということだ。 なのでまずは前回のように検索の擬似コードを書いてsen_recordsを取得してみよう。それは次のようになる:
こんな感じだろうか(くどういようだが間違っている可能性もある)。 同じ検索プログラムは、Luceneでは次のようになる:
こんな感じである。 Sennaではまずインデックスのインスタンスを取得するためにsen_index_open()関数を使用する。 LuceneではIndexWriterのときと同様、インデックスのインスタンスを取得するのではなく、検索用のIndexSearcherのインスタンスを取得する。 次にSennaでは取得したインデックスと検索文字列をsen_index_sel()関数に渡して検索結果であるsen_recordsを得る。なおこの関数の説明はマニュアルでは「indexから、(中略)stringを含む文書を取り出し、sen_recordsインスタンスとして返します」とあるだけでこのstringに「クエリー書式」で説明されている検索式が書けるのかどうかはっきりしなかった。擬似コードでは検索式が書けるものと解釈しているが私の理解が間違っている可能性もある。 そして取得したsen_recordsを引数にしてsen_records_next()関数を呼び、sen_records内のレコードポインタをひとつずつ進めることでレコードのキーを取得する。レコードの実体はSennaでは保存しないため、プログラマがキーを使ってどこからかもって来なければならない。 LuceneではIndexSearcherのsearch()メソッドにQueryオブジェクトを渡して検索結果であるHitsオブジェクトを得る。なのでsen_recordsがHitsに相当することになろう。 ところでLuceneのsearch()メソッドには検索式をそのまま渡すことはできず、検索式からQueryオブジェクトを作ってそのQueryオブジェクトを渡さなければならない。 通常このQueryオブジェクトを作るには、QueryParserという検索式文字列を解釈するパーサを使って検索式を解釈させてQueryを得る。手順としてはLuceneの方が面倒だが、クエリー書式と検索エンジンは別のものとして独立させているのがLuceneの特徴といえるのかもしれない。 なお、検索式を解釈するためにAnalyzerが必要となるが、通常はインデックスを作成したときのAnalyzerを指定する。たとえば、インデックスを作成したときに形態素解析を行った場合はQueryParserにも形態素解析のAnalyzerを、N-gramで作成した場合はQueryParserにもN-gramのAnalyzerを指定する。 以下はSennaの検索結果を保持するsen_recordsとLuceneのHitsのAPI対応表である:
Sennaのbasic APIとして紹介されている部分のLuceneとの比較は以上である。 ここまでの私の感想は、SennaのAPIマニュアルは読みやすく、数回読んだだけで(擬似コードだが)プログラムが書けてしまうのは非常に好感触だ(しつこいようだが、プログラムは私のマニュアル解釈ミスを含んでいる可能性があり、ただ書けた気になっているだけかもしれない)。 気になった点はLuceneの「フィールド」に相当する概念がないことである。まだbasic APIしか読んでいないので相当するものがあるのかもしれない。しかしもしないとすると、フィールドごとにインデックスを作成するということになってしまうのかもしれず、もしそうであればプログラマの作業が大変なものになる。 どのくらい面倒くさいか、具体例をあげてみよう。 たとえばタイトルフィールドと本文フィールドからなる単純な文書を検索する場合を考える。 まずインデックスのインスタンスを2つ取得しなければならない:
そして、検索を2つのインデックスに対して別々に行い、2つの検索結果セットを得る:
ここでプログラマは2つの検索結果セットを取得して困惑することになる。片方のフィールドのみにヒットした文書もあれば両方のフィールドにヒットした文書もあるだろう。タイトルインデックスでは高スコアを獲得した文書が本文インデックスでは低いスコアにとどまった場合、その文書は何位にランキングさせればいいのだろう。こういったことをプログラマが処理しなければならなくなる。 この例ではタイトルフィールドと本文フィールドという2つのフィールドしかもたない文書を考えたが、実際のアプリケーションでは数十の必須/オプションフィールドが入り混じった複雑な文書を扱うことが多いので、この方法ではすぐに破綻してしまう。 この辺はTritonnやLudiaなどが隠蔽しているのかもしれないが、ではマージする際の基準はスコアなのか(スコアしかないだろう)。しかし、両方のインデックスの文書件数が異なる場合、idf値などが比較できないのではないか。あるいは「同じ文書件数である」と決め打ちしているのだろうか。 またそもそもSennaではどのようにスコア計算がされているのかがわからなかった。なので「クエリー書式」で一部のスコアに影響を与える「数値」を指定できるようになっているが、どのくらいを指定していいのかとまどうのではないだろうか。 ちなみにLuceneではスコア計算はSimilarityクラスのJavadocで明確に説明されている: SimilarityのJavadoc http://lucene.zones.apache.org:8080/hudson/job/Lucene-Nightly/javadoc/org/apache/lucene/search/Similarity.html そんなわけで、basic APIを読んだ限りではLuceneの「フィールド」に相当する概念がないように見えるがないはずはないので、advancedやlow-levelまで読み込めばきっとあるにちがいない、と思ったのであった。 2007.11.26 Monday
WebベースのLuke
Highlighter作者のMark Harwood氏作成のLuke.warというプログラムがある。
これはその名のとおりWebベースのLukeだ。GWT(Google Web Toolkit)が使われており、画面遷移がなく使いやすいツールとなっている。 Lucene Contribに入ってからここに書こうと思っていたが、少し時間がかかりそうだ。 現時点では以下からダウンロードできる: http://www.inperspective.com/lucene/Luke.war このwarには動作に必要なLucene Coreが含まれていないので、trunkから作成またはnightlyから取得したlucene-core-2.3-dev.jarをWEB-INF/libに自分で入れてやらないといけない。 従来からのSwing版Lukeは動作させるのにウィンドウ環境が必要だが、Luke.warであればウィンドウ環境のないサーバマシンで動いているLuceneのインデックスをクライアントPCからブラウズできるので便利だろう。 2007.11.22 Thursday
[宣伝] Software Design 2007年12月号
今発売中の「Software Design 2007年12月号」に、リクルートの牧野さん執筆によるSolrの記事が掲載されている。
全文検索システム「Solr」徹底活用ガイド ちなみにSolrは「ソーラー」と発音する。 2007.11.21 Wednesday
LuceneとSennaの比較:basic API編(sen_index)
引き続きLuceneとSennaの比較を行う。
今回はAPIのうちSennaでbasic APIと分類されている中のsen_index型に関するAPIと、Luceneでそれに相当するAPIの比較を行う。 まずSennaのマニュアルの印象だが、簡潔にして非常にわかりやすい。 なんとなくわかった気になったので、今回は一覧表ではなく擬似コードで表現してみる。 まずは、Sennaでインデックスを作成するプログラムである:
こんな感じであろうか(まちがってるかも)。 同じことは、Luceneでは次のようになる(擬似コード):
Luceneではこんな感じだ。上記のaddDoc()は長くなるので下記のようなサブルーチンにまとめてある:
Sennaではまず、プログラムの開始で「初期化」を、プログラムの終わりで「終了処理」というものをプロセスごとに呼ぶ必要がある。これに相当するものはLuceneにはない。 次にインデックスの作成は、Sennaではsen_index_create関数を使って作成し、インデックスのインスタンスを表現するsen_index型のオブジェクトを受け取る。 これに対しLuceneでは「インデックスのインスタンス」を表現するものはなく、代わりにその名のとおり「インデックスに書き込むインスタンス」であるIndexWriterをnewで作成する。 ここで注目すべきポイントがある。 Sennaではsen_index_create関数にflagを指定するが、flagには次の組み合わせを指定する:
Sennaではインデックス単位で文字列の正規化方法やトークナイズ(単語抽出)方法を決定するようだ。しかもそのオプションはずいぶん限定的のように見える。上の表以外のフィルタリングやシノニム展開をしたい場合、プログラマはどのように対処すればいいのだろうか?もっともそういった要求は、basic APIではなくadvanced APIやlow-level APIで処理できるのかもしれない(まだ読んでいないのでわからない)。 Luceneでも文字列分析(Analyzerで行う)の指定は一見インデックス単位のように見えるが、フィールドごとに動作を変えることができる。フィールドごとに分析方法を変える場合、LuceneではPerFieldAnalyzerWrapperを使って行う。Analyzerはあらかじめ用意されているものを使用してもいいし、自分で作ることもできる。Analyzerを通じてさまざまなトークンの抽出や展開、変換・削除などのフィルタリングが可能となっている。 次に文書の登録であるが、Sennaではsen_index_upd関数を使って行う。sen_index_updは新規追加だけではなく、既存文書の変更や削除も行えるようだ。この関数で面白いと思うのは、既存文書の変更や削除を行う際に文書IDだけでなく、登録済み文書の文字列も要求している点である(私のマニュアルの読み方が誤ってなければ)。文書IDを要求するのはわかるが文書の文字列まで渡す必要があるのはなぜだろう。もしかしたら、単語表から単語を削除するのに文書の文字列を必要としているのかもしれない。もしそうだとすると、文書IDと異なる文書文字列を変更や削除のときに渡してしまった場合、インデックスはどうなってしまうのだろう。インデックスの整合性がとれなくなってしまうのだろうか。 Luceneでは文書はDocumentクラスのオブジェクトである。Documentは複数のFieldからなり、Fieldに文書の文字列はもちろん、フィールドの名前(上記プログラムではフィールド名を"text"としている)、ストアするかどうか、および分析するかどうかなどを指定する(これら以外にもTermVectorの指定などがあるが省略する)。 Sennaは文書をストアしないことを特徴としている検索エンジンなので(将来バージョンではストアすることが選択できるようだ)、外部文書のプライマリキーである文書IDが索引付けの際に必要となる。Luceneは文書IDに相当するものはLuceneエンジンの方で自動的に振られる。もし自分で決めたプライマリキーをLuceneの文書に持たせたいなら、"id"というようなフィールドを作成し、そこにキー文字列を登録すればよい。 マニュアルをここまで読んだ限りでは、SennaはLuceneの「フィールド」に相当する概念がないように見える。もしかしたらSennaでは複数フィールドを実現するのに、複数のインデックスを同時にオープンする必要があるのかもしれない(この辺は私の理解が間違っている可能性は大いにある)。 2007.11.15 Thursday
GData at contrib
Lucene contribのGDataが削除されようとしている:
http://issues.apache.org/jira/browse/LUCENE-1055 これはLucene 2.3リリースの準備作業の一環で、しばらくメンテナンスされていない同コンポーネントをtrunkから削除しようというもの。オリジナルの作者も「メンテする時間がないです」と賛成票を投じている。 ちなみにLucene contribのGDataコンポーネントは、(使ったことがないので違うかもしれないがおそらく)GData(Google Data API)仕様に沿ったLuceneインデックスへのI/Oができるサーブレット(WARファイル)を提供するためのものである。 Luceneのメーリングリストで現在、非公式に「GData使っている人、消されたら困る人いますか?」と呼びかけが行われている。 2007.11.14 Wednesday
LuceneとSennaの比較:クエリー書式編(後編)
(前編はこちら)
LuceneとSennaの検索式構文を比較する。なお、私のSennaの知識はかなり怪しいのであくまで参考程度にとどめて欲しい。 比較一覧
以下、いくつかの構文について簡単に解説する。 *E数値1[,数値2] Sennaの「クエリー書式」には、「プラグマ」を指定でき、これはそのうちのひとつである。 マニュアルによれば「検索結果の数が数値1よりも小さい場合、完全一致→非わかち書き→部分一致の順に自動的に検索処理方法を切り替えます。(以下略)」とのことである。「検索結果の数」をSennaエンジンで見て、その値と指定した値とを比べて検索処理方法を順次切り替えるというのだから、かなり特殊なものである。 マニュアルではわかりにくいが、こちらで公開されているPDFファイルによると、「適合率と再現率を両取りする機能」を実現するためのものらしい。Sennaの特徴の一端を構成する機能のようである。 LuceneにはQueryParserでこれに相当するものはない。Luceneではもう少し低レベルのレイヤで行うべき仕事であり、同じことはプログラムを書く(独自Queryクラスなど)ことで対応可能と考えられる。 *W[数値[:重み][,数値[:重み]]... Sennaのマニュアルによれば「数値で指定されたセクション番号のみを対象に検索します。」とのことだが、「セクション」がよくわからなかった。「セクション」をSennaのマニュアルで検索しても用語の解説は見つからない。 そこでGoogleで「Senna セクション」を検索すると、次のような記事が検索できた: [Tritonn][開発] Sennaのマルチセクション機能に対応 http://d.hatena.ne.jp/mir/20070619/p1 これから想像を膨らますとLuceneのフィールドに相当するものだろうか?もしそうであれば、Luceneでは次の書式となる: フィールド名1:文字列^重み フィールド名2:文字列^重み ... このクエリを使う場面を想像しやすいように、具体的なフィールド名をあげて説明してみよう。 たとえばタイトルと本文というフィールドがあり、両方のフィールドでJavaという検索語を検索するとき、タイトルフィールドを本文フィールドよりも重要とみなしたいときは、Luceneでは次のように検索式を指定することができる: タイトル:Java^2 本文:Java^1 こうすることで、本文フィールドにJavaという単語を含む文書よりもタイトルフィールドにJavaを含む文書のほうが高いスコアを得て、上位表示されやすくなる。 なお、Luceneではフィールドの長さを考慮したlengthNormという正規化係数もデフォルトで効くようになっているため、通常はこのような指定は不要である。どういうことかというと、一般的にタイトルフィールドのほうが本文フィールドよりも長さは短いのでこのような重みを指定しなくても、タイトルフィールドで検索語がヒットしたほうが本文フィールドでヒットした文書より高スコアを獲得するようになっているからである。 (参考)スコア計算の様子がわかるデモ公開 http://lucene.jugem.jp/?eid=112 >単語 または <単語 Luceneでは重要度を0より大きなFloatで指定できる。デフォルトの重要度が1.0である。 *S[数値]"文字列" 「特徴語検索」ということであるが、特徴語というのはどうやら文字列に多く出現する単語のことのようである。 機能としては面白い雰囲気があるが、実際の現場ではあまり使えないのではないかと思う。 それよりも形態素解析で得られた品詞を使って名詞や人名を特徴語とみなしそのときにスコアを上げる、という使い方のほうが実用的のように思うがどうだろう。 (参考)人名がヒットしたときはスコアを上げる http://lucene.jugem.jp/?eid=134 *N[数値]"文字列" Luceneではフレーズ検索における単語同士の距離(slop)の許容値を指定することに相当する。 なお、Luceneでは単語同士の距離が遠い文書はスコアが下がるようになっている。このことを利用すると、次のような実用的なアプリケーションが書ける: (参考)2つの単語間の距離をスコアに反映する http://lucene.jugem.jp/?eid=130 2007.11.12 Monday
ThinkIT記事「Hibernate Searchで全文検索システム構築」
JBossチームとのコラボによるThinkIT記事第2弾が公開された。
Hibernate Searchで全文検索システム構築 当初この記事のタイトルはJBossチームの方発案による「Hibernate Searchでオブジェクトとサーチをマッピング」であったが、長すぎるのかひねりすぎるのかで編集部により上記のようなタイトルに変更されてしまった。私としてはオリジナルの方がキャッチーで好きだが、SEOの関係などがあるのかもしれない。 このHibernate Searchの記事は残暑厳しいころに書いて提出してあって、私としてはほとんど忘れていた。どうやらThinkITの待ち行列に長らく並んでいたようである。今割りと新鮮な気持ちで読み返してみると、「こういう発想もあるんだなあ」と書いた当時思ったことを思い出したりしている。 「こういう発想」とは、「全文検索エンジンをRDB的視点からシームレスに使いたいという要望を実現するフレームワークが必要である」、という考え方だ。なのでTritonnやLudiaにHibernate Searchは近いところがあるかもしれない。もっとも、Hibernate自身はO/RマッパーであるからRDB的視点というのとはちょっと違うかもしれない(そういう側面もあり「Hibernate Searchでオブジェクトとサーチをマッピング」というタイトルはHibernate Searchというフレームワークをよく表現できていてよかったと思っている)。 同じThinkITに掲載されているLudiaの記事によると、Ludiaは(Sennaの特徴を受け継いで)検索対象文書データを二重に持つことを避けるため、文書はPostgreSQLに持たせる、という方針が根底にあるようだ。 Hibernate Searchはどうかというと、Luceneを使っているので、検索対象文書データをRDBで一元管理するのかそれとも検索エンジンにも持たせて二重管理にするのかは選択可能である。 この「データの二重管理」は、その言葉から受ける印象はネガティブなものだが、RDBと検索エンジンで二重に持つのはそれなりに意味があり、短絡的な連想は禁物である。これは運用の容易性と検索性能のトレードオフの問題である。つまり、RDBで管理しているデータを検索エンジンでも持てば検索結果一覧をすばやく返せるが両者のデータの整合性をどのようにとればいいかという運用を考えなければならない。逆にRDBだけにデータを持たせればデータ管理は楽になるが、検索結果一覧をすばやく返すことができなくなる。この2つの項目はどちらを重要視するかはアプリケーションにより異なるので、「データ管理をストレージエンジンにまかせます」という決め付けを検索エンジンで行ってしまうのはちょっとどうかと思っている。具体的には「検索性能が出ないのではないか」、という心配だ。 実用的な検索アプリケーションでは、検索とは転置索引から単語を引いて文書ID一覧を持ってくるだけではなく、その文書の中身をどこからかもってきてユーザに提示するところまでが仕事となる。このとき、文書の中身をRDBから持ってくるのでは遅くなるはずだ。 この点はいつかTritonnやLudiaとLuceneの検索性能を比較して明らかにしたいと思っているが、Hibernate Searchでもってデータを一元管理するか二重に持たせるかのフラグを切り替えるだけでLuceneについては試せるので、これを先にやってもいいかもしれない。 2007.11.10 Saturday
LuceneとSennaの比較:クエリー書式編(前編)
プログラマにとって、複数のプログラミング言語を学ぶことはよいことだとされている。
そこで思った。では複数の検索エンジンを学ぶことはどうだろう。それもやはりよいことに違いない。 幸い、日本には日本人により開発されたオープンソースの検索エンジンがいくつかある。オープンソースなので、学ぶにあたっては必要なだけソースを読むことが可能である。日本で開発されたので、日本語の検索は当然可能だ。マニュアルも日本語で用意されているのがうれしい。 さすがにソースコードを読むほどの時間はないが、どの検索エンジンもマニュアルはよく整備されているのでインタフェースからいろいろ読み取るくらいならできそうだ。 ということで、Luceneと他の検索エンジンの比較をしてみようと突然思い立った(いつまで続くかわからない)。ただし、他の検索エンジンについてはマニュアル情報のみを参考にする。場合によっては、Googleなどで用語やAPIを調べることもあるかもしれないが、基本は検索エンジンのマニュアル情報を正とする。 まずは、MySQLバインディングのTritonnやPostgreSQLバインディングのLudiaで最近注目度がますますアップしているSennaを取り上げ、Luceneと比較してみよう。 APIの比較ができればよいが、まずは簡単な検索式構文の比較からやってみたい。なお、「検索式構文」とは、簡単にいうと検索窓に入力するクエリー文字列のシンタックスのことである。 それぞれのマニュアルは、次のとおりである: Lucene:Query Parser Syntax http://lucene.apache.org/java/docs/queryparsersyntax.html Senna:クエリーの書式 http://qwik.jp/senna/query.html 比較の前に、LuceneのQueryParserについて簡単に説明しておこう。 Luceneでは検索のために、検索条件を表現するQueryオブジェクトを必要とする。Queryは抽象クラスなので、検索にはその具象クラスのインスタンスを作成して使う。具象クラスはあらかじめ用意されているもので18個あり、contribを含めると20以上あるだろう。もちろん、自分でQueryクラス自身を書くことも可能である。 基本的なQueryクラスは、次のJavadocで確認できる: http://lucene.zones.apache.org:8080/hudson/job/Lucene-Nightly/javadoc/org/apache/lucene/search/Query.html これらのQueryオブジェクトをQueryのコンテナであるBooleanQueryでつなげていくことで複雑なクエリを作成できるのがLuceneの特徴である。 このようなQueryオブジェクトを検索窓に入力された検索式文字列から簡単に作成するAPIがLuceneでは用意されていて、それがここで取り上げるQueryParserである。 QueryParserは検索式構文を解析するためにJavaCCを使っている。QueryParserを拡張することもできるし、QueryParserそのものを置き換えてしまうことも可能である。たとえば、以下の記事では独自のシンプルなQueryParserの書き方を解説している: 独自QueryParserの作成 http://lucene.jugem.jp/?eid=96 よってここで比較する検索式構文は、Luceneの場合はあくまでもLuceneが標準で用意しているQueryParserが解釈するルールを論じることに過ぎない。QueryParserはLuceneの数多あるQueryオブジェクトのいくつかを限定的に検索式としてルール化しているだけであることをあらかじめお断りしておく(ルール化していないQueryクラスのバリエーションのほうが多いということ)。 おそらく、Sennaも内部では似たような感じではないかと想像する。 つきつめればインデックスにアクセスするAPI、もっと極論すればインデックスのフォーマットの比較ということになろうが、きりがないのでお互いマニュアル化されている「検索式構文」を比較するのにとどめる。 2007.11.05 Monday
LuceneのHighlighterの重要性
みずほ情報総研吉川氏著の「サーチアーキテクチャ」を読んでいる(未読了)。
さすがにコンサルタントだけあって、検索に関するさまざまな項目について、独自の調査や他社の調査結果を引用しつつ、数字的裏づけを示して説得力のある説明や考察が随所になされている。 Luceneに関連するところで一例をあげると、Highlighterについての重要性の説明があり、感心したのでここでとりあげたい。 ちなみにLuceneのHighlighterは、検索結果一覧表示時に文書のタイトルなどと一緒に表示する要約文を生成するコンポーネントであり、検索キーワードを強調表示(ハイライト)した要約文を抽出できることからHighlighterと命名されている。Googleの検索結果表示を例として拝借すると、LuceneのHighlighterは下図の赤枠部分を表示できるコンポーネントである。 Highlighter周りのプログラミング方法について詳しくは、Lucene本の6.2を参照のこと。 「サーチアーキテクチャ」によれば、ユーザの検索行動に関する調査結果により、Highlighterの出力はどのように重要なのかが具体的に説明されている。同書の「5.1.2 説明文の表示技術」に、検索ユーザが検索結果文書をクリックするかどうかの判断基準として:
という具合にHighlighterが出力する情報(=説明文)がユーザによりかなり重要視されていることが説明されている。 さらに読み進めていくと、説明文を表示する方法には次の3つの方法があるが、これも同様の調査結果により1.の方法が好まれている、としている:
LuceneのHighlighterはGoogleと同様に1.の方法で出力している。ちなみにNamazuは2.のようであり、文書の先頭に検索キーワードがたまたま含まれていた場合は強調表示ができるが、含まれていない場合は強調表示ができないようだ。 これまでは私の場合、Highlighterの必要性・重要性を認識していて顧客に説明したくても、Namazuとの比較でLuceneのHighlighterのよさを説明するのがせいぜいであった。 しかし「サーチアーキテクチャ」によって、Highlighterの必要性・重要性が数字的に裏付けられたといえるだろう。 (なお、Namazuと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 |