用滑动窗口与线性回归将音频信号转换为“Token”序列:一种简单的音频特征编码方法
在深度学习和语音处理领域,如何将原始音频信号有效地表示为离散的“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、信号处理、轻量级模型
📬 欢迎留言交流更多音频特征工程技巧!