同样的架构、同样的算力,喂不同的数据,训出来的模型差一个档次。2023 年之后业界的共识逐渐清晰:模型能力的天花板,越来越多由数据决定,而不是参数量。今天讲训练数据从「原始网页」变成「能训出好模型的语料」要经过的四道核心工序——合成、策展、去重、质量过滤。这是学术视角的「数据是怎么回事」,不是生产 ETL 的调参手册。
合成数据 = 用一个强模型当「数据工厂」批量造训练样本,本质和 AlphaGo 的自我对弈(self-play)同源:不靠外部喂棋谱,让系统自己生成经验再从中学习。对后端的你,更熟的类比是用 faker 造测试夹具(fixtures)——只不过这里造的不是假数据填表,而是高质量的「指令-答案」对,用来真正训练模型。
痛点:人工写的高质量数据又少、又贵、又不够多样。一个标注员一天写不了几百条优质问答,而模型要吃几百万条。Self-Instruct(Wang et al. 2022)给出的机制是「自举」:拿少量人写的种子任务当样例,让强模型照着风格生成大量全新的「指令+输入+输出」三元组,再过滤掉无效和重复的,回头去微调模型本身。用这套方法,原始 GPT-3 在指令遵循上有了接近人工标注版本的跃升。
phi-1(Gunasekar et al. 2023,"Textbooks Are All You Need")把这思路推到极致:用 GPT-3.5 生成「教科书质量」的合成代码教材,只用约 70 亿 token、13 亿参数的小模型,在 HumanEval 上做到 50.6% pass@1——数据质量直接换算成了等效参数量。这是「数据 > 规模」最有力的实证之一。
from anthropic import Anthropic client = Anthropic() # 需要 ANTHROPIC_API_KEY # Self-Instruct 核心:用强模型从少量种子任务"自举"出大量新样本 seed = ["把这段话改写成正式语气", "给这段代码写单元测试"] def synthesize(seed, n=5): prompt = (f"参考这些任务:{seed}\n" f"生成 {n} 个风格类似但内容全新的「指令+输入+理想输出」三元组," f"以 JSON 数组返回。") resp = client.messages.create( model="claude-opus-4-7", max_tokens=2048, messages=[{"role": "user", "content": prompt}]) return resp.content[0].text # 后续要 JSON 解析 raw = synthesize(seed) # 关键:生成≠可用。必须过滤——丢掉与种子过于相似、格式错误、答案错的
数据策展就是大语言模型的 ETL 管线:原始 Common Crawl 网页是「数据湖」里的脏数据,要经过抽取、清洗、过滤、去重一连串 transform,才变成能进训练的「数据仓库」。每个阶段都是一个可独立度量增益的算子——和你搭数据管线时给每个 stage 加监控指标,是同一种工程纪律。
痛点:原始网页绝大部分是垃圾——导航栏、广告、SEO 农场、乱码、模板化样板文字。直接拿去训练就是经典的 garbage in, garbage out。FineWeb(Penedo et al. 2024)系统化地公开了一条 15 万亿 token 的策展管线,核心阶段如下:
FineWeb 真正的方法论贡献不是「这套流程」,而是每加一个阶段就训一个小模型、用下游 benchmark 量化它的增益——把「这步清洗有没有用」从拍脑袋变成可证伪的实验。去污染(decontamination)尤其关键:若测试集泄漏进训练数据,评测分数全是虚高的作弊分。
import trafilatura # 网页正文抽取,去掉导航/广告/页脚 # 极简管线:每个阶段都是可独立度量的过滤算子(lang/symbol 为示意函数) def curate(raw_html: str): text = trafilatura.extract(raw_html) # ① 抽正文 if not text or len(text) < 200: # ② 太短:丢 return None if detect_lang(text) != "zh": # ③ 语言过滤 return None if symbol_ratio(text) > 0.1: # ④ 符号/乱码占比过高:丢 return None return text # FineWeb 方法论:每加一个过滤阶段,训个小模型量化它对 benchmark 的增益
去重和你熟的内容寻址存储(content-addressable storage)/ Git 对象去重是一回事:同样内容只该存一份。难点在「近似重复」——同一篇文章被 1000 个站转载、改了标题和首段。精确哈希抓不住,需要MinHash,它扮演的角色类似布隆过滤器(Bloom filter):用概率签名廉价判断「这两份是不是几乎一样」,而不做昂贵的逐字对比。
痛点:网页语料重复率惊人。Lee et al. 2021("Deduplicating Training Data Makes Language Models Better")发现 C4 里有一句 61 词的英文句子重复了六万多次。重复带来三个害处:模型逐字背诵(隐私/版权风险)、算力浪费在冗余样本上、训练-测试泄漏。该论文证明:去重后模型背诵原文的概率降到约1/10,且用更少步数达到同等或更好的准确率。
机制分两层:精确去重用哈希(整段一字不差才算重复);近似去重用 MinHash + LSH。核心直觉是Jaccard 相似度——两个文档词集合的交集占并集的比例。直接算 N 篇两两对比是 O(n²),海量语料下不可行。MinHash 的巧思:给每篇文档生成一个固定长度的「签名」,使得两签名相同位的比例 ≈ 它们的 Jaccard 相似度,于是相似度估计被压成廉价的签名比对,再配 LSH 把可能相似的文档分到同一个桶,只在桶内比较。
但重复不是越少越好。Muennighoff et al. 2023("Scaling Data-Constrained Language Models")发现:数据受限时,有意重复(多跑几个 epoch)到约 4 轮,损失几乎和用全新数据没差别,之后才快速衰减。所以去重要删的是意外的网页冗余,不是禁止一切重复——刻意的 epoch 重复在预算内完全健康。
from datasketch import MinHash, MinHashLSH # pip install datasketch # 近似去重:用 MinHash 估计 Jaccard 相似度,避免 O(n²) 两两对比 def sig(text, num_perm=128): m = MinHash(num_perm=num_perm) for tok in set(text.split()): # 用词集合做签名 m.update(tok.encode("utf8")) return m lsh = MinHashLSH(threshold=0.8, num_perm=128) # 相似度>0.8 视为重复 docs = {"d1": "今天天气不错 出去走走", "d2": "今天天气不错 出门散步"} for name, text in docs.items(): s = sig(text) if lsh.query(s): # 已有近似重复 → 跳过 continue lsh.insert(name, s) # 否则入库
质量过滤 = 给训练语料装一道垃圾邮件过滤器 / 内容评分门禁。和你熟的反垃圾系统同构:先用启发式规则(关键词、比例阈值)拦掉明显垃圾,再用一个训练好的分类器给灰色地带打分、按阈值放行。区别只在于这里过滤的不是邮件,而是要不要把这段文本喂给模型。
痛点:去重之后仍有大量低价值文本——语法混乱的 SEO 农场、纯链接列表、无意义的机器生成内容。它们不重复,但对模型学习几乎没贡献甚至有害。质量过滤分两档机制:
关键工程直觉:用 LLM 打分太贵,过滤 15T token 跑不起。所以套路是「LLM 当老师标少量样本 → 训个 fastText/embedding 轻量分类器 → 用它跑全量」,每条几毫秒。这和 phi-1 的「textbook quality」筛选同源——好数据的定义,由一个可复制的打分器固化下来。
from anthropic import Anthropic client = Anthropic() # FineWeb-Edu 思路:LLM 当老师给"教育价值"打分(只标少量样本) def edu_score(text: str) -> int: resp = client.messages.create( model="claude-haiku-4-5-20251001", max_tokens=8, messages=[{"role": "user", "content": f"给文本的「教育/知识价值」打 0-5 分,只回数字:\n{text[:1000]}"}]) return int(resp.content[0].text.strip()) # 保留 ≥3 分的。这批 LLM 标注用来训一个 fastText 轻量分类器, # 真正过滤海量语料时跑那个分类器(每条几毫秒),而不是调 LLM