「少々」が「しょも」になる — 許可文字リストが日本語を削っていた
問い合わせ窓口のアバターについて、こういう報告を受けた。
「しょうしょうおまちください」が「しょもおまちください」となります。
音声モデルを何度も焼き直した直後だったので、まずモデルを疑った。結論から言うと、モデルもパラメータもキャッシュも正常で、TTSに渡る入力テキストが壊れていた。
川下から順に潰した
疑った順に書く。この順番自体が反省点になる。
モデル。窓口サイトがどのモデルを引くかをDBで辿った。最新の学習済みモデルを正しく引いていた。
キャッシュ。TTSキャッシュのテーブルがあったので、古い音声を返している可能性を見た。中身は1か月以上前の別プロバイダのエントリだけで、無関係だった。
合成パラメータ。ランタイムは style_weight=2.0、sdp_ratio=0.8 を使っていた。私の検証は 1.0 / 0.4 だったので、ここが原因ではないかと考えた。振って測った。
style_weight 1.0 / 2.0 / 3.0 / 4.0 → 全部 ratio 1.00
sdp_ratio 0.2 / 0.4 / 0.6 / 0.8 → 全部 ratio 1.00
全12スタイル × weight 2.0 → 全部 ratio 1.00
感情スタイル。12スタイル全部で「少々お待ちください。」を合成したが、どれも正常だった。
経路。音声パイプラインを別サービスに移していたので、そちらが独自に合成している可能性を見た。合成はバックエンド側で行っており、経路は同じだった。
ここまでで数時間。全部シロだった。
前処理の出力を1回 print した
残っていたのは入力テキストだけだった。
>>> _clean_tts_text('少々お待ちください。')
'少お待ちください。'
「々」が消えている。そして消えた文を合成すると、こう読まれた。
入力 '少々お待ちください' → 聞こえ '少々お待ちください' (正常)
入力 '少お待ちください' → 聞こえ 'ショーをお待ちください' ← これ
「ショーを」が「しょも」に聞こえていた。5分で終わる確認を、最後にやった。
原因: 漢字の範囲に「々」が入っていない
前処理には、顔文字・絵文字・特殊記号を落とすホワイトリストがあった。
# TTS前処理: 読み上げに不要な記号・顔文字を除去
_TTS_ALLOWED_RE = re.compile(
r"[^-ゟ" # ひらがな
r"゠-ヿ" # カタカナ
r"一-鿿" # 漢字
r"ヲ-゚" # 半角カタカナ
r"a-zA-Za-zA-Z"
r"0-90-9"
r"、。!?,.ー"
r"\s"
r"]"
)
意図は明快で、実装も素直だ。問題は 一-鿿(CJK統合漢字)に**「々」が含まれないことにある。「々」は 々 で、CJK記号・句読点ブロックにいる。漢字ではなく記号**として分類されている。
日本語を書く人間にとって「々」は漢字の仲間だが、Unicodeの分類はそうなっていない。
網羅したら7種類あった
1文字見つかったということは、他にもあるはずだ。日本語で使うのにCJK統合漢字の外にある文字を総当たりした。
| 文字 | 例 | 結果 | 実害 |
|---|---|---|---|
| 々 | 少々・日々 | 少・日 | 「しょも」 |
| 〆 | 〆切 | 切 | 「きり」 |
| 〇 | 〇月〇日 | 月日 | 日付が消える |
| 𠮷(CJK拡張A) | 𠮷野家 | 野家 | 固有名が壊れる |
| 髙 﨑(互換漢字) | 﨑山 | 山 | 人名が壊れる |
| 〜 | 10〜20分 | 1020分 | 数値が別物 |
| : | 3:30 | 330 | 時刻が別物 |
接客窓口で「﨑山さま」が「山さま」になるのは、かなりまずい。「10〜20分」が「せんにじゅっぷん」になるのも同様だ。
一方 % & 「」 も落ちるが、これは読み上げに不要なので意図どおりだった。全部を残せばいいという話ではない。
記号は温存しても直らなかった
素直に許可リストへ 〜 と : を足した。悪化した。
'午後330に開始します' → 聞こえ '午後330に…' (数値が誤り)
'午後3:30に開始します' → 聞こえ '5も30、30に開始します' ← 温存すると更に悪い
TTSは : を時刻として解釈できず、「ごも」のような雑音を出す。〜 も「から」ではなく読点として扱われる。
除去すると意味が変わり、温存すると読めない。 どちらも不正解だった。
日本語に開く
正解は3つ目の選択肢だった。前処理の段階で日本語に変換してしまう。
_TTS_CLEAN_PATTERNS = [
# ⚠️ 数値の意味を持つ記号は、除去でも温存でもなく「日本語に開く」。
# 除去すると「10〜20分」→「1020分」、温存しても TTS が読めず
# 「3:30」→「5も30、30」のような雑音になる(実測)。
(re.compile(r"(\d)\s*[〜~~]\s*(\d)"), r"\1から\2"), # 10〜20 → 10から20
(re.compile(r"(\d{1,2})\s*[::]\s*(\d{2})"), r"\1時\2分"), # 3:30 → 3時30分
...
]
⚠️ 置換は除去より前に置く。 後だと 10〜20 が先に 1020 になってしまい、置換対象が消えている。
そして読みに必要な文字は許可リストに足す。
r"一-鿿" # 漢字
# ⚠️ 日本語の読みに必要なのに、CJK統合漢字の外にある文字群。
# 実機で「少々お待ちください」が「少お待ちください」になり、
# 「ショーをお待ちください」と発音された。
r"々〆〻" # 々 〆 〻(繰り返し・略字)
r"〇" # 〇(漢数字ゼロ)
r"㐀-䶿" # CJK拡張A(異体字・人名)
r"豈-" # CJK互換漢字(髙 﨑 等の人名異体字)
実音声で確認した。
'少々お待ちください' → '少々お待ちください' ✅
'髙橋・﨑山' → '髙橋・﨑山' ✅
'10から20分ほどかかります' → '10から20分ほどかかります' ✅
'午後3時30分に開始します' → '午後3時30分に開始します' ✅
'受付は9時00分から17時00分です' → '9時0分から17時0分' ✅
⚠️ 波ダッシュには別の落とし穴がある。〜(U+301C) と ~(U+FF5E) は別文字で、環境によって使われる方が違う。片方だけ書くと片方が漏れる。両方入れた。
ついでに見つかった: 発音辞書が効いていなかった
原因を追う過程で、この窓口サイトに発音辞書が1件も適用されていないことが分かった。辞書はプロジェクト単位でスコープされていて、既存の44件は全部別プロジェクトに紐づいていた。
窓口の声で読ませてみた。
| 表記 | 正しい読み | 実際の読み |
|---|---|---|
| 液冷 | エキレイ | できげ |
| 主な | オモナ | オーナー |
| 従量課金 | ジューリョーカキン | 重量価値 |
| 行っています | オコナッテイマス | 言っています |
**「行っています」が「言っています」**になるのは、接客文で頻出する上に意味が反転する。
44件のうち自社製品名・人名の10件を除いた34件は、どのサイトでも必要な汎用語(技術用語と、訓読み/音読みが割れる日本語)だった。他の3プロジェクトへ展開した。
学んだこと
入力を最初に見る。 モデル・パラメータ・キャッシュ・経路を数時間かけて潰し、最後に前処理の出力を1回 print して終わった。順番が逆だった。
ホワイトリストは「何を通すか」を決めているつもりで、「何を落とすか」を決めている。 落とすものを列挙する方式なら、想定外の文字は通る。通すものを列挙すると、想定漏れが即座に欠落になる。日本語のように文字種が多い言語では、ホワイトリストの網羅は難しい。
除去か温存かの二択に見えるとき、三つ目がある。 記号は「落とすと意味が変わり、残すと読めない」という板挟みだったが、日本語に開くという選択肢があった。前処理は「不要なものを消す場所」だと思い込んでいて、「意味を保ったまま形を変える場所」だとは考えていなかった。
同じ分類の文字を総当たりする。 「々」が1つ見つかった時点で、他にも同じ理由で落ちる文字があると考えて調べた。7種類出た。1つ直して終わりにしていたら、次は人名の異体字で同じ報告を受けていたはずだ。
シリーズ: 拡散TTSから実用ボイスを量産する
キャプション1行から声を設計し、学習コーパスを製造して、役割別の実用ボイスを量産するまでの記録です。この記事は第3部 品質ゲートにあたります。
← 前: AIが「こんにちわー」と伸ばす癖は、どこで身についたのか → 次: ハルシネーション対策のコードが、ハルシネーションの時だけ動かなかった
シリーズ全18本
- 音質で選んだTTSが、会話には遅すぎた
- 声をガチャで引く
- 「ナレーターっぽい声」を24候補から機械に選ばせる
- 品質ゲートを厳しくするほど、棒読みが生き残る
- 学習後に話速は変えられない
- 生成するたび「録音場所」が変わるTTS
- クリップ1本の粗さが、スタイル全体を嗄れさせる
- AIが「こんにちわー」と伸ばす癖は、どこで身についたのか 9. 「少々」が「しょも」になる — 許可文字リストが日本語を削っていた ← いまここ
- ハルシネーション対策のコードが、ハルシネーションの時だけ動かなかった
- 品質ゲートが許した「3文字」が、モデルの口癖になった
- 治せる欠陥で候補を落としていた
- 文字起こしでは見つからない欠陥がある
- 70分かけた学習素材が、ネットワークの一瞬の瞬きで消えた
- “ja” を “JP” と書いて喃語モデルができるまで
- 登録経路が4つ、管理画面が0
- デプロイのたびに互いの成果を消していた
- 測れていないものを閾値で追い込むと、必ず失敗する
知見の元になったノートは 拡散TTSから実用ボイスを量産する製造パイプライン にまとめてあります。