MroongaのNormalizer/Tokenizer/TokenFilter

# 前説

2020-02-16に[Aurora MySQLのtextをEC2 MariaDBレプリカでMroonga全文検索](https://qiita.com/masudakz/items/1afafcca1c01d8b9c276)を書きました。
そこのTodoのうち「NormalizerNFKC100のカスタマイズ」について書きます。

# tokenizer TokenMecab

GroongaドキュメントだとBigram推しです。

> 一般的に TokenBigram が適切なトークナイザーです。トークナイザーについてよく知らない場合は TokenBigram を使うことをオススメします。

ここでは形態素解析器MeCabで進めます。Groongaドキュメントにもある下記特性を重視してのことです。

> 再現率より適合率に優れています。 TokenBigram では `京都` というクエリーで `東京都` も `京都` も見つかりますが、この場合は `東京都` は期待した結果ではありません。 TokenMecab を使うと `京都` というクエリーで `京都` だけを見つけられます。

それと前記事に書いた[読みがな検索](https://qiita.com/masudakz/items/1afafcca1c01d8b9c276#%E8%AA%AD%E3%81%BF%E3%81%8C%E3%81%AA%E6%A4%9C%E7%B4%A2)を実現してくれるのも `TokenMecab` です。

tokenizerについては前記事の通りです。

```
tokenizer "TokenMecab(\'use_reading\',true)"
```

`TokenMecab` を使うことを前提として、normalizer/token_filterを調整していきます。

# NormalizerNFKC100

normalizer は MeCab の前にソース文字列を正規化処理します。
表記の揺れを無くせば `MeCab` の形態素解析処理がうまくいくところを有効 `true` にします。

- `"unify_prolonged_sound_mark", true`
  - 長音記号を正規化
- `"unify_middle_dot", true`
  - 中点を"·" (U+00B7 MIDDLE DOT)に正規化
- `"unify_katakana_v_sounds", true`
  - "ヴァヴィヴヴェヴォ"を"バビブベボ"に正規化

逆に正規化すると、 `MeCab` が形態素分割のヒントにしている情報を無くしてしまうのもあります。
ここでは下記をtrueにするべきではありません。

- `unify_hyphen_and_prolonged_sound_mark`
  - ハイフンと長音記号を"-" (U+002D HYPHEN-MINUS)に正規化
  - カタカナ長音をハイフンにするのは `MeCab` 辞書に登録している語の切り出しに悪影響があります
  - 電話番号/郵便番号専用の normalizerなのでしょう
- `unify_kana`
  - 全角ひらがな、全角カタカナ、半角カタカナの文字を同一視
  - カタカナとひらがなの切れ目がなくなると、辞書にない未知の外来語の切り出しなどに悪影響があります
  - ひらがなに正規化してしまうので、カタカナだけで辞書に登録している語の切り出しに悪影響があります
- `unify_kana_case`
  - 全角ひらがな、全角カタカナ、半角カタカナの小さな文字を大きな文字と同一視
  - 小文字ありで辞書に登録している語の切り出しに悪影響があります

# TokenFilterNFKC100

token_filters は `MeCab` が出力した各形態素に対して適用する変換です。後処理なので `MeCab` の辞書や分かち書き処理への悪影響リスクがありません。

- `"unify_kana", true`
  - 全角ひらがな、全角カタカナ、半角カタカナの文字を同一視
  - "あイｳｪおヽヾ" -> "あいうぇおゝゞ"
  - とうふ/トウフ、さんま/サンマなどを同一視できます
- `"unify_kana_case", true`
  - 全角ひらがな、全角カタカナ、半角カタカナの小さな文字を大きな文字と同一視
  - ぉおゃやゅゆょよゎわゕかゖけ -> おおややゆゆよよわわかかけけ
  - キャノン/キヤノンなどを同一視できます
- `"unify_hyphen", true`
  - ハイフンを"-" (U+002D HYPHEN-MINUS)に正規化

後処理でも日本語全文検索目的なら採用するべきでないオプションもあります。

- `unify_kana_voiced_sound_mark`
  - パパ＝ババ＝ハハ　と見なすことになり意味が違ってくる
- `unify_katakana_bu_sound`
  - ヴァ -> ブ は情報欠落の弊害が多い

# 実装コード

```sql
CREATE FULLTEXT INDEX content ON diaries(content) COMMENT
'tokenizer "TokenMecab(\'use_reading\',true)",
 normalizer "NormalizerNFKC100(
   \'unify_prolonged_sound_mark\', true,
   \'unify_middle_dot\', true,
   \'unify_katakana_v_sounds\', true)",
  token_filters "TokenFilterNFKC100(
   \'unify_kana\', true,
   \'unify_kana_case\', true,
   \'unify_hyphen\', true)"';
```

COMMENTの中なので、syntax error が出ません。
> 余分なカンマ１つ、空白１つの不足で死ぬんだ

過酷な世界ですが、 `schma` コマンドで確認可能です。
groongaスキーマ全体が出力されるので、必要なところを抜き出す必要はありますが。

```sql
> SELECT mroonga_command('schema --output_pretty yes')\G
```
```json
        "normalizer": {
          "id": 83,
          "name": "NormalizerNFKC100",
          "options": [
            "unify_prolonged_sound_mark",
            true,
            "unify_middle_dot",
            true,
            "unify_katakana_v_sounds",
            true
          ]
        },
        "token_filters": [
          {
            "id": 216,
            "name": "TokenFilterNFKC100",
            "options": [
              "unify_kana",
              true,
              "unify_kana_case",
              true,
              "unify_hyphen",
              true
            ]
          }
        ],
```

いい感じにできました。


# 実現できないこと

OCRや手書きの誤認識で発生する誤表記の救済はルールによる正規化処理では難しいです。

- カタカナの `リ` ひらがなの `り`
- カタカナの `ソ`　と `ン`