Soup数据去重实战:MinHash与语义嵌入双引擎,揪出改写型重复样本的完整指南
【免费下载链接】SoupFine-tune LLMs from one YAML. Layer streaming trains an 8B model on a 4 GB laptop GPU.项目地址: https://gitcode.com/GitHub_Trending/soup12/Soup
在 LLM 微调中,Soup(一个"从一份 YAML 开始微调大模型"的开源工具)内置了soup data dedup数据去重命令,用MinHash近似去重 +语义嵌入(SemDeDup)双引擎,帮你揪出训练集里的重复样本——尤其是换了个说法、表面完全不同的"改写型重复"。这篇文章带你从零看懂两种去重原理、它们的实测差异,以及新手最容易踩的阈值坑,5 分钟跑通一条完整的去重流水线。
为什么微调前必须做数据去重?
重复样本会让模型过度记忆某些表达,训练 loss 好看,泛化却变差。更隐蔽的是:重复不一定是复制粘贴。
样本A:"Write a function that sorts a list of integers" 样本B:"Write a function that sorts an array of integers"A 和 B 意思完全一样,但用词不同——这就是改写型重复(rewording)。普通的哈希去重对它无能为力,这正是 Soup 提供两套去重后端的原因。
MinHash 去重:快、轻、默认后端
soup data dedup默认使用MinHash + LSH(局部敏感哈希):
- 把每条样本拆成连续 3 个词的shingle(如
"sorts a list"); - 为每个 shingle 集合生成 128 维 MinHash 签名,用 LSH 索引加速相似度查找;
- 两行样本的Jaccard 相似度超过阈值(默认
0.8),判定为重复,只保留先出现的那条。
它的优点是快且零模型依赖——不加载 torch、不下载模型,属于 Soup 的轻量核心功能。但它比较的是词面重叠,换个词就认不出来了。
相关实现:
- MinHash 去重主逻辑:src/soup_cli/commands/data.py
语义嵌入去重:专治改写型重复
加上--semantic参数后,Soup 改用SemDeDup思路:先把每行文本用嵌入模型(默认all-MiniLM-L6-v2)编码成向量,再按余弦相似度做贪心去重——新行与任何已保留行的余弦值 ≥ 阈值(默认0.8)就丢弃。
soup data dedup train.jsonl --semantic -o clean.jsonl soup data dedup train.jsonl --semantic --threshold 0.85 --field text -o clean.jsonl官方文档里有一组很直观的实测对比(同批数据、同一嵌入模型):
| 样本对 | 余弦值 | MinHash | --semantic |
|---|---|---|---|
| 完全相同的重复行 | 1.000 | 抓到 | 抓到 |
| "sorts a list" / "sorts an array" | 0.908 | 漏掉 | 抓到 |
| 改写句 "sorts a list of ints" | 0.880 | 漏掉 | 抓到 |
| "Add two numbers" / "Multiply two numbers" | 0.759 | 保留(正确) | 保留(正确) |
可以看到:改写型重复的余弦普遍在 0.88 上下,MinHash 一律漏检,而语义去重能稳定抓住,同时不会误伤真正不同的样本。
语义去重的核心算法(贪心余弦、可审计的pairs报告)在 src/soup_cli/utils/semdedup.py,官方说明见 docs/data.md。
阈值怎么调?3 个新手必知要点
- 两个后端的
--threshold不通用。MinHash 分支的阈值是 Jaccard 相似度,--semantic分支是余弦相似度,两套标尺,一个数值在 A 后端调好,搬到 B 后端毫无意义。 - 语义去重不是"改写检测器"。实测中,真改写的余弦(0.49–0.76)与必须保留的不同样本(0.54–0.76)区间重叠——把阈值降到 0.7 去追"改写召回率",删掉的是真实训练行,这种静默数据损失比留一条重复更糟。所以默认 0.8 是刻意保守的,只建议在自有数据上验证后微调,并且检查被删的样本。
- 语义去重单次上限 5 万行。它是 O(n²) 的贪心匹配,超过上限会直接报错(而不是偷偷抽样),此时先用
soup data sample采样,再按 tests/test_issue349_measured_fit.py 这类测试的思路在自己的机器上验证性能。
快速上手:3 步跑通去重流水线
# 第 1 步:安装依赖(二选一) pip install "soup-cli[data]" # 只跑 MinHash 去重 pip install "soup-cli[train]" # 需要 --semantic 语义去重 # 第 2 步:MinHash 去重(轻量快速,默认) soup data dedup ./data/train.jsonl --threshold 0.8 # 第 3 步:语义去重(改写型重复一网打尽) soup data dedup ./data/train.jsonl --semantic -o clean.jsonl不指定-o时,输出文件默认为<输入文件名>_deduped.jsonl,且必须位于当前工作目录之下。--field text可以指定只用某个字段参与比较,不指定则拼接行内所有文本字段。
去重不止一条命令:Soup 里的其他防线
去重能力还散落在其他命令里,构成完整的数据质量防线:
- 生成时去重:
soup data generate --prompt "..." --count 200 --dedup-with existing.jsonl,合成数据前先和已有数据集比对,避免新数据与旧数据重复; - 偏好数据体检:
soup data lint的near_duplicates检查直接复用了soup data dedup的 MinHash 内核,能揪出 DPO 偏好对里的近似重复(见 docs/data.md); - 构建期精确去重:
soup build内建 5 个变换,其中dedup_exact负责逐字去重,且支持 SQLite 追踪的增量重变换。
更多命令速查见 docs/commands.md 与 docs/data.md。
总结
| 维度 | MinHash(默认) | --semantic语义去重 |
|---|---|---|
| 比较对象 | 词面 shingle(Jaccard) | 嵌入向量(余弦) |
| 改写型重复 | ❌ 漏检 | ✅ 能抓住 |
| 依赖 | soup-cli[data] | soup-cli[train]+ 小嵌入模型 |
| 速度 | 快,LSH 加速 | O(n²),上限 5 万行 |
| 阈值含义 | Jaccard 相似度 | 余弦相似度 |
一句话选型:先用默认 MinHash 扫一遍精确近似重复,再用--semantic补刀改写型重复;阈值保持默认 0.8,想动它之前,先看看它准备删掉什么。去重做对了,你的微调数据才算真正"干净"。
【免费下载链接】SoupFine-tune LLMs from one YAML. Layer streaming trains an 8B model on a 4 GB laptop GPU.项目地址: https://gitcode.com/GitHub_Trending/soup12/Soup
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考