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?

AIが「こんにちわー」と伸ばす癖は、どこで身についたのか

0
Posted at

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

学習させた音声モデルに「こんにちは。」と読ませると、「こんにちわぁ」と伸びた。台本には伸ばす指示など書いていない。

指摘はこうだった。

こんにちはですが、「こんにちわぁ」と発音し語尾にアクセントがつきます。何か混ざったような感じでしょうか。

「何か混ざったような」というのが正確で、実際に混ざっていた。学習コーパスに、語尾の伸びたクリップが入っていた。

問題は、それを検出する仕組みが原理的に機能しない作りだったことだ。

台本突合はしていた

コーパス生成では、TTSで読ませた音声をWhisperで書き起こし、台本と突き合わせている。

def _kana(s: str) -> str:
    # カタカナ→ひらがな
    return "".join(chr(ord(c) - 0x60) if "" <= c <= "" else c for c in s)

_PUNCT_RE  = re.compile(r"[、。!?!?…・\s「」ー〜,\.]")
_REPEAT_RE = re.compile(r"(.)\1+")

def _collapse(s):
    return _REPEAT_RE.sub(r"\1", _PUNCT_RE.sub("", s or ""))

def judge_transcript(script_text, transcript, ...):
    a = _kana(_collapse(script_text))
    b = _kana(_collapse(transcript))
    sm = difflib.SequenceMatcher(None, a, b)
    ...

正規化してから比較する。素直な実装だ。

ここで _PUNCT_RE を見てほしい。落とす文字の中に (長音記号) が入っている。そして _REPEAT_RE は同じ文字の連続を1つに圧縮する。

台本: こんにちは
転写: こんにちわー

正規化後:
  台本 → こんにちは
  転写 → こんにちわ      ← 「ー」が消える

一致率は高い。「は」と「わ」の1文字違いだけになる。語尾が伸びていたという情報が、正規化の段階で捨てられている。

母音の連続でも同じことが起きる。

転写: こんにちわあ  →  _REPEAT_RE で「あ」の連続が圧縮 →  こんにちわ

つまりこの検証は、どうやっても語尾の伸びを検出できない。長音を無視するために書いた正規化が、長音を検出したい場面でも同じように働いていた。

正規化としては正しい。表記ゆれを吸収して内容の一致を見るなら、長音は落とすべきだ。目的が2つあるのに、正規化が1つしかなかったのが問題だった。

生の書き起こしで判定する

内容の一致を見る判定と、語尾の伸びを見る判定を分けた。後者は正規化前の文字列を使う。

_TAIL_LONG_RE = re.compile(r"[ーぁ-ん]$")

def trailing_elongation_mismatch(script_text: str, raw_transcript: str) -> bool:
    """台本に無い語尾伸びを検出する。

    ⚠️ raw_transcript は whisper の生の書き起こしを渡すこと。
    かな正規化は長音を捨てるため、正規化後の文字列では検出できない。
    """
    script = (script_text or "").rstrip("。、!?!? ")
    trans  = (raw_transcript or "").rstrip("。、!?!? ")
    if not script or not trans:
        return False

    # 台本の末尾文字が、転写では「伸ばされて」いないか
    tail_script = script[-1]
    # 長音記号が台本に無く転写にある
    if "" not in script and trans.endswith(""):
        return True
    # 同じ母音の重複が転写にだけある(例: 「です」→「ですぅ」「ですう」)
    if len(trans) > len(script) and trans[len(script)-1:].startswith(tail_script):
        extra = trans[len(script):]
        if extra and all(c in "ぁぃぅぇぉあいうえおー" for c in extra):
            return True
    return False

判定を分けたので、呼び出し側は2つを別々に見る。

res  = judge_transcript(text, tr["text"])                  # 内容の一致(正規化あり)
tail = trailing_elongation_mismatch(text, tr["text"])      # 語尾の伸び(生の文字列)

if tail:
    continue          # 語尾が伸びていたら即再抽選(内容が合っていても採らない)
if res.ok:
    save(wav)

