全文検索エンジンGroongaでサジェスト機能(補正)を使ってみる

> もっとgroongaを知ってもらおう!ということで週刊groongaをはじめました。毎週木曜にgroongaやmroonga、rroongaのトピックを投稿予定です。

[gihyo.jp](http://gihyo.jp/)さんでgroongaの隔週連載が最終回を迎えました。groongaの最新情報と今後に興味があれば一読をおすすめします。該当記事は[「最終回　groongaの今と未来」](http://gihyo.jp/dev/clip/01/groonga/0011) です。

第1回から第10回までの過去記事については、[隔週連載Groonga](http://gihyo.jp/dev/clip/01/groonga)のページを参照してください。

今年も11/29に[全文検索エンジンGroongaを囲む夕べ 4](http://atnd.org/events/43461)を開催することになりました。Groongaのいろんな話を聞きたい人にはおすすめです。すでに80名の参加希望者がいます。参加登録はお早めに! 発表者も[募集](http://atnd.org/events/43461#application)しています。残りの発表者枠は2つです。

# はじめに

オープンソースの[カラムストア機能付き全文検索エンジンGroonga](http://groonga.org/ja/)を公開しています。最新のバージョンは2013年9月29日にリリースした[3.0.8](http://groonga.org/ja/docs/news.html#release-3-0-8)です。

前回は、Groongaでサジェスト機能を利用するときの具体例のうち、補完について紹介しました。

* [全文検索エンジンgroongaでサジェスト機能(補完)を使ってみる](http://qiita.com/groonga/items/891ceade90ca45df6462)

今回は、残りのうち補正について紹介します。

# Groongaのサジェスト機能とは

全文検索エンジンgroongaの特徴のひとつに、サジェスト機能があります。
このサジェスト機能では、補完、補正、提案ができるようになっています。

* [補完](http://groonga.org/ja/docs/suggest/introduction.html#completion)
* [補正](http://groonga.org/ja/docs/suggest/introduction.html#correction)
* [提案](http://groonga.org/ja/docs/suggest/introduction.html#suggestion)

補完は一部の入力をもとにして、登録済みの語の中から補完候補の語を返します。補正は誤入力をもとにして、登録済みの補正ペア(誤入力に対応した正しい入力のペア)から補正された語を返します。提案は入力をもとにして、登録済みの関連クエリペア(キーワードとそれに関連するクエリのペア)から追加のキーワードを含む新しいクエリを返します。

サジェスト機能の個別の機能についてはドキュメントに簡単な例がありますが、一連の流れをまとめて説明しているわけではないのでちょっとわかりにくいかもしれません。

[チュートリアル](http://groonga.org/ja/docs/suggest/tutorial.html)のセクションも用意していますが、まだTODOという状態で空っぽです。

提案機能を使った具体例としては[groonga 3.0.3のリリースアナウンス](http://sourceforge.jp/projects/groonga/lists/archive/dev/2013-April/001337.html)で「学習時の重み付けのカスタマイズをサポート」というトピックがあるので、それが参考になるかもしれません。

上記の状況なので、実際に使ってみようとしてどうしたらいいの、となってしまうかもしれません。
というわけで、もう少し補正について具体的な例を上げて使い方を紹介してみます。

丁度、テストデータにサジェスト機能を使ったものがあるので実際に試してみましょう。

今回の例では次のようにして検索データを準備します。
基本的な流れは前回紹介した補完の場合と似ていますね。

1. groonga-sugguest-create-datasetでデータセットの準備
2. 学習用データの読み込み

上記をすべて実行すると検索する準備ができます。
補完のときは参照用のデータを登録しましたが、今回はそれがないのが違いです。

## groonga-sugguest-create-datasetでデータセットの準備

サジェスト機能を利用するには、あらかじめテーブルやカラム定義が必要です。

Groongaではサジェスト機能を利用するのに必要なテーブルやカラムなどのスキーマ定義を `groonga-suggest-create-dataseet` を使うことで、まとめて行うことができます。

このコマンドの詳細については、[groonga-sugguest-create-dataset](http://groonga.org/ja/docs/reference/executables/groonga-suggest-create-dataset.html)を参照してください。

では、実際にデータセットを作成してみましょう。
作成するデータセットは、ruremaとします。

```
% mkdir -p testdb
% groonga-suggest-create-dataset testdb/db rurema
```

上記コマンドを実行すると、 'rurema' というデータセットを作成できます。

作成途中のログも次のように表示されます。

```
> register suggest/suggest
true
> table_create event_type TABLE_HASH_KEY ShortText
true
> table_create bigram TABLE_PAT_KEY|KEY_NORMALIZE ShortText --default_tokenizer TokenBigram
true
> table_create kana TABLE_PAT_KEY|KEY_NORMALIZE ShortText
true
> table_create item_rurema TABLE_PAT_KEY|KEY_NORMALIZE ShortText --default_tokenizer TokenDelimit
true
> column_create bigram item_rurema_key COLUMN_INDEX|WITH_POSITION item_rurema _key
true
> column_create item_rurema kana COLUMN_VECTOR kana
true
> column_create kana item_rurema_kana COLUMN_INDEX item_rurema kana
true
> column_create item_rurema freq COLUMN_SCALAR Int32
true
> column_create item_rurema last COLUMN_SCALAR Time
true
> column_create item_rurema boost COLUMN_SCALAR Int32
true
> column_create item_rurema freq2 COLUMN_SCALAR Int32
true
> column_create item_rurema buzz COLUMN_SCALAR Int32
true
> table_create pair_rurema TABLE_HASH_KEY UInt64
true
> column_create pair_rurema pre COLUMN_SCALAR item_rurema
true
> column_create pair_rurema post COLUMN_SCALAR item_rurema
true
> column_create pair_rurema freq0 COLUMN_SCALAR Int32
true
> column_create pair_rurema freq1 COLUMN_SCALAR Int32
true
> column_create pair_rurema freq2 COLUMN_SCALAR Int32
true
> column_create item_rurema co COLUMN_INDEX pair_rurema pre
true
> table_create sequence_rurema TABLE_HASH_KEY ShortText
true
> table_create event_rurema TABLE_NO_KEY
true
> column_create sequence_rurema events COLUMN_VECTOR|RING_BUFFER event_rurema
true
> column_create event_rurema type COLUMN_SCALAR event_type
true
> column_create event_rurema time COLUMN_SCALAR Time
true
> column_create event_rurema item COLUMN_SCALAR item_rurema
true
> column_create event_rurema sequence COLUMN_SCALAR sequence_rurema
true
> table_create configuration TABLE_HASH_KEY ShortText
true
> column_create configuration weight COLUMN_SCALAR UInt32
true
> load --table configuration
> [
> {"_key": "rurema", "weight": 1}
> ]
1
```

次のようにたくさんのデーブルが作成されていることがわかります。

* event_type
* bigram
* kana
* item_rurema
* pair_rurema
* sequence_rurema
* event_rurema
* configuration

これで、データを格納する入れ物ができました。
次の学習用データの読み込みに進みます。

## 学習用データの読み込み

スキーマの定義ができたので、学習用データを登録します。

学習用のデータを登録する `load` コマンドが記載された learn.grn を次のようにしてtestdbに反映します。

```
% groonga testdb/db < learn.grn
[[0,1381979722.02598,0.0464754104614258],786]
```

[learn.grn](https://raw.github.com/groonga/groonga/master/test/command/fixture/suggest/rurema/learn.grn)は次のようになっています。

登録件数が多いので、内容を省略しています。

学習用データはデータセット名が 'rurema' なので `event_rurema` テーブルに登録します。(タイムスタンプと入力、typeがsubmitなら確定したという意味になります。)

```
load --table event_rurema --each 'suggest_preparer(_id, type, item, sequence, time, pair_rurema)'
[
["sequence", "time", "item", "type"],
["e61684e407fc1ed00cd982dd821618a1fc71e35f",1312130117.0,"awesome_print",null],
["e61684e407fc1ed00cd982dd821618a1fc71e35f",1312130126.0,"awesome_print","submit"],
...
["1bb27c64e1d11dd3962a5c928256f4e55ce60a40",1312147805.0,"signature","submit"],
["6443c4622148ef8243a8daef5c4a37ade7635c8c",1312148085.0,"a",null],
["6443c4622148ef8243a8daef5c4a37ade7635c8c",1312148085.0,"ave",null],
["6443c4622148ef8243a8daef5c4a37ade7635c8c",1312148086.0,"avera",null],
["6443c4622148ef8243a8daef5c4a37ade7635c8c",1312148087.0,"average",null],
["6443c4622148ef8243a8daef5c4a37ade7635c8c",1312148088.0,"average","submit"],
["6443c4622148ef8243a8daef5c4a37ade7635c8c",1312148094.0,"avg","submit"],
["6443c4622148ef8243a8daef5c4a37ade7635c8c",1312148098.0,"s",null],
["6443c4622148ef8243a8daef5c4a37ade7635c8c",1312148098.0,"su",null],
["6443c4622148ef8243a8daef5c4a37ade7635c8c",1312148098.0,"sum",null],
["6443c4622148ef8243a8daef5c4a37ade7635c8c",1312148102.0,"sum","submit"],
["ae69d285c8e40a66f461b0b34016771fa1acc732",1312148586.0,"base",null],
...
["d674ea0b818d6069a98f33921fc649e87191dce4",1312158927.0,"array#delete","submit"],
["5ded27aa3f8a4f52055e95d251db81691f322b65",1312162235.0,"systme",null],
["5ded27aa3f8a4f52055e95d251db81691f322b65",1312162235.0,"systme","submit"],
["5ded27aa3f8a4f52055e95d251db81691f322b65",1312162238.0,"system",null],
["5ded27aa3f8a4f52055e95d251db81691f322b65",1312162239.0,"system","submit"],
["1bb27c64e1d11dd3962a5c928256f4e55ce60a40",1312162851.0,"logging","submit"],
...
```

これで、学習用のデータを登録し、検索用のデータの準備ができました。

タイムスタンプとつきあわせてみると、上記からはいくつか補正して再検索していることがわかります。

最初の補正して検索している例は、次の通りです。

* averageで検索
* avgに訂正して再度検索
* sumに訂正して再度検索

もうひとつ補正して入力している例は、次の通りです。

* systmeで検索
* systemに訂正してで再度検索

## Groongaで補正してみよう

では、Groongaを対話的に起動して補正を試してみましょう。

補正したいときには、 suggestコマンドの ``--types`` に``correct`` を指定します。

「average」を指定してみるとどうなるでしょうか。

```
> suggest --table item_rurema --column kana --types correction --frequency_threshold 1 --query average
[[0,1382582144.0399,0.000342369079589844],{"correct":[[1],[["_key","ShortText"],["_score","Int32"]],["avg",1]]}]
```

「avg」が補正候補として返ってきました。
今度は「avg」を指定してみるとどうなるでしょうか。

```
> suggest --table item_rurema --column kana --types correction --frequency_threshold 1 --query avg
[[0,1382582146.24192,0.00053858757019043],{"correct":[[1],[["_key","ShortText"],["_score","Int32"]],["sum",1]]}]
```

「sum」が補正候補として返ってきました。

今度は「systme」を指定してみるとどうなるでしょうか。

```
> suggest --table item_rurema --column kana --types correction --frequency_threshold 1 --query systme
[[0,1382582274.67882,0.00036311149597168],{"correct":[[1],[["_key","ShortText"],["_score","Int32"]],["system",1]]}]
```

結果として「system」が返ってきました。期待通りですね。

この補正機能をうまく使うと、よくある間違いから検索キーワードを補正して「もしかして」という候補を提示することができるでしょう。

あらかじめよくある間違いがわかっているなら、補正ペア用意して登録することもできます。

例えば、'grroonga' を 'groonga' に補正(rがひとつ多い!)するデータは次のような感じになります。

```
["9443c4622148ef8243a8daef5c4a37ade7635c8e",1312662530.0,"grroonga","submit"],
["9443c4622148ef8243a8daef5c4a37ade7635c8e",1312662580.0,"groonga","submit"]
```

上記を先ほどのlearn.grnに追加してあげると'grroonga'と入力したときの補正ができるようになります。

```
> suggest --table item_rurema --column kana --types correction --frequency_threshold 1 --query grroonga
[[0,1382586614.50103,0.000566482543945312],{"correct":[[1],[["_key","ShortText"],["_score","Int32"]],["groonga",1]]}]
```

きちんと'grroonga'でなくて'groonga'ですよ、という結果が返ってきていますね。

# まとめ

今回は、全文検索エンジンGroongaのサジェスト機能のうち補正について紹介しました。
まだチュートリアル等が充実していないので、とっつきにくいかったサジェスト機能の参考になったでしょうか。

Groongaに興味を持ったなら、まずは[インストール](http://groonga.org/ja/docs/install.html)して試してみてください。

Groongaの基本的な動作を知るための[チュートリアル](http://groonga.org/ja/docs/tutorial.html)もあります。インストールしたら試してみてください。