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?

NNSVSのv5音響モデルを自作コーパスで安定化させようとして上手くいかなかった話

0
Last updated at Posted at 2026-09-01

1. はじめに

自作の歌唱コーパスは単一歌唱者で、収録時間は約47分。NNSVS の v4 音響モデルでは pitch-shift data augmentation で7倍にデータ拡張すれば辛うじて安定するが、v5 音響モデルは15倍(recipe 名の psda15)に拡張しても安定しない。それを何とか安定させようとして悪戦苦闘した記録。

実験には試行順に 実験1〜9 の連番を振る(3.2)。実験結果(3.3)は定性まとめ + lf0 / mgc[0] の定量比較に分けて記載する。同時比較したデータは v5 baseline / 実験5-7(実験6,7 は mgc[0])/ 実験9 しか残っておらず、それ以外は当時のメモと判断からの要約。

本文中の nnsvs 本体コードへのリンクは fork taroushirani/nnsvs の python3.13-support ブランチ(commit 821cf4d、2026-08-27)固定の permalink。recipe 側の config は公開リポジトリに含まれないためリンクなし。


2. v5 音響モデルの構成と問題点

構成

症状

  1. 長母音(ロングトーン)で F0 を保てない — ドリフト・遅いゆらぎ。全ノートの楽譜からの乖離率(off_note_rate)も高い
  2. mgc0のフレーム間ジッター(フレームごとに値が細かく高速にばたつく現象)
  3. teacher-forcing 指標(F0 RMSE / VUV err)は v4 より良いのに、自由生成では劣化
    — exposure bias の典型症状

