2017.12.15 Friday
スポンサーサイト
一定期間更新がないため広告を表示しています
| スポンサードリンク | - | | - | - |
関口宏司のLuceneブログOSS検索ライブラリのLuceneおよびそのサブプロジェクト(Solr/Tika/Mahoutなど)について
2007.07.25 Wednesday
NGramTokenizerとEdgeNGramTokenFilter
Lucene 2.2のcontribにN-gramで単語を切り出すNGramTokenizerとEdgeNGramTokenFilterが追加された(同時にEdgeNGramTokenizerとNGramTokenFilterも追加されたが、これらはあまり使われない気がする)。
これまでLuceneのN-gram Analyzerといえば、CJKAnalyzerであった。これはCJK文字のときにだけ2文字単位で切り出すもので、bi-gramとも呼ばれるものである。 CJKAnalyzerのサンプルプログラムを以下に示す:
このプログラムを実行すると、次のようになる:
CJKAnalyzerは上記のように日本語の文字を2文字単位に切り出す。このとき、となりどうしのトークンの文字が互いに重なるように切り出すのがN-gramの特徴である。たとえば、「メガネ」は「メガ」と「ガネ」に分割される。こうすることで後に「メガネ」で検索するときにも「(メガ)(ガネ)」という成句で検索されるために文章中の「メガネ」がうまい具合に検索できるのだ。 CJKAnalyzerを使う動機としては、(形態素解析の)JapaneseAnalyzerと比較して辞書のメンテナンスが不要で流行語に強い、などが理由にあげられる。 一方、CJKAnalyzerを敬遠する理由としては、(Lucene本にも書いた例だが)「京都」で引いたときに「東京都」が引っかかってしまうとか(これはGoogleでもみられる現象だ)、1文字の単語が検索できない、などがあげられる。「京都」で「東京都」が検索できてしまうのは目をつぶれるとしても、後者は特に困った問題である。たとえば、先のサンプルの例文はCJKAnalyzerを使っているときは、「顔」という検索語では検索できない。 NGramTokenizer NGramTokenizerはコンストラクタで指定した大きさのトークンを文章から切り出すTokenizerである。たとえば、次のように指定すると、bi-gramとして動作する:
また次のようにすれば、3文字ずつ切り出すtri-gramとして働くようになる:
これだけだとただ単にCJKAnalyzerがちょっと発展したくらいにしか思えないが、次のようにすることで1〜3文字を切り出すように動作するのがNGramTokenizerの便利なところだ:
これをCJKAnalyzerの代わりに使用すれば、先ほどの「顔」で検索できない、という問題も解消できる。 具体的なプログラムを示すと、まずNGramTokenizerはAnalyzerではないので、使用するにはAnalyzerを作成する必要がある。簡単なAnalyzerは次のようなプログラムになるだろう:
上記のMyAnalyzerを最初のプログラム例のCJKAnalyzerと入れ替えて使用する。そして実行すると、次のようになる:
EdgeNGramTokenFilter EdgeNGramTokenFilterの用途としてはおそらく、形態素解析で切り出された単語をさらにN-gramで分割するときに使用するものと考えられる。 先ほどのMyAnalyzerのプログラムをWhitespaceAnalyzerとEdgeNGramTokenFilterを使うように、次のように変更してみる:
そして(WhitespaceAnalyzerなので)適当な英文を選んで最初のプログラムのTEXTに代入して実行すると、次のようになる:
WhitespaceAnalyzerの代わりにJapaneseAnalyzerを使用すれば、日本語を形態素解析した後にそれぞれの単語をさらにN-gramで分割する、ということができるはずなので、興味のある人はやってみていただきたい。 2007.07.17 Tuesday
deprecatedとなったQueryFilter
ここ数回にわたってLucene 2.2の新機能を紹介してきたが(まだ紹介していない機能は別の回にまわす)、今回はLucene 2.2でdeprecatedとなった機能であるQueryFilterについて説明しよう。
QueryFilterはインデックスのドキュメントを「篩(ふるい)」にかけるFilterの一種で、同じFilterの一種であるCachingWrapperFilterのように、以前作成したBitSetオブジェクトを保存するキャッシュを内蔵するFilterである。Filter(抽象クラス)、QueryFilterおよびCachingWrapperFilterの関係はLucene 2.1までは図Aのようであった。 Lucene本のP207-211にはそれぞれのFilterについて説明があるが、本を書いた当時は同じキャッシュのコードがQueryFilterとCachingWrapperFilterにあったため、美しくないなと感じた記憶がある。 Lucene 2.2でこの部分にリファクタリングが入り、QueryFilterはCachingWrapperFilterのサブクラスとなってQueryFilterそれ自身からはキャッシュのコードが取り除かれた。さらにQueryFilterはdeprecatedにマークされ、QueryWrapperFilterというFilterが新規に追加されて、Filterクラスは全体として図Bのようになった。 これにより、Lucene 2.2からは次のようなプログラムをコンパイルするとdeprecatedの警告メッセージが表示されるようになった:
メッセージは警告なので無視してもかまわない。以前のプログラムは同じキャッシュの性能効率で動作することが保証されている。 警告メッセージが出ないよう以前のプログラムを直す場合は、QueryFilterを使っている部分をQueryWrapperFilterとCachingWrapperFilterを組み合わせた形で次のようにプログラミングすればよい:
こうすれば、警告メッセージも出ず、プログラムも以前と同じ性能で動作する。 ところでFilterクラス群がこのように落ち着く前に、図Cのように単純にQueryFilterからキャッシュ機能を取り除く案が提案されていた: http://issues.apache.org/jira/browse/LUCENE-857 この案では、キャッシュのコードの冗長部分をただ単にQueryFilterから取り除いただけである。したがって、Lucene 2.1以前のQueryFilterを使ったプログラムがLucene 2.2でもキャッシュが効くようにするには次のようにプログラミングしなければならない:
しかしこの案ではLucene 2.1以前のQueryFilterを使ったプログラムは警告メッセージも出ずにコンパイルできてしまい、気づかずにLucene 2.2にアップグレードしてしまった場合はキャッシュが効かなくなるので性能が格段に落ちてしまう。 そこで最終的に落ち着いた案は図Bのような形である。これにより、以前のプログラムをコンパイルするとdeprecatedの警告が表示され、新しい使い方にスムーズに移行を促すことができ、警告をとりあえず無視して運用しても性能を落とすことがなくなる。 コミッターのちょっとした気遣いが光ったissueといるといえるだろう。 2007.07.09 Monday
CJKAnalyzerを使って強調表示を行う
梅雨の合間の、青空が気持ちのいいある平日。自宅でこのブログの記事を執筆しようとしたそのとき、電話が鳴った。ただ「はい」とだけ言い、受話器の向こうの声に耳を澄ます。最近の多くの人にも当てはまることだと想像するが、近頃私は自宅の電話に出るときに意識して名乗らないようにしている。かなりの確率でセールスの電話であったり、間違い電話で名乗ったとたんに無言で切られ、一部の個人情報を伝えてしまったことに納得のいかない思いをすることがしばしばだからである。
しかし電話は若い元気な女性の声で、 「いつもお世話になっております。リクルートの××です」と名乗った。 なぜ自宅に?という思いと同時に、仕事の電話かもしれないではないかという計算が働く。私も、 「お世話になっております。関口です」とそつなく返した。 しかしほどなくして、(こちらの)仕事になるような電話ではないことが判明した。電話の女性はタウンワークの者だといい、求人広告を掲載しませんか、と勧誘してきたのである。 この後笑い話となる二人の会話がしばらく成立したのは、第一に彼女の方に原因があることは間違いないが、私の方にもその布石となる出来事がその数時間前にあったのだった。 その出来事とは弊社丸の内オフィスにかかってきたやはり一本の電話である。それも果たして求人広告を出しませんか、という別の業者からの電話であった。 最近の私の頭の中にはいつも求人のことはあるにはある。しかし、日ごろの忙しさにかまけて実行動はなかなか取れていなかった。だから普段セールスの電話は歓迎しない私も(これも一種のセールスの電話であろう)、この機会にシステム(料金体系とか)を聞いてみようか、という気分にそのときはなっていた。そしてその業者から一通り説明を聞いた後「じゃあ、検討します」とお決まりの文句を言って電話を切ったのが数時間前のことだ。 そんなことがあったので、タウンワークからの電話を受け取ったときも「彼らの嗅覚も大したものだなあ、こんなタイミングで電話してくるなんて」と感心しただけだった。 「求人は考えていないこともないのですが、雑誌に広告を出すというほど大げさなことは考えていないのですが」と正直な気持ちをまず話した。 「はい、とりあえず、本日はお店におうかがいさせていただき、ごあいさつをさせていただきたいのですが、ご都合はいかがでしょうか」 オフィスをお店と表現するところなどさすがタウンワークである。へんな感心をしつつ、しかしここはお店でもオフィスでもなく自宅であることを思い出した。自宅にごあいさつにこられると面倒なので、とりあえず電話で聞けるだけの情報を聞きたいと思った私は、別の質問を続けることにした。 「しかし、タウンワークに求人出して人が来ますかね?」 「はい、たくさん来ますよー」 「そんなもんですかねー。広告出すといっても、ただじゃないんですよね?」 「はい、1万×千円です」 「なるほどー。それで期間的には?」 「一週間です」 「一週間ねー。それで人が来ますかね?」 「はい、これまで広告を出したことがなければ、絶対に効果があります!」電話の女性の声はますます元気だ。 しかし私は納得がいかなかった。なぜなら数時間前の別の求人広告業者のシステムの説明では、×十万円・半年間のWeb掲載で2〜3人紹介できますという話で、あたかも半年間で2〜3人紹介できることがすごいことのように話していたからだった。 Webの半年間掲載でそれだけなのに地域密着型のタウンワークにたったの一週間掲載でそんなに人が集まるものだろうか。なにかがおかしい、そう感じた私は、電話を取ったときに最初に感じた違和感に正対することにした。そして、このように尋ねた。 「そうですか。ところで、こちらの電話番号はどのようにお調べになったのでしょう?」 「はい、私は××と××の地域を担当しておりまして、以前お店におうかがいしたときに店長さんがいらっしゃらなかったので、本日改めましてお電話をさせていただきました!」 私は社長である前に一技術者であることに誇りを持って仕事をしている人間である。しかし名刺には社長と印刷してあるので、社長と呼ばれることもあり、その自覚もなくはない。しかし店長といわれたのは今回が初めてである。いよいよおかしい。そこで思い切って、さらに尋ねたのだった。 「そうですか。でもここはお店とかではなくて・・・。失礼ですがひょっとしてお間違いではありませんか?」 「あれ、サーティワンアイスクリームさんじゃないんですか」 「あー、ちがいますねー」PCの画面を見るともう十分くらい話している。なんということだろう。 「きゃー、失礼しました。すみませんが御社名はなんとおっしゃるのでしょうか」 「ロンウイットです」 「失礼しました。ロンウイット様、求人広告はいかがですか」 さすがリクルートの営業だ。この女性を採用しようか、ふとそんな思いがよぎったりもした。しかし弊社がまず欲しいのは営業ではなく技術者なので、その考えは思いとどまった。 その後私は弊社のような業種がタウンワークに広告を出して果たして人が集まるのか、それに対して女性は大丈夫、コンピュータ業界の会社もタウンワークで人を集めていますよ、という議論でさらに5分ほど費やし、「じゃあ、検討します」というやはりお決まりの文句で受話器を置いたのであった。はー。 さて、本題に話を移すが、今回は最近まで気がつかなかったLuceneの改善点について話そうと思って書き始めたのだった。それはHighlighterのことである。 これはLucene本にも書いたことが、CJKAnalyzerはHighlighterと組み合わせて使用することができない。しかしそれはLucene 2.0までの話で、Lucene 2.1からはCJKAnalyzerでもHighlighterが正しく動作するように改善されたのだった。うかつな話で、私はLuceneが2.2になった最近までそのことに気がつかなかった。 この修正は次のURLで参照できる: http://issues.apache.org/jira/browse/LUCENE-627 では早速試してみよう。以下はCJKAnalyzerを使ったHighlighterのサンプルプログラムである:
上記の赤字の部分に適当な文章(長文)と検索キーワードをそれぞれ埋め込んで実行する。たとえば文章として適当に選んだニュース記事と検索キーワードに「赤城 事務所費」と記述してコンパイル・実行すると、Lucene 2.1以降では次のようになる:
正しく強調表示されているのがわかるだろう。しかしLucene 2.0以前のバージョンでは、次のように強調表示が正しく動作しない。
2007.07.02 Monday
Lucene 2.2のPre-analyzed Fields
Lucene 2.2にTokenStreamを引数にとる新しいFieldのコンストラクタが追加された。
これが何かを話す前に、まずこれまでのFieldのコンストラクタにはどんなものがあったのかおさらいしておこう。 まずはFieldの値にString型の引数をとるコンストラクタがあり、これは最もよく使われるFieldコンストラクタの基本形である。 このコンストラクタはFieldの値の文字列をインデックスに登録するかどうかをStore.YESまたはStore.NOで指定し、さらにその文字列を索引付けするかどうか、索引付けする場合に単語に分割するかどうかをIndex.NO、Index.UN_TOKENIZED、Index.TOKENIZEDで指定できるコンストラクタである。 String型の引数を取るFieldのコンストラクタが最も使われるので、ついつい忘れがちだが、この他にもReader型を引数にとるものとbyte[]型を引数にとるものがある。これらはString型のとは違い、StoreやIndexを指定しない。StoreやIndexの値はこれらのコンストラクタにおいてはあらかじめ決められている。 Readerの場合はIndex.TOKENIZEDで索引付けされ、その代わり登録はされない。byte[]のバージョンは(バイトストリームであるために)索引付けはされずStoreに関してはYESまたはCOMPRESSのどちらかが指定できる。byte[]のコンストラクタではStore.NOを選ぶとIllegalArgumentExceptionがスローされる。これは索引付けもせず登録もしないのは意味のない行為であるとみなされるためだ。 以上はLucene 1.9でも同様でありLucene本にも記載されている(P.91)。 Lucene 2.2ではこれらのコンストラクタに新たにTokenStream型の引数を取るコンストラクタが加わった。これは次のJIRAにファイルされた機能で、通称「Pre-analyzed Fields」と呼ばれるフィールドを生成するものである: https://issues.apache.org/jira/browse/LUCENE-580 Pre-analyzedフィールド、つまりフィールドのインスタンスを生成する前にすでに文章が単語分割されている、ということである。Lucene 2.2の新機能として「フィールドのインスタンスを生成する前にすでに文章が単語分割されている」などという説明を読むとなんだかすごいことのように感じるが、なんのことはない。単語分割はプログラマが自分で行わなければならないのだ。どうやるかというと、AnalyzerクラスのtokenStream()メソッドを使ってTokenStreamを取得するのである。 ではこのコンストラクタはどんなときに使うと便利かというと、インデックスに登録する前に何らかの理由で文章を単語分割したい場面にたまに出くわすが、そのようなときに使うとよいだろう。たとえば私のある客先では、Fieldを作成する前に(別に後でもいいのだが)文章の単語数をカウントする必要があった。 Lucene 2.1当時はこのためにその文章をAnalyzerに通し、TokenStreamの出力から単語数をカウントしてTokenStreamは捨てていた。そしてFieldはFieldでその文章を使って作るので、二重にAnalyzerにかけることになって少々効率が悪かった。 このような場面でLucene 2.2ではTokenStreamは捨てずにそのままFieldのコンストラクタに渡して索引付けに使うことができるので効率がよくなる。もっとも、TokenStreamを引数にとるコンストラクタではStore.NOとなるので、文章を登録したい場合は使えないので注意が必要だ。 Lucene 2.2のFieldのコンストラクタをまとめると、次のようになる(TermVectorは省略):
|
+ 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 |