歌曲作曲结构去重系统 V2 优化方案
1 V1 评估基线
测试集:20 首歌 × 5 种变换 + 20 条负样本,共 120 条。
| 指标 | V1 结果 |
|---|---|
| Accuracy | 0.6917 |
| Precision | 1.0000 |
| Recall | 0.6300 |
| F1 | 0.7730 |
| 假阳性(FP) | 0 |
| 漏判(FN) | 37 |
按变换类型分解:
| 变换类型 | V1 Accuracy | 说明 |
|---|---|---|
| lowpass(低通滤波) | 1.00 | 音色变化,CENS 完全应对 |
| negative(不同曲) | 1.00 | 假阳性为零,区分度良好 |
| tempo_fast(加速 10%) | 0.80 | 时间压缩,部分吸收 |
| pitch_down(降半音) | 0.50 | 转调鲁棒性不足 |
| tempo_slow(减速 10%) | 0.45 | 时间拉伸,信息密度变化大 |
| pitch_up(升半音) | 0.40 | 转调鲁棒性最差 |
核心结论:
- 精确率满分,系统不会误判不同歌曲,可以放心部署
- 主要缺陷集中在转调(pitch)和减速(tempo_slow),合计贡献了 37 条漏判中的约 30 条
- V1 的主音对齐(
np.roll到能量最大音级)对连续半音偏移效果有限
2 问题根因分析
2.1 转调(pitch_up / pitch_down)失败的根本原因
ffmpeg asetrate 变调是连续音高偏移(非整数半音),而 CENS 的 12 个 bin 是离散的。np.roll 只能对齐整数个半音,偏移量不是整数半音时无法对齐,导致相似度显著下降(失败样本 top1 相似度集中在 0.78~0.90,未过阈值)。
2.2 减速(tempo_slow)失败的根本原因
时间拉伸后每帧重复内容增多,resample 到 128 帧时对应的音乐内容在时间轴上与原版错位。问题本质是时间对齐失败,而非特征表达力不足——多尺度拼接解决的是后者,对前者无效。唯一有效的解法是允许时间轴弹性匹配的 DTW。
3 V2 优化方案
3.1 转调鲁棒性:12 路循环对齐
方案: 查询时对特征矩阵穷举 12 种半音偏移(np.roll(chroma, -k, axis=0),k=0..11),取与库中向量相似度最高的那一路作为最终相似度。
实现位置: service.py 的 query() 方法,特征提取本身不变。
代价: 每次查询变为 12 次向量检索,pgvector 单次检索延迟约 10ms 级别,12 路合计约 100ms,可接受。
预期提升: pitch_up / pitch_down accuracy 从 0.4/0.5 提升至接近 1.0。
# 伪代码示意
best: dict[int, float] = {}
for shift in range(12):
shifted = np.roll(chroma, -shift, axis=0).flatten()
for song_id, sim in query_vector(shifted, top_k):
best[song_id] = max(best.get(song_id, -1.0), sim)
3.2 速度变化鲁棒性:DTW 精排
方案: Cosine 召回 Top-K 后,用 DTW 对原始 12×128 矩阵做精排,替换纯相似度阈值判断。
实现:
from scipy.spatial.distance import cdist
def dtw_distance(chroma_a: np.ndarray, chroma_b: np.ndarray) -> float:
# chroma shape: (12, 128),按列(时间帧)计算帧间欧氏距离矩阵,再做 DTW
...
适用场景: 对 tempo 变化(±20% 以内)效果显著,对大幅转调效果有限(需配合 3.1)。
部署策略: 不修改数据库结构,仅在召回后增加一个 Python 计算步骤。
预期提升: tempo_slow accuracy 从 0.45 提升至 0.85 以上。
3.3 调性估计升级(可选)
方案: 用 Krumhansl-Schmuckler 调性轮廓替代"能量最大音级"作为主音估计,使 np.roll 对齐更精准。
评估方式: 先跑 3.1(穷举 12 路),若效果已满足需求则跳过本项。
4 优先级与实施顺序
| 优先级 | 方案 | 改动范围 | 预期收益 |
|---|---|---|---|
| P0 | 3.1 12 路循环对齐 |
service.py query 逻辑 |
pitch 问题基本解决 |
| P1 | 3.2 DTW 精排 | 新增精排函数,不改数据库 | tempo 问题显著改善 |
| P2 | 3.3 调性估计升级 | extractor.py |
按需实施 |
P0 和 P1 不需要重新入库,改动最小,建议优先实施并重新跑评估基线后再决定是否做 P2。
5 V2 目标指标
| 变换类型 | V1 | V2 目标 |
|---|---|---|
| lowpass | 1.00 | 1.00 |
| negative | 1.00 | 1.00 |
| tempo_fast | 0.80 | ≥ 0.95 |
| pitch_down | 0.50 | ≥ 0.95 |
| tempo_slow | 0.45 | ≥ 0.85 |
| pitch_up | 0.40 | ≥ 0.95 |
| 整体 Recall | 0.63 | ≥ 0.92 |
| 整体 F1 | 0.773 | ≥ 0.958 |
Precision 维持 1.0(不引入假阳性)。