診断

  • 学習時は teacher-forcing により 4 ストリームが計算グラフ上は独立している。
    mgc / bap / vuv は 正解ラベル y_lf0(・y_bap)を条件に使い、lf0_model の予測を参照しない(multistream.py#L495-L509; v4 の NPSSMultistreamParametricModel も同構造)。lf0_model を更新するのは lf0 ストリーム自身の損失のみ。
  • 唯一の結合点は「1 個の nn.Module である」ことに由来する 単一 optimizer・単一グローバル clip_grad_norm_。
  • v5 の diffusion 向け学習設定(AdamW lr=1e-3、clip_norm=10、StepLR 100ep 半減、300ep)は AR な LSTM デコーダには過激。epoch 60 時点の実効 lr は v4(Adam lr=1e-4、StepLR 20ep 半減、clip_norm=1、100ep)の約 80 倍。
  • clip_grad_norm_ はモデル全体のグローバルノルムに働くため、巨大な DiffNet がノルムを支配し、lf0_model の勾配は実質クリップされない。
  • stream_wise_loss: false(v5 全 recipe の設定)では損失が全ストリーム誤差を合算し総要素数(≈67·B·T)で平均する(train_acoustic.py#L74-L103)。 mgc(60 次元)が lf0(1 次元)の約 60 倍の raw 勾配量を backward 前から供給している。
  • 別の見立て: v5 のジョイント学習は lf0 を「音符追従の弱い局所解(損失地形上の谷。一度落ちると学習を続けても抜け出しにくい)」に収束させる。保守的な設定で全ストリームを回帰学習する v4 の lf0 とは別物。

3. 行った実験と結果

3.1 評価基盤

utils/f0_stability_eval.py を新規作成。TensorBoard の F0 RMSE / VUV err は teacher-forcing 下(直前フレームの正解を条件に与えた 1-step 予測)で計算されるため、exposure bias の症状(ロングトーンのドリフト、時折の音程外し)が数値に出ない。本スクリプトは 合成済み wav を楽譜と突き合わせて 自由生成時の安定性を定量化する。

共通処理

  • 合成 F0: 合成 wav から pyworld.harvest で F0 抽出(frame period 5 ms、f0_floor 100 Hz、f0_ceil 1000 Hz)。さらに pyworld.cheaptrick でスペクトル包絡を求め、フレーム毎の log_power = log(Σ spectrogram) を得る。
  • 楽譜 F0: duration調整済みHTSフルコンテキストラベルから acoustic/linguistic features を作り、楽譜のピッチ情報(MIDI ノート)を Hz 化。ノート毎に一定値の目標ピッチ列(ビブラートやしゃくり等の表情を含まない)。
  • 合成 F0・楽譜 F0 とも C4 基準の cent に変換して比較。有声フレーム(f0 > 0)のみ対象。
  • 楽譜 lf0 から note_segments でノート区間に分割し、以下をノート単位で計算する。

(A) 長母音メトリクス — ノート長 ≥ 0.5 s、かつ有声フレーム ≥ 2 のノートのみ

各ノートで「合成 cent − 目標 cent」の系列を作り:

  • mean_dev_cent — 上記偏差の平均。持続音での静的なピッチのずれ(バイアス)。長母音ノート全体で mean / median を報告。
  • drift_cent_per_sec — 合成 cent を時刻(秒)で線形回帰した傾き。持続音中にピッチがどれだけ滑っていくか。exposure bias によるドリフトはここに出る。長母音ノート全体で mean(abs) と median(abs) を報告(3.3 の表は median(abs))。
  • detrended_sigma_cent — 上記線形トレンドを除いた残差の標準偏差。平均やドリフトとは別の、無方向なジッター。mean / median を報告。※ 自然なビブラートもここを増やすため、値が小さいほど良いとは限らない(モデル間の相対比較用)。
  • octave_jumps — 連続する有声フレーム間で |Δcent| > 600(半オクターブ)となった回数の総和。ピッチの急な飛び・倍/半ピッチ検出。全実験で 0。
  • log_power drift/sec — フレーム毎 log_power を時刻で線形回帰した傾き。持続音中のラウドネス(音量)ドリフト = 振幅の不安定性。mean(abs) を報告。
  • log_power detrended sd — 同残差の標準偏差。ラウドネスの揺れ。

(B) 全ノートメトリクス — 中央区間に有声フレームを 1 つ以上持つ全ノート

  • 中央区間: ノートから両端のマージン(--margin-sec 0.05 s、短いノートではノート長の 25 %)を除いた区間。アタック/リリースの遷移やポルタメントを除外する。
  • note_dev_cent — 中央区間の合成 cent の中央値 − 目標 cent。ノート 1 個につき 1 値で、そのノートの定常ピッチが楽譜からどれだけ外れているか。
  • off_note_rate — |note_dev_cent| > 100 cent(半音)のノートの割合(%)。明確に音程を外したノートの率。
  • dev_p95_cent — 全ノートの |note_dev_cent| の 95 パーセンタイル。少数の極端な外れ値に頑健な、ワーストケースのピッチ精度。

補助: フレーム毎 |score − pred|

  • 全ノートの有声フレームで |合成 cent − 目標 cent| を集め、median / p95 を報告(長母音限定でも中央区間限定でもない、遷移区間も含む)。
  • median = 曲全体を通した典型的なフレーム単位の楽譜追従誤差。低いほど旋律線に密着。

mgc[0] 統計(音響モデル出力から直接、wav ではない)

  • std / diff_std(フレーム間差分の標準偏差)/ max_abs_diff / n_outlier(|diff| > 3σ)。
  • diff_std がフレーム間ジッターの指標で、3.3 の表で使用。

解釈上の注意

楽譜目標はノート毎一定値なので、表情豊かで自然な歌唱でもスコアは 0 にならない。これらの指標は 安定性と楽譜追従性 を測るものであり、絶対的な音質評価ではない。モデル間の相対比較に用いる。

3.2 各実験の動機と内容

各実験は前の実験の結果を受けて次を設計している(試行順)。recipe は括弧内。

実験1(学習なし)— ストリーム切り分け
動機: mgc[0] ジッターが lf0 の不安定さに引きずられて出ているのか、拡散 mgc/bap モデル固有なのかを、学習コストをかけずに判定したい。
内容: v4 の lf0 と v5 の mgc/bap を組み合わせて合成(逆の組み合わせも)。

実験2(v5lf0fix)— optimizer を穏当化
動機: v5 の学習設定(AdamW lr=1e-3・clip_norm=10・300ep)は拡散モデル向けで、AR な lf0 デコーダには実効 lr が過大。lf0 側に穏当な設定を与えればドリフトが収まるか。
内容: 学習全体を lr 2e-4 / clip_norm 1.0 に下げて v5 baseline から継続学習。

実験3(v5e2pitchreg)— 楽譜ピッチへの正則化
動機: 実験2 でドリフトは減ったが不足。楽譜ピッチへ引き戻す正則化項を足せば、持続音のドリフトをさらに抑えられるか。
内容: 実験2 + pitch_reg_weight 0.01。

注意: 当時の nnsvs 本体の pitch_reg_weight の実装にバグがあり、正則化が
意図通りに効いていなかった可能性がある。実験3 の結果(下記)は再実行時に
異なる挙動になりうる。

実験4(v5e3warmstart)— v4 の lf0 から出発
動機: v5 のジョイント学習が lf0 を悪い局所解へ収束させている疑い。既に良好な v4 の lf0 重みから初期化すれば、良い局所解に留まれるか。
内容: 実験2 + v4 checkpoint から lf0_model を warm-start。

実験5(v5e4freeze)— lf0/vuv を凍結
動機: 実験4 で warm-start しても高 lr の継続学習で lf0 が再劣化。ならば lf0/vuv を凍結して一切動かさず、拡散 mgc/bap だけ学習すれば lf0 は v4 水準のまま保てるはず。
内容: 実験4 + lf0_model/vuv_model を freeze、diffusion mgc/bap のみ学習。

実験6(v6mixedft)— 多話者混合 DB 事前学習
動機: 本命は mgc[0] の安定化。対象 DB は pitch-shift data augmentation で約15倍にしているが、
話者・音色の多様性は素の1話者47分ぶんのままで、mgc[0] の土台には不足の可能性。多話者混合 DB で事前学習してから対象 DB へ fine-tune すれば mgc[0] が安定するか(lf0 は実験5 で解決済み扱いなので副次確認)。
内容: 混合 DB(7 話者・話者 one-hot 条件付け)で拡散音響モデルを事前学習 → 対象 DB へ fine-tune。denoise_fn 全体 + vuv.fc を unfreeze、lf0 は凍結。

実験7(v6mixedft denoiserpartial)— denoiser の出力側だけ動かす
動機: 実験6 では継続学習しても mgc[0] が収束しない(best/latest でほぼ同一)。denoise_fn 全体を動かすと揺れが残る仮説 → 出力に近い層だけ動かせば安定するか。
内容: 実験6 と同じ warm-start から、mgc/bap の denoise_fn 出力側のみ unfreeze (末尾 3 residual layer + skip/output projection、trainable 2.6M)。lf0/vuv は凍結。

実験8(v5tier1gradgroups)— サブモジュール別 grad-clip
動機: 単一グローバル clip_grad_norm_ を DiffNet が支配し、lf0 の勾配は実質クリップされない(§2 診断)。param_group と grad-clip をサブモジュール別に分ければ、lf0 に適正なクリップ・mgc/bap に緩いクリップを同時に与えられるか。混合 DB に頼らず対象 DB 単体での改善を狙う。
内容: lf0_model(clip 1.0)を mgc_model+bap_model(clip 5.0)から分離、 vuv は catch-all。v5 baseline から継続学習。

実験9(v5tier2split)— 学習自体を 2 段階に分割
動機: teacher-forcing 下で 4 ストリームは独立。学習も分ければ optimizer 干渉が完全に消える。まず lf0/vuv だけを v4 相当設定で学習し、その後 mgc/bap を学習。
内容: Stage A = lf0/vuv のみ 100ep(v5 の 300ep から warm-start)→ Stage B = mgc/bap のみ 300ep。執筆時点では継続学習中(〜185ep)。

3.3 実験結果

定性まとめ

# 結果
実験1 mgc[0] ジッターは lf0 を差し替えても残り、拡散 mgc/bap 側にも要因あり。lf0 由来分と mgc 固有分を切り分け
実験2 lf0 ドリフトは軽減するが v4 水準には未達
実験3 ドリフトはさらに抑制されるがビブラートも縮む。単独では不十分。ただし当時の pitch_reg_weight 実装にバグがあり結果は再現時に変わりうる(3.2 の注意)
実験4 一旦改善するが、高 lr の継続学習で再劣化
実験5 lf0 が v4 水準に到達。以降の標準構成
実験6 継続学習だけでは mgc[0] 不安定性は解消せず(best/latest でほぼ同一)
実験7 mgc[0] は実験6 比で明確に改善。ただし土台の混合 pretrain の lf0 が悪く、モデル全体の優劣・到達水準は判定不能
実験8 mgc/bap の clip 緩和でむしろ mgc[0] が悪化 → clip は締める方向が正しいと確認。lf0 の grad norm が mgc/bap の 1/100〜1/200 であることを実測し、§2 の診断を裏付け
実験9 lf0 が v5 の局所解から出発したため v4 水準に未達(下記 lf0 表、3.4)。Stage B は学習進行中のため暫定値

同時比較のデータがあるのは v5 baseline / 実験5 / 実験9(いずれも f0_stability_eval.py、 dev/eval 10 発話、CPU、確率的 1 サンプル)。実験6〜7 は mgc[0] のみ別途 best checkpoint で実測。他は手元のメモからの要約。

lf0(F0)の定量比較

列は 3.1 の指標に対応: drift med|abs| = drift_cent_per_sec の median(abs)、detrended σ = detrended_sigma_cent の median、off_note = off_note_rate、dev_p95 = dev_p95_cent、frame med = 補助のフレーム毎 |score − pred| の median。すべて小さいほど安定側。

構成 drift med|abs| (cent/s) detrended σ (cent) off_note (%) dev_p95 (cent) frame med (cent)
v5 baseline (300ep) 103.5 70.1 3.1 70.9 17.5
実験5 best 98.6 60.6 0.5 27.5 12.1
実験5 ep300 100.0 68.6 0.5 38.4 13.4
実験9 暫定、Stage B 〜185ep 93.5 76.3 3.1 66.0 23.8
  • 音程精度(off_note / dev_p95 / frame med)は実験5 が突出。これらは lf0_model のみで決まる。
  • 実験9 は音程精度で実験5 の約 2 倍悪く、v5 baseline とほぼ同等。Stage A の lf0/vuv は確定済み、Stage B は lf0 に触れないため、epoch 300 完了後もこの差は縮まらない。
  • ドリフト・detrended σ(ロングトーンの遅いゆらぎ・無方向ジッター)は全構成で大差なく、共通の未解決課題として残る。

mgc[0]の定量比較

同一パイプライン合成の mgc[0](音響モデル出力から直接算出、wav ではない)。log-pow drift = log_power drift/sec の mean(abs)、mgc0 diff_std = mgc[0] のフレーム間差分の標準偏差。

構成 log-pow drift mgc0 diff_std
v5 baseline (300ep) 2.91 0.506
実験5 best 3.50 0.483
実験5 ep300 3.32 0.499
実験9 暫定 3.66 未測定
  • 実験5・実験9 とも v5 baseline と概ね同水準。実験9 は mgc/bap を実験5 と同じ diffusion-freeze 系設定で学習しているため、mgc[0] の挙動もほぼ一致する。
  • mgc[0] ジッターは「学習を続ける・optimizer を変える」だけでは動かない(実験6・実験8 も同様)。

別パイプライン(混合 DB 事前学習 → fine-tune、lf0 は両者で共通・凍結)での
実験6 vs 実験7、best checkpoint・4 発話:

発話 実験6 diff_std 実験7 diff_std 実験6 max_abs_diff 実験7 max_abs_diff
#1 0.407 0.286 8.376 3.984
#2 0.761 0.572 9.341 3.571
#3 0.568 0.332 7.139 3.906
#4 0.661 0.336 7.959 2.484
  • 実験7(denoise_fn 出力側のみ unfreeze)は実験6(denoise_fn 全体 unfreeze)比でdiff_std −25〜49 %、max_abs_diff −45〜69 %。lf0 は両者で共通・凍結なのでクリーンな A/B。
  • 「denoiser 全体を動かすより出力側だけ触る方が mgc[0] が安定」の仮説を支持。
  • ただし土台の混合 pretrain の lf0 が悪く、実験5 / v5 baseline と同一パイプラインの横並び未実施のため、モデル全体の優劣・到達水準は判定不能。

3.4 実験9が実験5に届かない理由

「v4 相当の optimizer 設定」は忠実にコピーしたが、それだけでは v4 相当にはならなかったようだ。

  • 初期値: 実験9 Stage A は v5 の 300ep diffusion checkpoint(音符追従の弱い局所解)から warm-start している。v4 の lf0 は v4 レジーム(全ストリーム BiLSTM 同時学習)で得た別の局所解。
  • 実効学習予算: StepLR 20ep 半減で epoch 60 以降は lr がほぼゼロ、train loss も横ばい。 約 40 実効 epoch の微小ステップでは別の局所解へ移れない。

4. 現時点の結論

  • v5 のジョイント学習、および v5 の局所解からの短時間 fine-tune は、良好な lf0 を生まない。
  • 実用構成は 実験5: v4 の lf0/vuv を warm-start + freeze し、diffusion mgc/bap のみ学習。
  • mgc[0] 不安定性は継続学習では解消しないが、denoise_fn の出力側のみ unfreeze する部分 fine-tune(実験7)で明確に抑えられることを確認(実験6 比 diff_std −25〜49 %)。ただし土台の lf0 が悪いため、今後追加実験を予定。
  • 「v5 の lf0_model が原理的にダメ」なのか「v5 のジョイント学習の挙動が lf0 を悪い局所解に押し込むだけ」なのかは未切り分け。後者であれば、v4 レジームの初期値から学習すれば改善可能性がある。
  • 現状で素の v5 で良好な結果を得られるのは、単一歌唱者・高品質・6.5 時間と大容量の波音リツのみでは?

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?