【elasticsearch】Kuromojiとngramで検索精度を上げてみる

elasticsearchのKuromojiは十分強力だが、辞書が貧弱な部分も多々ある。
例えば、Kuromojiを使って「仮面ライダークウガ」で形態素解析すると、「仮面」「ライダークウガ」で分かれる。
一方、「仮面ライダー龍騎」で形態素解析すると、「仮面ライダー」「龍」「騎」で分かれる。

なので、「仮面」で検索したら「仮面ライダークウガ」のみがヒットし、「仮面ライダー」で検索したら「仮面ライダー龍騎」がヒットする。
もちろん、「ライダー」で検索しても何もヒットしない。

そこで、nGram Token Filterで単語を分割して認識させたら「仮面」で検索しても「ライダー」で検索しても両方ヒットするようになったので紹介する。

elasticsearch6.6.0
Analyzerでkuromojiを利用

Docker for Mac(2.0.0.3)でdocker-composeしてelasticsearchを動かしている。
docker-compose.ymlは省く。
Dockerfileは以下のように作成した。

```Dockerfile:Dockerfile
FROM elasticsearch:6.6.0

RUN elasticsearch-plugin install analysis-kuromoji
RUN elasticsearch-plugin install analysis-icu

COPY elasticsearch.yml /usr/share/elasticsearch/config/elasticsearch.yml
```

##まずはsettingsとmappingsが書かれたjsonを作ろう。
settingsは、アナライザーを定義するものである。
１つのトークナイザーと複数のフィルタで構成されているのがアナライザーだ。

mappingsは、index内の構成を定義するものである。
ここで `analyzer` にsettingsで書いたアナライザーを指定するのを忘れないように！（じゃないとアナライザーが動かない）
`search_analyzer` は、もしかしたらなくても大丈夫かもしれない。
なくても大丈夫だとわかった方はぜひコメントを。

`my_ngram` に分割する最小文字と最大文字を指定している。どのように判断して都度分割文字数を決めているのかわからないが、elasticsearch側でよしなにやってくれているよう。
`kuromoji_stemmer` は「ライダー」「ライダ」も同じものとして扱ってくれる優れモノ。

```json-doc:create_index.json
{
  "settings": {
    "analysis": {
      "filter": {
        "my_ngram": {
          "type": "ngram",
          "min_gram": 2,
          "max_gram": 3
        }
      },
      "analyzer": {
        "my_kuromoji_analyzer": {
          "type": "custom",
          "tokenizer": "kuromoji_tokenizer",
					"filter": [
            "kuromoji_stemmer",
            "my_ngram"
          ]
        }
      }
    }
  },
  "mappings": {
    "type": {
      "properties": {
        "name": {
          "type": "text",
          "analyzer": "my_kuromoji_analyzer",
    			"search_analyzer": "my_kuromoji_analyzer"
        }
      }
    }
  }
}

```

##さあ次はindexを作ろう。
先ほど作ったjsonを元にindexを作る。
dockerで作っている方はelasticsearchのコンテナを起動しよう。
elasticsearchが起動したら、jsonを作ったフォルダに移動して、ターミナルで以下を実行。

```shell

curl -H "Content-Type: application/json" -X PUT 'http://localhost:9200/index名?pretty' -d @create_index.json

# 作成されているか確認
curl -H "Content-Type: application/json" -XGET http://localhost:9200/index名/_settings?pretty
```

##次は、indexにデータを投入
今回のindexはフィールドが一つのシンプル設計なので、わかりやすいと思う。
そろそろindexとかtypeとか何のことかよくわからなくなってくる方もいると思うので、以下にDBと比較した表を用意した。

| RDB | Elasticsearch |
|:--|:--|
| データベース | インデックス |
| テーブル | マッピングタイプ |
| カラム（列） | フィールド |
| レコード（行） | 文書（ドキュメント） |

ちなみにこの表は以下のリンク先から拝借した。ありがとうございます。
https://qiita.com/rjkuro/items/95f71ad522226dc381c8


jsonファイルをまた作る。
これが投入するデータとなる。

```json-doc:data.json
{ "index" : {} }
{ "name" : "仮面ライダー龍騎" }
{ "index" : {} }
{ "name" : "仮面ライダークウガ" }
```


以下コマンドを実行してデータをindexに登録する。

```shell


curl -H "Content-Type: application/json" -X POST http://localhost:9200/index名/type/_bulk?pretty --data-binary @data.json

# 登録されているか確認
curl -H "Content-Type: application/json" -XGET 'localhost:9200/index名/_search?pretty'
```

##検索してみよう
最後は、登録したデータを検索する。以下コマンド。

```shell

#まずは「仮面」
curl -H "Content-Type: application/json" -XGET 'http://localhost:9200/index名/type/_search?pretty=true' -d '
{
  "query" : {
    "simple_query_string" : {
      "query": "仮面",
      "fields": ["name"]
    }
  }
}'

#次は「ライダー」
curl -H "Content-Type: application/json" -XGET 'http://localhost:9200/index名/type/_search?pretty=true' -d '
{
  "query" : {
    "simple_query_string" : {
      "query": "ライダー",
      "fields": ["name"]
    }
  }
}'
```

それぞれのコマンドで両方の仮面ライダーが出力されていたら成功。
本当は、「仮面」「ライダー」それぞれ決め打ちで認識させたかったので、
ユーザー辞書とかシノニム辞書とか使ってみたが、検索で引っかからなかった。
何か良い方法を知っている方がいたらぜひ教えてほしい。

最後まで読んでくれた君、東京ドームシティで僕と握手

##参考
[Elasticsearch の Kuromoji でユーザー辞書を使う](https://qiita.com/kijtra/items/d391179e1ea0be65e3f5)
[データベースとしてのElasticsearch](https://qiita.com/rjkuro/items/95f71ad522226dc381c8)