Python 实战:用一套“可校验 + 可回流”的流水线生成高质量 JSONL(适用于 SFT 数据工程)
做大模型 SFT,很多人卡在两件事:1)生成的数据量不小,但质量参差不齐2)训练完发现模型不听话,却说不清是哪类样本有问题解决办法不是“再生成一堆数据”,而是用 Python 把数据工程做成流水线:生成(build)校验(validate)修复/剔除(fix/filter)统计(metrics)回流(bad rows
作者:lh
做大模型 SFT,很多人卡在两件事:
1)生成的数据量不小,但质量参差不齐
2)训练完发现模型不听话,却说不清是哪类样本有问题
解决办法不是“再生成一堆数据”,而是用 Python 把数据工程做成流水线:
- 生成(build)
- 校验(validate)
- 修复/剔除(fix/filter)
- 统计(metrics)
- 回流(bad rows → 下一轮增量训练)
本文给你一份通用的工程设计思路(偏落地),你可以直接套在“画像→课程推荐→学习规划”的任务上。
1. 项目目录:先把工程结构搭清楚
推荐一个轻量但可扩展的结构:
sft_data_pipeline/
data/
profiles.jsonl
catalog.json
out_train.jsonl
out_test.jsonl
bad_rows.jsonl
reports/
metrics.json
samples.md
src/
build_candidates.py
synth_answer.py
validate_and_filter.py
split_train_test.py
utils_schema.py
utils_metrics.py
run.sh
这样你就能把“生成”和“校验”分离:生成可以快,校验可以严。
2. Python 里最关键的三件事:Schema、验证器、错误分桶
2.1 用“固定 Schema”约束输出结构
SFT 最容易出的问题是:输出 JSON 不可解析、字段多/少、类型错误。
在 Python 侧,建议把“输出允许字段”写死,做硬校验。
你不一定非要上复杂库,用纯 Python 也能做 80% 的事:
- 顶层必须包含:selected
- selected 必须是 list
- 每个 item 必须包含 goods_id
- goods_id 必须在 candidates 的 goods_id 集合中
- 禁止出现未定义字段(防止模型乱加)
2.2 验证器:宁可严一点,不要“将就能用”
在工程里,把“过不了验证”的样本统一写入 bad_rows.jsonl,同时记录 bad_reason,比如:
- json_parse_fail
- out_of_candidates
- missing_field
- extra_field
- empty_selected_when_should_pick
- hallucinated_course_info
2.3 错误分桶:为闭环服务
错误分桶不是为了好看,是为了告诉你下一轮该补什么数据:
- 越界多 → 强化“只能从 candidates 选”的样本
- 结构错多 → 增加严格 JSON 的示例与反例
- 理由空泛 → 强制引用 brief/teacher/direction
3. 一个通用的“校验 + 回流”脚本思路
你可以把每条样本看成:
- input:{user_profile, user_query, candidates, constraints}
- output:模型生成的 JSON 字符串(或 dict)
校验器做的事就是三步:
1)json.loads(output) 能不能成功
2)结构是否符合 schema
3)内容是否符合业务约束(比如 goods_id 必须在 candidates)
然后把结果写入两份文件:
- out_train.jsonl:合格样本
- bad_rows.jsonl:不合格样本(带原因)
这种“先生成、后过滤”的方式,扩到 10k/100k 很好用,因为你不需要生成时就完美。
4. 统计指标:用 Python 给数据集做体检
建议你至少输出这几类指标到 metrics.json:
- total_rows
- valid_rows
- bad_rows
- bad_breakdown(每种 bad_reason 的数量与占比)
- json_parse_success_rate
- out_of_candidates_rate
- avg_selected_len
- empty_selected_rate
同时再抽样输出一些可读样本到 samples.md(比如每种错误各抽 3 条),你会非常省时间。
5. 数据增强:Python 侧要做“可控扩增”,不要随机发散
你要把画像扩到 10k,Python 侧可以做“受控扩增”:
5.1 画像字段扰动(不改变人设逻辑)
- time_per_week_hours 在一个合理范围抖动(如 ±1~2 小时)
- risk_preference 在相邻档位变化(保守↔平衡↔积极)
- goal 用同义表达替换(“稳健增值/长期定投/提升交易体系”)
5.2 候选集合扰动(保持同方向)
- 同方向 candidates 随机替换 10%~30%
- 保留 top1~top2 不动,尾部课程做扰动 这样能让模型学会“在相近候选里做选择”,而不是死记答案。
5.3 负样本扩增(强烈建议)
- candidates 为空
- candidates 只有 1 门
- constraints 要求方向,但 candidates 全不满足
- user_query 与画像矛盾(比如画像保守但问高杠杆)
负样本比例做到 10%~30%,模型会更稳。
6. 并发生成:Python 里怎么把速度拉满
如果你是调用 API 生成 output(teacher model 合成),Python 并发建议:
- I/O 密集:asyncio + aiohttp 或 ThreadPoolExecutor
- 重点控制:concurrency(并发数)与 retry(指数退避)
经验值(按接口限制调):
- 并发 16~64 通常就很快了
- 一定要做:超时、重试、失败落盘(别丢数据)
7. 最后:把流水线做成“一条命令可复现”
你可以用一个 run.sh 或 Python main.py 串起:
1)profiles 扩增 → profiles_aug.jsonl
2)构造 candidates → inputs.jsonl
3)调用模型生成 output → raw_outputs.jsonl
4)校验过滤 → out_train.jsonl + bad_rows.jsonl
5)统计报告 → metrics.json + samples.md
6)切分 train/test
这就是“可复现数据工程”,也是你博客文章里最能体现专业度的一点。
结语:Python 在 SFT 里最重要的价值是“可验证”
很多人把 Python 只当“胶水”,但在 SFT 工程里,Python 的核心价值是:
把业务规则变成程序能验证的约束,把失败样本变成可迭代的闭环。
当你做到:
- 输出可解析
- ID 不越界
- 错误可分桶
- 数据可回流 微调就会越来越像工程,而不是玄学。