0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

「少々」が「しょも」になる — 許可文字リストが日本語を削っていた

0
Posted at

📝 この記事は forge.workstyle.tech に掲載した記事の転載です。

問い合わせ窓口のアバターについて、こういう報告を受けた。

「しょうしょうおまちください」が「しょもおまちください」となります。

音声モデルを何度も焼き直した直後だったので、まずモデルを疑った。結論から言うと、モデルもパラメータもキャッシュも正常で、TTSに渡る入力テキストが壊れていた

川下から順に潰した

疑った順に書く。この順番自体が反省点になる。

モデル。窓口サイトがどのモデルを引くかをDBで辿った。最新の学習済みモデルを正しく引いていた。

キャッシュ。TTSキャッシュのテーブルがあったので、古い音声を返している可能性を見た。中身は1か月以上前の別プロバイダのエントリだけで、無関係だった。

合成パラメータ。ランタイムは style_weight=2.0sdp_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本
  1. 音質で選んだTTSが、会話には遅すぎた
  2. 声をガチャで引く
  3. 「ナレーターっぽい声」を24候補から機械に選ばせる
  4. 品質ゲートを厳しくするほど、棒読みが生き残る
  5. 学習後に話速は変えられない
  6. 生成するたび「録音場所」が変わるTTS
  7. クリップ1本の粗さが、スタイル全体を嗄れさせる
  8. AIが「こんにちわー」と伸ばす癖は、どこで身についたのか
    9. 「少々」が「しょも」になる — 許可文字リストが日本語を削っていた ← いまここ
  9. ハルシネーション対策のコードが、ハルシネーションの時だけ動かなかった
  10. 品質ゲートが許した「3文字」が、モデルの口癖になった
  11. 治せる欠陥で候補を落としていた
  12. 文字起こしでは見つからない欠陥がある
  13. 70分かけた学習素材が、ネットワークの一瞬の瞬きで消えた
  14. "ja" を "JP" と書いて喃語モデルができるまで
  15. 登録経路が4つ、管理画面が0
  16. デプロイのたびに互いの成果を消していた
  17. 測れていないものを閾値で追い込むと、必ず失敗する

知見の元になったノートは 拡散TTSから実用ボイスを量産する製造パイプライン にまとめてあります。


元記事: https://forge.workstyle.tech/blog/the-character-that-broke-the-tts-input/?utm_source=qiita&utm_medium=crosspost&utm_campaign=the-character-that-broke-the-tts-input

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?