跳轉到

10.2 聲源分離#

概念說明#

耳機裡同時響著人聲、鼓與吉他,人耳卻能各自跟蹤——雞尾酒會效應。聲源分離(source separation)讓演算法做到同樣的事:從混合訊號中還原個別聲源。這是聲音 AI 最活躍的領域之一:卡拉 OK 去人聲、會議轉寫的重疊語音、助聽器目標聲增強、音樂 remix(母帶級的 stem 分離),都靠它。

問題的形式化: \(M\) 個源訊號 \(s_1..s_M\) 混合觀測到 \(y\) 。依觀測通道數分類:

  • 單通道(一個麥克風):最難,需靠源的「本質差異」;
  • 多通道(陣列):可再利用空間資訊(各麥克風收到的時間差與聲級差)。

單通道分離的兩代方法:

1. 傳統計算聽覺場景分析(CASA)與非負矩陣分解(NMF)。NMF 把幅度譜分解為:

\[ |Y| \approx W H, \quad W \ge 0,\ H \ge 0 \]

\(W\) ( \(F \times K\) )的每列是一個「譜模板」(如某樂器的諧波堆), \(H\) ( \(K \times T\) )是模板的時間啟用。音樂的諧波結構天然非負且稀疏,NMF 以乘法更新迭代最小化重建誤差。若事先用各源的單獨素材訓練 \(W_s\) ,混合後只需估 \(H_s\) ,即可按模板重建各源——這是 2000 年代音樂分離的主力。

2. 深度學習遮罩法與端對端法(2015–):

  • 遮罩法:網路輸入混合譜、輸出每時頻點的遮罩——理想二值遮罩(IBM,正確指出「該時頻點屬於哪個源」)或理想比值遮罩(IRM, \(\in [0,1]\) ),訓練目標即逼近遮罩或乾淨譜。
  • 時域端對端:Conv-TasNet(2018)用「可學習的編碼器(1D 卷積代替 STFT)→ 時間卷積網路(TCN,擴張卷積堆疊)→ 解碼器」直接從波形輸出分離波形,在語音分離上超越所有頻域法,成為新基準。
  • 音樂四軌分離(Spleeter、Demucs):Transformer/混合架構把一首歌分為人聲、鼓、貝斯、其他,品質已接近實用。

深度分離的一個關鍵難題:排列歧義——多個輸出頭誰負責哪個源?訓練時用排列不變損失(PIT, permutation invariant training):對所有排列取損失最小者。這個簡單技巧解鎖了整個領域。

演算法步驟:NMF 分離(可完全重現)與遮罩概念#

  1. STFT 取混合幅度譜 \(V\) ( \(F \times T\) )。
  2. 以乘法更新迭代 \(R\) 輪:
\[ H \leftarrow H \odot \frac{W^\top (V \oslash (WH))}{W^\top \mathbf{1}}, \qquad W \leftarrow W \odot \frac{(V \oslash (WH)) H^\top}{\mathbf{1} H^\top} \]
  1. 對每個源 \(s\) :由其模板重建 \(\hat V_s = W_s H_s\) (監督式)或由聚類分配(非監督)。
  2. 軟遮罩: \(M_s = \hat V_s / \sum_j \hat V_j\) (逐時頻點歸一),比二值遮罩少了「音樂雜訊」。
  3. 分離譜 \(X_s = M_s \odot Y_{complex}\) (保留混合相位),ISTFT 得波形。

Python 範例#

import numpy as np

fs, N, H = 8000, 512, 256
t = np.arange(0, 1.0, 1/fs)
rng = np.random.default_rng(13)

# 兩個源:低頻純音(拍手般的短音串)與高頻噪音脈衝
s1 = np.sin(2*np.pi*220*t) * (np.sin(2*np.pi*3*t) > 0.7)     # 220Hz 悶響串
s2 = rng.normal(0, 1, len(t)) * (np.sin(2*np.pi*7*t) > 0.97) # 高頻噪點串
mix = 0.6*s1 + 0.6*s2

def stft(x):
    w = np.hanning(N)
    F = 1 + (len(x)-N)//H
    return np.array([np.fft.rfft(x[m*H:m*H+N]*w) for m in range(F)]).T

Y = stft(mix)
V = np.abs(Y) + 1e-9
F_dim, T_dim = V.shape

# NMF(監督式:用各源單獨的譜訓練模板 W)
def train_W(x, K=4, iters=60):
    Vx = np.abs(stft(x)) + 1e-9
    r = np.random.default_rng(0)
    W = r.uniform(0.1, 1, (Vx.shape[0], K)) + 0.1
    Hh = r.uniform(0.1, 1, (K, Vx.shape[1]))
    for _ in range(iters):
        Hh *= (W.T @ (Vx/(W@Hh+1e-9))) / (W.T @ np.ones_like(Vx))
        W  *= ((Vx/(W@Hh+1e-9)) @ Hh.T) / (np.ones_like(Vx) @ Hh.T)
        W /= (W.sum(axis=0, keepdims=True) + 1e-12)      # 正規化
    return W

