自作カナ変換プラグインでElasticsearchの日本語検索をいい感じにする

# はじめに

[Elasticsearch](https://www.elastic.co/jp/products/elasticsearch) には日本語を形態素解析してくれる [公式プラグイン](https://www.elastic.co/guide/en/elasticsearch/plugins/current/analysis-kuromoji.html) が存在します。
長い日本語の文章からキーワードをいい感じに抜き出してくれてとても便利なのですが、単語だけのデータとか短い日本語は苦手なようで、うまくいかないことがあります。

たとえば、「サイトウ」さんみたいに漢字が「斉藤」とか「斎藤」とか「齋藤」とか何種類かある場合は全然マッチしてくれません。
[kuromoji_readingform token filter](https://www.elastic.co/guide/en/elasticsearch/plugins/current/analysis-kuromoji-readingform.html) で全部カナに変換しておくことである程度吸収できるのですが、これを使うときはあらかじめ [kuromoji_tokenizer](https://www.elastic.co/guide/en/elasticsearch/plugins/current/analysis-kuromoji-tokenizer.html) でトークンごとに分けておく必要があります。

検索ボックスに何をを入力するか考えてみてほしいのですが、こういう検索をするのは珍しいと思います。

<img width="809" alt="image.png" src="https://qiita-image-store.s3.amazonaws.com/0/69029/466c4e83-0ee7-dd53-fb00-37c3c745d6d5.png">

私ならこうします。

<img width="791" alt="image.png" src="https://qiita-image-store.s3.amazonaws.com/0/69029/d2961aa9-6b06-717b-9f34-3b454c0dd959.png">

検索ワードの切れ目は自分で決めたい人が多いのではないのでしょうか。
つまり、

> あらかじめ _kuromoji_tokenizer_ でトークンごとに分けておく必要があります。

のせいで

`Elasticsearch` `日本語検索` `自作プラグイン`

こうなってほしい検索クエリは

`Elasticsearch` `日本語` `検索` `自作` `プラグイン`

みたいに区切れて、検索対象が多い場合はゴミデータばかり上位に出てきてしまいます。

もうひとつ、日本語を入力するときはいきなり __漢字__ で入力せずに __かな__ で入力してから変換しますよね？
これだと漢字に変換するまで期待した検索結果が出ないので、サジェストとかインクリメンタルサーチには使えません。

前置きが長くなりましたが、この辺を解決しようという話です。

# Elasticsearch の analyzer について

Elasticsearch では 検索ワード と 検索対象ドキュメント は _analyzer_ を通して検索しやすい形に変換してからマッチングに使用します。
詳しいことは[公式ドキュメント](https://www.elastic.co/guide/en/elasticsearch/reference/current/analyzer-anatomy.html)に書いてありますが、ざっくりまとめると3つの要素で構成されていて

1. char_filter (character filter)
2. tokenizer
3. filter (token filter)

の順で処理されます。

それぞれ `char_filter` は文字列全体の変換、 `tokenizer` はトークンに区切る処理、 `filter` はそれぞれのトークンごとに変換を行います。

ここで注目したいのは `character filter` の結果が `tokenizer` に入るという点です。
_kuromoji_readingform token filter_ は `token_filter` で全段の `tokenizer` には _kuromoji_tokenizer_ しか使えませんが、これを `character filter` で使えれば自由にトークンを区切ることができます。

# 漢字→カナ変換プラグイン

ということで、同じことを考える人はいるはず！！と思って探してみたのですが見つからなかったので漢字→カナ変換プラグインを自作しました。

https://github.com/bgpat/elasticsearch-analysis-japanese

プラグイン開発には Elasticsearch 公式の [elasticsearch-analysis-kuromoji](https://github.com/elastic/elasticsearch-analysis-kuromoji) を参考にしました。

## インストール

Docker で Elasticsearch を用意してプラグインをインストールします。

```console
$ docker run --name=es -p9200:9200 -d elasticsearch:5.6.5
9b8458e2c86d1990aca9ef64a8111d0d169aac8524a88737c020db855f00c3e8
$ docker exec es bin/elasticsearch-plugin install https://github.com/bgpat/elasticsearch-analysis-japanese/releases/download/v0.0.1/elasticsearch-analysis-japanese-es5.6.5.zip
-> Downloading https://github.com/bgpat/elasticsearch-analysis-japanese/releases/download/v0.0.1/elasticsearch-analysis-japanese-es5.6.5.zip
[=================================================] 100%   
-> Installed elasticsearch-analysis-japanese
```

比較対象に _analysis-kuromoji_ もインストールしておきます。

```console
$ docker exec es bin/elasticsearch-plugin install analysis-kuromoji
-> Downloading analysis-kuromoji from elastic
[=================================================] 100%   
-> Installed analysis-kuromoji
```

プラグインが有効になるのは次回起動時なので再起動します。

```console
$ docker restart es
es
```

## 検証

[analyzer の動作を確認するための API](https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-analyze.html) があるのでこれを使います。
(`curl` コマンドだと JSON を書くのが大変なので [Insomnia](https://insomnia.rest/) で検証しています)

### デフォルト設定

何も指定しないときは [standard analyzer](https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-analyzers.html) になります。
[Standard Tokenizer](https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-standard-tokenizer.html) + [Lower Case Token Filter](https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-lowercase-tokenfilter.html) の組み合わせで英文をいい感じに正規化してくれます。
もちろん日本語には対応していないのでこうなります。

<img width="645" alt="image.png" src="https://qiita-image-store.s3.amazonaws.com/0/69029/5a168a38-396a-3eeb-70f1-f6b97cbe2f78.png">

### kuromoji analyzer

[kuromoji analyzer](https://www.elastic.co/guide/en/elasticsearch/plugins/current/analysis-kuromoji-analyzer.html) を使ってみます。
日本語の文章をいい感じに正規化してくれます。

<img width="644" alt="image.png" src="https://qiita-image-store.s3.amazonaws.com/0/69029/ad49af4d-e17b-46cb-1a50-e60299c31b0e.png">

検索対象ドキュメントとしてはまだいい(?)かもしれませんが、検索ワードでこれだと困ります。

### kuromoji_tokenizer + kuromoji_readingform token filter

今度は _kuromoji analyzer_ をバラして _kuromoji_readingform token filter_ でカナに変換します。

<img width="641" alt="image.png" src="https://qiita-image-store.s3.amazonaws.com/0/69029/7bc07593-d897-2500-49fe-88e00dc2404b.png">

当然、 _kuromoji analyzer_ のときとほとんど変わりません。

### whiltespace tokenizer + kuromoji_readingform token filter

欲しかった [whiltespace tokenizer](https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-whitespace-tokenizer.html) でのスペース区切りと _kuromoji_readingform token filter_ でのカナ変換です。

<img width="650" alt="image.png" src="https://qiita-image-store.s3.amazonaws.com/0/69029/25ac2d68-b55b-75dd-d64b-b5a7c19342f2.png">

空白区切りにはなっていますが、カナ変換が機能していません。

### whitespace tokenizer + katakana_transform

_whitespace tokenizer_ と自作プラグインの _katakana_transform_ です。

<img width="652" alt="image.png" src="https://qiita-image-store.s3.amazonaws.com/0/69029/c6c0ecb8-50c5-860d-c394-18ae396a9e59.png">

欲しかった機能が実現できました。
ついでに試してみます。

<img width="649" alt="image.png" src="https://qiita-image-store.s3.amazonaws.com/0/69029/9da2bc38-1798-41fb-8fe9-646a86414e2c.png">

これで「サイトウ」さんの漢字がうろ覚えでも検索できるようになりました。

# 実用例

今回開発したプラグインは http://syllabus.kstm.cloud/ で使っています。
(検索対象のドキュメントには[所属する大学のシラバス](https://campus-2.shinshu-u.ac.jp/syllabus/syllabus.dll/top)を用いました)

自作プラグインの他に、ローマ字入力の途中でもインクリメンタルサーチできるよう [icu_normalizer](https://www.elastic.co/guide/en/elasticsearch/plugins/current/analysis-icu-normalization-charfilter.html) でカナ→ローマ字変換を行っています。
また、検索速度を上げるために [edge-ngram tokenizer](https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-edgengram-tokenizer.html) で対象ドキュメントをインデクシングしています。(`prefix` は正規表現を使っているので遅いとか)
一応 `kuromoji analyzer` でもインデックスを作っているので、完全一致したときは必ず上位に出てきます。

コードは https://github.com/kstm-su/es-syllabus-web に置いてあります。

# まとめ

`tokenizer` を制限しない__カナ変換プラグイン__で自分好みの日本語検索をする仕組みを紹介しました。

カナ変換のアルゴリズム上 `ipadic-neologd` にある単語しかできないためまだ満足とはいえませんが、大体のケースには対応できるのでは？と思います。