こんにちは、フリーランスエンジニアのmohです。この記事はほとんどAIが書いたものを、私が加筆修正しています。検証不十分な部分もあるかと思いますが、ご容赦ください。ご指摘等ございましたら、Github issueか、Xでお願いいたします。
前回の記事で、Qwen3-TTS を self-host しているパイプラインで、日本語スクリプトに英数字が混ざると発音が崩壊する問題と、TTS 直前に LLM で英数字をカタカナ読みに置換する対処を書きました。しかし誤読は消えた代わりに、イントネーションが崩れました。原因を切り分けて他の TTS も調べた話です。
結論
- 漢字の誤読 (「14時」を「じゅうじ」と読む等) を潰すため、TTS 直前に LLM でスクリプトを全文かな化した。誤読は消えたが、イントネーションが不自然になった
- self-host 版と同系のマネージド版を比べても変わらなかった。TTS にとって全文かなは分布外、という構造の問題で、エンジンを替えても直らない
- 導入時に見抜けなかったのは、検証が whisper 書き起こしの CER だけだったから。CER はイントネーションを測れない (罠その1)
- whisper の通常書き起こしは漢字語の誤読を隠す。「ヒガシクモ」と誤読した音声が文脈から「東雲」に綴り直され、正読と誤判定していた。カタカナ強制の音写でようやく露出した (罠その2)
- 「かな化か、誤読を我慢するか」は二択ではない。SSML phoneme などで漢字文脈を保ったまま特定語だけ読みを指定できる TTS が複数ある。
前回までのあらすじ: 英数字カタカナ化から全文かな化へ
前回入れた英数字のカタカナ置換は、発音崩壊と hang の対処としてはうまく働きました。ただ漢字は温存していたので、漢字の読みは TTS 任せのままです。そして Qwen3-TTS はときどき読み間違えます。手元で踏んだ例では「14時」が「じゅうじ」になりました (whisper 書き起こしが「10時」相当になることを複数 run で確認)。
漢字を渡す限りこの種の誤読は防げないので、正規化を拡張して、スクリプト全文を読みがなに開いてから TTS に渡すようにしました。漢字ゼロ・英数字ゼロの完全かな表記です。狙いどおり誤読は消えました。
ところが本番に出したあと生成を聴いてみると、どうも読み方が不自然です。読み間違いではなく、イントネーションがおかしい。
切り分け: モデル固有か、構造の問題か
まず疑ったのは self-host しているモデル (1.7B の Base checkpoint) の地力でした。同系のマネージド API に載せ替えれば直るのか。ref audio とテキストを両経路で完全に同一にし、エンジンだけ差し替えて聴き比べました。
- 漢字入力: イントネーションは自然。ただし誤読は出る (実例: 「東雲」→「とううん」)
- かな入力: イントネーションが不自然。self-host とマネージドで有意差なし
トレードオフは「漢字 = 自然だが時々誤読 / かな = 正読だが韻律劣化」です。
他の TTS はどうしているか
構造の問題だと分かったので、他の TTS がこの問題をどう扱っているのか調べました。
漢字読み精度はモデル間で大差がある
常用漢字の読み精度ベンチ JKYB-Parakeet Edition (13,536 文、TTS 41 モデル実測) によると、TTS が漢字文を正しく読める率はモデルによって 60% 台から 95% 超までばらつきます。抜粋すると:
- Paratts v1: 95.3%
- VOICEVOX: 93.9%
- Gemini 3.1 Flash TTS: 93.7%
- MiniMax Speech 2.8 HD: 92.2%
- Fish Audio S2 Pro: 89.4%
- ElevenLabs v3: 88.1%
- Qwen3-TTS 1.7B Base: 72.2%
自分の使っているモデルは、素の読み精度で上位に 16〜23 ポイント劣る位置でした。誤読が多いのは業界の宿命ではなく、Qwen3-TTSが読みに弱かった。なおこのベンチは 1 位 Paratts のベンダー自身によるものなので、その点は割り引く必要がありますが、手法とデータは公開されており相対比較には使えます。記事自身も書いているとおり誤読ゼロの TTS は存在しないので、見るべきは誤読率の水準と、出た誤読を直す手段があるかです。
読み指定機構を持つサービスが複数ある
こちらが今回の調査の収穫でした。漢字文脈を保ったまま、特定の語だけ読みを指定する機構を持つサービスが普通にあります。
| サービス | 方式 | アクセント指定 |
|---|---|---|
| Amazon Polly | SSML phoneme (x-amazon-yomigana / x-amazon-pron-kana) | 可 (pron-kana) |
| Google Cloud TTS | SSML phoneme (alphabet="yomigana") | 可 |
| Azure Speech | SSML phoneme + custom lexicon | ja-JP は phonetic set 定義あり |
| Fish Audio | インライン phoneme タグ (OpenJTalk 式ローマ字) | 可 |
| MiniMax | pronunciation_dict (辞書型) | 不可 |
| ElevenLabs | pronunciation dictionary (辞書型) | alias は不可 |
Polly なら <phoneme alphabet="x-amazon-yomigana" ph="ひろかず">浩一</phoneme> のように書けます。インライン型は出現箇所ごとに指定できるので同表記異読にも対応でき、Polly / Google / Fish Audio はアクセントまで指定できます。辞書型 (語単位の一括置換) は、かな置換と同じでアクセントを指定できない弱点が残ります。
つまり「全文かな化で韻律を犠牲にする」か「誤読を我慢する」かの二択は、使っているモデルにこの機構が無いというだけの話でした。
罠: whisper は誤読を隠す
読み指定機構を持つ候補として、Fish Audio (S2.1 系。ref audio を API に渡すだけの zero-shot clone) を試しました。ここで2つ目の罠を踏みます。
漢字まみれのテスト文を投げ、生成音声を whisper-1 で書き起こして誤読チェックをしました。書き起こしは「東雲」。正読と判定しました。ところが実際に聴くと「ヒガシクモ」と読んでいます。誤読です。
whisper のような ASR は正書法のテキストを出力します。音声が「ヒガシクモ」でも、文脈から「東雲」という正しい表記に綴り直してしまう。通常の書き起こしは、漢字語の誤読を構造的に隠すわけです。しかも「14時→ジウジ」のような数字の誤読は書き起こしに出ます。数字は検出できるのに漢字語だけ素通りする、という非対称な死角で、半分だけ機能する検証は全く機能しない検証より信じてしまうぶんたちが悪い。
対処は音写への切り替えです。gpt-4o-transcribe に次のプロンプトを付けます。
発音を聞こえたとおりカタカナのみで書き起こす。漢字とひらがなは使用禁止。
これで「ヒガシクモ」がそのまま出てきました (入力は 16kHz mono の mp3 への変換が必要でした。44.1kHz の wav は unsupported_format で弾かれます)。ASR で TTS の読みを検証するなら、正書法出力ではなく音写です。
phoneme タグは韻律を壊さない
Fish Audio のインライン phoneme タグで、誤読した「東雲」の読みを指定してみます。
<|phoneme_start|>shi0no1no1me1<|phoneme_end|>
OpenJTalk 式のローマ字にピッチアクセントの数字を添える記法です (数字の振り方は暫定で、規則の確定はこれから)。結果は「シノノメ」と正読。タグ文字列がそのまま読み上げられることもなく、音声の尺も指定なし版と ±0.14 秒でした。ドキュメントには旧モデルの対応しか明記が無かったのですが、S2.1 系の API でも効くことを実機で確認できました。
全文かな化との違いは効き方の局所性です。かな化は文全体の表記を変えるので韻律の手掛かりを丸ごと消しますが、phoneme タグは誤読した語だけのピンポイント指定で、文の残りは漢字かな交じりのまま。韻律を保ったまま読みだけ直せます。ちなみに数字・時刻・金額・割合 (「14時30分」「4,980円」「96.5%」) や英字略語は、Fish Audio では素の表記のままで正読でした。
まとめ
全文かな化は「誤読を直す代わりに韻律を殺す」対処でした。誤読の原因だけでなく、アクセントを決める手掛かりまで一緒に消していたからです。すなおにphoneme 指定を持つ TTS への乗り換えが良さそうです。
方法論の教訓は2つです。音声の検証を文字ベースの指標だけで済ませないこと。CER はイントネーションを測れないし、測れない軸でちょうど劣化は起きます。最後は耳です。もう1つ、ASR で TTS の読みを検証するときは正書法出力を使わないこと。whisper は誤読を文脈で正しい漢字に綴り直して隠します。カタカナ強制の音写で確認します。