語尾の伸びは、内容が合っていても不合格にする。 内容の一致度がどれだけ高くても、伸びていたら学習素材に入れない。ここを緩めると癖が焼き付く。

許容の設計

とはいえ完全にゼロにすると歩留まりが落ちる。感情の乗った音声では、多少の伸びは自然に出るからだ。

最終的にこういう2条件のORにした。

def accept(text, transcript):
    v    = judge_transcript(text, transcript)
    tail = trailing_elongation_mismatch(text, transcript)
    return (v.ratio >= 0.82 and tail <= 2) or (v.ratio >= 0.70 and tail == 0)
  • 内容がよく合っている(0.82以上)なら、語尾の伸びを2つまで許す
  • 内容の一致がやや低い(0.70以上)なら、語尾の伸びは0でなければならない

「内容が怪しくて、かつ語尾も伸びている」クリップを確実に落とす形だ。感情表現による認識低下は許容しつつ、伸びの癖だけは通さない。

キャプション側でも殴る

もう1つ効いたのが、生成時のキャプションに書いてしまうことだった。

役割別の声を設計するとき、キャプションにこう入れた。

ニュース原稿を正確に読み上げる女性アナウンサーの声。明瞭で聞き取りやすく、
落ち着いた知的なトーンで、語尾まではっきりと発音する。

最後の「語尾まではっきりと発音する」がそれだ。

効果は明確だった。24候補×5プローブ文=120クリップを生成して測ったところ、語尾の伸びが全件ゼロ。品質ゲートで弾く前に、そもそも伸びた音声が生成されなくなった。

役割込みcaption(語尾まではっきり) → 語尾伸び 0/120

ゲートは「悪いものを捨てる」仕組みだが、捨てれば歩留まりが落ちる。上流で出さないようにできるなら、そのほうが安い。 キャプションで指示できるTTSなら、品質ゲートが見ている項目をキャプションにも書いておく価値がある。

話体との関係

この現象を追ううちに、もう一段深い構造が見えた。語尾の伸び方は用途によって望ましさが違う

  • ナレーター、アナウンサー、コールセンター → 語尾を締めてほしい
  • VTuber、配信 → 伸ばしたほうが自然

そこで用途ごとに「話体」を定義し、コーパスの台本と品質ゲートの厳しさを話体ごとに変えるようにした。業務系の話体では語尾ゲートを厳格に適用し、カジュアル系では緩める。

重要なのは、話体はコーパスに焼き付いて、合成時には変えられないことだ(学習後に話速は変えられない)。同じ声で「締まった語尾」と「伸びる語尾」の両方が欲しければ、同じ設計値(キャプション+seed)で話体だけ変えて2本焼く。実際そうしている。

まとめ

正規化は目的ごとに分ける。 内容の一致を見る正規化と、特定の異常を見る正規化は別物。1つで兼ねようとすると、どちらかが機能しなくなる。

捨てる情報を意識する。 _PUNCT_RE を入れたのは正しい判断だったが、その情報を必要とする判定が後から生まれた。正規化のコメントに「何を捨てているか」を書いておくと、次に触る人が気づける。

上流で出さないほうが安い。 ゲートで捨てると歩留まりが落ちる。生成側で指示できるなら、そちらで殴る。

症状はモデルの振る舞いに出る。 コーパスのデータを見ても「語尾が伸びたクリップが数本ある」だけで、異常には見えない。学習して喋らせて初めて癖として現れる。データセットの検査だけでは足りず、学習後の出力を確認する工程が要る。


シリーズ: 拡散TTSから実用ボイスを量産する

キャプション1行から声を設計し、学習コーパスを製造して、役割別の実用ボイスを量産するまでの記録です。この記事は第3部 品質ゲートにあたります。

← 前: クリップ1本の粗さが、スタイル全体を嗄れさせる
→ 次: 「少々」が「しょも」になる — 許可文字リストが日本語を削っていた

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

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


元記事: https://forge.workstyle.tech/blog/where-did-the-elongated-ending-come-from/?utm_source=qiita&utm_medium=crosspost&utm_campaign=where-did-the-elongated-ending-come-from

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?