日本語の小文字（捨て仮名）を大文字（通常の文字）に変換するLucene/Elasticsearchのフィルター

# はじめに

Lucene 9.11およびElasticsearch 8.15に日本語の小文字（捨て仮名）を大文字（通常の文字）に変換するフィルターを実装しましたので使い方を紹介します。

以下公式のドキュメントです。

Lucene (Javadoc)
- [JapaneseHiraganaUppercaseFilter](https://lucene.apache.org/core/9_11_0/analysis/kuromoji/org/apache/lucene/analysis/ja/JapaneseHiraganaUppercaseFilter.html)
 - [JapaneseKatakanaUppercaseFilter](https://lucene.apache.org/core/9_11_0/analysis/kuromoji/org/apache/lucene/analysis/ja/JapaneseKatakanaUppercaseFilter.html)

Elasticsearch

- [hiragana_uppercase token filter](https://www.elastic.co/guide/en/elasticsearch/plugins/current/analysis-kuromoji-hiragana-uppercase.html)
- [katakana_uppercase token filter](https://www.elastic.co/guide/en/elasticsearch/plugins/current/analysis-kuromoji-katakana-uppercase.html)

# 概要

日本語のドキュメントを検索するアプリケーションを開発していると、とても古い文書を取り扱うことがあります。例えば：

- 特許
- 法律
- 契約書
- 文学

そしてある時期の日本語文書では、「ょ」「っ」などの小文字（捨て仮名）の表記が大文字（通常の文字）になっていることがあります。実際に私が見たことのある例で言うと、ある特許文書の中で「ストップウォッチ」が「ストツプウオツチ」と表記されていました。有名な例では企業名の「キヤノン」があげられるかもしれないですね。

当然このような表記のテキストをそのまま「ストップウォッチ」で検索してもヒットしません。したがってどちらでも検索にヒットさせるためには小文字を大文字にノーマライズする必要があります。簡単に思いつく方法としては、入力テキストを[Mapping character filter](https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-mapping-charfilter.html)で1文字1文字変換する方法がありますが、このフィルターはCharacter filterなのでトークナイズの前に実行されます。そうすると例えばKuromojiでトークナイズしようとした時、大文字に変換された後の単語が辞書に載っておらず、適切なトークナイズが行えないなどの問題が発生する可能性があります。従って、この変換は性質上トークンフィルターとして実装するべきものです。

# hiragana_uppercaseとkatakana_uppercase

というわけで、Luceneにhiragana_uppercaseおよびkatakana_uppercaseというトークンフィルターを実装したPRを送って、取り込んでもらいました。Lucene 9.11に取り込まれています。

https://github.com/apache/lucene/pull/12915

またこのフィルターはElasticsearchにも取り込まれており、バージョン8.15から利用できます。

https://github.com/elastic/elasticsearch/pull/106553

このフィルターはKuromojiの一部として実装されています。そのためElasticsearchで利用するためには[Japanese (kuromoji) analysis plugin](https://www.elastic.co/guide/en/elasticsearch/plugins/current/analysis-kuromoji.html)をインストールする必要があることに注意してください。

また私は利用したことがないのですが、SolrのSudachiプラグインにも取り込まれているようです。（ありがとうございます）

https://github.com/azagniotov/solr-lucene-analyzer-sudachi/pull/110

# Luceneでの使い方

Luceneのテストコードを参照してください。

https://github.com/apache/lucene/blob/main/lucene/analysis/kuromoji/src/test/org/apache/lucene/analysis/ja/TestJapaneseHiraganaUppercaseFilter.java

https://github.com/apache/lucene/blob/main/lucene/analysis/kuromoji/src/test/org/apache/lucene/analysis/ja/TestJapaneseKatakanaUppercaseFilter.java

Javadocは以下から参照できます。

 - https://lucene.apache.org/core/9_11_0/analysis/kuromoji/org/apache/lucene/analysis/ja/JapaneseHiraganaUppercaseFilter.html
 - https://lucene.apache.org/core/9_11_0/analysis/kuromoji/org/apache/lucene/analysis/ja/JapaneseKatakanaUppercaseFilter.html

# Elasticsearchでの使い方

Elasticsearchでの使い方は他のフィルターと同様簡単なので、いくつかのサンプルを貼っておきます。

ただし上でも書いている通り[Japanese (kuromoji) analysis plugin](https://www.elastic.co/guide/en/elasticsearch/plugins/current/analysis-kuromoji.html)に依存しているのでこちらのプラグインがインストールされていることが必要になります。

## Analyze

Analyze APIで動作を確認してみましょう。
```
GET /_analyze
{
  "tokenizer": "kuromoji_tokenizer",
  "filter" : ["hiragana_uppercase", "katakana_uppercase"],
  "text" : "ちょっとまって　ストップウォッチ"
}
```

結果は以下のようになります。

```json
{
  "tokens": [
    {
      "token": "ちよつと",
      "start_offset": 0,
      "end_offset": 4,
      "type": "word",
      "position": 0
    },
    {
      "token": "まつ",
      "start_offset": 4,
      "end_offset": 6,
      "type": "word",
      "position": 1
    },
    {
      "token": "て",
      "start_offset": 6,
      "end_offset": 7,
      "type": "word",
      "position": 2
    },
    {
      "token": "ストツプウオツチ",
      "start_offset": 8,
      "end_offset": 16,
      "type": "word",
      "position": 3
    }
  ]
}
```

正しく捨て仮名が通常の文字に変換されていることがわかります。

## インデックス定義例

実際にテキストをインデックスに書き込む際のインデックス定義は、特に細かい設定は必要なく（トークン）フィルターとして指定するだけです。

```
PUT test_sutegana
{
  "mappings": {
    "properties": {
      "message": {
        "type": "text",
        "analyzer": "japanese_analyzer"
      }
    }
  },
  "settings": {
    "index": {
      "analysis": {
        "analyzer": {
          "japanese_analyzer": {
            "filter": [
              "hiragana_uppercase",
              "katakana_uppercase"
            ],
            "tokenizer": "kuromoji_tokenizer"
          }
        }
      }
    }
  }
}
```

# アイヌ語のための表記

このフィルターを実装していて知ったのですが、日本語表記にはアイヌ語の仮名表記を実現するために、カタカナの捨て仮名に以下のような文字が定義されています。

```
ァ ィ ゥ ェ ォ ヵ ㇰ ヶ ㇱ ㇲ ッ ㇳ ㇴ ㇵ ㇶ ㇷ ㇷ゚ ㇸ ㇹ ㇺ ャ ュ ョ ㇻ ㇼ ㇽ ㇾ ㇿ ヮ
```

「ㇳ」以降の文字については知りませんでしたし、「ㇷ゚」なんて特に驚きです。しかもこの「ㇷ゚」については、内部的に2文字で表現されています。

https://ja.wikipedia.org/wiki/%E3%87%B7%E3%82%9A

例えば料理の名前に「チタタㇷ゚」というものがあるようですね。勉強になりました。

katakana_uppercaseではこれらの文字もサポートしています。



# まとめ

あまり利用する必要があるケースは多くないかもしれませんが、もしも日本語の大文字小文字を区別せずに検索したいという要件がある場合は、このフィルターが役に立ちますのでぜひ利用してください。