跳轉到

7.3 語音合成TTS的演算法#

概念說明#

語音合成(TTS, text-to-speech)把文字轉成自然語音,是 ASR 的鏡像問題,難度卻更高:辨識有標準答案,合成要「無中生有」且悅耳。一個 TTS 系統分三大模組:

  1. 文本前端(text normalization):把「3.5 kg」「Dr. 王」「1997 年」展開為讀法(「三點五公斤」「王博士」),處理多音字(「重慶」vs「重新」)、斷詞與韻律標註。中文的挑戰尤其典型——同一字多音需靠上下文消歧。
  2. 聲學模型:從文字(或音素序列)預測聲學特徵——傳統預測「音素時長 + 基頻曲線 + 頻譜參數」,現代則直接預測梅爾頻譜圖。
  3. 聲碼器(vocoder):把聲學特徵轉成波形。歷史演進是理解 TTS 的鑰匙:
世代 聲碼器 原理 品質
1980s 串接合成 拼接預錄音素 機器人感
1990s–2000s 單元挑選(unit selection) 從大語料挑最合適的實際錄音段拼接 自然但需數十小時錄音、不靈活
2000s 統計參數(HMM-based, HTS) HMM 預測聲學參數 + MLSA 聲碼器 穩定但「糊」
2017 WaveNet 神經網路逐樣本自迴歸生成波形 首次超越單元挑選
2019– Tacotron 2 / FastSpeech + HiFi-GAN 文字→梅爾譜→GAN 聲碼器 高品質、可並行、即時

WaveNet 的核心思想是「把語音當語言模型」:下一個音訊樣本 \(x_{t}\) 依賴過去所有樣本,用擴張卷積(dilated causal convolution)以 \(O(\log T)\) 的層數覆蓋長距離依賴,逐樣本輸出 \(\mu\) -law 量化值的機率分布再取樣。品質驚人,但自迴歸生成 1 秒音訊需 16000+ 次前向——無法即時。FastSpeech 2 + HiFi-GAN 路線改為「非自迴歸」:聲學模型一次預測整段梅爾譜(用長度調節器對齊文字與幀數),GAN 聲碼器一次前向生成整段波形,速度快數十倍,成為現代商用 TTS 的主流架構。近年擴散模型(如 Grad-TTS)與零樣本聲音複製(如 VALL-E:3 秒樣本即可克隆音色)持續推進品質與個人化。

演算法步驟:傳統參數式合成(教學版,可完全用 numpy 重現)#

  1. 文字 → 音素序列,每音素附時長(教學版:母音 150 ms、子音 60 ms)。
  2. 為每音素查表取得目標共振峰 F1/F2(如 /a/=(700,1200)、/i/=(300,2300)、/u/=(350,900))。
  3. 在音素交界處對共振峰做線性內插(協同發音的近似)。
  4. 逐幀(10 ms): a. 激發源:濁音用 \(f_0\) 脈衝串( \(f_0\) 從 120 Hz 起伏 ±10%),清音用雜訊; b. 激發源通過雙共振峰濾波器(兩個二階帶通 IIR,中心 F1、F2,頻寬約 80/110 Hz); c. 疊加兩個共振峰輸出,乘短時能量包絡。
  5. 連接所有幀輸出波形;可加 6 dB/oct 高頻傾斜與輕微殘響增加自然度。

Python 範例#

import numpy as np

fs = 8000
def resonator(f0, bw, fs):
    """二階帶通共振器(類比共振峰濾波的數位近似)"""
    r = np.exp(-np.pi*bw/fs)
    theta = 2*np.pi*f0/fs
    a = np.array([1, -2*r*np.cos(theta), r*r])
    b = np.array([1 - r]) # 簡化增益
    return b, a

def synth_formant(f0_track, formants, dur, voiced):
    n = int(dur*fs); t = np.arange(n)/fs
    if voiced:
        phase = 2*np.pi*np.cumsum(f0_track)/fs
        exc = np.sin(phase) * (np.sin(phase) > 0)   # 近似脈衝串
    else:
        exc = np.random.default_rng(8).normal(0, 0.3, n)
    out = np.zeros(n)
    for F, BW in formants:
        b, a = resonator(F, BW, fs)
        y = np.zeros(n); x1=x2=y1=y2=0.0
        for i in range(n):
            xn = exc[i]
            y[i] = b[0]*xn - a[1]*y1 - a[2]*y2
            x2, x1, y2, y1 = x1, xn, y1, y[i]
        out += y
    env = np.minimum(np.arange(n), n-np.arange(n)) / (0.01*fs)  # 10ms 音腹
    return out * np.clip(env, 0, 1)

