TEST_WORKFLOW.md 8.44 KB

歌词查重测试流程

本文档记录从已有歌词目录建立索引、生成测试集、批量评估和查看结果的完整命令。

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