kuromoji_readingformは、OpenSearchの日本語解析プラグイン(analysis-kuromoji)に含まれるトークンフィルターです。形態素解析で切り出した語を、辞書に載っている読み仮名に置き換えます。「寿司」は「スシ」に、use_romajiを有効にすれば「sushi」になります。
読みで索引を作れば、ひらがなで入力された検索語を漢字の文書に当てられます。ただし、設定例をそのまま写すと、ひらがなの検索語が別の語に分割されてほとんど当たらないという問題が起きます。この記事では、フィルターの動作と設定例に加えて、その原因と対策をOpenSearch 3.9.0で実行した結果をもとに説明します。
まとめ:kuromoji_readingformの動作と設定の要点
- 動作:kuromoji_tokenizerが付けた読み(辞書の読み)でトークンを置き換える。既定はカタカナ、
use_romaji: trueで修正ヘボン式のローマ字。 - 導入:
analysis-kuromojiプラグインを全ノードに入れて再起動する。Amazon OpenSearch Serviceでは全ドメインに同梱済み。 - 設定の基本形:本文フィールドは通常の日本語解析のまま残し、読み用のサブフィールド(multi-field)を足す。本文を読みで置き換えると同音異義語の誤ヒットが増える。
- 最大の落とし穴:検索側も同じアナライザーにすると、「とうきょう」が「とう/きょう」に、「かがく」が「か/がく」に分割されて当たらない。検索側は
keywordトークナイザーとicu_transformでカタカナ化するか、OpenSearch 2.13以降のkuromoji_completionを使う。 - 読みが合わない語:「日本」は「ニッポン」、未知語の「小鳥遊」は「コトリ/遊」になる。読みを固定したい語は
user_dictionary_rulesで読みを与える。
kuromoji_readingformの動作|辞書の読みでトークンを置き換える仕組み
kuromoji_readingformは単独では動きません。前段のkuromoji_tokenizerが、語を切り出すときに辞書(IPADIC)の読みを属性として各トークンに付けます。kuromoji_readingformはその読み属性を取り出して、トークンの文字列と差し替えるだけです。_analyzeにexplain: trueを付けると、トークナイザー段階でreading属性が付いていることを確認できます。
POST /_analyze
{
"tokenizer": "kuromoji_tokenizer",
"filter": ["kuromoji_readingform"],
"text": "寿司",
"explain": true,
"attributes": ["reading", "partOfSpeech"]
}
応答では、トークナイザー段階のトークンが"token": "寿司"・"partOfSpeech": "名詞-一般"・"reading": "スシ"を持ち、フィルター通過後に"token": "スシ"へ変わります。読みの出どころは辞書なので、辞書に無い語の扱いや、読みが複数ある漢字の扱いは辞書の内容で決まります(後述)。
use_romajiによるカタカナ出力とローマ字出力の違い
パラメータはuse_romajiの1つだけです。OpenSearchの公式ドキュメントでは既定値がfalse(カタカナ)で、名前だけを書いて"filter": ["kuromoji_readingform"]とした場合もOpenSearch 3.9.0ではカタカナが出ました。同じ文を両方の設定で解析した結果は次のとおりです。
| 入力 | use_romaji: false | use_romaji: true |
|---|---|---|
| 東京は日本の首都です | トウキョウ/ハ/ニッポン/ノ/シュト/デス | tōkyō/ha/nippon/no/shuto/desu |
| 寿司 | スシ | sushi |
| 新聞 | シンブン | shimbun |
| コーヒー | コーヒー | kohi |
| OpenSearch | OpenSearch | OpenSearch |
英単語や数字は読みを持たないので、そのまま残ります。全角の「ABC」は辞書に読みがあり「エイビーシー」になりました。Elasticsearchから設定を移す場合は注意が必要です。Elasticの公式ドキュメントは、事前定義のkuromoji_readingformを名前だけで使うとuse_romajiがtrueになると説明しています。OpenSearchでの実測とは逆なので、どちらの製品でもuse_romajiは明示して書くのが安全です。
未知語とひらがなトークンの扱い
辞書に無い語(未知語)には読み属性がありません。Luceneの実装(JapaneseReadingFormFilter)では、読みが無いトークンは次のように扱われます。
- ひらがなを含む未知語:ひらがなをカタカナに機械的に置き換えて、それを読みとして扱う。
- それ以外の未知語(漢字・半角カナ・英字など):カタカナ出力では元の文字列のまま残る。ローマ字出力では元の文字列をそのままローマ字化しようとする。
このため「小鳥遊」は「コトリ」と「遊」に分かれ、辞書に読みが無い「遊」は漢字のまま索引に入りました。半角カナの「スシ」も、前処理をしないと「スシ」のまま残ります。
OpenSearchでの導入|analysis-kuromojiプラグインとAmazon OpenSearch Service
セルフマネージドのOpenSearchでは、kuromoji_readingformを含むanalysis-kuromojiプラグインを全ノードに入れてから再起動します。公式ドキュメントも、全ノードへのインストールとクラスタの再起動を手順としています。
bin/opensearch-plugin install analysis-kuromoji
bin/opensearch-plugin install analysis-icu
2行目のanalysis-icuは、後述するひらがな→カタカナ変換(icu_transform)と、半角カナの正規化(icu_normalizer)に使います。読み仮名検索を組むならセットで入れておきます。導入後はGET _cat/plugins?vで全ノードに載っているかを確認できます。
Amazon OpenSearch Serviceでは、AWSの開発者ガイドが「Japanese (kuromoji) Analysis」と「ICU Analysis」を全ドメインに同梱(Included on all domains)と記載しており、インストール作業は不要です。同じ表には、日本語向けの推奨としてSudachi Analysis(OpenSearch 1.3以降・任意のプラグインとしてドメインに関連付け)も載っていますが、kuromoji_readingformはSudachiプラグインには含まれません。Sudachiで読みを使う場合は、同等のsudachi_readingformフィルターを設定します。
読み仮名フィールドの設定例|multi-fieldで本文と読みを分ける構成
本文フィールドを丸ごと読みに置き換えると、「科学」と「化学」、「橋」と「箸」のように読みが同じ語が区別できなくなります。そこで、本文は通常の日本語解析で索引し、読みはサブフィールドに別に持たせます。次の設定は、OpenSearch 3.9.0にそのまま投入して動作を確かめたものです。
PUT /shops
{
"settings": {
"analysis": {
"char_filter": {
"nfkc": { "type": "icu_normalizer", "name": "nfkc", "mode": "compose" }
},
"filter": {
"ja_reading": { "type": "kuromoji_readingform", "use_romaji": false },
"hira2kata": { "type": "icu_transform", "id": "Hiragana-Katakana" }
},
"analyzer": {
"ja_text": {
"type": "custom",
"char_filter": ["nfkc"],
"tokenizer": "kuromoji_tokenizer",
"filter": ["kuromoji_baseform", "kuromoji_part_of_speech", "lowercase"]
},
"ja_reading_index": {
"type": "custom",
"char_filter": ["nfkc"],
"tokenizer": "kuromoji_tokenizer",
"filter": ["ja_reading"]
},
"ja_reading_query": {
"type": "custom",
"char_filter": ["nfkc"],
"tokenizer": "keyword",
"filter": ["hira2kata"]
}
}
}
},
"mappings": {
"properties": {
"name": {
"type": "text",
"analyzer": "ja_text",
"fields": {
"reading": {
"type": "text",
"analyzer": "ja_reading_index",
"search_analyzer": "ja_reading_query"
}
}
}
}
}
}
ポイントはname.readingの索引側と検索側でアナライザーを分けていることです。索引側は「東京駅の駅弁」を「トウキョウ/エキ/ノ/エキベン」に分解して読みで登録します。検索側は入力全体を1トークンのままカタカナにするので、「とうきょう」は「トウキョウ」になって索引の語と一致します。この分け方をする理由は次の章で説明します。
検索は本文と読みの両方に投げ、本文側に重みを付けます。
POST /shops/_search
{
"query": {
"multi_match": {
"query": "とうきょう",
"fields": ["name^2", "name.reading"]
}
}
}
「寿司の名店」「東京駅の駅弁」「日本橋の老舗」などを登録して試すと、「すし」「とうきょう」「にほんばし」はいずれも漢字表記の文書に当たりました。今回の検証データでは、「寿司の名店」のスコアは、漢字の「寿司」で検索したときが1.49、ひらがなの「すし」で検索したときが0.48でした。これらは異なるクエリの実測値であり、同一クエリ内での文書の順位を比較した結果ではありません。name^2は、表記まで一致したときのスコアを上げるための重み付けです。
読みのサブフィールドを後から足す場合、既存の文書には読みが入っていません。マッピングにサブフィールドを追加したうえで、_update_by_queryか再索引で既存文書を入れ直す必要があります。
ひらがなクエリが当たらない原因|検索側の形態素解析で語が崩れる現象
kuromoji_readingformの設定例の多くは、索引側と検索側に同じアナライザーを使います。漢字で入力された検索語ならこれで問題ありません。ところが、ひらがなだけの検索語をkuromoji_tokenizerにかけると、辞書が漢字表記を前提にしているため、語の切れ目がずれます。
実測:とうきょう・かがく・にほんばしの分割結果
kuromoji_tokenizerで分割し、kuromoji_part_of_speechで助詞などを除いてからkuromoji_readingformで読みに変換した検証用アナライザーの結果です。前章のja_textやja_reading_indexとはフィルター構成が異なります。
| 検索語 | トークナイザーの分割 | 残ったトークン | 漢字の文書 |
|---|---|---|---|
| すし | すし(名詞) | スシ | 寿司:一致 |
| とうきょう | とう(副詞)/きょう(名詞) | トウ/キョウ | 東京駅:不一致 |
| かがく | か(助詞)/がく(名詞) | ガク | 化学:不一致 |
| にほんばし | に(助詞)/ほん/ば(助詞)/し | ホン/シ | 日本橋:不一致 |
| とうきょうえき | とうき/ょうえき | トウキ/ョウエキ | 東京駅:不一致 |
今回の表では「すし」は一致しましたが、「とうきょう」「かがく」「にほんばし」などは分割や品詞除去によって一致しませんでした。ひらがな検索の成否は、語の長さだけでなく、索引側と検索側で生成されるトークンが一致するかで決まります。「ひらがなで漢字の文書を探せる」というkuromoji_readingformの紹介は、この分割の問題を考えに入れていません。
対策1:keywordとicu_transformによる検索語全体のカタカナ化
前章の設定例が採っている方法です。検索側では形態素解析をせず、入力全体をkeywordトークナイザーで1トークンにし、icu_transformのHiragana-Katakanaでカタカナに変えます。この構成で「とうきょう」「かがく」「にほんばし」「きょうと」はすべて対応する漢字の文書に当たりました。
弱点は、索引側の1語と検索語全体が一致したときしか当たらないことです。「とうきょうえき」は索引側が「トウキョウ」「エキ」の2語なので一致しません。店名・地名・人名・商品名のように短い語で引く用途に向いた方法です。
対策2:kuromoji_completionのqueryモードによるかなの結合
OpenSearch 2.13.0で、kuromoji_completionのアナライザーとフィルターが追加されました。索引側(mode: index)は元の語に加えてローマ字の読みを同じ位置に出し、検索側(mode: query)は連続するかなを1トークンにまとめてからローマ字化します。かなを結合してから読みにするので、対策1と同じく分割の問題を避けられます。
"filter": {
"comp_index": { "type": "kuromoji_completion", "mode": "index" },
"comp_query": { "type": "kuromoji_completion", "mode": "query" }
}
このフィルター定義だけでは前章のshopsの動作は変わりません。comp_indexを索引用アナライザーに、comp_queryを検索用アナライザーに入れ、それぞれをサブフィールドのanalyzerとsearch_analyzerに割り当てます。
"analyzer": {
"ja_comp_index": { "type": "custom", "char_filter": ["nfkc"], "tokenizer": "kuromoji_tokenizer", "filter": ["comp_index"] },
"ja_comp_query": { "type": "custom", "char_filter": ["nfkc"], "tokenizer": "kuromoji_tokenizer", "filter": ["comp_query"] }
}
"name": {
"type": "text",
"analyzer": "ja_text",
"fields": {
"suggest": { "type": "text", "analyzer": "ja_comp_index", "search_analyzer": "ja_comp_query" }
}
}
実測では、索引側で「東京」は「東京/toukyou」、「寿司」は「寿司/susi/sushi」になり、検索側で「とうきょう」は「とうきょう/toukyou」、「にほんばし」は「nihonbasi」「nihonbashi」など4通りのローマ字に展開されました。「すし」「とうきょう」「かがく」「にほんばし」に加えて、ローマ字の「toukyou」「kagaku」でも漢字の文書に当たります。queryモードは「サッk」のような入力途中のIMEの文字列もまとめて扱うため、検索窓のオートコンプリートにはkuromoji_readingformよりこちらが向いています。
読みが合わない語|辞書の読み・未知語・同音異義語への対処
読みは辞書が決めるので、利用者が想定する読みと一致しない語が必ず出ます。設定前に、自分のデータに多い語を_analyzeに通して確かめておきます。
辞書の読みが1つに固定される語(日本→ニッポン)
IPADICでは「日本」単独の読みが「ニッポン」です。このため「日本の祭り」は「にっぽん」では当たり、「にほん」では当たりませんでした。一方「日本語」は「ニホンゴ」、「日本橋」は「ニホンバシ」と、複合語として辞書に載っている語は別の読みを持ちます。単独の「端」も「ハジ」が付き、「はし」では当たりません。採用する読みを1つに固定する場合はユーザー辞書を使います。複数の読みのどちらでも検索させたい場合は、同義語(synonym)などで別の読みを追加します。
user_dictionary_rulesによる未知語・人名の読みの指定
辞書に無い固有名詞は、読みも切れ目も崩れます。実測では「鬼滅の刃」が「オニ/メツ/ノ/ハ」、「葬送のフリーレン」が「ソウソウ/ノ/フリー/レン」、「小鳥遊」が「コトリ/遊」になりました。kuromoji_tokenizerのuser_dictionary_rulesで、表記・分割・読み・品詞の4列を書くと読みを指定できます。
"tokenizer": {
"ja_tokenizer": {
"type": "kuromoji_tokenizer",
"user_dictionary_rules": [
"小鳥遊,小鳥遊,タカナシ,カスタム名詞",
"葬送のフリーレン,葬送の フリーレン,ソウソウノ フリーレン,カスタム名詞"
]
}
}
この設定で「小鳥遊」は「タカナシ」(ローマ字出力なら「takanashi」)、「葬送のフリーレン」は「ソウソウノ/フリーレン」になりました。分割列と読み列は空白区切りで、語の数をそろえます。辞書を変えても索引済みの文書の読みは書き換わらないため、追加後は再索引が必要です。辞書ファイルの書式と運用はElasticsearchの日本語全文検索の記事でユーザー辞書の章として扱っています(OpenSearchでも書式は同じです)。
同音異義語の誤ヒットとスコアの付け方
読みで当てる以上、「かがく」で「化学の基礎」と「科学の基礎」が同じスコアで並び、「はし」では「橋」が当たります(「割り箸」は連濁で「ワリバシ」となるため当たりません)。これは仕様であり、フィルターの設定では防げません。前述のmulti_matchで本文フィールドに重みを付けると、本文側の一致を優先しやすくなります。ただし、順位は語の出現頻度やフィールド長などにも左右され、表記が一致した文書の上位表示を保証するものではありません。ひらがな入力で同音語が混ざる点は、検索結果に表記を表示して利用者に選ばせる設計で受け止めるのが現実的です。
ローマ字出力の注意点|修正ヘボン式の長音記号と表記の不一致
LuceneのToStringUtilのソースコメントにあるとおり、use_romaji: trueの出力は修正ヘボン式(modified Hepburn)です。「東京」のように長音記号付きの母音が出る語がある一方、「大阪」は「oosaka」、「コーヒー」は「kohi」となります。利用者が入力するローマ字表記と一致するかは、語ごとに確認が必要です。
| 入力 | ローマ字出力 | 利用者の入力例 | 一致 |
|---|---|---|---|
| 東京 | tōkyō | tokyo/toukyou | どちらも不一致 |
| 京都 | kyōto | kyoto | 不一致 |
| 大阪 | oosaka | osaka | 不一致 |
| コーヒー | kohi | coffee/kohi | kohiのみ一致 |
| 新聞 | shimbun | shinbun | 不一致 |
カタカナの長音符「ー」は出力から落ち、「ン」は「b」の前で「m」になります。「tokyo」でも「toukyou」でも「東京」に当たらなかったため、利用者のローマ字入力を受ける用途にuse_romaji: trueは向きません。ローマ字で引かせたいなら、IMEの打鍵に近い「toukyou」「susi/sushi」を出すkuromoji_completionを選びます。
なお、icu_transformのAny-Latinを漢字に直接かけると、日本語の読みではなく中国語の拼音になります(「寿司」は「shòu sī」)。日本語の漢字を読みへ変換するには、kuromojiやSudachiなど、日本語の読み情報を持つ解析器を使います。
半角カナと全角英数の前処理|icu_normalizerをchar_filterに置く理由
kuromoji_tokenizerは半角カナを全角と同じ語として扱いません。「スシ」「コーヒー」をそのまま解析すると、読みが付かずに半角のまま索引に入ります。トークン化の前にicu_normalizer(NFKC)をchar_filterとして置くと、「スシ」「コーヒー」に直ってから解析され、全角の「ABC」は「ABC」になります。設定例で索引側・検索側の両方にnfkcを入れているのはこのためです。なお、掲載設定のNFKC自体は小文字化しません。本文用のja_textでは後段のlowercaseで「abc」になりますが、読み用の両アナライザーでは「ABC」のままです。
ビルトインのkuromojiアナライザーも先頭で幅の正規化(cjk_width)をしていますが、カスタムアナライザーを組むと自分で入れない限りこの処理は入りません。カスタムに切り替えた途端に半角カナの文書が引けなくなる、という事故はこの差から起きます。
kuromoji_readingformを使うべきでない場面と代わりの手段
kuromoji_readingformが向いているのは、短い語を読みで引く検索です。店名・地名・人名・商品名について、読みは分かるものの漢字表記が分からない利用者向けの検索がこれに当たります。次の用途には使わないでください。
- 本文フィールドの置き換え:記事や文書の本文を読みだけで索引すると、同音異義語がすべて同じ語になり、適合率が下がる。読みは必ずサブフィールドに分ける。
- オートコンプリート:単独ではIME入力途中の文字列の結合や複数のローマ字表記への展開を行わない。OpenSearch 2.13以降なら
kuromoji_completionを候補にする。 - 表記ゆれ全般の吸収:「サーバー/サーバ」のような長音のゆれは
kuromoji_stemmer、同じ意味の別語はsynonym、辞書に無い語の部分一致はN-gramの役割で、読みへの変換では解決しない。
形態素解析とN-gramの使い分けやsynonymの置き場所など、日本語全文検索の設計全体はElasticsearchの日本語全文検索|kuromojiのanalyzer構成と辞書追加、N-gram併用の判断にまとめています。アナライザーの設定はElasticsearchとOpenSearchでほぼ共通なので、そのまま参考になります。両製品の差分はElasticsearchとOpenSearchの違いで確認できます。
よくある質問
kuromoji_readingformとは何ですか?
OpenSearchやElasticsearchの日本語解析プラグイン(kuromoji)に含まれるトークンフィルターです。kuromoji_tokenizerが辞書から付けた読みで各トークンを置き換え、カタカナかローマ字で出力します。
use_romajiの既定値はtrueとfalseのどちらですか?
OpenSearchの公式ドキュメントではfalse(カタカナ)で、OpenSearch 3.9.0ではフィルター名だけを書いた場合もカタカナが出ました。Elasticの公式ドキュメントは、事前定義のフィルターを名前だけで使うとtrueになると説明しています。どちらの製品でも明示して書くのが確実です。
ひらがなで検索して漢字の文書をヒットさせるにはどうすればよいですか?
索引側はkuromoji_tokenizerとkuromoji_readingformで読みを登録し、検索側は形態素解析をせずにkeywordトークナイザーとicu_transform(Hiragana-Katakana)でカタカナに変えます。検索側もkuromojiで分割すると「とうきょう」が「とう/きょう」に分かれて当たりません。OpenSearch 2.13以降ならkuromoji_completionのqueryモードも使えます。
Amazon OpenSearch Serviceでkuromoji_readingformは使えますか?
使えます。AWSの開発者ガイドでは、Japanese (kuromoji) AnalysisとICU Analysisが全ドメインに同梱されていると記載されており、プラグインのインストールは不要です。
kuromoji_readingformとicu_transformの違いは何ですか?
kuromoji_readingformは辞書を使って漢字を読みに変換します。icu_transformは文字種の機械的な変換で、ひらがなとカタカナの相互変換はできますが、漢字の読みは分かりません。漢字にAny-Latinをかけると中国語の拼音(「寿司」→「shòu sī」)になります。読み仮名検索では、索引側にkuromoji_readingform、検索側にicu_transformという組み合わせで使います。