在深度學習和語音處理領域,如何將原始音頻信號有效地表示為離散的“Token”序列,是語音識別、音頻生成等任務中的關鍵問題。常見的方法如Mel頻譜圖+向量量化(VQ)、wav2vec等已經非常成熟,但這些模型通常依賴復雜的神經網絡結構。
本文介紹一種輕量級、可解釋性強的音頻特征提取與“類Token化”編碼方法,僅使用余弦相似度、線性回歸和歸一化技術,即可將一段WAV音頻轉化為一個整數序列——我們稱之為“偽Token”序列。這種方法雖然不能替代現代語音模型,但非常適合用于教學、探索性數據分析或輕量級嵌入式應用。
🎯 目標
我們將實現以下功能:
- 讀取一段
.wav
音頻文件; - 對音頻進行預處理(去均值、標準化);
- 使用滑動窗口對相鄰樣本做線性回歸分析;
- 提取回歸斜率與擬合效果(余弦相似度)作為雙特征;
- 將特征歸一化并量化為整數,形成“Token”序列;
- 可視化結果并封裝成函數。
🔧 核心工具與庫
import numpy as np
import matplotlib.pyplot as plt
from scipy.io import wavfile
同時為了支持中文標題顯示,設置 Matplotlib 的字體:
plt.rcParams['font.sans-serif'] = ['Microsoft YaHei']
plt.rcParams['axes.unicode_minus'] = False
📐 核心算法定義
1. 余弦相似度(Cosine Similarity)
衡量兩個向量方向的一致性,反映線性擬合的質量。
def cosine_similarity(a, b):return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
2. 最小二乘法線性回歸
手動實現一元線性回歸,避免依賴 sklearn
。
def linear_regression(x, y):n = len(x)sum_x, sum_y = np.sum(x), np.sum(y)sum_xy = np.sum(x * y)sum_x2 = np.sum(x ** 2)slope = (n * sum_xy - sum_x * sum_y) / (n * sum_x2 - sum_x ** 2)intercept = (sum_y - slope * sum_x) / nreturn slope, intercept
3. Min-Max 歸一化(防除零)
def min_max_normalize(data):min_val = np.min(data)max_val = np.max(data)return (data - min_val) / (max_val - min_val + 1e-8)
🧪 主流程解析:main()
函數
我們以文件 "sound/cat/1-47819-C-5.wav"
為例,演示整個流程。
1. 音頻讀取與預處理
_, audio = wavfile.read("sound/cat/1-47819-C-5.wav")
left = (audio - np.mean(audio)) / np.std(audio) # 標準化
?? 注意:這里假設音頻是單聲道。如果是立體聲,請取某一通道(如
audio[:, 0]
)。
接著構造“左移-右移”數據對:
left = left[:left.size // 2 * 2] # 確保長度為偶數
left = left[:-1] # 去最后一個元素
right = left[1:] # 右移一位
這相當于構建了 (x_t, x_{t+1})
的時間序列對,可用于分析局部動態變化。
2. 滑動窗口分析
使用大小為 3200 的窗口,每步移動 1600(半重疊),進行局部線性擬合:
for i in range(0, len(left) - 1600, 1600):x, y = left[i:i + 3200], right[i:i + 3200]if len(x) > len(y): x = x[:len(y)]slope, intercept = linear_regression(x, y)sim = cosine_similarity(slope * x + intercept, y)sim_list.append(sim)slope_list.append(slope)
x
: 當前窗口內的原始信號片段;y
: 對應的“下一時刻”信號(右移);- 使用線性模型
y ≈ slope * x + intercept
擬合; - 計算預測值與真實值之間的余弦相似度,評估擬合質量;
- 記錄每個窗口的
slope
和similarity
。
3. 特征歸一化與“Token化”
將連續特征映射到整數空間,模擬 Token 編碼過程:
sim_list = min_max_normalize(sim_list) * 2048
slope_list = min_max_normalize(slope_list) * 2048 + 2048
sim_list
→ [0, 2048]slope_list
→ [2048, 4096]
然后轉換為整型:
sim_list = sim_list.astype(np.int16)
最后將兩者交錯拼接成一維序列:
tokens = np.hstack([sim_list, slope_list]).reshape([2, -1]).transpose([1, 0]).reshape(-1)
這一步實現了雙通道特征的交織編碼。
4. 可視化 Token 序列
plt.plot(tokens)
plt.title("音頻生成的偽Token序列")
plt.xlabel("Token索引")
plt.ylabel("Token值")
plt.show()
💡 封裝函數:wav_to_token(path)
我們將上述邏輯封裝為通用函數,適用于任意 .wav
文件:
def wav_to_token(path):_, audio = wavfile.read(path)# 單通道處理if len(audio.shape) > 1:audio = audio[:, 0]left = (audio - np.mean(audio)) / np.std(audio)left = left[:left.size // 2 * 2][:-1]right = left[1:]sim_list = []slope_list = []# 更小的窗口:1600采樣點,每800步滑動for i in range(0, len(left) - 800, 800):x = left[i:i+1600]y = right[i:i+1600]if len(x) != len(y):min_len = min(len(x), len(y))x, y = x[:min_len], y[:min_len]slope, intercept = linear_regression(x, y)pred = slope * x + interceptsim = cosine_similarity(pred, y)sim_list.append(sim)slope_list.append(slope)# 歸一化到 0~64 范圍(2^6)sim_tokens = min_max_normalize(np.array(sim_list)) * 64slope_tokens = min_max_normalize(np.array(slope_list)) * 64sim_tokens = sim_tokens.astype(np.int16)slope_tokens = slope_tokens.astype(np.int16)# 合并為乘積特征(非零過濾)res = sim_tokens * slope_tokensreturn res[res != 0] # 去除零值
? 返回的是一個整數數組,可視為該音頻的“特征Token序列”。
📊 方法特點總結
項目 | 描述 |
---|---|
優點 | - 不依賴深度學習框架 - 可解釋性強 - 計算開銷小 - 可用于邊緣設備 |
局限 | - 表達能力有限 - 無法捕捉高頻語義 - 對噪聲敏感 |
適用場景 | - 音頻分類初篩 - 異常聲音檢測 - 教學演示 - 低資源環境下的特征提取 |
🚀 拓展思路
你可以在此基礎上進一步改進:
- 加入頻域特征:對每個窗口做FFT,提取主頻作為第三Token維度;
- 向量量化(VQ):用KMeans對
(sim, slope)
向量聚類,真正生成離散Token; - 滑動窗口自適應:根據能量或過零率動態調整窗口大小;
- 時間對齊編碼:引入DTW對齊不同長度的Token序列;
- 用于對比學習:計算不同音頻Token序列間的距離,做相似性匹配。
📎 完整代碼下載
你可以將以下完整代碼保存為 audio_tokenizer.py
并運行:
import numpy as np
import matplotlib.pyplot as plt
from scipy.io import wavfile# 中文顯示支持
plt.rcParams['font.sans-serif'] = ['Microsoft YaHei']
plt.rcParams['axes.unicode_minus'] = Falsedef cosine_similarity(a, b):"""計算余弦相似度"""return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))def linear_regression(x, y):"""最小二乘法線性回歸"""n = len(x)sum_x, sum_y = np.sum(x), np.sum(y)sum_xy = np.sum(x * y)sum_x2 = np.sum(x ** 2)slope = (n * sum_xy - sum_x * sum_y) / (n * sum_x2 - sum_x ** 2)intercept = (sum_y - slope * sum_x) / nreturn slope, interceptdef min_max_normalize(data):min_val = np.min(data)max_val = np.max(data)return (data - min_val) / (max_val - min_val + 1e-8)def wav_to_token(path):_, audio = wavfile.read(path)if len(audio.shape) > 1:audio = audio[:, 0] # 取左聲道# 標準化left = (audio - np.mean(audio)) / np.std(audio)left = left[:len(left)//2*2][:-1]right = left[1:]sim_list = []slope_list = []for i in range(0, len(left) - 800, 800):x = left[i:i+1600]y = right[i:i+1600]if len(x) != len(y):min_len = min(len(x), len(y))x, y = x[:min_len], y[:min_len]slope, intercept = linear_regression(x, y)pred = slope * x + interceptsim = cosine_similarity(pred, y)sim_list.append(sim)slope_list.append(slope)sim_arr = min_max_normalize(np.array(sim_list)) * 64slope_arr = min_max_normalize(np.array(slope_list)) * 64sim_arr = sim_arr.astype(np.int16)slope_arr = slope_arr.astype(np.int16)res = sim_arr * slope_arrreturn res[res != 0]def main():path = "sound/cat/1-47819-C-5.wav"tokens = wav_to_token(path)plt.figure(figsize=(10, 4))plt.plot(tokens)plt.title("音頻生成的偽Token序列")plt.xlabel("Token索引")plt.ylabel("Token值")plt.grid(True)plt.tight_layout()plt.show()if __name__ == "__main__":main()
📣 結語
本文提出了一種新穎而直觀的方式,將音頻信號通過線性動力學建模 + 特征量化的方式轉換為離散序列。雖然它不是真正的“語音Token”,但它啟發我們思考:是否可以用更簡單的方法逼近復雜模型的部分能力?
這種“白盒”方法有助于理解音頻特征的本質,也為輕量級系統提供了一種可行的替代方案。
🔗 后續計劃:我們將嘗試用這類Token訓練一個RNN來“復現”原始音頻,敬請期待!
📌 關鍵詞:音頻處理、Token化、線性回歸、余弦相似度、滑動窗口、Python、信號處理、輕量級模型
📬 歡迎留言交流更多音頻特征工程技巧!