拡散モデルの生成を決定論化する — 同じ入力から必ず同じ声を
声質変換アプリのパラメータ調整をしていて、こんな場面に出くわしました。
スライダーを少し動かして生成 → 元に戻して生成 → なぜか声が微妙に違う
スライダーは完全に元の位置。録音も同じ。なのに出てくる声が前と一致しない。「さっきの方が良かった気がするけど、もう戻せない」——これでは、どのパラメータがどう効いたのかを検証しようがありません。原因は、拡散モデルが乱数ノイズから生成を始めることにありました。この記事は、生成を決定論化して「同じ入力なら必ず同じ声」を実現した話です。
なぜ拡散モデルは実行のたびに変わるのか
拡散モデル(CFM: Conditional Flow Matching)による生成は、ランダムなノイズから出発し、条件に沿ってそれを段階的に整えて目的の出力(ここではメルスペクトログラム)へ近づけていく処理です。
出発点がランダムということは、同じ条件でも初期ノイズが毎回違えば、たどり着く結果も少しずつ変わるということ。声質変換の場合、話者性も内容も同じなのに、生成のたびに声色や息づかいが微妙に揺れる。品質そのものは悪くなくても、「再現できない」という一点が実務では致命的でした。
該当の推論コードはこうなっています。乱数を固定しないと、この cfm.inference が呼ばれるたびに違う初期ノイズを引きます。
vt = _S["model"].cfm.inference(cat, torch.LongTensor([cat.size(1)]).to(dev),
mel2, style, None, STEPS, inference_cfg_rate=CFG)
解決: シードを固定して決定論化する
対策はシンプルです。生成の直前で PyTorch の乱数シードを固定します。
# 生成を決定論化: 同一入力(録音+スライダー)なら毎回同一の変換結果になる
# (CFM が乱数ノイズからサンプリングするため、未固定だと実行毎に少し変わる)
torch.manual_seed(1234)
if dev.type == "cuda":
torch.cuda.manual_seed_all(1234)
たった数行ですが、押さえどころが2つあります。
- CPUとGPUで乱数生成器が別。
torch.manual_seed()はCPU側の生成器を固定しますが、GPU(CUDA)側は別系統です。そのためdev.type == "cuda"のときはtorch.cuda.manual_seed_all()も呼び、実行環境に依存せず同じ結果が出るようにしています。 - 固定する場所は “生成の直前”。初期化時に一度だけ固定しても、その後の別処理で乱数が消費されれば、次の生成時のノイズは変わってしまいます。だから毎回の生成で、
cfm.inferenceを呼ぶ直前に固定し直すのが確実です。
これで「同じ録音 + 同じスライダー = 同じ声」が保証されました。値(1234)自体に意味はなく、固定されていることだけが重要です。
決定論化がもたらす3つの実務的価値
「毎回同じ結果になる」ことは、地味に見えて開発と製品の両面で効いてきます。
1. A/B比較が意味を持つ
パラメータAとパラメータBのどちらが良いかを比べるとき、生成が非決定論的だと「差がパラメータのせいなのか、乱数のブレのせいなのか」が切り分けられません。シードを固定すれば、変えた箇所以外はすべて同一。出力の違いは純粋にパラメータの違いに帰着します。比較実験の大前提が整います。
2. パラメータの効果を単独で検証できる
「ハスキー度のスライダーを10%上げると、声はどう変わるか」——この問いに答えるには、ハスキー度以外が固定されている必要があります。決定論化により、1つのスライダーだけを動かして生成すれば、その軸が音に与える影響を単独で観察できます。設計時のチューニングが、勘ではなく再現可能な観察に基づけるようになります。
3. バグ報告が再現できる
「この設定で変な音が出た」というユーザー報告も、決定論的なら同じ入力から同じ問題を再現できます。非決定論だと「手元では再現しない」で調査が止まりがちですが、シード固定ならバグを掴まえて離さない。これはデバッグの生産性を大きく変えます。
後処理は決定論と分けて考える
もう一点、設計上の工夫があります。話速・ピッチ揺らぎ・息づかいといった後処理DSPは、モデル生成とは切り離してあります。生成した素の波形をキャッシュし、後処理だけを再適用できるようにしているのです。
# 後処理DSPは再生成なしで再適用できるよう、素の生成波形をキャッシュ
_S["last_raw"] = wav.astype(np.float32).copy()
こうしておくと、後処理パラメータをいじるたびに拡散モデルを回し直す必要がなくなります。重い生成(決定論化された、揺らがない部分)は一度きり、軽い後処理(何度も試したい部分)は素波形から高速に再適用する。「決定論化して固定すべき層」と「素早く試行錯誤したい層」を分離する——再現性とイテレーション速度の両立は、この層分けから来ています。
落とし穴と学び
- “品質が良ければ非決定論でいい” は罠。生成物の品質と再現性は別の軸です。品質が十分でも、再現できなければ検証も比較もデバッグも成立しません。製品化を見据えるなら、早い段階で決定論化しておくべきでした。
- シード固定の位置が命。初期化時に一度だけ固定して満足すると、途中で乱数が消費されて結局揺れます。「生成の直前で毎回固定する」を徹底することが肝心です。
- CPU/GPUの乱数系統の違いを忘れない。片方だけ固定して「なぜかGPUだと再現しない」と悩むのは、非常にありがちな落とし穴です。
- 固定すべき層と試したい層を分ける。すべてを決定論にするのではなく、重くて再現性が要る生成は固定し、軽くて何度も試す後処理はキャッシュから再適用する。この線引きが開発効率を左右します。
まとめ
- 拡散モデル(CFM)は乱数ノイズから生成するため、同じ入力でも実行のたびに結果が微妙に変わる
- 生成の直前で
torch.manual_seed(1234)(CUDA時はtorch.cuda.manual_seed_allも)を呼び、決定論化する - シード固定の効果: A/B比較・パラメータ単独検証・バグ再現——いずれも「変えた箇所以外は同一」が前提として成立する
- 固定する場所は “生成の直前”、CPU/GPU双方の乱数系統を押さえるのがポイント
- 後処理DSPは素波形をキャッシュして分離。重い生成は固定・一度きり、軽い後処理は高速に再適用——再現性と試行錯誤の速さを両立させる