アペフチ

Groongaの類似文書検索

Groongaで学ぶ全文検索 2016-03-11に行って来た。

今日は、実際のユースケースに沿った話をしようということで、参加者のすごい具体的な業務の話をして、それにGroongaを入れる時にどういうふうにすればいいか、どこに注意すればいいかといった話をした。書けなさすぎるので、Mroongaを使ってウェブ日記に類似記事機能を入れるという喩え話にして書く。

この日記は、実際には静的HTMLをGitHub Pagesでホストしているが、仮に、MySQLに記事データを入れてそこからデータを引っ張って表示しているとしよう。その記事テーブルにはこんなカラムがあるだろう。

  • URL(パス)
  • タイトル
  • タグ
  • 本文

各記事の末尾に、その記事に関連する記事一覧を出したいとする。

関連記事の分類

読者に読むべき記事を提示する際、状況を二つに分けられる。

  • 読者が、自分が読みたい物を自覚している(「Groongaの記事を読みたい」)
  • 読者は自分が読みたいタイプの記事を自覚していない(「この人の文体は気持ちがいい。次に何を読もう」

前者の読者に対しては、テキストフィールドを使ったいわゆる検索機能を提供すればよい。

後者の読者には、記事の提供側(個人日記なので、僕のことだ)が何らかの基準で読むといいだろう記事を提示する。提示のアプローチは更にこう分類できる。

  • 全ユーザーに共通の記事を提示する
    • 僕がおすすめしたい記事一覧を出す
    • 日記の全記事中、人気のある記事一覧を出す(例えばPVを使って)
    • 今読んでいる記事に似た記事一覧を出す
  • 読者ごとにパーソナライズしたおすすめなどを提示する(例えばクッキーを使って。今日はこの話はしない)

類似文書検索を行う方法

このうち「今読んでいる記事に似た記事一覧を出す」というのは類似文書検索と呼ばれる一般的な検索方法を使うと可能で、Groonga(Mroonga)にはその機能が備わっているので、ここではそれを使う時の方法と注意点を述べる。

まず、検索エンジンの用意だが、Mroongaを使えば簡単だ。MroongaはMySQLに組み込んで使えるストレージエンジンで、Groongaを使った検索機能などを提供する。Mroongaで日記テーブルを作って記事の表示や検索をさせることができるが、記事追加といったデータを扱うマスターデータベースはInnoDBにし、MySQLのレプリケーション機能を使って作るレプリカをMroongaにする、ということができる。するとInnoDBの堅牢さやトランザクション機能にあやかりながらGroongaの高速検索機能を使える。データはレプリケーション機能を使って勝手に入ってくるので、自分で同期の仕組みを作る必要もない。

こうしておくと、記事を表示する時にレプリカであるMroongaから類似文書検索機能を使って似た記事一覧を出すことができる。Mroongaで類似文書検索をするには、SQLで

... MATCH(本文) AGAINST(今見ている記事の本文 IN NATURAL LANGUAGE MODE) ...

と書く。AGAINSTに記事の本文を与えることと、モードをNATURAL LANGUAGE MODEにすることがポイントだ(ここで「NATURAL LANGUAGE MODEにすると類似文書検索になる」というのはMroongaがそうしてあるということで、MySQL一般のことではない)。

それから、トークナイザーに、MeCabを使うことも重要だ。N-gramだと精度の著しく低い結果になってしまう。どうしてか。それを説明するには類似文書検索の概要を説明する必要がある。(時間があったら書く -> なかった)

Mroongaでの類似文書検索の注意点

今見ている記事の本文を使って類似文書検索をし、似ている記事の上位n件を表示すると、そこに今見ている記事その物が入ることが非常に多い(当たり前だ)。なので、条件句でそれを除くようにしておく。

「似ている」ということの精度を高めるために、本文以外の情報を使うとよい。例えば、タグが共通している記事はスコアを上げるのみを提示するなど(Groongaではできるのだが、Mroongaではスコアをどうこうというのはできないらしい)。

その他の記事提示方法

始めの分類の「読者が、自分が読みたい物を自覚している」という状況のため、検索エリアを設けたとする。検索語を入れている途中で、この日記の中で見つかりそうな単語を補完して提示すると、見栄えがいいし、検索しても無駄そうなキーワードも早めに分かってよい。

Mroongaの最新リリースで、これ(サジェスト補完機能)を実現する機能が入った。ただ、MroongaはなるべくMySQLのユーザーが自然に使えることを重視している(上のNATRUAL LANGUAGE MODEなど、既存のMySQLの物をうまくGroongaに当てはめて使っている)が、この機能はそうはいかないので、強引にGroongaのフル機能を使えるようにする。Groongaの機能を自由に使うには

... MATCH(...) AGAINST('*SS title:@"..." && point >= 100' IN BOOLEAN MODE) ...

と、AGAINSTの中を*SSで始める。これを使ってGroongaのprefix_rk_search(prefix romaji kana search)関数を使うと、上のサジェスト補完機能を実現できる。prefix_rk_searchはGroongaには以前のバージョンからあったので、「Mroongaの最新のリリースでサジェスト補完機能を実現する機能が入った」のではなく、「Groongaのフル機能を使えるようにするプラグマ(*SSのこと)が入った」というのが正しい。

このprefix_rk_search()は、ローマ字やかなを使って、漢字が入っているカラムに対して前方一致検索ができる、という機能だ(だいぶ雑な説明。詳細はドキュメントを読まれたい:http://groonga.org/ja/docs/reference/operations/prefix_rk_search.html)。

ただ、これは通常の検索と違い、検索キーワード入力が終わる前に何度もリクエストを投げるのでそこには注意が必要だ。検索専用サーバーを立てるなどの対応が必要になるかも知れない。

ユーザーの入力を楽にするまた一つの方法として、やはり最新のリリースで、曖昧検索も入った。この日記では「Groonga」というキーワードをよく使っているので、検索すると何件か記事がヒットする。検索時に「Groonag」と綴り間違いをしてしまうかも知れない(僕はこの間違いをよくする。本当によくする)。それでも「Groonga」で検索した時の結果を出してしまう機能が曖昧検索だ。これの詳細は開発者のブログ記事を参照されたし:http://blog.createfield.com/entry/2016/02/28/014432