#音声合成#TTS#信号処理#データセット

クリップ1本の粗さが、スタイル全体を嗄れさせる — 5本の選び方


使っているTTSは、感情スタイルを「代表クリップ数本」から作る。喜びスタイルなら、喜びの音声を数本渡すと、その平均的なスタイルベクトルが登録される。

最初は素直に、そのグループのクリップから先頭5本を渡していた。

clips = corpus_clips[emotion][:5]
register_style(model_id, style_name=emotion, clips=clips)

このスタイルで合成すると、声が嗄れた。 全部の文で、常に。

200本のうち1本が効く

原因は、渡した5本の中に粗いクリップが混ざっていたことだった。

生成音声には一定の確率で品質の低いものが出る。声がガサつく、ピッチが不安定に揺れる、途中でオクターブが飛ぶ。品質ゲート(台本との突合)は通る。内容は正しく読めているので、音の粗さは検出されない。

コーパス全体は約200本あるので、粗いのが数本混ざっていても学習全体への影響は薄い。ところがスタイル登録に使うのはたった5本だ。そこに1本混ざると影響が20%になる。

そして平均への希釈が期待ほど効かない。スタイルベクトルは埋め込み空間での平均だが、粗い音声は「嗄れ」という方向に大きく外れた位置にあることが多い。4本の正常なクリップと1本の外れ値を平均すると、重心はしっかり外れ値の方向に引っ張られる。

結果として、そのスタイルで合成した音声すべてに嗄れが乗る。1本の粗さが、そのスタイルの全出力に伝播する。

何を測れば「粗い」と言えるか

聴いて「粗い」と感じるものを分解すると、主に2つだった。

ジッタ(周期の揺らぎ)。声帯振動の周期が安定していない状態。ガサついた印象になる。

オクターブ跳躍。F0推定が隣接フレームで2倍/半分に飛ぶ。実際に声が裏返っている場合と、推定器の誤りの場合があるが、どちらも「不安定な音」として現れる。

この2つを測って、少ないものを選ぶ。

def stability_score(wav_bytes):
    """(jitter, octave_jump_rate) を返す。小さいほど安定。"""
    f0 = extract_f0_series(wav_bytes)          # 10msごとのF0系列
    voiced = f0[f0 > 0]
    if len(voiced) < 10:
        return None                            # 有声区間が短すぎて判定不能

    # ジッタ: 隣接周期の相対差の平均
    periods = 1.0 / voiced
    jitter = float(np.mean(np.abs(np.diff(periods))) / np.mean(periods))

    # オクターブ跳躍: 隣接フレームで比が2倍/半分に近い箇所の割合
    ratio = voiced[1:] / voiced[:-1]
    jump  = float(np.mean((ratio > 1.8) | (ratio < 0.55)))

    return jitter, jump


def stable_top(clips, n=5):
    """安定した順に n 本を選ぶ"""
    scored = []
    for c in clips:
        s = stability_score(c.wav)
        if s is None:
            continue
        jitter, jump = s
        scored.append((jitter + jump * 2.0, c))    # 跳躍は重めに見る
    scored.sort(key=lambda x: x[0])
    return [c for _, c in scored[:n]]

オクターブ跳躍を重め(×2.0)にしているのは、聴感への影響が大きいからだ。ジッタは連続的な粗さだが、跳躍は「声が裏返る」という離散的な破綻として耳につく。

登録側はこう変わった。

# 変更前
clips = corpus_clips[emotion][:5]

# 変更後
clips = stable_top(corpus_clips[emotion], n=5)

これで嗄れが消えた。

有声区間が短いクリップを弾く

if len(voiced) < 10: return None の部分が地味に要る。

短い掛け声(「はい!」「よし!」など)や、無声音が多い文(「ですし」「しっかり」)では、有声フレームが数個しか取れない。そこからジッタを計算しても意味がなく、たまたま小さい値が出て「最も安定している」と誤判定されることがあった。

判定不能なものは候補から外す。5本選べなければ、その時点で選べるだけ選ぶ。

⚠️ 低い声でジッタが過大に出る

この指標には既知の弱点がある。自己相関でF0を取ると、低い声ほど推定を誤る。倍音を基本周波数と取り違えたり、フレーム間で推定が飛んだりする。それがジッタとして計上される。

実測では、男性の声で31〜56%という値が出た。人間の正常な発声のジッタは1%未満なので、明らかに計測誤差が支配的だ。

したがって、

  • 同じ話者内で相対比較する分には使える。誤差が一様に乗るので、順位は意味を持つ
  • 話者間、特に男女間の比較には使えない。低い声が一律に不利になる

スタイル登録は「同じ話者のクリップの中から選ぶ」用途なので、前者に該当する。問題なく使えた。

別の用途(複数の候補音声から良い声を選ぶ)で同じ指標を重み付きで使ったところ、男性候補が全員マイナススコアになるという事故が起きた(「ナレーターっぽい声」を24候補から機械に選ばせる)。指標の適用範囲を間違えた例で、そちらでは重みを下げて別の判断材料を出す形に直した。

品質ゲートの層を分ける

この件で分かったのは、「内容が正しい」と「音が綺麗」は別のゲートが要るということだった。

ゲート 見るもの 落とすもの
台本突合(Whisper) 内容の一致・台本外の音 幻覚、読み間違い、欠落
語尾忠実 台本に無い語尾伸び 語尾の癖が焼き付く素材
音響正規化 周波数特性・音圧 チャンネル特性のばらつき
安定選抜 ジッタ・オクターブ跳躍 粗い音・嗄れ

台本突合は内容しか見ないので、粗い音声を通す。音響正規化(生成するたび「録音場所」が変わるTTS)はスペクトルの形は揃えるが、時間方向の不安定さ(ジッタ)は直せない。それぞれが別の層を見ていて、代替が効かない。

そして安定選抜だけは「落とす」ではなく「選ぶ」ゲートだった。他は不合格を除外するが、これは合格者の中から上位を採る。少数精鋭で使うリソースには、除外ではなく選抜が要る。

影響範囲から逆算して厳しさを決める

一般化すると、こういう考え方になる。

1本あたりの影響が大きい箇所ほど、厳しく選ぶ。

  • 学習コーパス200本 → 1本の影響は0.5%。品質ゲートで足切りすれば十分
  • スタイル登録5本 → 1本の影響は20%。足切りでは不十分で、上位を選抜する必要がある

同じ素材プールから取っていても、使われ方によって必要な品質が違う。コーパスに入れてよいクリップと、スタイル代表にしてよいクリップは、別の基準で選ぶ。

これを見落としていたのは、「品質ゲートを通ったのだから使える」と考えていたからだった。ゲートは「最低限使える」を保証するもので、「代表として適切」は保証しない。

まとめ

  • 少数で使うリソースは、1本の外れ値が全体を壊す。5本中1本なら影響20%で、平均では希釈しきれない
  • 粗さはジッタとオクターブ跳躍で測れる。跳躍は聴感への影響が大きいので重めに見る
  • 有声区間が短いクリップは判定不能として外す。誤って「最も安定」と判定される
  • 低い声でジッタが過大に出る。同一話者内の相対比較にのみ使う
  • 除外ゲートと選抜ゲートは別物。少数精鋭で使う箇所には選抜が要る
  • 影響範囲から逆算して厳しさを決める。同じ素材でも使われ方で必要な品質が違う

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

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

← 前: 生成するたび「録音場所」が変わるTTS → 次: AIが「こんにちわー」と伸ばす癖は、どこで身についたのか

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

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

← 記事一覧へ