歌词查重测试流程
本文档记录从已有歌词目录建立索引、生成测试集、批量评估和查看结果的完整命令。
1. 准备目录
已有曲库放在:
data/library/
支持文件:
.lrc
.txt
生成的测试样本会放在:
data/generated_eval/incoming/
测试集标注 CSV 会放在:
data/generated_eval/eval_100.csv
评估结果会放在:
outputs/results/
2. 建立已有曲库索引
如果刚往 data/library 新增了一批样本,建议先运行处理脚本:
python scripts/process_library.py \
--library-dir data/library \
--index outputs/indexes/library_lyrics.pkl
这个脚本会:
1. 扫描并隔离纯音乐占位样本,例如包含【曲库专用】或“此歌曲为没有填词的纯音乐”的文件。
2. 重建 outputs/indexes/library_lyrics.pkl。
3. 输出处理报告 outputs/results/library_process_report.json。
如果你想先看会处理哪些文件,不实际移动和重建索引:
python scripts/process_library.py \
--library-dir data/library \
--dry-run
如果要顺手生成并评估 500 条测试样本:
python scripts/process_library.py \
--library-dir data/library \
--index outputs/indexes/library_lyrics.pkl \
--eval-size 50000 \
--positive-ratio 0.3 \
--eval-csv data/generated_eval/eval_50000.csv \
--eval-out outputs/results/library_eval_50000.csv
隔离出来的文件默认会移动到:
data/quarantine/no_lyrics_placeholders/
也可以只手动建索引:
python -m lyric_dedup.cli build-index \
--lyrics-dir data/library \
--index outputs/indexes/library_lyrics.pkl
索引文件:
outputs/indexes/library_lyrics.pkl
注意:如果修改了 data/library,或修改了预处理/判重逻辑,需要重新执行本步骤。
3. 生成生产评估样本
python -m lyric_dedup.cli generate-eval-set \
--library-dir data/library \
--lyrics-dir data/generated_eval/incoming \
--csv data/generated_eval/eval_50000.csv \
--index outputs/indexes/library_lyrics.pkl \
--eval-index data/generated_eval/eval_50000.csv.index.pkl \
--size 50000 \
--positive-ratio 0.3
如需生成更贴近业务边界的 hard 口径测试集:
python -m lyric_dedup.cli generate-eval-set \
--profile hard \
--library-dir data/library \
--lyrics-dir data/generated_eval/hard_incoming \
--csv data/generated_eval/eval_hard_5000.csv \
--index outputs/indexes/library_lyrics.pkl \
--eval-index data/generated_eval/eval_hard_5000.csv.index.pkl \
--size 5000 \
--positive-ratio 0.3
默认生产评估口径:
应去重: 30%
不应去重: 70%
生成器会先清理 data/generated_eval/incoming/ 下旧的 .txt / .lrc 生成文件,再写入新样本。
业务口径:
positive_* = 应去重,全曲歌词样式变化,包括少量错别字/英文拼写错误扰动
negative_real_holdout_full_song = 不应去重,完整真实歌词,已从评估索引中排除
negative_fragment = 不应去重,单曲片段
negative_shared_chorus = 不应去重,重复副歌碰撞
negative_translation_only = 不应去重,仅翻译相似
negative_same_theme_synthetic = 不应去重,同主题新歌词
edge_short_or_placeholder = 不应去重,短歌词/占位边界样本
hard 口径额外强调真实业务边界,而不是故意制造反常难题:
positive_realistic_variant = 应去重,同曲平台版本噪声、较完整缺段、整段翻译附加、真实录入/OCR 错
negative_near_neighbor_holdout_full_song = 不应去重,和曲库有较多行重合的真实 holdout 新歌
negative_long_fragment = 不应去重,较长但不完整的单曲片段
negative_catalog_mashup = 不应去重,多首真实歌词片段组成的串烧/混剪式输入
生成器会扫描整个曲库并按有效歌词行数、语言类型、文件来源前缀分层采样。它会分出一批 holdout 完整歌词作为真实新歌负样本,并生成一个排除 holdout 的评估索引。每次还会输出:
data/generated_eval/eval_50000.csv.manifest.json
data/generated_eval/eval_50000.csv.index.pkl
manifest 里重点看:
library_files 曲库歌词文件数
holdout_records 从评估索引中排除、作为真实新歌负样本的数量
sample_type_counts 各样本类型数量
line_count_bucket_counts / language_bucket_counts / source_bucket_counts
unique_source_records 本次评估覆盖了多少真实源文件
4. 严格评估:只把 duplicate 算作去重
python -m lyric_dedup.cli evaluate-csv \
--index data/generated_eval/eval_50000.csv.index.pkl \
--csv data/generated_eval/eval_50000.csv \
--base-dir data/generated_eval \
--out outputs/results/library_eval_50000.csv
这个口径下:
duplicate -> 预测应去重
review -> 预测不应去重
new -> 预测不应去重
适合评估自动拦截的 precision,重点看:
false_positive
5. 召回评估:把 duplicate 和 review 都算作抓到可疑样本
python -m lyric_dedup.cli evaluate-csv \
--index data/generated_eval/eval_50000.csv.index.pkl \
--csv data/generated_eval/eval_50000.csv \
--base-dir data/generated_eval \
--positive-decisions duplicate,review \
--out outputs/results/library_eval_50000_review_positive.csv
这个口径下:
duplicate -> 预测应去重
review -> 预测应去重
new -> 预测不应去重
适合评估可疑样本召回,重点看:
false_negative
6. 查看总体指标
严格口径:
cat outputs/results/library_eval_100.csv.summary.json
召回口径:
cat outputs/results/library_eval_100_review_positive.csv.summary.json
指标含义:
accuracy 总正确率
precision 预测应去重的样本里,有多少是真的应去重
recall 真实应去重的样本里,有多少被系统抓到
f1 precision 和 recall 的综合指标
true_positive 应去重且预测应去重
false_positive 不应去重但预测应去重,误杀
true_negative 不应去重且预测不应去重
false_negative 应去重但预测不应去重,漏召
7. 查看每条样本结果
open outputs/results/library_eval_100.csv
如果不能使用 open,可以直接查看 CSV:
python -c 'import csv; rows=csv.DictReader(open("outputs/results/library_eval_100.csv", encoding="utf-8")); [print(r["id"], r["decision"], r["correct"], r["reason"], sep=" | ") for r in rows]'
8. 查看失败样本
严格口径失败样本:
python -c 'import csv; rows=csv.DictReader(open("outputs/results/library_eval_100.csv", encoding="utf-8")); [print(r["id"], r["source"], r["decision"], r["reason"], sep=" | ") for r in rows if r["correct"] == "False"]'
查看某个样本的完整候选:
python -m lyric_dedup.cli check-file \
--index outputs/indexes/library_lyrics.pkl \
--file data/generated_eval/incoming/neg_068_mixed_fragments.txt \
--max-candidates 10
9. 核对测试集分布
python -c 'import csv, collections; rows=list(csv.DictReader(open("data/generated_eval/eval_10.csv", encoding="utf-8"))); print(len(rows)); print(collections.Counter(r["expected"] for r in rows)); print(collections.Counter(r["sample_type"] for r in rows)); print(collections.Counter(r["sample_type"] for r in rows if r["expected"]=="应去重")); print(collections.Counter(r["sample_type"] for r in rows if r["expected"]=="不应去重"))'
核对生成目录文件数:
find data/generated_eval/incoming -type f | wc -l
10. 运行代码测试
python -m pytest tests
编译检查:
python -m compileall -q lyric_dedup tests
11. 关于测试集不重复
当前自动生成的 100 条是规则覆盖测试集,不保证样本之间规范化后完全不重复。
如果要求 100 条测试样本彼此不重复,并且仍使用默认比例:
size = 100
positive_ratio = 0.6
则至少需要:
60 首互不重复的种子歌词
原因:应去重样本是全曲变体,同一首歌的多个样式变化规范化后仍然是同一首歌。
更稳妥的真实准确率评估方式是准备人工标注 CSV:
id,file,expected
case-001,incoming/song_a.lrc,应去重
case-002,incoming/song_b.txt,不应去重
然后直接执行第 4 节或第 5 节的 evaluate-csv。