LLMによる音韻検索の性能評価


# 概要
大規模言語モデル（LLM）の音韻理解能力を測るために、LLMを音韻検索データセットに適用したときの精度を求めてみました。
以下の3つのLLMモデルを用いて評価を行いました：

- GPT-4o
- GPT-4o-mini
- Gemini 2.0 Flash
- GPT-4.5

評価の結果、LLMによる音韻検索精度は、比較的単純なルールベースの手法より低かったです。
このことから、汎用的なLLMが日本語の音韻を理解する能力には改善の余地がまだまだありそうだと思われます。

# 背景
gpt-4.5より前のLLMは日本語のダジャレやラップなどの生成があまり得意ではありません。

https://x.com/shimajiroxyz/status/1899042190736957937

この原因として、LLMは日本語の音韻の概念をうまく学習できていない可能性があります。
そこで、LLMに音韻が類似した単語を検索するタスクをさせることで、LLMの音韻理解能力を測ってみようと思いました。

# 方法
## プロンプト
LLMに音韻的な類似性を判断させるため、以下のプロンプトを使用しました。

```
You are a phonetic search assistant.
You are given a query and a list of words.
You need to rerank the words based on phonetic similarity to the query.
When estimating phonetic similarity, please consider the following:
1. Prioritize matching vowels
2. Substitution, insertion, or deletion of nasal sounds, geminate consonants, and long vowels is acceptable
3. For other cases, words with similar mora counts are preferred
You need to return only the reranked list of index numbers of the words, no other text.
You need to return only topn index numbers.

Example:
Query: タロウ
Wordlist: 
0. アオ
1. アオウヅ
2. アノウ
3. タキョウ
4. タド
5. タノ
6. タロウ
7. タンノ
Top N: 5
Reranked: 6, 4, 5, 7, 2
```

このプロンプトは以下の情報を含みます。

1. **音韻的類似性の基準**：
   - 母音の一致を優先
   - 撥音、促音、長音の変化は許容
   - モーラ数の類似性も考慮

2. **出力フォーマットの指定**：
   - インデックス番号のみを返すように指定
   - 具体例を示して期待する出力形式を明確化


## LLMによる音韻検索の手順
すべての候補語をLLMに渡すことはコスト的に厳しいので、編集距離ベースの手法で候補を絞ってからLLMにリランクさせることにしました。
1. 編集距離ベース（母音と子音の編集距離の和: Vowel Consonant Editdistance）でtop100を取得（recall@100=0.861）
2. 取得した候補をLLMに送信し、LLMの判断結果を取得
4. top10の結果を評価

