Seed-VC 架構深度解析 — 以四階段結構將聲音分解為「誰、說什麼、怎麼說」

Back
Category : News

orca_forge

📝 原文(日文)發表於 forge.workstyle.tech



理解「聲音」——分解其組成要素

當你想把錄下的聲音轉換成另一個人的聲音時,第一個浮現的問題是:

「聲音」究竟是什麼?

即使說出相同的詞句,不同的說話者會產生不同的聲音。即使是同一位說話者,根據所說內容的不同也會產生不同的聲音。再加上語調變化,就會更加不同。換句話說,語音是一種由多個獨立資訊混合而成的訊號——至少包括:誰在說話(說話者身分)說了什麼(內容)、以及怎麼說的(韻律)

語音轉換(VC)的核心挑戰就在這裡。如果你單純地處理語音來替換說話者身分,內容和語調往往也會跟著改變。在這篇文章中,我們將說明我們採用作為「聲音設計」應用程式後端的 Seed-VC 如何解決這個問題——透過使用四個模組分離並處理資訊,同時逐步講解實際的模型載入與推論程式碼。




設計理念:將可分離的資訊委託給專用模組

Seed-VC 的核心思想是:「不要讓單一大型模型包辦一切。」而是按類型分解語音的組成要素,將每個要素分配給專用模組,最後再將它們重新組合起來。啟動時的模型載入流程如下:

model, semantic_fn, f0_fn, vocoder_fn, campplus_model, mel_fn, mel_fn_args = load_models(a)

進入全螢幕模式

退出全螢幕模式

雖然回傳了七個元件,但可以歸納為四個功能層:

  1. whisper (semantic_fn) — 提取內容(說了什麼)作為語意特徵
  2. campplus (campplus_model) — 將說話者身分(誰在說話)編碼成單一向量嵌入
  3. CFM/DiT (model.cfm) — 以擴散模型根據上述兩者生成梅爾頻譜圖
  4. BigVGAN (vocoder_fn) — 將梅爾頻譜圖轉換回可聽見的波形

每個角色——「提取語意」、「提取說話者」、「繪製頻譜圖」、「轉換成聲音」——都乾淨地分離開來。這種分工就是我們能夠只替換說話者身分,同時保持其他一切不變的原因。讓我們依序來看每個步驟。




步驟 1:whisper — 只提取「說了什麼」

在內容提取上,我們使用語音辨識模型 Whisper 的編碼器。我們不使用解碼器(轉錄)。而是直接取用其中間表示(一序列語意特徵)作為特徵向量。

s_alt = _semantic(torchaudio.functional.resample(src_t, _S["sr"], 16000))

進入全螢幕模式

退出全螢幕模式

一個關鍵點:輸入在處理前會重新取樣到16kHz。由於 Whisper 是為 16kHz 音訊所設計,即使主要轉換模型以 44.1kHz 運作,我們在語意提取前總是會降採樣到 16kHz。產生的序列幾乎不包含音高或音色資訊——它代表的是與所說內容對齊的表示。這是我們之後能夠自由替換說話者身分的先決條件。

注意:Whisper 有一個限制——一次只能處理最多 30 秒的音訊。較長的音訊需要進行分塊處理。這個限制本身曾是個重大陷阱(詳見我們的另一篇文章:「語音轉換中『說話變慢』臭蟲的元兇是 Whisper 的 30 秒限制」)。




步驟 2:campplus — 將「誰在說話」壓縮成 192 維向量

說話者身分提取由CAMPPlus這個說話者嵌入模型負責。它接收音訊,計算 fbank 特徵,並輸出一個單一的 192 維向量

feat = torchaudio.compliance.kaldi.fbank(w16, num_mel_bins=80, dither=0, sample_frequency=16000)
feat = feat - feat.mean(dim=0, keepdim=True)
return _S["campplus_model"](feat.unsqueeze(0)).squeeze().detach().cpu().numpy()

進入全螢幕模式

退出全螢幕模式

「說話者身分 = 單一向量」這件事對我們的「聲音設計」應用程式至關重要。因為向量可以相加、混合或內插,我們會預先從 18 位說話者的乾淨錄音計算嵌入作為「錨點」,然後根據滑桿輸入使用加權平均來混合它們。

w = design_weights(slider_values, bank, **kw)
emb = (w[:, None] * bank.embeddings).sum(axis=0)

進入全螢幕模式

退出全螢幕模式

我們為「年齡」、「音高」、「沙啞度」等可解釋軸定義滑桿,計算權重 w,然後對錨點嵌入進行加權平均。這種「透過混合聲音來設計」的操作之所以可行,正是因為說話者身分被模組化為獨立的向量。如果說話者身分與內容糾纏在一起,混合就會破壞所說的詞句。




步驟 3:CFM/DiT — 使用擴散來「繪製」梅爾頻譜圖

一旦我們取得語意特徵(內容)和說話者嵌入(身分),就會進入合成階段。這就是擴散模型(CFM:Conditional Flow Matching;以 DiT 實作)登場的地方。它從隨機噪聲開始,逐步生成以輸入為條件的梅爾頻譜圖。

vt = _S["model"].cfm.inference(cat, torch.LongTensor([cat.size(1)]).to(dev),
                               mel2, style, None, STEPS, inference_cfg_rate=CFG)
