AI 学院 · LLM大模型线 · 节点 03 / 14

数据工程:亲手下载、清洗、导出第一批语料

模型是数据的函数。这一课你会亲手下载一个真实开源语料、写清洗脚本、导出带元数据的 JSONL——把「垃圾进垃圾出」变成可执行的纪律。全程约 60 分钟。

状态 手把手 · 必修前置 02 LLM 是什么 · Python 地基预计 60 分钟
本课模式:五步走——下载真实语料 → 看它长什么样 → 写清洗脚本 → 导出 JSONL → 去污染检查。每步可复制命令、对照输出、出错看怎么办。

📋 本课你将完成

步骤 1

下载一个真实语料(10 分钟)

未完成

🎯 这一步的目标:选一个 50–200MB 的开源语料下载到本地,作为本课的练习对象。

创建数据工作目录
mkdir -p ~/llm-course/data-lab
cd ~/llm-course/data-lab
▶ 预期输出(点开对照)
(无输出即成功)
下载 TinyStories 训练集(约 213MB,本课用它做清洗练习)
curl -L -o tinystories.txt https://huggingface.co/datasets/roneneldan/TinyStories/resolve/main/TinyStories-train.txt
▶ 预期输出(点开对照)
  % Total    % Received % Xferd  Average Speed ...
100  213M  100  213M    0     0   ...(下载完成)
如果出现:curl 报 404 / 无法访问
怎么办:用镜像:把 huggingface.co 换成 hf-mirror.com 再试。
如果出现:下载太慢
怎么办:文件只有 213MB,耐心等;或换用更小的数据集(如 TinyShakespeare,几 MB)。
看语料前 500 字符长什么样
head -c 500 tinystories.txt
▶ 预期输出(点开对照)
One day, Lily met a Shoggoth...(一段英文儿童故事)
✅ 检查点:tinystories.txt 存在且能读出内容。
步骤 2

写清洗脚本骨架(15 分钟)

未完成

🎯 这一步的目标:把清洗五步(去重/过滤/语言/隐私/去污染)落成可运行的 Python 脚本。本课实现前两步,其余留接口。

创建 clean.py
nano clean.py
▶ 预期输出(点开对照)
(在编辑器里粘贴下面的骨架)
骨架代码(照抄)
import re, hashlib
seen = set()
def dedup(text):
    h = hashlib.sha256(text.encode()).hexdigest()
    if h in seen: return None
    seen.add(h); return text
def quality(text):
    # 简单质量过滤:太短、太多噪音、非英文比例高则丢弃
    if len(text) < 100: return False
    ascii_ratio = sum(1 for c in text if ord(c) < 128) / max(len(text), 1)
    if ascii_ratio < 0.7: return False
    return True
def main():
    with open("tinystories.txt", encoding="utf-8") as f:
        lines = f.read().splitlines()
    kept = 0
    for i, line in enumerate(lines):
        if not line.strip(): continue
        d = dedup(line.strip())
        if d is None or not quality(d): continue
        kept += 1
    print("原始行数:", len(lines), "| 清洗后:", kept)
if __name__ == "__main__":
    main()
▶ 预期输出(点开对照)
(保存:Ctrl+O 回车,退出:Ctrl+X)
运行清洗脚本
python3 clean.py
▶ 预期输出(点开对照)
原始行数: 211400 | 清洗后: 211400(TinyStories 本身较干净,所以清洗前后接近——换真实网页语料就会看到明显差异)
✅ 检查点:clean.py 能运行并打印统计。
步骤 3

导出 JSONL 带元数据(15 分钟)

未完成

🎯 这一步的目标:把清洗后的数据导出成带来源/语言/哈希的 JSONL——对齐 Lumen「数据即训练元数据」。