## データセット
[soramimi-phonetic-search-dataset](https://github.com/jiroshimaya/soramimi-phonetic-search-dataset) v0.0を使用しました。
このデータセットは「〇〇で歌ってみた」替え歌から取得された音韻ペアと候補語に基づきます。元歌詞のフレーズをクエリとして、替え歌単語を検索するタスクとなっています。

## 比較条件
### 評価指標
評価指標としてrecall@10（上位10件の結果に正解が含まれている割合）を使用します。

### 比較モデル
以下の4つのLLMモデルを使用して評価を行いました：
- GPT-4o
- GPT-4o-mini
- Gemini 2.0 Flash
- GPT-4.5-preview

ただしGPT-4.5-previewは高価なため後述の「ソートなし」の条件のみ評価しています。

### 候補語の提示方法
LLMに候補語を渡す際の順序として、以下の2つの条件を比較しました：

1. **スコア順**：
   - Vowel Consonant Editdistanceのスコア順のまま
   - 意味検索のリランクタスクに近い条件

2. **あいうえお順**：
   - 候補語を五十音順にソート
   - LLMの純粋な音韻検索能力を測定するための条件

# コード
リランクに使用したコード例を示します。
まずVowel Consonant Editdistanceを計算する関数を作ります。

```Python
import editdistance as ed
import jamorasep

def rank_by_vowel_consonant_editdistance(
    query_texts: list[str], wordlist_texts: list[str], vowel_ratio: float = 0.5
) -> list[list[str]]:
    """
    母音と子音の編集距離に基づくランキング関数

    Args:
        query_texts: クエリのリスト
        wordlist_texts: 単語リスト
        vowel_ratio: 母音の重み（0.0-1.0）

    Returns:
        list[list[str]]: 各クエリに対する単語のランキング結果
    """
    query_moras = [
        jamorasep.parse(text, output_format="simple-ipa") for text in query_texts
    ]
    query_vowels = [[m[-1] for m in mora] for mora in query_moras]
    query_consonants = [
        [m[:-1] if m[:-1] else "sp" for m in mora] for mora in query_moras
    ]
    wordlist_moras = [
        jamorasep.parse(text, output_format="simple-ipa") for text in wordlist_texts
    ]
    wordlist_vowels = [[m[-1] for m in mora] for mora in wordlist_moras]
    wordlist_consonants = [
        [m[:-1] if m[:-1] else "sp" for m in mora] for mora in wordlist_moras
    ]

    final_results = []
    for query_vowel, query_consonant in zip(query_vowels, query_consonants):
        scores = []
        for wordlist_vowel, wordlist_consonant in zip(
            wordlist_vowels, wordlist_consonants
        ):
            vowel_distance = ed.eval(query_vowel, wordlist_vowel)
            consonant_distance = ed.eval(query_consonant, wordlist_consonant)
            distance = vowel_distance * vowel_ratio + consonant_distance * (
                1 - vowel_ratio
            )
            scores.append(distance)

        ranked_wordlist = [
            word for word, _ in sorted(zip(wordlist_texts, scores), key=lambda x: x[1])
        ]
        final_results.append(ranked_wordlist)
    return final_results
```

そして上記関数をcreate_reranking_function関数に渡し、編集距離ベースで候補を絞ったあとにLLMでリランクする処理とします。
下記コードは、LLMにわたすときに、候補をスコア順にするコードです。あいうえお順にするときは、該当箇所のコメントアウトを外します。

```Python
from litellm import batch_completion
def create_reranking_function(
    base_rank_func: Callable[[list[str], list[str]], list[list[str]]],
    rerank_input_size: int,
    rerank_model_name: str,
    rerank_batch_size: int,
    rerank_interval: int,
    topn: int,
    **base_rank_kwargs,
) -> Callable[[list[str], list[str]], list[list[str]]]:
    """
    ベースのランキング関数とLLMによるリランクを組み合わせた関数を作成する

    Args:
        base_rank_func: ベースのランキング関数
        rerank_input_size: リランクに使用する候補数
        rerank_model_name: リランクに使用するモデル名
        rerank_batch_size: リランクのバッチサイズ
        rerank_interval: リランクのインターバル
        topn: 最終的な出力数
        **base_rank_kwargs: ベースのランキング関数に渡す追加の引数

    Returns:
        組み合わせたランキング関数
    """

    def combined_rank_func(
        query_texts: list[str], wordlist_texts: list[str]
    ) -> list[list[str]]:
        # ベースのランキングを実行
        base_ranked_wordlists = base_rank_func(
            query_texts, wordlist_texts, **base_rank_kwargs
        )

        # 上位N件を取得してリランク
        topk_ranked_wordlists = [
            wordlist[:rerank_input_size] for wordlist in base_ranked_wordlists
        ]
        # あいうえお順に並べ替え
        # topk_ranked_wordlists = [
        #    sorted(wordlist, key=lambda x: x[0]) for wordlist in topk_ranked_wordlists
        # ]
        reranked_wordlists = rerank_by_llm(
            query_texts,
            topk_ranked_wordlists,
            topn=topn,
            model_name=rerank_model_name,
            batch_size=rerank_batch_size,
            rerank_interval=rerank_interval,
        )
        return reranked_wordlists

    return combined_rank_func
```


# 結果
## 各手法の性能比較
各条件のrecall@10は以下の通りでした：

| モデル | スコア順 | あいうえお順 |
|--------|----------|--------------|
| Vowel Consonant EditDistance | 0.676 | - |
| GPT-4o | 0.595 | 0.518 |
| GPT-4o-mini | 0.642 | 0.491 |
| Gemini 2.0 Flash | 0.565 | 0.507 |
| GPT-4.5-preview | 0.614 | - |

参考として、Vowel Consonant Editdistanceのスコア順をそのまま用いた場合のrecall@10は0.676（氷に記載の通り）、recall@100は0.861でした。

すべてのLLMモデルがベースラインを下回る結果となりました。
また「スコア順」のほうが「あいうえお順」よりも精度の低下が抑えられていました。
モデルごとの差は「スコア順」と「あいうえお順」で傾向が異なりました。
「スコア順」ではgpt-4o-miniがベースラインからの低下が少なかったです。gpt-4.5が次ぐスコアでした。
「あいうえお順」では逆にgpt-4o-miniが最もベースラインからの低下が大きかったです。ただ全体的に似たりよったりの精度でした。

なお上記は1回のみの施行の結果です。temperatureは0.0に設定していますが、評価のたびに値が変わる可能性があります。
# 考察
## LLMの性能
LLMを適用したことで、ベースラインよりも精度が低下したため、少なくとも今回使用したデータセットにおける音韻検索の能力が既存のLLMは高くないようです。
ただ、gpt-4.5については、WebUIで確認された韻を踏む能力の高さを考えると、ベースラインを下回ることもgpt-4o-miniより低スコアであることも多少違和感があります。ひょっとするとgpt-4.5が考える音韻類似度がデータセットが想定するものと異なっているだけかもしれません。現状のプロンプトでも最低限必要な情報は含めているつもりですが、より詳細な指示によって改善の余地はあるかもしれません。

## 入力順序の影響
スコア順の方がベースラインの低下が抑えられています。これはLLMに与えられた候補の順序を維持するようなバイアスがあることを示唆します。
「スコア順」のgpt-4o-miniがgpt-4oなどよりも高精度であったことは直感に反しますが、「あいうえお順」では逆にgpt-4o-miniが一番低かったことを踏まえると、単にgpt-4o-miniは元の順序をなるべく変えないような挙動をしただけかもしれません。

gpt-4.5の「あいうえお順」を試したら、「あいうえお順」の中ではgpt-4.5が最も高精度になる可能性はありそうです。
（一回の評価で10ドル近くかかるため、勇気がいります）

2025/03/12追記: gpt-4.5-previewの（ほぼ）「あいうえお順」を実施したところ精度は0.58くらいで、期待通り、LLMの中では高かったです。
## 生データの目視分析
gpt-4.5がうまく音韻検索できなかった事例を眺めてみます。

### アッテ-マルテ

```
クエリ：アッテ
GPT-4.5の出力：カッタ、ハッタ、ホッタ、アツタ、ニッタ、ラッツ、イッキ、ウィット、ノット、マック
正解：マルテ
```


このケースは正解データ自体の妥当性を考える必要があります。「マルテ」が正解となっているのは、歌唱において「ウ段」の音が促音（ッ）と互換性を持つケースがあるためと推測されます。ただし、これは元の歌唱データの影響が大きく、文字列のみからの類似性判断は困難かもしれません。


### アレホド-カネモト

```
クエリ：アレホド
GPT-4.5の出力：アラウホ、アレン、マルセド、アラオカ、アラハリ、アカホリ、アカホシ、アオト、アイモト、アキモト
正解：カネモト
```


プロンプトでは母音の一致を重視するよう指示しており、「カネモト」が上位に来るべきでした。しかしGPT-4.5は語頭の「ア」の一致と2文字目の子音の一致を重視し、「アラウホ」「アレン」などを上位に選んでいます。これは一つの基準とはいえますが、プロンプトの指示に反する結果となっています。


### アト-カトウ

```
クエリ：アト
GPT-4.5の出力：エト、オト、ウト、サト、セト、ソト、ムト、アノ、アオト、アキト
正解：カトウ
```


GPT-4.5は母音よりも語頭の子音の一致を重視してしまっています。第4位の「サト」は妥当な選択といえます。
正解の「カトウ」は実際の発音が「カトー」であり、音韻的な類似性が高いはずですが、GPT-4.5が「ou」を長音として認識できていない可能性があります。


### イキテ-イシゲ

```
クエリ：イキテ
GPT-4.5の出力：イシタ、イチタ、イダテ、イクタ、シキタ、ウキタ、アキタ、アキト、イケベ、イリエ
正解：イシゲ
```

人間の感覚では、母音と子音を総合的に考えれば「イシゲ」は明確な正解候補となるはずですが、GPT-4.5は「イシタ」などを優先しています。これも子音の一致を過度に重視した結果と考えられます。

### gpt-4.5の傾向について
目視での分析から、GPT-4.5には以下の特徴が見られました：

1. **子音重視の傾向**：
   - 母音よりも子音の一致を優先する傾向が強い
   - この傾向は人間の直感的な判断とはズレがある

2. **考えられる要因**：
   - GPT-4.5の主要な学習言語が英語であることによる可能性
   - 日本語は音韻の基本単位がカナで、母音の出現頻度が高い
   - 一方、英語は母音の出現頻度が相対的に低く、子音の影響が大きい
   - 英語の音韻類似度指標には子音のみを使用するもの（Soundexなど）も存在


## その他
リランク時にインデックス番号を返すようにLLMに指示していますが、最初は単語文字列を返すように指示する方法も試していました。ただgpt-4o-miniが候補にない単語を返すハルシネーションが多かったので、インデックス番号を返す方法を最終的に採用しました。データが残っていないのですが、gpt-4oやgeminiでは、単語文字列を返すように指示するほうが多少精度が高かったです。LLMの動作原理を考えると、インデックス番号の確率的なズレのほうが、単語文字列の確率的なズレよりも起こりやすいはずなので、一般的な傾向としてそうなるのかなと思います。

## まとめ
本研究では、LLMの日本語音韻理解能力を評価するため、音韻検索タスクにおける性能を測定しました。主な知見は以下の通りです：

- 既存のLLMは、単純な編集距離ベースの手法と比較して低い性能を示しました。この結果は、現状のLLMの日本語音韻理解能力にまだ改善の余地があることを示唆しています。ただしプロンプトも十分によく検討されたとはいえないため、モデルだけの問題ではない可能性もあります。
- GPT-4o-miniが最も高い性能を示しましたが、これは入力順序の維持バイアスの影響である可能性があります

本記事がなにかのお役に立ちましたら幸いです。