Machine Learning Systems 第三章:Data Engineering 与 Dataset Compilation
核心结论:在机器学习系统中,数据决定模型最终学会什么行为,因此数据不是训练前的一次性输入,而是需要被采集、验证、编译、版本化和持续维护的“源代码”。
本文依据《Machine Learning Systems》Volume I 的 Data Engineering 章节整理。重点不是罗列大数据工具,而是理解数据如何沿着完整路径抵达加速器,以及数据质量、存储布局和训练—推理一致性为什么会直接决定模型行为与训练效率。
1. Data Engineering 不是 Data Cleaning
传统软件中,源代码直接描述程序逻辑,编译器负责将它变成可执行文件。机器学习系统的关系发生了反转:
- 训练代码描述“如何从数据中提取规律”;
- 数据决定“最终提取出什么规律”;
- 模型是数据经过优化过程编译得到的可执行产物。
只修改训练数据,不修改一行模型代码,系统行为仍可能完全改变:
- 缺少边界样本,模型就在边界条件下失败;
- 标签存在系统性偏差,模型会学习这种偏差;
- 训练集包含历史偏见,模型会把偏见固化进参数;
- 训练集与生产分布脱节,离线指标再高也无法保证线上效果。
因此,本章把 Data Engineering 重新定义为 Dataset Compilation。它不是简单地删除缺失值、修正格式,而是把原始观测逐步转换成模型可消费的训练数据。
| 编译器阶段 | Dataset Compiler 中的对应操作 |
|---|---|
| 词法与语法分析 | 摄取原始数据、解析记录、检查 Schema |
| 类型检查 | 校验字段类型、取值范围和必填约束 |
| 优化 | 去重、过滤、特征转换、数据增强 |
| 代码生成 | 生成训练可读的 Tensor、Shard 或列式数据 |
| 构建产物管理 | 数据集版本、转换参数、模型与数据 Lineage |
这个类比的关键不在术语,而在工程要求:既然数据是源代码,就必须像代码一样被测试、审查和版本化;既然重新训练相当于重新编译,就必须知道每个模型究竟由哪一版数据和哪一版转换逻辑产生。
训练集、验证集和测试集也因此形成三条信任边界:
- 训练集可以影响模型参数;
- 验证集可以影响模型选择和流水线决策;
- 测试集只用于在决策完成后估计泛化能力。
重复样本跨越数据集边界、同一用户的数据同时出现在训练集和测试集、使用未来信息计算历史特征,本质上都是信息穿透了这三条边界,即 Data Leakage。
2. 从原始数据到 GPU:完整的数据路径
理解训练 I/O 的第一步,是把“加载一个 Batch”展开成完整路径。概念上可以表示为:
这条路径中,任何一段供给速度不足,都会让 GPU 等待:
- 原始数据与摄取:数据可能来自数据库、对象存储、API、设备流或人工标注系统,格式、更新频率和可靠性并不一致。
- 验证、清洗与转换:Schema 检查、缺失值处理、去重、特征生成、数据增强和序列化通常消耗 CPU。
- 分片与存储:文件格式、压缩算法、Shard 数量、分区均衡性和存储介质决定可获得的 I/O 吞吐。
- DataLoader:多个 Worker 并发读取、预取、缓存并组装 Batch。
- CPU 内存与解码:JPEG 解码、文本 Tokenization、音频特征提取等操作可能在数据进入 GPU 前形成 Choke Point。
- Host-to-Device 传输:Batch 最终经主机内存和设备互连进入 GPU。
训练单步时间由最慢阶段决定:
等价地,训练吞吐由计算能力与数据供给能力中较低的一方决定:
这解释了一个常见误判:GPU 利用率低不一定说明 Kernel、Batch Size 或模型结构存在问题。若磁盘已经跑满、CPU Worker 忙于解码、PCIe 传输断断续续,GPU 只是数据系统故障的受害者。
3. 数据的物理属性:Data Gravity、Entropy 与 Feeding Tax
3.1 Data Gravity:数据移动受物理带宽约束
数据迁移时间的下界非常直接:
其中,(D\_{\text{vol}}) 是数据量,(BW) 是有效带宽。
本章给出的数量级判断是:将 1 PB 数据通过 100 Gb/s 专线传输,理想情况下也需要约 22 小时;若出口流量按 0.09 美元/GB 计费,成本约为 9 万美元。实际迁移还包括重新验证、Schema 迁移、下游同步和 Lineage 更新,工程周期远大于纯粹的网络传输时间。
因此,本章给出一条简单但重要的经验:
- GB 级数据通常可以移动到计算侧;
- PB 级数据通常应让计算靠近数据。
这就是 Data Gravity。数据越大,围绕它形成的存储、处理和治理系统越难搬迁。Data Lakehouse 让计算引擎靠近存储,本质上是在减少大规模重复复制,而不是单纯改变产品形态。
3.2 Information Entropy:数据量不等于信息量
一百万张几乎相同的图片占用大量空间,却只包含很少的新信息;一万张覆盖不同口音、光照和异常场景的样本,数据量更小,却可能更能改善模型。
本章用下面的比例描述单位移动成本带来的训练价值:
这个关系揭示了为什么去重和定向补充边界样本往往比盲目扩充数据集更有效:
- 去重减少需要存储、传输和解码的字节;
- 补充错误样本与长尾样本提高每字节携带的有效信号;
- 两者同时改善数据选择收益。
“更多数据总是更好”是本章明确反对的观点。更多低信息密度数据只会提高 Data Gravity 和 Feeding Tax,并不能保证模型学到新的东西。
3.3 Feeding Tax:GPU 为等待数据付出的时间
Data Gravity 关心整批数据移动一次需要多久,Feeding Tax 关心训练期间能否持续把数据送到 GPU。
假设加速器的计算上限为 25,365 img/s,每张压缩图片为 150 KB,则持续喂满加速器需要约:
不同存储介质面对这个需求的结果完全不同:
| 存储路径 | 典型顺序吞吐 | 对 3.8 GB/s 需求的影响 |
|---|---|---|
| SATA SSD | 约 500 MB/s | 明显成为瓶颈 |
| 本地 NVMe | 约 3–7 GB/s | 有机会满足单卡需求 |
| 对象存储单连接 | 约 100 MB/s | 需要大量并发读取并隐藏延迟 |
若 SATA SSD 只能提供 500 MB/s,则最多供给约 3,333 img/s。相对于 25,365 img/s 的理论计算上限,GPU 利用率只有约 13%。此时增加 GPU、优化算子或提高峰值 FLOPS 都没有意义。
排查 GPU Starvation 时,应沿路径依次测量:
- 存储实际读取吞吐与 IOPS;
- DataLoader Worker 数量及其 CPU 利用率;
- 图片、音频或文本的解码吞吐;
- 预取队列是否经常为空;
- Host-to-Device 传输是否连续;
- GPU Kernel 之间是否存在明显空洞。
4. Four Pillars:评价数据系统的统一框架
本章用四个相互制约的支柱评价整个数据生命周期。
| 支柱 | 核心问题 | 典型机制 |
|---|---|---|
| Quality | 数据是否正确、完整且覆盖部署环境? | Schema 检查、语义监控、漂移检测、标签审核 |
| Reliability | 故障、重试和上游变化发生时,流水线能否继续工作? | Backoff、Checkpoint、DLQ、Circuit Breaker、Fallback |
| Scalability | 数据量、来源和并发扩大后,成本与吞吐是否仍可接受? | 分区、并行处理、局部聚合、分层存储 |
| Governance | 数据来源、权限、同意、偏差和处理历史是否可追踪? | Provenance、Lineage、访问控制、数据文档 |
四个支柱不是独立清单。更严格的逐条验证可以提高 Quality,却会增加延迟、内存占用和拒绝路径,给 Scalability 与 Reliability 带来压力;更严格的隐私要求可能减少可用训练数据,影响覆盖率;强一致的 Feature Store 能降低 Training-serving skew,却可能在网络分区期间牺牲可用性。
工程设计不是让某一项无限增强,而是明确约束与代价。
4.1 Data Cascade:小错误为何会变成系统事故
Data Cascade 指上游数据问题沿着采集、标注、特征工程、训练、评估和部署不断传播,并在下游被放大。
本章用 zip_code 说明这种静默传播:
- 上游将字段从整数改为字符串,以支持国际邮编;
- 下游没有 Data Contract,仍把
"02139"转成整数; - 前导零丢失,值变成
2139; - 模型把它视为从未见过的新类别;
- 默认处理逻辑把该地区错误地判为高风险。
整个流水线可能没有崩溃,训练任务也可能正常结束,但数据语义已经被破坏。普通单元测试很难发现这种问题,因为它们通常验证“程序是否成功执行”,而不是“数据是否仍表达原来的现实含义”。
防止 Data Cascade 需要四类约束同时存在:
- Data Contract 明确类型、单位、取值范围和语义;
- 验证系统在入口快速失败或隔离异常记录;
- Lineage 记录哪些下游产物使用过问题数据;
- 运行期监控把数据变化与模型行为变化关联起来。
5. Data Acquisition:从部署缺口出发,而不是从数据量出发
数据获取的起点不应是“还能收集多少”,而应是“部署分布还缺少什么”。
本章把常见来源分为预先存在的数据集、Web Scraping、Crowdsourcing 和 Synthetic Data。它们解决的是不同约束:
- 预先存在的数据集:启动快,便于建立 Baseline,但分布常与真实部署环境不一致;
- Web Scraping:规模大、成本低,但包含上下文噪声、时间错位、版权和来源追踪问题;
- Crowdsourcing:可并行扩展,并带来人群多样性,但质量控制和任务设计成为新瓶颈;
- Synthetic Data:适合生成稀有事件和可控扰动,却继承生成器没有覆盖的盲区。
对 Keyword Spotting(KWS)系统而言,真实数据负责锚定口音、麦克风、房间和背景噪声等真实变化,合成数据负责在这些已知轴上扩大覆盖。合成数据适合补充真实数据,而不是替代真实数据。
这也是本章反复强调的设计方式:先确定 Coverage Gap,再选择能够以可接受成本、质量和治理风险填补该缺口的数据来源。
6. Data Pipeline Architecture:把异构输入变成统一表示
本章把数据流水线称为 Dataset Compiler 的前端。外部数据可能来自关系表、JSON、文件、设备流或对象存储,入口层必须完成三件事:
- 解析不同协议和格式;
- 验证 Schema、范围和基本质量;
- 转换为下游稳定依赖的统一内部表示。
在统一边界之后,来源系统可以继续演化,而处理和训练阶段不必理解每种外部格式。
6.1 Mechanical Quality 与 Semantic Quality
数据验证不能只检查 Schema。本章区分两种质量:
- Mechanical Quality:字段是否存在、类型是否正确、是否为空、数值是否越界;
- Semantic Quality:数据分布、标签含义和业务语义是否仍然正确。
例如,age 字段全部是合法整数,且都没有缺失,但某次上游改动把所有年龄默认成 25。机械检查全部通过,语义已经彻底失效。
因此,生产数据质量至少需要两层检查:
def validate_record(row):
assert row["user_id"] is not None
assert 0 <= row["age"] <= 120
assert row["country_code"] in ALLOWED_COUNTRIES
def validate_batch(batch, baseline):
assert null_rate(batch, "age") < 0.05
assert distribution_distance(batch["age"], baseline["age"]) < THRESHOLD
assert duplicate_rate(batch, "user_id") < DUPLICATE_LIMIT
第一层阻止明显无效的记录进入流水线;第二层检查统计分布和批次行为。两者缺一不可。
6.2 Drift:数据仍合法,但世界已经变化
本章区分三种分布漂移和一种标签质量漂移:
| 漂移类型 | 发生变化的分布 | 示例 |
|---|---|---|
| Covariate Shift | (p(x)) | 摄像头型号变化导致图像像素分布变化 |
| Label Shift | (p(y)) | 疾病流行率或商品类别占比发生变化 |
| Concept Drift | (p(y\mid x)) | 欺诈者改变策略,旧特征不再对应旧风险 |
| Label Quality Drift | 标签可靠性 | 标注员更换、规范变化、自动标注模型退化 |
PSI、KL Divergence 等指标可以衡量特征分布变化,但 Concept Drift 通常需要真实标签才能确认。Label Quality Drift 则要监控 Inter-annotator Agreement,并用周期性专家抽检校准自动标注结果。
漂移指标是告警信号,不是自动重训命令。是否重训还要结合业务影响、样本量、监控窗口和模型性能变化。
6.3 Reliability:失败记录不能拖死主流水线
生产流水线必须假设网络中断、数据源不可用、Schema 演化、资源耗尽和局部坏数据一定会发生。
- 短暂网络错误使用 Exponential Backoff;
- 多次失败的记录进入 Dead Letter Queue(DLQ);
- 连续失败的服务由 Circuit Breaker 暂停调用;
- 关键特征不可用时,回退到缓存值、旧值或近似值;
- 长任务通过 Checkpoint 从最近成功位置恢复。
DLQ 不只是“坏数据垃圾桶”。其中的异常记录可能代表新支付类型、新设备格式或模型没有覆盖的边界条件,因此它也可以成为下一轮数据获取和模型改进的输入。
6.4 Batch、Streaming、ETL 与 ELT
Batch 与 Streaming 的选择,本质上取决于数据价值随时间衰减的速度:
- Batch 用更高延迟换取更低成本、更容易的重试和调试;
- Streaming 用持续运行的复杂性换取数据新鲜度;
- 大多数系统采用混合方式,例如实时处理会话行为,夜间批量更新长期画像。
ETL 与 ELT 则决定转换发生在哪里:
| 模式 | 优势 | 代价 | 更适合 |
|---|---|---|---|
| ETL | 入库前完成验证与压缩,存储成本低 | 特征定义变化时需重新处理原始数据 | 稳定 Schema、严格合规 |
| ELT | 保留原始数据,转换逻辑可快速修改 | 原始数据存储大,重复计算多 | 特征快速迭代、非结构化数据 |
本章进一步把 ELT 的“延迟转换”延伸到 DataLoader:随机裁剪、噪声注入、频谱生成或 Tokenization 可以在训练时动态执行,而不必把每一种增强结果都物化到存储中。
真正的决策是由谁付费:
- 预计算特征:存储付费,训练更快、复现更容易,但存在陈旧风险;
- 动态计算特征:CPU 和训练时延付费,实验灵活、存储较省,但可能造成 GPU Starvation;
- 混合方案:昂贵且稳定的特征预计算,便宜且时效敏感的特征动态计算。
7. Systematic Data Processing:转换必须可重复
7.1 Training-serving consistency 不只是共享代码
训练和推理必须使用相同的转换逻辑,这被本章称为 Consistency Imperative。但共享函数还不够,训练阶段产生的状态也必须被保存并在推理阶段复用。
典型状态包括:
- 标准化使用的 Mean 与 Standard Deviation;
- 类别编码使用的 Vocabulary;
- 缺失值填充规则与统计量;
- Tokenizer 词表和版本;
- 音频 FFT Window、Hop Length、MFCC 系数数量;
- 图像缩放、裁剪和颜色归一化参数。
错误做法是训练时根据训练集算出 mean=0.5,推理时又根据线上流量算出另一组均值。即使两边调用同一个函数,特征分布仍会发生偏移。
因此,完整的一致性约束应包含:
最后一项尤其容易被忽略。训练“过去 30 天购买次数”时,只能使用预测时点之前已经发生的数据;若使用当前数据库回填历史特征,就会引入未来信息。Feature Store 的 Point-in-time Correctness 正是为了解决这个问题。
7.2 Idempotency、Determinism 与 Checkpoint
Idempotent Transformation 保证相同输入被重复处理时,最终状态不因重试次数而改变。
def process(record, output_table):
result = transform(record)
output_table.upsert(key=record.id, value=result)
若改成无条件 append,任务失败后的重试会生成重复样本,悄悄改变训练分布。upsert 使重试得到相同的最终状态。
Idempotency 与 Determinism 相关但不相同:
- Idempotency 关注重复执行是否改变最终状态;
- Determinism 关注相同输入是否总产生相同输出。
使用当前时间、未固定的随机数或可变全局状态都会破坏 Determinism。更稳妥的做法是显式保存参考时间,并从输入 ID 派生随机种子。Checkpoint 则记录已完成的分区和处理状态,使长任务只重做失败后的部分。
7.3 Distributed Processing:先局部归约,再跨网络聚合
分布式数据处理的主要税负不是算术运算,而是协调和数据移动。本章比较了 100 个节点对 1 TB 特征计算全局均值的两种方法:
- 收集全部数据到单节点:仅网络传输就约 100 秒;
- 每个节点先计算局部 Sum 与 Count,再汇总 100 份小结果:约 0.05–0.2 秒。
因此,可归约操作应尽量在数据本地完成:
Sum、Mean、Count 适合 Local-then-Reduce;Join、Cross Product 等会扩大数据的操作则天然承受较高网络成本。分布式框架不会消除这个物理约束,只会帮助调度、容错和并行执行。
7.4 Lineage:必须能回答“这个模型从哪里来”
完整 Lineage 至少应连接:
Raw Data Version
→ Transformation Code Commit
→ Transformation Parameters
→ Processed Dataset Version
→ Label Version
→ Training Configuration
→ Model Artifact
它的价值不是生成漂亮的血缘图,而是在模型退化时能够快速区分:
- 原始数据是否变化;
- 转换逻辑是否变化;
- 转换参数是否变化;
- 标签策略是否变化;
- 训练配置是否变化。
没有 Lineage,排障只能依赖人员记忆和日志考古;有 Lineage,问题可以转化为两个模型构建快照之间的差异比较。
8. Data Labeling:Ground Truth 也是代理值
“Ground Truth”并不意味着绝对真理。标签仍然是人或自动系统对现实的判断,可能含有歧义、专业知识差异和系统性错误。
标签粒度越细,系统代价越高:
- Classification 每个样本通常只需一个类别;
- Bounding Box 需要对象类别与位置坐标,标注耗时可达到分类的 10–20 倍;
- Segmentation 对每个像素赋标签,一张 (1920\times1080) 图片约有 210 万个像素标签。
质量控制不能只追求“多数人投票”。低一致性可能说明样本本身模糊,也可能说明任务需要专家知识。更合理的架构是分层升级:
- 普通样本由自动预标注或众包处理;
- 多标注员结果用于计算 Agreement;
- 低一致性和高风险样本进入专家审核;
- 周期性抽检用于校准人工与自动标注系统。
AI-assisted Labeling 的定位是放大器,而不是完全替代人类。机器负责清晰、重复、规模化的部分,人类负责模糊、长尾和高风险判断。Active Learning 也不是“免费减少标签”:它用推理计算筛选高信息样本,因此必须把候选池打分成本纳入预算。
9. Storage Architecture:数据布局决定训练吞吐
9.1 先看 Access Pattern,再选存储系统
训练与在线推理需要完全不同的访问模式:
- 训练反复扫描大量样本,更关注顺序吞吐;
- 在线推理读取单个用户或对象的特征,更关注随机访问延迟与 IOPS;
- 探索阶段需要灵活读取原始异构数据;
- 归档阶段更关注成本与保留时间。
因此,Database、Data Warehouse 和 Data Lake 不是相互替代的三种潮流,而是服务于不同访问模式:
- Database:高 IOPS、小块随机读、事务一致性;
- Data Warehouse:列式扫描、批量分析和特征工程;
- Data Lake:低成本保留图像、音频、文本等大规模原始数据。
成熟系统往往同时使用三者,并通过数据目录和 Lineage 连接。
9.2 为什么小文件多、Shard 不合理会拖慢训练
本章指出,小文件随机读取的实际性能可能显著低于介质标称的顺序吞吐。原因可以从书中的吞吐框架直接推出:每个文件都带来打开、定位、元数据查询和独立 I/O 请求,真正传输样本字节的比例下降,Overhead 上升。
把训练样本组织成适当大小的 Shard,可以:
- 将大量小随机读转化为较大的顺序读;
- 让不同 Worker 独立读取不同 Shard;
- 减少共享文件句柄竞争;
- 让预取与缓存以连续块工作;
- 控制每个 Worker 的局部工作集。
但 Shard 不是越多越好:
- 太少会限制并行度;
- 太多会增加元数据开销并破坏顺序读取;
- 大小不均衡会制造 Straggler。
本章给出的例子是:八卡训练中,七个 Worker 在 12 ms 内完成数据读取,一个 Worker 因分区过大耗时 180 ms,那么同步训练的有效 Batch 时间就是 180 ms。最慢 Shard 决定了全局速度。
9.3 CSV、Parquet 与 Format Efficiency
有效带宽不只由硬盘决定,还取决于读取的字节中有多少真正被模型使用:
假设一张表有 100 列,模型只需要其中 20 列:
- CSV 按行组织,通常需要读取整行,(\eta\_{\text{format}}\approx0.2);
- Parquet 按列组织,可以只读取所需列,(\eta\_{\text{format}}\approx1)。
在忽略元数据开销的简化模型下,Parquet 获得约 5 倍有效吞吐。这不是硬盘真的快了 5 倍,而是 80% 无用字节不再进入数据路径。
列式布局还便于按列压缩。低基数类别可使用 Dictionary Encoding,排序列可使用 Run-length Encoding,从而进一步减少需要从存储移动到 CPU 的字节。
9.4 压缩减少 I/O,却增加 CPU 解码
压缩并非压得越小越好。本章比较了两类取舍:
- gzip 压缩比约 6–8 倍,解压吞吐约 120 MB/s;
- Snappy 压缩比约 2–3 倍,解压吞吐约 500 MB/s。
对于需要反复扫描几十个 Epoch 的训练任务,解压速度可能比容量节省更重要。若 CPU 解压无法跟上 GPU 消费速度,较高压缩率反而会把存储瓶颈转化为 CPU Decode Bottleneck。
因此应测量端到端吞吐,而不是只比较文件大小:
9.5 DataLoader、Prefetch、Cache 与本地 NVMe
DataLoader 的目标不是“读到数据”这么简单,而是用并行读取、预取和缓存把存储延迟隐藏在 GPU 计算之后。
常见优化顺序是:
- 将工作集从远程对象存储缓存到本地 NVMe;
- 调整 Worker 数量,直到 CPU 供给达到 GPU 需求或存储上限;
- 使用 Prefetch 保持待消费 Batch 队列非空;
- 将大量小文件整理为均衡 Shard;
- 选择减少无效字节的格式和可快速解码的压缩;
- 决定数据增强在 CPU、DataLoader 还是更靠近加速器的位置执行。
本章给出的数量级是:本地 NVMe 可提供约 5–7 GB/s 顺序吞吐,而对象存储单连接常为 100–500 MB/s。这个差距直接决定一次实验是几分钟开始训练,还是每个 Epoch 都要等待数小时。
10. Data Versioning 与 Feature Store
10.1 Data Versioning:代码没变,不代表实验可复现
模型由代码、数据、转换参数和训练配置共同决定。Git 适合管理小型文本差异,却不适合直接保存大量二进制数据和模型 Checkpoint。
本章介绍的典型方式是:
- Git 保存轻量级指针和代码 Commit;
- 大文件存储保留内容寻址的数据快照;
- 模型注册记录代码、数据、特征快照和训练配置之间的关系。
dvc add data/training.parquet
git add data/training.parquet.dvc
git commit -m "Track training dataset v1"
dvc push
复现实验时,代码版本与数据指针必须一起恢复。否则,同一份训练代码在今天和三个月后运行,可能读取到完全不同的数据。
10.2 Feature Store 不是简单的特征数据库
Feature Store 的核心目标是:
- 统一特征定义与转换逻辑;
- 为训练提供高吞吐的 Offline Store;
- 为推理提供低延迟的 Online Store;
- 支持按历史时点读取特征,保证 Point-in-time Correctness。
例如,要预测用户是否会在 1 月 15 日流失,训练样本只能使用 1 月 14 日及以前可见的特征,而不能读取今天数据库中的最终状态。否则,训练数据会包含推理时不可能获得的信息。
因此,Feature Store 更接近“特征转换与时态一致性层”,而不是“存放 Feature 的地方”。它同时解决计算逻辑复用、离线—在线访问模式差异和历史特征回放问题。
11. Operational Data Health:数据系统必须持续维护
流水线成功运行不代表 Data Engineering 已经完成。用户行为、设备、Schema 和标注规范会持续变化,本章把积累的隐式耦合与缺失文档称为 Data Debt。
| 债务类型 | 典型表现 | 主要治理方式 |
|---|---|---|
| Documentation Debt | 字段含义、来源和转换无人能解释 | Data Card、字段说明、Lineage |
| Schema Debt | 多套日期解析、散落的空值补丁、版本分支不断增加 | Data Contract、快速失败 |
| Quality Debt | 已知标签错误、重复和偏差长期不处理 | 错误 Backlog、抽检、专项修复 |
| Freshness Debt | 训练数据与生产分布持续分离 | Drift Monitoring、按信号触发重训 |
数据债务不是线性积累。本章用复利形式表达:
未记录的假设会与新的 Workaround 叠加,旧模型产生的预测又可能成为下游的新标签或特征,因此一个问题会提高以后新增问题的排查成本。
11.1 排障顺序:先查数据,再怀疑模型结构
模型线上效果下降时,本章建议按现象逐步定位:
- 准确率随时间逐渐下降:先检查 Data Drift 与 Freshness;
- 训练准确率显著高于验证准确率:检查 Overfitting 和数据切分;
- 验证准确率显著高于生产准确率:检查 Training-serving skew;
- 不同子群表现不一致:检查 Coverage 与 Bias;
- 上述问题均排除后:再深入模型结构。
这不是说模型一定没有问题,而是一个更符合生产故障分布的诊断顺序。模型代码通常在上线前被集中测试,而数据分布会在上线后持续变化,因此先验证数据假设更有效。
12. 总结:把数据流水线当成长期运行的编译系统
把 Data Engineering 当作一次性清洗:
- 训练前运行若干脚本,得到一份“干净数据”;
- 训练完成后不再跟踪原始数据、转换参数和版本;
- 线上退化时先调模型,依赖日志和人员记忆寻找原因;
- 结果是数据漂移、标签变化和存储瓶颈长期隐藏。
把 Data Engineering 当作 Dataset Compilation:
- 原始观测经过采集、摄取、验证、转换、标注、分片和存储;
- 数据、代码、转换参数和模型产物形成完整 Lineage;
- DataLoader、CPU Decode、存储和 GPU 被视为一条端到端供给路径;
- Quality、Reliability、Scalability 和 Governance 贯穿整个生命周期;
- 运行期持续监控 Drift、Training-serving skew 和 Data Debt。
最终需要记住四点:
- 数据决定模型行为:数据缺失的信息,模型架构无法凭空恢复。
- 数据移动受物理约束:训练速度由数据供给和计算能力中较弱的一方决定。
- 转换一致性不可妥协:共享代码、共享参数和正确时间语义缺一不可。
- 数据质量会持续衰减:版本、监控和治理不是附加功能,而是生产 ML 的基础设施。
Dataset Compilation 的本质:把原始世界中的不稳定观测,编译成模型可以高速消费、能够准确复现、并且在生产环境中持续保持语义一致的数据产物。