TL;DR
- 読み上げ動画の原稿と、音声合成エンジンが実際に読んだカナを突き合わせて、文脈依存の誤読をLLMに検出させる工程を作りました
- スライド3枚では動きました。17枚で1回破れて、対策を入れました。79枚で決定的に破れました
- 79枚の実測: 候補46件 → 敵対的検証(LLMにもう一度厳しく判定させる)を通過23件 → そのうち18件が誤修正。適合率 5/23
- つまり機械を信じて自動適用すると、正しい読みを18箇所壊します
- 破れ方は4種類あり、どれもLLMによる再判定では止まりませんでした。止めたのは全部、決定論のガードです
- ガードを足して同じ79枚で再実測: 候補27件 → 通過5件、適合率 5/5
- 一番効いたガードは精度改善ではなく「人が決めたものはLLMに聞かない」という構造でした
観測期間: 2026-07 〜 2026-08
はじめに
やろうとしていたのは、地味な作業の自動化です。
スライドと読み上げ原稿から解説動画を自動生成しています。音声合成エンジンは日本語をそれなりに読みますが、固有名詞や熟語を間違えます。だから公開前に読みを点検する工程が必要です。
点検は2段構えにできます。
- 語単位の辞書: 「この語はこう読む」を登録する。固有名詞や熟語の訓読み誤りはこれで潰せます
- 文脈依存の誤読: 辞書では潰せません
後者の代表が 方 です。「〜した方(かた)へ」と「方向のほう」で読みが変わります。辞書に 方→かた と登録すると「ほう」の用法まで壊れます。同じ問題が 行った(おこなった/いった)にもあります。
そこで、LLMに判定させることにしました。
各文について、
原稿(漢字) + 合成エンジンの実読み(カナ)
の対をLLMに渡し、文脈から誤読を指摘させる。
これはいわゆる LLM-as-judge です。人間が全文を耳で確認するのは現実的ではないので、機械に判定させる。設計としては素直だと思います。
そして、規模を上げたら壊れました。この記事はその実測記録です。
1. 3枚では動いた
最初の実装は動きました。実例として、スライド2の「SNSで不安を目にした方への」が ホオ(ほう) と読まれていたのを検出し、かた に修正しました。まさに狙っていたケースです。
この段階で、いくつか落とし穴を踏んで対策しています。これは後の話の前提になるので書いておきます。
落とし穴1: カナの長音は母音で綴られる
使っている音声合成エンジンの読み表記は、長音を母音で綴ります。
情報 → ジョオホオ
統計 → トオケエ
提供 → テエキョオ
これを知らせずにLLMへ渡すと、正しい長音を軒並み「誤読」と誤検出します。初回は6件の指摘のうち5件が長音の空振りでした。
対策は、システムプロンプトで長音カナの規則を明示して「母音綴りは誤りではない」と釘を刺すことです。
落とし穴2: 「修正後の文」をLLMに書かせると幻覚する
最初は「この文を正しい読みに直した文を返して」という指示にしていました。原文に無い語を捏造してきました。
対策は、LLMに返させるものを絞ることです。
LLM が返すのは:
surface = 原文中の語
correct = 正しいカナ
修正文は コード側で決定論的に置換する:
sentence.replace(surface, ひらがな化(correct))
surface が原文に無ければ、幻覚として捨てる。
生成をLLMに任せず、判定だけ任せるという切り分けです。これは後で効いてきます。
落とし穴3: 長音の空振りが残る
規則を明示しても残りました。「個人情報監査→コジンジョオホオ… の ジョオホオ を ジョウホウ に直せ」と言ってくる。発音は同じです。
ここで入れた対策が、後の話の主役になります。
round-trip 検証:
修正文を実際に音声合成エンジンへ通し、
読み(カナ)が本当に変わるときだけ採用する。
長音の綴り違いはエンジンが同一発音に畳むので、この1手で全部落ちます。
LLMの判定を、LLMではないもので検証するという発想です。これは正しい方向でした。ただし、後で見るように不十分でした。
落とし穴4: 方 は素の指示では取りこぼす
方 を名指しでプロンプトの最重要チェック項目にしました。LLMは ホオ を「正しい長音」と解釈してしまい、意味の判定(人を指すなら「かた」)に踏み込めなかったからです。
2. 17枚で破れた
3枚で動いたので17枚に広げました。破れました。
破れ方が重要です。round-trip検証は「読みが変わったか」しか見ておらず、「正しく変わったか」を見ていませんでした。
ゴミ置換が通過しました。
本当 → ホンマト
担い手 → ナナイテ
横断 → ヨコダン
「のではなく」(助詞)→ ノデハ
個人情報保護 → モーラが欠落
どれも読みは変わります。だからround-tripを通過します。そして全部改悪です。
3枚では出ませんでした。17枚で噴出しました。
対策(a): 長音正規化したround-trip
「え列+イ/エ」「お列+ウ/オ」を長音記号に畳んでから比較するようにしました。
漢字の 比例=ヒレエ と、かなの ひれい=ヒレイ は、綴りが違うだけで実質同じ(というか「えー」を「えい」にする改悪)です。素のround-tripはこれを見逃しますが、正規化して比較すると落ちます。
対策(b): 敵対的検証パス
検出した各案を、別のプロンプトでもう一度厳しく判定させる工程を足しました。
「元の読みは本当に誤りか」
「correct は本当に正しいか」
「壊れた読み・助詞は→わ・既に正しい語なら reject」
「迷えば reject」
この2手で、候補27件 → 正規化で14件 → 敵対的検証で4件に収束しました。残ったのは 方×3 と 行った=おこなった だけです。
この時点で私は、問題は解決したと思っていました。
3. 79枚で決定的に破れた
別の案件で79枚の規模になりました。ここで壊れ方が変わりました。
実測です。
| 段 | 件数 |
|---|---|
| LLMが出した候補 | 46 |
| 敵対的検証を通過 | 23 |
| そのうち誤修正 | 18 |
| 正しかったもの | 5 |
適合率 5/23 ≒ 22%。
つまり、--apply で自動適用すると、正しい読みを18箇所壊します。79枚の動画で18箇所の改悪は、致命的ではないにせよ、明らかに品質を下げます。しかも人手で全部確認するなら、そもそも自動化した意味がありません。
そして重要なのはここです。
破れ方は4種類あり、どれも LLM の再判定では止まらなかった。
敵対的検証は入っていました。「迷えば reject」と指示していました。それでも23件通過して18件が誤りでした。
敵対的検証は「明らかな非語」しか弾きません。 それらしく見える誤りは通します。
以下、4種類を順に書きます。
(a) 人手で確定済みの辞書語を上書きしてくる
一番怖かったのがこれです。
実在の人物の名前の読みを、LLMが5つの文で誤って提案してきました。
その人の名前の正しい読みは、人手の正解読み表にも、音声合成の辞書にも、そう書いてあります。人が調べて確定したものです。それを機械が「違う読みでは」と言ってくる。しかも複数の文で繰り返すので、素朴に「複数箇所で指摘されている=確度が高い」と読むと、正しい読みを壊します。
対策は、精度の改善ではありませんでした。
辞書(words / replacements / ポーズ指定)に重なる候補は、
LLM に聞く前に落とす。
判定の対象から外す、という構造的な対策です。この考え方をコードのコメントにこう書きました。
辞書は人が決めた結論であって、多数決の材料ではない。
これが、この記事で一番書きたいことです。
LLM-as-judge を設計するとき、私たちは「判定の精度をどう上げるか」を考えます。しかしその前に決めるべきことがあります。そもそも何を判定の対象にするか です。人が調べて確定したものを判定の対象に入れると、機械はそれを覆そうとします。そして機械の判定は確率的なので、覆される確率はゼロにはなりません。
ひとつ注意点があります。「辞書語と重なるなら落とす」を素朴に実装すると、1文字の語で誤って落ちます。例 という候補が 統計作成等特例 という辞書語に含まれる、という理由で潰れてしまう。なので1文字の surface はこの規則の対象外にしました。ガードにもガードが要ります。
(b) surface に文節や文の断片を返してくる
LLMには「原文中の語」を返させる設計でした。文節や文の断片を返してきます。
そして置換すると、文の中身が消えます。
「平均に到達することは、必要です」
→ surface として文の断片を返す
→ 置換後: 「へいきんにとおたつ」
("することは、必要です" が消滅)
もっとひどい例では、20モーラの文が244モーラに膨張しました。1文で約4分の異音が生成されます。
対策は2つの決定論ガードです。
1. 句読点を含む surface、8文字を超える surface を落とす
2. 置換の前後で総モーラ数が大きく動いたら落とす
2番目が効きます。置換で文の長さが激変するのは、ほぼ確実に事故です。LLMの出力の意味を検証するのではなく、出力を適用した結果の形を検証しています。
(c) wrong が surface 全体の読みになっていない
LLMには「元の読み(wrong)」と「正しい読み(correct)」の両方を返させていました。wrong が surface の一部の読みしか指していないケースがありました。
surface = 「他大学」
wrong = 「タ」 ← "他" だけの読み
correct = 「ホカ」
置換の結果: 「他大学より」 → 「ほかより」
("大学" が消える)
対策はこれです。
surface を実際に音声合成へ通した読みと、LLM が申告した wrong の
モーラ差が2を超えたら落とす。
やっているのは、LLMの前提そのものを検証することです。「あなたは何を見て誤読だと言っているのか」を確認しています。前提がずれていれば、結論が正しいわけがありません。
これは LLM-as-judge の設計として一般化できると思っています。判定結果だけを検証するのでは足りません。判定の入力として何を認識したかを申告させて、それを機械的に検証すると、前提のずれを捕まえられます。
(d) 自分の正規化コードにバグがあった
4つ目はLLMのせいではありません。
長音正規化(対策(a))が「ケエエエ ≡ ケイエイ」を同一と見なせていませんでした。原因は、直前の生の文字を見て判定していたことです。ケイエイ の2文字目は イ で、これはエ段の文字ではないので、続く エ が畳まれずに残り、不一致になっていました。
修正は「直前に確定した母音を持ち越す」形にすることでした。これだけで 経営 の空振り2件が自動で落ちました。
書いておきたいのは、このバグが17枚では見えなかったことです。79枚に広げて初めて、該当語が出てきました。
4. プロンプト側にも足した
決定論ガードだけでなく、プロンプトにも足しました。LLM側の知識の穴だったものです。
-
連用形+
方=かた(言い方・書き方・動き方)。この語義が抜けていて、3件を「ほう」に壊していました -
助数詞の慣用(
4日間=よっかかん)。「よんにちかん」に作り変えていました -
語形を変える案は reject(
作れれば→ツクラレバのような活用の改変)
これらは「プロンプトを直せば直る」種類の問題です。決定論ガードで落とせるものとは性質が違います。
区別しておくと設計しやすいと思います。
| 種類 | 対処 |
|---|---|
| LLMの知識・語義の穴 | プロンプトに書く。直る |
| LLMの出力形式の逸脱 | 決定論ガードで落とす。プロンプトでは直らない |
| 人が確定した結論への干渉 | 判定の対象から外す。ガードでもプロンプトでもない |
3番目が構造の問題で、一番重要だと思っています。
5. 再実測
同じ79枚で、ガードを足して測り直しました。
| 対策前 | 対策後 | |
|---|---|---|
| LLMが出した候補 | 46 | 27 |
| 検証を通過 | 23 | 5 |
| うち誤修正 | 18 | 0 |
| 適合率 | 5/23 ≒ 22% | 5/5 = 100% |
通過した5件の内訳は、人名の読み1件、行っています=おこなって、方=かた、語=ご、もう1件の固有名詞です。最後の人名だけが判断を要するもので、残りは明確に正しい。
再現率については正直に書きます。対策後に見逃しが増えたかどうかは測っていません。 79枚全文を人が耳で確認していないので、真の誤読の総数が分かりません。分かるのは「出した提案のうち何割が正しかったか」だけです。
ただ、この工程の設計としては適合率を優先するのが正しいと考えています。理由は非対称だからです。
見逃し → 元の読みのまま。品質は上がらないが、下がらない
誤修正 → 正しい読みが壊れる。品質が下がる
自動適用する工程では、改悪しないことが改善することより優先します。見逃しは後で耳で拾えます。
6. 「指摘0件」を成功と読んではいけない
もうひとつ、同じ規模で踏んだ別の問題を書きます。これは判定の精度とは無関係です。
以前の実装は、各スライドのLLM呼び出しが失敗しても、そのスライドを飛ばして続行するだけでした。
AWSのセッション期限が切れて79枚すべての呼び出しが失敗したとき、レポートは「指摘0件」でした。
つまり成功に見えました。「全文を点検して問題なし」と「1文も点検できなかった」が、同じ出力になっていたわけです。
対策は単純です。
未検査のスライドを数えてレポート冒頭に警告を出す。
1枚でも検査できていなければ、非ゼロで終了する。
そして規則にしたのはこれです。
「指摘0件」を通し検査の証拠として使ってよいのは、
終了コードが0のときだけ。
これは品質ゲートの一般的な設計だと思います。検査が0件を返したとき、それが「問題がない」なのか「検査していない」なのかを、出力から区別できるようにする。 区別できないと、壊れた検査器が最も静かな成功として振る舞います。
7. 前段に人手の正解表を置く
最後に、この工程の外で効いた対策を1つ。
案件に人手の正解読み表があるなら、LLMに聞く前に、それと実読みを突き合わせてズレた語だけ辞書に入れる工程を前に置きます。
実測では、96語の正解表のうちズレていたのは6語だけでした。残り90語は元から正しく読めていました。
ここで大事なのは、正解表を丸ごと辞書に入れてはいけないことです。元から正しく読めていた90語まで、辞書のアクセント指定で上書きすることになり、正しい読みを壊します。
そして辞書が先に効いていれば、LLMがその語を蒸し返しても、§3(a)のガードで落ちます。層の順番が防御になっています。
まとめ
- LLM-as-judge は規模で壊れる。3枚で動いても、17枚で破れ、79枚でもっと破れる
- 敵対的検証(LLMにもう一度厳しく判定させる)は「明らかな非語」しか弾かない。 それらしい誤りは通す
- 79枚実測で、検証通過23件のうち18件が誤修正だった(適合率22%)
- 4種類の破れ方は、どれもLLMの再判定では止まらなかった。止めたのは全部決定論のガード
- 生成をLLMに任せず、判定だけ任せて置換はコード側で決定論的に行う
- LLMの判定は、LLMでないもので検証する(合成エンジンへの round-trip)
- round-trip は「変わったか」しか見ない。「正しく変わったか」は見ない
- 出力の意味ではなく、出力を適用した結果の形を検証する(モーラ数の激変を落とす)
- LLMの前提そのものを検証する(何を誤読と認識したかを申告させ、機械的に照合する)
- 人が決めた結論は、判定の対象から外す。辞書は多数決の材料ではない
- 自動適用する工程では、適合率を再現率より優先する。改悪しないことが優先
- 「指摘0件」が「問題なし」なのか「検査していない」なのかを、出力から区別できるようにする
- ガードにもガードが要る(1文字の語を辞書語の一部として潰さない)
一番大きな学びは、精度の話ではありませんでした。
LLM-as-judge を設計するとき、
「判定の精度をどう上げるか」より先に、
「何を判定の対象にするか」を決める。
そして、人が調べて確定したものは対象に入れません。機械の判定は確率的なので、対象に入れれば必ず一定の確率で覆されます。精度を上げても、確率をゼロにはできません。
この件で機械が書き換えようとしたのは、実在の人物の名前でした。それを「精度の問題」として扱うべきではないと考えています。その話は note 側に「人の名前の読みを、機械が5回書き換えようとした:人が決めた結論は多数決の材料ではない」として書きました。
この記事は 七夕研究所 Qiita Organization の記事です。開発と運用で実際に踏んだものを順次書いています。