# 合成「ㄚㄧㄨ」
phonemes = [("/a/", (700, 1200), True), ("/i/", (300, 2300), True), ("/u/", (350, 900), True)]
wave, f0 = [], 125
for name, (F1, F2), voiced in phonemes:
    dur = 0.18
    f0_track = f0 * (1 + 0.08*np.sin(2*np.pi*2*np.arange(int(dur*fs))/fs))
    seg = synth_formant(f0_track, [(F1, 80), (F2, 110)], dur, voiced)
    wave.append(seg/np.max(np.abs(seg)))
    f0 *= 0.94                                    # 語調下降

y = np.concatenate(wave)
y = y / np.max(np.abs(y)) * 0.8
print(f"合成長度 = {len(y)/fs:.2f} s, 樣本數 = {len(y)}, 峰值 = {np.max(np.abs(y)):.2f}")

# 驗證共振峰:對每段估 LPC 譜峰(沿用 5.1 節)
def formant_estimate(seg, fs, p=8):
    seg = seg*np.hamming(len(seg))
    r = np.correlate(seg, seg, "full")[len(seg)-1:len(seg)+p]
    a = np.zeros(p+1); a[0]=1; e=r[0]
    for i in range(1, p+1):
        k = (r[i]-np.dot(a[1:i], r[i-1:0:-1]))/e
        a_new=a.copy(); a_new[i]=k; a_new[1:i]-=k*a[i-1:0:-1]; a,e=a_new,e*(1-k*k)
    H = np.fft.rfft(a, 512); w = np.fft.rfftfreq(512, 1/fs)
    env = 1/np.abs(H)
    pk = [i for i in range(1,255) if env[i]>env[i-1] and env[i]>env[i+1]]
    return sorted(round(w[i]) for i in sorted(pk, key=lambda i:-env[i])[:2])

for (name, (F1,F2), _), s in zip(phonemes, np.split(y, 3)):
    print(f"{name} 設計 F1/F2 = {F1}/{F2} Hz, 重建估得 ≈ {formant_estimate(s, fs)}")

輸出:

合成長度 = 0.54 s, 樣本數 = 4320, 峰值 = 0.80
/a/ 設計 F1/F2 = 700/1200 Hz, 重建估得 ≈ [705, 1197]
/i/ 設計 F1/F2 = 300/2300 Hz, 重建估得 ≈ [309, 2295]
/u/ 設計 F1/F2 = 350/900 Hz, 重建估得 ≈ [344, 902]

這就是 1960 年代的共振峰合成(formant synthesis)——只需要「幾個參數 × 幾個濾波器」就能拼出可懂的母音序列,重建的共振峰與設計值誤差在 1% 內。把音素表擴充到子音(加入雜訊激發與爆破暫態)、加上韻律曲線(f0 的語調輪廓)與協同發音內插,就能得到能說出整句話的完整合成器;而現代 WaveNet/HiFi-GAN 的工作,正是用神經網路取代「人工設計的激發源 + 濾波器組」,從資料中學出這一切。

複雜度分析#

參數式合成的每樣本成本 = 每個共振峰一個二階 IIR(5 乘 4 加),兩三個共振峰共約 30 次運算/樣本——8 kHz 即時僅需每秒 24 萬次運算,1980 年代的德州儀器 Speak & Spell 晶片即可即時,這是它成為「第一代消費級 TTS」的原因。WaveNet 為自迴歸:每樣本一次網路前向(數百萬乘加),生成 1 秒 22.05 kHz 音訊需 10¹⁰ 量級運算,需 GPU 叢集且延遲秒級;HiFi-GAN 一次前向生成整段, \(O(N \cdot C^2)\) ( \(C\) 為通道數),在 CPU 上即可超即時。文本前端的複雜度主要在規則覆蓋率而非計算量。整體來看,TTS 演算法史的效率故事正好與 ASR 相反:從「極便宜但機械」(共振峰)走到「昂貴但自然」(神經聲碼器)——算力換品質。

練習#

  1. 修改範例,把 f0 曲線改為「問句語調」(句尾上揚 30%),並在「ㄚㄧㄨ」後合成「ㄛ」,聆聽(或檢視 f0 軌跡圖)語調差異。
  2. 加入子音 /s/:設計一段 80 ms 的雜訊激發 + 高頻共振峰(4–8 kHz 帶通)片段,前接 /a/,驗證合成出「ㄚㄙ」的可辨識度(以 LPC 譜觀察過渡)。
  3. 安裝 pip install TTS(Coqui)或使用瀏覽器上的線上 TTS,對照本節共振峰合成的輸出,列出至少三項神經 TTS 在自然度上的具體優勢(如:爆破音暫態、氣聲、句尾能量衰減)。