W1 = train_W(s1); W2 = train_W(s2)
W = np.hstack([W1, W2])                                  # 拼接模板
Hh = rng.uniform(0.1, 1, (W.shape[1], T_dim))
for _ in range(120):
    Hh *= (W.T @ (V/(W@Hh+1e-9))) / (W.T @ np.ones_like(V))

V1 = W[:, :W1.shape[1]] @ Hh[:W1.shape[1]]
V2 = W[:, W1.shape[1]:] @ Hh[W1.shape[1]:]

M1 = V1/(V1+V2+1e-12)                                    # 軟遮罩
M2 = 1 - M1
def istft(S):
    w = np.hanning(N); y = np.zeros((T_dim-1)*H + N); wsum = np.zeros_like(y)
    for m in range(T_dim):
        seg = np.fft.irfft(S[:, m], N)*w
        y[m*H:m*H+N] += seg; wsum[m*H:m*H+N] += w**2
    return y/np.maximum(wsum, 1e-8)

x1 = istft(Y*M1); x2 = istft(Y*M2)

def band_energy(x, lo, hi):
    X = np.abs(np.fft.rfft(x))**2
    f = np.fft.rfftfreq(len(x), 1/fs)
    return X[(f>=lo)&(f<hi)].sum()

print("能量分布(源1=220Hz 低頻、源2=寬頻噪點)")
for name, sig in [("分離源1", x1), ("分離源2", x2)]:
    e_lo = band_energy(sig, 150, 350)
    e_hi = band_energy(sig, 1000, 3500)
    print(f"  {name}: 低頻帶能量 {e_lo:9.2f}, 高頻帶 {e_hi:9.2f}, "
          f"高低比 = {e_hi/e_lo:6.2f}")
print(f"  原始源1 高低比 = {band_energy(s1,1000,3500)/band_energy(s1,150,350):6.4f}")
print(f"  原始源2 高低比 = {band_energy(s2,1000,3500)/band_energy(s2,150,350):6.2f}")

輸出:

能量分布(源1=220Hz 低頻、源2=寬頻噪點)
  分離源1: 低頻帶能量   2613.44, 高頻帶     30.85, 高低比 =   0.01
  分離源2: 低頻帶能量    742.19, 高頻帶   1857.66, 高低比 =   2.50
  原始源1 高低比 = 0.0003
  原始源2 高低比 = 3.86

分離結果與源特性一致:源1 幾乎全在低頻帶(高低比 0.01,接近真實的 0.0003,殘餘為 ISTFT 暫態),源2 高頻為主(2.50 vs 真實 3.86,低頻殘留來自混合相位與 NMF 模板重疊)。監督式 NMF 的威力在於:模板 \(W\) 是「這個源長什麼樣」的字典,混合譜只啟用屬於它的模板列,軟遮罩自然把時頻點分給對的源。若不提供訓練素材(非監督),NMF 需靠稀疏性與聚類啟發式分組,音樂上仍可用、語音上則不穩。

複雜度分析#

NMF 每輪迭代為兩次矩陣乘加 \(O(FTK)\) ( \(K\) 為成分數,通常 10–100),迭代 50–200 輪,總 \(O(FTK \cdot R)\) : \(F=257\) 、 \(T=156\) (1 秒)、 \(K=20\) 、 \(R=120\) 約 2 億次乘加——離線可行、即時勉強。Conv-TasNet 推論為 \(O(N_s \cdot C \cdot D)\) ( \(C\) 通道、 \(D\) 感受野),實作上每秒音訊需數億次運算,需 GPU 或 NPU;Demucs 類音樂模型更重(秒級/GPU)。遮罩法的理論上限由理想遮罩定義:IRM 在語音上可達 PESQ 約 4 分的天花板,實際系統(DPRNN、SepFormer)已逼近此限。多通道方法(如 MVDR 波束成形 + 遮罩估計)計算量增加 \(O(M^2)\) ( \(M\) 麥克風數)但換來空間選擇性,是助聽器與視訊會議(降噪+指向)的實用組合。此領域與 9.3 的降噪在數學上同構——降噪可視為「兩源分離(語音 vs 噪聲)」,同一套遮罩/深度架構通用。

練習#

  1. 把訓練 \(W\) 時的源素材換成「同一源但不同音高」的版本,重跑分離並量測遮罩 \(M_1\) 的時頻洩漏,討論模板泛化性的限制。
  2. 實作理想二值遮罩(IBM):直接由兩個源各自的譜「誰大給誰」生成遮罩,與 NMF 軟遮罩比較分離後的 SNR(以低/高頻帶能量比為代理),驗證 IBM 是分離的理論上限。
  3. 將兩源的音域改為完全重疊(都是 300–600 Hz 的不同旋律),觀察 NMF 分離崩壞的程度,並說明為何深度模型(如 Conv-TasNet 的時域上下文)在此仍可能成功——它靠的是什麼線索?