# Summary > **一句话**:Lab 4 让你体会「垃圾进 → 垃圾出」的现实——同样的网络结构,喂优质文本就能显著压低验证损失。 ## 它干嘛的? - 整体目标:构建一个数据预处理管道,将原始网络数据转换为高质量的训练数据 - 为什么重要:大语言模型训练需要海量高质量数据,但原始数据往往包含垃圾、重复、有害内容,需要清洗 ## 数据处理流程(通俗版) 1. 收集原始数据:从网络爬取HTML页面 2. 提取文本:去掉HTML标签,保留文字内容 3. 语言筛选:只保留目标语言(如英语) 4. 隐私保护:遮盖个人信息 5. 内容审核:过滤不当内容 6. 质量检查:应用质量规则 7. 去重处理:删除重复内容 8. 输出训练数据:得到干净、高质量的训练文本 # Cues [[FastText]] [[N-gram]] ```Java assignment4-data/ ├── fasttext (核心分类器) ├── nltk (文本处理) ├── transformers (预训练模型) ├── tiktoken (分词) ├── resiliparse (网页解析) ├── fastwarc (数据格式) └── 其他工具包 ``` # Notes ## 测试用例总结 ### Level 1: 原子级组件测试 (最小粒度) | 测试文件 | 测试函数 | 测试目的 | 状态 | | --------------- | ----------------------------------------- | --------------------------- | --- | | test_extract.py | test_extract_text_from_html_bytes | 验证HTML文本提取功能,从HTML字节流中提取纯文本 | ✅ | | test_langid.py | test_identify_language_english | 验证英语语言识别功能 | ⚠️ | | test_langid.py | test_identify_language_chinese_simplified | 验证中文语言识别功能 | ⚠️ | | test_pii.py | test_mask_emails_single | 验证单个邮箱地址的PII脱敏功能 | ⚠️ | | test_pii.py | test_mask_emails_multiple | 验证多个邮箱地址的PII脱敏功能 | ⚠️ | | test_pii.py | test_mask_phones_single | 验证单个电话号码的PII脱敏功能 | ⚠️ | | test_pii.py | test_mask_ips | 验证IP地址的PII脱敏功能 | ⚠️ | ### Level 2: 组件级测试 (小型组合单元) | 测试文件 | 测试函数 | 测试目的 | 状态 | | ---------------- | -------------------------------- | ----------------------------- | --- | | test_pii.py | test_mask_emails_existing_string | 验证已存在脱敏标记的文本处理 | ⚠️ | | test_quality.py | test_classify_quality | 验证文本质量分类功能(区分高质量wiki和低质量cc文本)| ✅ | | test_toxicity.py | test_classify_nsfw | 验证NSFW内容分类功能 | ⚠️ | | test_toxicity.py | test_classify_toxic_speech | 验证有害言论分类功能 | ⚠️ | ### Level 3: 模块级测试 (复杂组合组件) | 测试文件 | 测试函数 | 测试目的 | 状态 | |---------|---------|---------|------| | test_quality.py | test_gopher_valid_input | 验证Gopher质量过滤器的有效输入处理 | ⚠️ | | test_quality.py | test_gopher_less_than_50_non_symbol_words | 验证少于50个非符号词的文本过滤 | ⚠️ | | test_quality.py | test_gopher_more_than_100000_non_symbol_words | 验证超过10万个非符号词的文本过滤 | ⚠️ | | test_quality.py | test_gopher_average_word_length_less_than_3 | 验证平均词长小于3的文本过滤 | ⚠️ | | test_quality.py | test_gopher_average_word_length_greater_than_10 | 验证平均词长大于10的文本过滤 | ⚠️ | | test_quality.py | test_gopher_more_than_30_percent_lines_ending_with_ellipsis | 验证超过30%行以省略号结尾的文本过滤 | ⚠️ | | test_quality.py | test_gopher_less_than_80_percent_words_with_alphabetic_character | 验证少于80%词包含字母字符的文本过滤 | ⚠️ | ### Level 4: 系统级测试 (完整系统/管道) | 测试文件 | 测试函数 | 测试目的 | 状态 | | --------------------- | ------------------------------------------- | --------------------- | --- | | test_deduplication.py | test_exact_line_deduplication | 验证精确行去重功能,处理多个文档文件 | ⚠️ | | test_deduplication.py | test_minhash_deduplication_exact_duplicates | 验证MinHash算法对精确重复文档的去重 | ⚠️ | | test_deduplication.py | test_minhash_deduplication_fuzzy_duplicates | 验证MinHash算法对模糊重复文档的去重 | ✅ | ## 项目结构 ```Java assignment4-data/ ├── cs336_data/ # 你的实现目录(基本为空) │ └── __init__.py ├── cs336-basics/ # 来自assignment1的预训练模型代码 │ ├── cs336_basics/ │ ├── pyproject.toml │ └── scripts/ ├── tests/ # 测试文件 │ ├── fixtures/ # 测试数据 │ │ ├── moby.html # HTML测试文件 │ │ ├── moby_extracted.txt # 期望的提取结果 │ │ ├── high_quality_wiki_reference.txt │ │ ├── low_quality_cc.txt │ │ ├── documents_with_line_duplicates/ │ │ ├── documents_line_deduplicated/ │ │ └── documents_with_fuzzy_duplicates/ │ ├── adapters.py # 测试适配器(需要实现) │ ├── test_extract.py # HTML文本提取测试 │ ├── test_langid.py # 语言识别测试 │ ├── test_pii.py # PII脱敏测试 │ ├── test_quality.py # 文本质量测试 │ ├── test_toxicity.py # 有害内容测试 │ └── test_deduplication.py # 去重测试 ├── pyproject.toml # 项目配置 └── README.md ``` ## 项目代码执行流程 ### 1. UV 环境管理器启动 ```bash uv run pytest ``` 当执行这个命令时: 1. UV 读取 `pyproject.toml`,解析项目依赖 2. 创建或激活虚拟环境 3. 在虚拟环境中执行 pytest ### 2. Pytest 初始化流程 2.1 配置加载 ```toml # pyproject.toml 中的 pytest 配置 [tool.pytest.ini_options] testpaths = ["tests"] pythonpath = ["."] ``` 2.2 测试发现 ```Java Pytest 扫描 tests/ 目录,找到所有 test_*.py 文件: tests/ ├── test_extract.py ├── test_langid.py ├── test_pii.py ├── test_quality.py ├── test_toxicity.py └── test_deduplication.py ``` ### 3. 测试执行流程(以 test_extract.py::test_extract_text_from_html_bytes 为例) 3.1 Fixture 加载 ```python # tests/common.py FIXTURES_PATH = Path(__file__).parent / "fixtures" ``` 3.2 测试函数执行 ```python # tests/test_extract.py:8-17 def test_extract_text_from_html_bytes(): # 1. 读取HTML测试文件 moby_path = FIXTURES_PATH / "moby.html" with open(moby_path, "rb") as f: moby_bytes = f.read() # 2. 读取期望结果 moby_expected_path = FIXTURES_PATH / "moby_extracted.txt" with open(moby_expected_path) as f: moby_expected_text = f.read() # 3. 调用适配器函数 actual_output = run_extract_text_from_html_bytes(moby_bytes) # 4. 验证结果 assert moby_expected_text == actual_output ``` 3.3 适配器调用 ```python # tests/adapters.py:6-8 def run_extract_text_from_html_bytes(html_bytes: bytes) -> str | None: # 原本是: raise NotImplementedError # 现在是: 调用你的实现 return extract_text_from_html_bytes(html_bytes) ``` 3.4 实际实现执行 ```python # cs336_data/extract.py (你需要创建) def extract_text_from_html_bytes(html_bytes: bytes) -> str | None: """从HTML字节流中提取纯文本""" # 你的实现代码 pass ``` ## 实现各组件的代码 你需要实现以下核心功能模块: ### 1. 文本提取模块 (extract.py) ```python def extract_text_from_html_bytes(html_bytes: bytes) -> str | None: """从HTML字节流中提取纯文本内容""" # 使用BeautifulSoup或其他HTML解析库 pass ``` ### 2. 语言识别模块 (langid.py) ```python def identify_language(text: str) -> tuple[str, float]: """识别文本语言,返回语言代码和置信度""" # 使用langid或fasttext等库 pass ``` ### 3. PII脱敏模块 (pii.py) ```python def mask_emails(text: str) -> tuple[str, int]: """脱敏邮箱地址,返回处理后的文本和脱敏数量""" pass def mask_phone_numbers(text: str) -> tuple[str, int]: """脱敏电话号码""" pass def mask_ips(text: str) -> tuple[str, int]: """脱敏IP地址""" pass ``` ### 4. 内容分类模块 (classifiers.py) ```python def classify_nsfw(text: str) -> tuple[str, float]: """分类NSFW内容""" pass def classify_toxic_speech(text: str) -> tuple[str, float]: """分类有害言论""" pass def classify_quality(text: str) -> tuple[str, float]: """分类文本质量""" pass ``` ### 5. 质量过滤模块 (quality.py) ```python def gopher_quality_filter(text: str) -> bool: """Gopher质量过滤器,实现多个质量检查规则""" pass ``` ### 6. 去重模块 (deduplication.py) ```python def exact_line_deduplication(input_files: list[Path], output_directory: Path): """精确行去重""" pass def minhash_deduplication( input_files: list[Path], num_hashes: int, num_bands: int, ngrams: int, jaccard_threshold: float, output_directory: Path, ): """MinHash模糊去重""" pass ``` ## 训练数据 Assignment 4 的数据处理管道包括: ### 1. 原始数据来源 - HTML网页内容 - 多语言文本 - 包含PII的文本 - 不同质量的文本(wiki vs cc) - 重复和相似文档 ### 2. 数据处理流程 ```Java 原始数据 → 文本提取 → 语言识别 → PII脱敏 → 质量过滤 → 去重 → 训练数据 ``` ### 3. 数据质量检查 - **Gopher质量过滤器**:检查词数、词长、标点符号等 - **内容安全**:NSFW和有害言论检测 - **重复检测**:精确重复和模糊重复 ## 核心组件的作用 | 组件 | 作用 | 通俗解释 | |------|------|----------| | **HTML文本提取** | 从网页中提取纯文本 | 就像"剥洋葱",去掉HTML标签,只保留文字内容 | | **语言识别** | 判断文本语言 | 像"翻译官",告诉计算机这段文字是什么语言 | | **PII脱敏** | 隐藏个人信息 | 像"涂黑笔",把邮箱、电话、IP等敏感信息遮盖 | | **内容分类** | 判断文本类型和质量 | 像"质检员",区分好坏内容,过滤垃圾信息 | | **质量过滤** | 应用质量规则 | 像"筛子",过滤掉太短、太长、质量差的文本 | | **去重** | 删除重复内容 | 像"整理员",去掉重复和相似的内容,节省存储空间 | ## Assignment 4: Data 通俗解释 ### 它干嘛的? - **整体目标**:构建一个数据预处理管道,将原始网络数据转换为高质量的训练数据 - **为什么重要**:大语言模型训练需要海量高质量数据,但原始数据往往包含垃圾、重复、有害内容,需要清洗 ### 要实现的东西 你要实现一个完整的数据处理流水线,包括: 1. **文本提取**:从HTML网页中提取纯文本 2. **语言识别**:判断文本语言,过滤非目标语言 3. **隐私保护**:脱敏邮箱、电话、IP等个人信息 4. **内容过滤**:检测和过滤NSFW、有害言论 5. **质量评估**:应用Gopher等质量规则 6. **去重处理**:删除重复和相似内容 ### 数据处理流程(通俗版) 1. **收集原始数据**:从网络爬取HTML页面 2. **提取文本**:去掉HTML标签,保留文字内容 3. **语言筛选**:只保留目标语言(如英语) 4. **隐私保护**:遮盖个人信息 5. **内容审核**:过滤不当内容 6. **质量检查**:应用质量规则 7. **去重处理**:删除重复内容 8. **输出训练数据**:得到干净、高质量的训练文本 完成后,你就有了一个能自动处理原始网络数据、输出高质量训练数据的完整管道!🎉 ## 1. Lab 4 在整个课程中的定位 |作业|关键词|你要掌握的核心技能|产出物| |---|---|---|---| |Lab 1 _(Basics)_|**Transformer** 最小实现|搭积木:Embedding、Self‑Attention、FFN、优化器|能跑通一个 100 M 参数级别的中文/英文小模型| |Lab 2 _(Efficiency)_|内核融合、Flash‑Attn、混合精度|把 Lab 1 加速到单卡吃得下、跑得快|秒/step↓、显存↓| |Lab 3 _(Scaling)_|数据/模型并行、ZeRO|多 GPU 多机训练|Gbit 以上带宽下稳定训练| |**Lab 4 _(Data)_**|**高质量语料管道**|清洗 + 去重 + 分词 + 分片 + 验证|1. `cs336_data`  Python package2. 一个.tar.gz 提交包3. 在官方 leaderboard 取得越低的 val loss 越好 ([GitHub](https://raw.githubusercontent.com/stanford-cs336/assignment4-data/main/README.md "raw.githubusercontent.com"))| |Lab 5 _(Alignment)_|RLHF / DPO|安全、守护式微调|基于人偏好对话模型| --- ## 2. 官方仓库结构速览 ```Java assignment4-data/ ├── cs336-basics/ # 教学组给的“参考实现”训练脚本 ├── cs336_data/ # 现在几乎为空,等你填逻辑 ├── README.md # 作业说明 & 目录约定 └── test_and_make_submission.sh ``` `README.md` 明确写道:`cs336_data` “**基本是空的!这里才是你要写代码的地方**” ([GitHub](https://raw.githubusercontent.com/stanford-cs336/assignment4-data/main/README.md "raw.githubusercontent.com"))。 此外还有一个独立仓库 `assignment4-data-leaderboard` 用来记成绩;表头按 _Validation Loss_ 排序 ([GitHub](https://raw.githubusercontent.com/stanford-cs336/assignment4-data-leaderboard/main/README.md "raw.githubusercontent.com"))。 --- ## 3. Lab 4 要解决的 5 件事 > 下文每条都给出「**为什么**要做」→「**怎么**做」→「可选进阶」→「常见坑」。 ### 3.1 解析原始 Common Crawl 分片 - **为什么** 原始 WARC/CC-MAIN 文件动辄 TB 级,既含 HTML 标签也混着非文本资源。 - **怎么做** 使用 `warcio` 或 `gzip` 流式读取;遇到 `Content-Type: text/html` 时用 `BeautifulSoup` 抽正文。控制台打印进度 + 错误计数,别一次性读进内存。 - **进阶** - 只保留你关心的语言(如英文)→ 快速 `langdetect`,后续更准确用 `fasttext`。 - **常见坑** - `warcio` 在部分坏块上会抛 `ArchiveLoadFailed`; 要 `try/except` 跳过。 ### 3.2 文本质量过滤 |过滤维度|建议阈值|检测方法| |---|---|---| |Unicode 可打印比例|≥ 0.9|`sum(c.isprintable() for c in text)/len(text)`| |非字母字符比例|≤ 0.3|正则| |重复行 / boiler‑plate|去掉 `<header>、<nav>、cookie banner` 等|XPath、`justext`| - **进阶**: - 训练一个极小 GPT‑2 (≈ 25 M) 做 _ppl filter_——剔除困惑度>100 的文档。 - **常见坑**: - 先 `strip()` 后再测长度;别把全空行当高质量文本。 ### 3.3 去重 - **为什么** 重复文本会让模型过拟合,虚高 loss。 - **怎么做** - 粗去重:`hashlib.md5(text)` 放到 `set()`; - 近似去重:`simhash` 或 MinHash;`datasketch` 库能做 128‑bit MinHash + LSH。 - **进阶** - 在 token 级别用分块 sort‑shuffle,其效果与 _ExactDedup_ 相近但内存亲民。 - **常见坑** - 切记分片并行化后仍要全局去重,否则同一 url 会分到不同 worker,结果重复。 ### 3.4 分词与分片 - **为什么** 训练脚本固定用 `tiktoken`‑like BPE;一条样本默认 2048 token。 - **怎么做** ```python enc = tiktoken.get_encoding("gpt2") ids = enc.encode(text) for i in range(0, len(ids) - 2048, 2048): yield ids[i:i+2048] ``` - **进阶** - 按 “自然段落” 拼接再截断,能减少句子被腰斩的概率。 - **常见坑** - token 长度不足 64 时可丢弃——短上下文对语言模型价值不大。 ### 3.5 生成可提交的数据集 - **要求** 1. 把所有 `token_id` 序列写成 `uint16` / `uint32` NumPy 数组,压成 `npz` 或 `bin`。 2. `./test_and_make_submission.sh` 能一键跑完并输出 `val_loss=...`。 - **验证** - 用 `cs336-basics` 提供的训练脚本跑 1‑2 epoch;只看 _validation loss curve_ 即可,下降越快通常说明你的数据更“干净”。 --- ## 4. 与 Lab 1 的联系 & 差异 |维度|Lab 1 (模型)|Lab 4 (数据)| |---|---|---| |主要代码|`cs336_basics`|`cs336_data`| |关注点|**算子实现与训练 loop**|**原料质量**| |成功指标|模型在固定数据集的 _val loss/perplexity_|固定模型在你数据上的 _val loss_| |典型 debug|梯度爆炸 / 学不到|loss 不降或波动大=数据脏| --- ## 5. 逐步落地指南(给 Java 后端工程师的最小可行路径) 1. **先把仓库跑通**: ```bash git clone https://github.com/stanford-cs336/assignment4-data.git cd assignment4-data uv pip install -e . ./test_and_make_submission.sh # 先跑 baseline,确认环境 ok ``` 2. **写数据管道**(推荐放 `cs336_data/pipeline.py`): - 用 `IterableDataset`,边读边 token 化,保证 < 3 GB 内存也能跑。 - 日志用 Python `logging`,打 INFO 行数、WARN 错误数。 3. **本地 sanity check**: ```python from cs336_data.pipeline import build_dataset ds = build_dataset("/path/to/raw") print(next(iter(ds))) # 应该是长度 2048 的 list[int] ``` 4. **小规模训练**: - 设置 `num_train_tokens=5e6`,单 3090 约 15 min 即能看到趋势。 - 追踪验证集 loss,如果 > baseline 的 4.0 就说明过滤做坏了。 5. **优化**: - 尝试不同语言检测+去重组合,每改一次都留 _commit_,方便 A/B Test。 - 观察 loss 曲线的斜率;前 1000 step 确定改动好坏,剩下靠耐心。 6. **打包 & 提交**: ```bash ./test_and_make_submission.sh # 生成 submission.tar.gz # 按 README 指示 PR 至 leaderboard 仓库 ``` --- ## 6. 常见报错速查表 |报错信息|可能原因|快速修复| |---|---|---| |`UnicodeDecodeError: 'utf-8' codec can't decode`|直接用 `open(..., 'r')` 读 gzip|用 `gzip.open` + `.decode("utf‑8", errors='ignore')`| |`CUDA out of memory`|token 序列过长 or batch 太大|调小 `batch_size`, 或把 `sequence_length` 固定 1024/1536| |验证 loss 一直是 `nan`|出现空样本 / 非数字 token|pipeline 最后加 `assert len(seq) == 2048 and all(isinstance(x, int))`| --- ## 7. 延伸阅读 & 工具 |主题|推荐资源| |---|---| |Common Crawl 解析|[https://commoncrawl.org](https://commoncrawl.org/) 官方 FAQ| |质量过滤 heuristics|[Pile 论文 §3.1]、[OpenWebText2 数据集博客]| |近似去重算法|“SimHash: hashing for similarity” (Google 2002)| |多进程流水线|Python `multiprocessing` + `queue.SimpleQueue`| |分词器|`tiktoken`, `sentencepiece`, `huggingface/tokenizers`| --- ### 小结 - **核心心法**:数据质量>模型大小——想象你在调优搜索引擎,先把网页垃圾扫干净。 - **最小目标**:实现 ➊ 基本清洗 + ➋ 行级 MD5 去重 + ➌ BPE 分词 + ➍ 固定 2048‑token 切片。做到这四步,验证 loss 就能比 baseline(4.0↑)明显下降,踏进 leaderboard 前列没问题。 - **进阶方向**:软指标过滤(ppl filter)、多语言混合策略、MinHash 去重、Dataset‑streaming。 祝你玩得开心,也欢迎把你的 leaderboard 名次分享出来!