vt = vt[:, :, mel2.size(-1):]

進入全螢幕模式

退出全螢幕模式

觀察引數可以看出這個階段如何整合先前模組的輸出:

  • cat — 語意特徵(內容)。提示音訊(pc)與目標條件(cond)的串接
  • style — 來自步驟 2 的說話者嵌入(= 混合後的設計嵌入)
  • mel2 — 提示音訊的梅爾頻譜圖。生成後,我們會裁剪前 mel2.size(-1) 幀,只保留新內容
  • STEPS / inference_cfg_rate — 擴散步數與無分類器引導的強度

模型以分離的引數接收內容(cat說話者身分(style。它將它們解釋為指令:「用這個說話者的聲音繪製這個內容。」如果你固定其中一個並改變另一個,只有預期的屬性會改變。這種分離設計直接反映在推論介面上。




處理韻律:length_regulator 與 F0

「怎麼說的」——韻律(速度與音高變化)則有些不同。它無法完全與內容解耦,因此不使用專用模組,而是作為內容與說話者身分之間的連接點來處理。

首先是速度(持續時間)。來自 Whisper 的語意序列需要被拉伸以符合所需的梅爾幀數。這由 length_regulator 負責。

cond, *_ = _S["model"].length_regulator(s_alt, ylens=torch.LongTensor([mel.size(2)]).to(dev),
                                        n_quantizers=3, f0=None)

進入全螢幕模式

退出全螢幕模式

透過傳入目標幀長(ylens),語意序列會被調整到所需的持續時間。接著是音高(F0)。在 44.1kHz F0 條件模型中,length_regulator 可以接受 f0 引數,得以進行韻律控制,例如將載音的音高範圍向目標說話者的範圍偏移。

lf[F0_alt > 1] = lf[F0_alt > 1] - m_alt + m_ori   # 將載音 F0 偏移至目標中位數
shifted = torch.exp(lf)
cond, *_ = _S["model"].length_regulator(S_alt, ylens=tgt_len, n_quantizers=3, f0=shifted)

進入全螢幕模式

退出全螢幕模式

這是一個簡單的操作:在對數域中依中位數差進行偏移。韻律並未被視為獨立模組,而是作為注入的參數。這反映了一種務實的妥協——嘗試完全解耦往往會導致不自然的結果。




步驟 4:BigVGAN — 將梅爾轉換回可聽見的聲音

擴散模型生成的是梅爾頻譜圖——一種聲音的「藍圖」。最後一步,將它轉換成人類能聽見的波形,由聲碼器 BigVGAN 負責。

wav = _S["vocoder_fn"](vt.float()).squeeze().detach().cpu().float().numpy().reshape(-1)

進入全螢幕模式

退出全螢幕模式

雖然只是一行程式碼,但這一步對音質影響很大。即使使用相同的梅爾頻譜圖,不同的聲碼器也會產生不同的聲音。我們的 44.1kHz 模型能夠輸出「真正的寬頻」,正是因為 BigVGAN 是以高取樣率訓練的(詳見我們的另一篇文章:「22.05kHz 與 44.1kHz」)。




陷阱與經驗教訓

以下是實作與操作這個四層架構時的一些重要心得:

  • 各層的取樣率不同。語意提取固定在 16kHz,轉換模型以 22.05kHz 或 44.1kHz 運作,而說話者嵌入使用 16kHz fbank。如果誤解各層預期的取樣率,音質就會在不知不覺中下降。在每個階段明確重新取樣是最安全的做法。
  • 將說話者身分表示為單一向量開啟了各種應用。如果說話者身分被嵌入在擴散模型內部,我們就無法實作「聲音混合」。模組化分離不僅關乎品質——它還直接提升了產品的表現力
  • 韻律無法完全分離——應據此設計。速度與 F0 和內容緊密相關,因此將它們作為透過 length_regulator 注入的條件參數是務實的解決方案。強行完全解耦往往會產生不自然的偽影。
  • 每一層都可以替換。聲碼器可以在 BigVGAN、HiFiGAN 或 Vocos 之間切換。語意提取器可以在 Whisper 與 CNHuBERT 之間切換。模組化設計帶來擴展性——每個階段都可以獨立升級。



總結

  • Seed-VC 的語音轉換採用四層架構whisper(語意)+ campplus(說話者)+ CFM/DiT(擴散梅爾生成)+ BigVGAN(聲碼器)
  • 設計理念是將語音分解為「誰、說什麼、怎麼說」,並將每個部分分配給專用模組
  • 透過將說話者身分表示為單一 192 維向量,我們得以混合多位說話者——這是我們「聲音設計」應用程式的核心
  • 擴散模型以分離的引數接收內容(cat)與說話者身分(style)——這種分離設計直接反映在推論介面上
  • 韻律(速度與 F0)並未完全解耦;而是作為條件參數透過 length_regulator 注入
  • 每一層在不同的取樣率與角色下運作,且都可替換。這種模組化提升了品質、表現力與擴展性。

https://dev.to/orca_forge/in-depth-explanation-of-the-seed-vc-architecture-decomposing-voice-into-who-what-and-how-in-a-3hjh

https://www.worldprogramming.org/posts/in-depth-explanation-of-the-seed-vc-architecture-decomposing-voice-into-who-what-and-how-in-a-4-stage-structure-kmicmk