创建 export.py
nano export.py
▶ 预期输出(点开对照)
(在编辑器里粘贴下面的代码)
导出代码(照抄)
import json, hashlib
def main():
    out = open("mars-corpus.jsonl", "w", encoding="utf-8")
    for i, line in enumerate(open("tinystories.txt", encoding="utf-8")):
        text = line.strip()
        if not text: continue
        h = hashlib.sha256(text.encode()).hexdigest()[:12]
        rec = {"text": text, "source": "tinystories", "lang": "en", "sha": h, "seq": i}
        out.write(json.dumps(rec, ensure_ascii=False) + "
")
    out.close()
    print("done")
if __name__ == "__main__":
    main()
▶ 预期输出(点开对照)
(保存:Ctrl+O 回车,退出:Ctrl+X)
运行并查看前 2 行
python3 export.py
head -2 mars-corpus.jsonl
▶ 预期输出(点开对照)
done
{"text": "One day, Lily met a Shoggoth...", "source": "tinystories", "lang": "en", "sha": "3fa8c2d1e0b9", "seq": 0}
(每行一个 JSON 对象,带元数据——这就是训练管道的标准格式)
✅ 检查点:mars-corpus.jsonl 每行都是带元数据的合法 JSON。
步骤 4

去污染检查(10 分钟)

未完成

🎯 这一步的目标:检查语料里是否混入了评测集文本——这是「评估作弊」的源头,必须养成习惯。

写一个简单的 n-gram 重叠检查
python3 -c 'import json
sample = [json.loads(l)["text"] for l in open("mars-corpus.jsonl")][:100]
probe = "Once upon a time, there was a little girl named Lily"
hits = [s for s in sample if probe[:20] in s]
print("命中:", len(hits))'
▶ 预期输出(点开对照)
命中: 1(这个开头在语料里出现过——说明如果你用它做评测,必须排除这段)
理解含义
▶ 预期输出(点开对照)
(发现命中不用慌——训练语料本来就可能含类似文本。关键是:你的评测集样本必须从语料里剔除,否则评估是背答案。)
✅ 检查点:你理解为什么去污染是硬纪律,并知道怎么检查。
步骤 5

认识你的差异化数据:火星因果链(10 分钟)

未完成

🎯 这一步的目标:理解 Praxeon 事件数据(决策—状态—后果)为什么比通用语料更有价值,以及怎么导出。

读事件结构(已实现的 M2 游标)
▶ 预期输出(点开对照)
Praxeon 的 EventEnvelope(app/chain/mars/proto)是追加式事件流:cursor + 事件体
导出思路:把事件流按「决策 → 状态变化 → 后果」重组成训练样本
示例样本:{'decision': 'A 提案投票通过', 'state': 'org.budget.allocated', 'consequence': 'Prax 分配触发', 'evaluation': 'Proof 签发'}
✅ 检查点:你能说出一条火星因果链样本长什么样,以及为什么它比通用语料稀缺。
🧪 交互实验 · JSONL 查看器

把导出的 JSONL 粘一行进来,看它的字段和可追溯性——好的训练数据每行都能回答「从哪来、是什么、可复核吗」。

检查点:这条数据有来源吗?有语言标签吗?有哈希吗?——三有才是合格的训练元数据。

❓ 老师答疑(卡住了先看这里)

❓ TinyStories 太干净了,清洗脚本看不出效果怎么办?
正常。TinyStories 是人工筛选过的,清洗前后差异小。想看到清洗的价值,下载真实网页语料(如 C4 的一个 shard)再跑——去重和质量过滤会筛掉大量重复和噪音。本课用干净语料是为了先跑通管道。
❓ 清洗脚本要写到多复杂?
先最小可用:去重 + 基本质量过滤就够跑通。真实项目 60% 时间在清洗,但那是把 pipeline 逐步加厚的过程——不要一步到位,先跑起来,再按发现的问题加规则。
❓ 为什么每条数据要带 sha 哈希?
为了可复现:训练谱系要求「数据快照哈希 + 代码哈希」锚定。有了 sha,你就能说清「这个模型训练时用的数据是哪一份」——Lumen 核心原则 6。
❓ 火星因果链数据怎么导出?现在还没实现吧?
事件流(EventEnvelope + cursor)已在 app/chain/mars 实现;「重组成训练样本」这步还没有现成脚本——这正好是 Lumen L0.0 的交付物之一(数据管道脚本)。你可以在本课练习后,自己写第一个导出脚本。

📝 自测清单(全勾才算过)

全勾 → 节点 03 / 14 完成。未勾 → 回看对应步骤,别急着往前走。

📚 推荐资料