从零搭一个合规数据集:爬规则、清洗、造样本、第一次微调
/ 13 min read
Table of Contents
我那个内容合规模型项目,前面已经写了评估口径之战和接手后重做数据集。这篇往更前推——在模型能开始训练之前,数据是怎么从无到有搭起来的。这是整个项目最源头、也几乎全是我从零做的一个月(2026 年 3 月底到 4 月底)。
时间隔得久,当时的对话记录已经没了,但磁盘上的爬虫、清洗脚本、生成脚本、审核脚本和几份交接/总结文档都还在。下面全部基于这些真实产物。
一、爬规则:逆向接口,不是硬解 HTML
合规模型要”对着规则判断”,第一步得有规则。各平台的官方规则都在它们的帮助中心网站上,我用 Playwright 驱动无头 Chromium 去爬,核心手段是拦截页面的 XHR/JSON 响应做接口逆向,而不是去解析渲染后的 HTML(HTML 结构一变爬虫就废,接口稳定得多)。
逆向出来的接口大致是”列表接口(带分页)+ 分类树接口 + 详情接口”。爬法是:加载入口页 → 轮询等待页面安全校验过去 → 监听响应收集规则 id → 点各个分类、滚动触发分页 → 逐个访问详情页提取正文。配随机 UA 池和随机延迟,block 掉字体/图片/媒体资源加速。
三个站爬下来:抖音生活服务学习中心 692 篇、小红书广告帮助中心 300 篇、小红书创作学院 266 篇。光是把分类树和分页结构逆向对,就迭代了六个爬虫版本加五个探测脚本——分页参数、分类树藏在哪个接口、有没有藏在前端的状态里,全是试错试出来的。
还有一部分规则是以图片形式发布的。爬虫里内置了 OCR(PaddleOCR 中文)把图片里的规则识别出来,再用大模型把 OCR 的粗文本整理成规范的 Markdown。爬虫不只是抓网页,是抓网页 + 识图 + 清洗一条龙。
二、清洗:连 .doc 二进制都得手解
除了平台规则,还要清洗一批法律法规和政策文件(广告法、电商法这些是模型判断的兜底依据)。金融、网络类法规各清洗了四十篇。
这里有个具体到位的坑:很多政策文件是老式的 Word 97 .doc 格式,不是现在的 .docx。.doc 是 OLE 复合二进制,普通库读不了。我是手解 OLE 结构——用 olefile 读 WordDocument 流,按 FIB 文件头的偏移量(正文起始位置、字符数)取出 UTF-16 编码的正文,再交给大模型格式化成 Markdown。还做了”已转换就跳过”的增量逻辑。为了清洗几十篇老政策去啃二进制格式,这种活不性感,但绕不过去。
视频号那批规则更是搭了条五步流水线(清洗 → 标签发现 → 按条款语义切分 → 生成标准数据集 → 关键词/实体增强),处理了四千多个爬来的原始文档。领导验收时挑出五个问题——标题重复、平台字段缺失、id 没前缀、标签不准、混进了别的平台的内容(爬取页面夹带)——我全部在代码层(清洗提示词加过滤规则)修掉了,而不是手工改数据。
三、设计分类体系
规则有了,得有一套分类体系把它们组织起来,模型才能学”违反的是哪一类、哪一条”。我建了一套三级分类(一级域 → 二级 → 三级),一级分成广告规范、内容规范、交易规范、直播规范、账号规范、平台运营、法律法规这几个域,每条规则带描述和适用平台,用前缀 id(ADV-/CON-/TRD-/…)编码。后来针对单个平台(小红书)又细化成”违法违规/公序良俗/未成年人/侵权/危险行为/营销导流/不当行为/色情低俗”八大类、每类拆成多条具体规则。分类体系是数据集的骨架,它设计得糙,后面所有标注和评估都会跟着糊。
四、造样本:三个模型分工,和一个被我抓出来的作弊
样本要的是”一段文案 + 它违反/不违反哪条规则”的成对数据。这里有两条线。
一条是违规文本 → 合规改写的指令对(输入含违规的文案,输出改写后的合规版本)。我把它拆成两步:先独立生成违规文本(生成时不知道一会儿要改写它),再针对性改写。这个”两步分离”是有意的——如果让模型一次性”生成一个违规文案再改成合规”,它会提前放水、造出来的违规根本不够违规。分开之后违规场景才真实。
这条流水线一天内迭代了三版(404 → 419 → 506 条),第二版修了六个真实问题,其中两个特别值得记:
- 模型会拒答:生成色情、违禁类的样本时,生成模型经常返回”暂时无法回答”,而这句拒绝被当成正常输出存进了数据集。加了一个
is_refusal()检测把这类过滤掉。 - 一个”永远满分”的作弊评估:有一项安全规范性检查,代码里写的是
skip_safety_check: true——也就是说它跳过了真实检查、永远给满分。这是个自欺的指标。我把它改成真实检查(验证改写后的文本里是否还残留违规词)。一个评估如果设计成永远过,那它就是个摆设——这跟我后来在评估口径上那么较真,是同一个教训的早期版本。
另一条是违规判定的问答对(喂给微调的核心格式:给规则 + 文案,输出结构化 JSON 判定)。抖音 780 条、严格 1:1 的违规/合规比例,小红书、公众号各几百条。生成时也踩了模型拒答的坑,解法是三个模型分工:用一个模型生成合规样本、另一个生成违规样本、第三个兜底——因为单个模型对”有损人格尊严""未成年人”这类规则的合规样本会拒绝生成。还挂了个审核脚本每三十分钟采样十条,检查违规标题是否唯一、违规原文是否真在文案里、字数够不够。
样本质量上我定了两条原则:违规场景要用自然叙事、不直接点明”这违规了”(否则模型学的是关键词不是语义);合规场景要专门选边界案例(历史研究、新闻报道、防诈骗科普这种”长得像违规但其实合规”的),逼模型真正理解才判得对。
五、第一次微调:exp_001 到 003
数据齐了,开始微调。基座是 Qwen2.5-7B-Instruct,LLaMA-Factory + LoRA(rank=64, alpha=128,只训 2% 的参数),单卡 4090。喂数据前强制校验每条的角色序列、assistant 必须是合法 JSON、违规原文必须能在文案里精确找到。
跑了三轮,真实数字:
| 实验 | 配置 | 数据量 | 最终 loss | 小红书规则归类 |
|---|---|---|---|---|
| exp_001 | 5 epoch | 1761 | 0.040 | 65% |
| exp_002 | 3 epoch | 1761 | 0.170 | 62% |
| exp_003 | 3 epoch + 小红书过采样 | 2056 | 0.123 | 97% |
几个真实的判断:
- loss 0.040 是过拟合。Instruct 模型微调的 loss 起点本来就低(0.3-0.5,不是 base 模型的 2.0+),加上输出是规律的 JSON,所以 loss 容易很低。5 epoch 的 0.040 是在背数据,3 epoch 就够了。这也是后来在 exp_005 上”loss=0.0137 是假象”那个判断的同一个直觉。
- 公众号数据里 8.1% 的违规原文定位偏移:830 条里有 67 条,违规原文不是文案的精确子串——因为标注时”精简”了原文。我写了个头尾锚点法去修(能修的修、修不了的剔)。小红书、抖音的数据干净不用修。教训很直接:违规原文必须是精确子串,否则模型会学到”精简引用”这个坏习惯。
- 小红书规则归类弱(62%)的根因:40 种规则只有 340 条数据,平均每种不到 9 条,语义相近的规则(“封建迷信”和”违规营销”、“公序良俗”和”不良价值观”)互相混淆。过采样把每种补到 20 条,归类一下从 62% 提到 97%。但我心里清楚:过采样是复制、治标不治本,真正的解是补真实新样本——这个认知一直延续到后面 exp_009 那次失败的 Hard Negative 尝试。
- 一个容易踩的数据泄露陷阱:必须先切验证集、再对训练部分过采样,否则过采样会把验证集样本复制进训练集,指标虚高。
小结
这一个月没有训练一个最终模型,但它把整个项目的地基打了出来:规则从哪来(逆向接口爬 + 识图)、脏数据怎么清(连 .doc 二进制都手解)、样本怎么造(两步分离、三模型分工、拒答过滤)、第一版模型什么水平(exp_003 归类 97%)。
回头看,后面在 exp_005 到 exp_010 上踩的很多坑——loss 假象、原文必须精确子串、过采样治标不治本、评估口径不能放水——在这最早的一个月里其实都已经露过头了。一个模型的天花板,很大程度上在它见到第一条训练数据之前,就已经被这些数据工程的决定锁住了。