AI 工程基础体系 · 第 4/100 篇。内容覆盖机器学习、深度学习与生成式 AI;模型、数据、评测、权限和成本会作为同一生产系统处理。

机器学习数据工程:采集、清洗、切分、特征、泄漏和版本治理

机器学习系统的输入不是“一个训练文件”,而是一条持续运行的数据链路:

数据源采集原始层清洗切分特征训练/评测部署反馈\text{数据源} \rightarrow \text{采集} \rightarrow \text{原始层} \rightarrow \text{清洗} \rightarrow \text{切分} \rightarrow \text{特征} \rightarrow \text{训练/评测} \rightarrow \text{部署} \rightarrow \text{反馈}

数据工程的核心任务,是让这条链路同时满足四个条件:

  1. 语义正确:每条样本、标签和特征都代表预期业务含义。
  2. 时间正确:训练时只能使用预测时已经可获得的信息。
  3. 可复现:能够知道某个模型使用了哪些数据、代码、特征和配置。
  4. 可治理:数据的权限、隐私、成本、质量和变更都有明确边界。

如果只提高模型复杂度,而没有解决这些问题,常见结果是:离线指标很高、上线效果下降;模型无法解释;重新训练无法复现;数据泄露或权限违规;训练成本持续增长却没有可验证收益。


一、先定义机器学习数据对象

1. 样本、特征、标签和预测时点

设第 ii 条样本为:

zi=(xi,yi,ti,gi)z_i=(x_i,y_i,t_i,g_i)

其中:

  • xix_i:输入特征;
  • yiy_i:目标标签;
  • tit_i:预测时点或样本生成时点;
  • gig_i:分组标识,例如用户、设备、患者、商品或文档。

模型学习一个函数:

y^=fθ(x)\hat y=f_\theta(x)

但工程上最重要的问题不是“模型能否计算 fθf_\theta”,而是:

在时刻 tit_i 做出预测时,xix_i 中的每个字段是否已经可用?

例如,要预测用户在今天是否会退款:

  • 用户注册时间可以作为特征;
  • 过去 30 天的订单数可以作为特征;
  • 今天之后生成的退款审核结果不能作为特征;
  • 退款发生之后人工填写的“退款原因”不能用于预测退款本身。

因此,特征必须绑定一个可用时间

aij=feature j 在样本 i 上可用的时间a_{ij}=\text{feature }j\text{ 在样本 }i\text{ 上可用的时间}

只有满足:

aijtia_{ij}\le t_i

的特征,才符合在线预测的因果时间约束。这里的“因果”不是要求建立完整的因果模型,而是要求预测输入不能来自预测结果之后。

2. 监督学习数据的双时间问题

很多业务数据至少有两个时间:

  • 事件时间:事情实际发生的时间,例如订单创建时间;
  • 入库时间:系统观察到该事件的时间,例如日志到达数据仓库的时间。

两者不一定相同。一个在昨天发生、今天才入库的事件,在昨天的预测中不可用。

可以将数据记录写成:

r=(entity,event_time,available_time,value)r=(\text{entity}, \text{event\_time}, \text{available\_time}, \text{value})

训练特征时,应该按照 available_time 做“as-of join”,而不是简单地按事件时间连接。否则,迟到数据会悄悄进入历史特征,造成时间穿越。

3. 训练分布与生产分布

机器学习通常假设训练样本和未来样本来自相同或相近分布:

(X,Y)Ptrain(X,Y),(X,Y)Pprod(X,Y)(X,Y)\sim P_{\text{train}}(X,Y),\qquad (X,Y)\sim P_{\text{prod}}(X,Y)

理想情况下:

Ptrain(X,Y)=Pprod(X,Y)P_{\text{train}}(X,Y)=P_{\text{prod}}(X,Y)

实际中可能出现:

  • 协变量偏移P(X)P(X) 变了,但 P(YX)P(Y\mid X) 基本不变;
  • 标签分布偏移P(Y)P(Y) 变了;
  • 概念漂移P(YX)P(Y\mid X) 变了;
  • 采样选择偏差:训练数据只覆盖了容易观察到的样本。

数据工程不能消除所有分布变化,但必须记录采样范围、时间范围、过滤条件和来源,使变化可测量,而不是把它们误认为模型随机波动。


二、数据采集:先保证语义和可追溯性

1. 采集不是简单导出数据库

采集层通常连接:

  • 业务数据库和数据仓库;
  • HTTP API、第三方平台;
  • 应用日志、埋点、消息队列;
  • 文件、对象存储;
  • 图像、音频、视频和文档;
  • 人工标注平台;
  • 检索增强生成系统中的网页、知识库和用户反馈。

每种来源都需要定义至少以下信息:

字段 作用
来源系统 说明数据由哪个系统产生
主键或事件 ID 支持去重和幂等
事件时间 表示业务事件发生时间
到达时间 表示数据何时可被系统观察
模式版本 说明字段类型和语义
数据所有者 负责定义和质量确认
权限级别 决定谁可以读取和使用
保留期限 控制隐私和存储成本

**模式(schema)**不仅是字段名和数据类型,还应包括单位、枚举值、时区、是否允许为空、语义定义和变更兼容性。

例如,amount 如果没有说明是“人民币分”还是“人民币元”,即使类型是 float,数据也并不完整。

2. 原始层与派生层要分离

常见的数据分层可以是:

  1. 原始层(raw):尽量保留源数据原貌,追加采集元数据;
  2. 标准层(normalized):统一字段名、类型、时区和编码;
  3. 特征层(feature):生成供训练或服务使用的特征;
  4. 训练快照(snapshot):固定样本集合、标签和特征版本。

原始层通常采用追加写入而不是原地覆盖。这样做的原因是:如果清洗逻辑出现错误,可以从原始数据重新生成派生层;如果直接修改唯一副本,错误可能无法恢复。

但“保留原始数据”不等于无限保留。原始层可能包含个人信息、密钥、用户内容或受版权约束的材料,仍然需要访问控制、加密、脱敏和删除策略。

3. 采集必须处理重复、重试和迟到

分布式采集通常会发生以下情况:

  • 请求超时,但服务端已经成功写入;
  • 消费者重启后重复消费消息;
  • 同一业务事件被多个系统上报;
  • 分区或网络故障导致数据延迟;
  • 上游字段突然增加、删除或改变类型。

因此,采集任务需要明确交付语义:

  • 至少一次(at-least-once):可能重复,但尽量不丢失;
  • 至多一次(at-most-once):可能丢失,但不重复;
  • 恰好一次(exactly-once):在特定系统边界内避免重复处理,但并不自动保证端到端业务效果。

工程上常见做法是使用稳定的 event_id 做幂等键:

INSERT INTO raw_events (
    event_id,
    event_time,
    available_time,
    payload,
    source,
    ingested_at
)
SELECT
    :event_id,
    :event_time,
    :available_time,
    :payload,
    :source,
    CURRENT_TIMESTAMP
WHERE NOT EXISTS (
    SELECT 1
    FROM raw_events
    WHERE event_id = :event_id
);

这段 SQL 只能在数据库约束或事务机制配合下可靠工作。生产表还应对 event_id 建立唯一约束,否则两个并发事务可能同时通过 NOT EXISTS 检查并写入重复记录。

迟到数据也不能简单丢弃。应记录水位线(watermark)和允许迟到范围。例如,某天的数据在事件时间结束后 24 小时内仍可能到达,那么日批任务需要在窗口关闭前持续修正,或者明确“冻结版本”和“补数版本”的区别。

4. 标注数据是采集链路的一部分

标签不是天然正确的字段,而是一个测量过程。需要区分:

  • 业务真实结果,例如是否最终违约;
  • 人工判断,例如是否属于违规内容;
  • 代理标签,例如用户是否点击;
  • 规则生成标签,例如关键词命中;
  • 模型生成标签,例如教师模型伪标签。

标注系统应记录:

  • 标注指南和标签定义;
  • 标注者或标注角色;
  • 标注时间;
  • 多人标注结果;
  • 冲突解决方式;
  • 标签版本;
  • 无法判断和拒答样本。

如果标签定义变化,不能只覆盖原字段。例如,“高风险用户”的规则从过去 90 天改为过去 30 天,旧标签和新标签实际上属于不同任务。


三、数据清洗:不能把所有异常都删除

清洗的目标不是让数据“看起来整齐”,而是将不可用、含义不明或不符合任务定义的记录处理为可解释状态。

1. 类型、单位和范围

以下检查经常比复杂异常检测更重要:

  • 时间是否带时区;
  • 数值单位是否统一;
  • 枚举值是否超出定义;
  • 字符编码是否一致;
  • 数值是否溢出;
  • 字段是否发生字符串到数字的隐式转换。

例如,年龄出现 999,可能是错误值,也可能是“未知”的编码。若直接将其当作真实年龄,模型会学习到一个没有业务意义的分支;若直接删除,则可能丢失“未知年龄”本身具有的信号。正确做法是先确认源系统语义,再决定将其转为缺失、单独类别或修复。

2. 缺失值的含义

缺失值至少有三种不同含义:

  1. 没有采集到;
  2. 该字段对该样本不适用;
  3. 用户或系统有意未填写。

对数值特征使用均值填补时,训练集均值应为:

μtrain=1ntrainitrain,xi observedxi\mu_{\text{train}}= \frac{1}{n_{\text{train}}} \sum_{i\in \text{train},x_i\text{ observed}}x_i

不能使用包含验证集或测试集的全量均值。即使均值本身不包含标签,也会让验证集参与训练预处理,产生轻微但真实的信息泄漏。

通常还应增加缺失指示变量:

mj={1,xj 缺失0,否则m_j= \begin{cases} 1,&x_j\text{ 缺失}\\ 0,&\text{否则} \end{cases}

因为“缺失”可能与目标有关。例如,只有高价值客户才填写某项资料,缺失状态本身就是信息。

3. 重复样本与近重复样本

完全重复可以按业务主键、事件 ID 或内容哈希处理。近重复更复杂:

  • 同一用户的多次记录;
  • 同一商品的不同抓取版本;
  • 同一文档的轻微改写;
  • 同一图片的压缩、裁剪或重新编码;
  • 同一问题在训练集和评测集中的模板变体。

如果近重复样本被随机切入训练集和测试集,模型可能只是记住了实体或模板,而不是学习任务规律。去重应在切分前进行,但去重规则本身也要版本化,因为规则变化会改变样本集合。

4. 异常值不是自动删除对象

异常值可能是:

  • 传感器故障;
  • 输入错误;
  • 真实极端事件;
  • 新业务状态;
  • 对抗性输入。

删除极端值相当于改变目标分布。比如风控数据中的大额交易可能正是最重要的样本。应先区分“测量错误”和“真实极端”,并记录处理原因。对连续值进行截尾(winsorization)、对数变换或稳健缩放时,也必须只在训练数据上估计参数。

5. 文本、图像和多模态清洗

生成式 AI 和深度学习数据还需要处理:

  • 文本编码、语言识别、乱码;
  • HTML、脚本、导航模板和重复页;
  • 图片损坏、尺寸异常、重复图像;
  • 音频采样率、静音和截断;
  • 文本与图片的错配;
  • 用户隐私、版权和许可证;
  • 恶意指令、提示注入和训练数据投毒。

对大语言模型而言,清洗不能只按“低质量文本”过滤。代码、表格、方言、短文本和不完整对话是否有价值,取决于任务。过度过滤可能减少领域覆盖;过滤不足则可能把个人信息、恶意内容或评测集答案带入训练。


四、数据切分:评测可信度的基础

1. 为什么不能先全量处理再随机切分

设原始数据为 DD,如果先在 DD 上计算归一化参数、词表、特征选择结果或去重规则,再切分训练集和测试集,那么测试集已经影响了训练流程。

正确抽象是:

DsplitDtrain,Dvalid,DtestD \xrightarrow{\text{split}} D_{\text{train}},D_{\text{valid}},D_{\text{test}}

然后:

ϕtrain=fit-transformer(Dtrain)\phi_{\text{train}}= \text{fit-transformer}(D_{\text{train}})

Xvalid=ϕtrain(Dvalid),Xtest=ϕtrain(Dtest)X_{\text{valid}}=\phi_{\text{train}}(D_{\text{valid}}),\qquad X_{\text{test}}=\phi_{\text{train}}(D_{\text{test}})

其中 ϕ\phi 是预处理或特征变换器。它的参数只能从训练数据估计。

2. 随机切分的适用条件

随机切分隐含了样本近似独立同分布的假设:

z1,,zn近似Pz_1,\ldots,z_n\overset{\text{近似}}{\sim}P

如果同一个用户、设备、商品或文档产生多条样本,这个假设通常不成立。此时随机切分会导致实体重叠:

  • 用户历史记录进入训练集,另一条记录进入测试集;
  • 同一患者的多次就诊跨集合出现;
  • 同一文档的段落跨集合出现。

应按实体分组切分,使同一 gig_i 只出现在一个集合中:

gi=gjsplit(i)=split(j)g_i=g_j \Rightarrow \text{split}(i)=\text{split}(j)

分类任务还要关注各集合的类别比例,但分层抽样不能违反时间或分组约束。

3. 时间切分更接近线上预测

对于随时间变化的任务,应使用过去训练、未来评测:

ttrain<tvalid<ttestt_{\text{train}}<t_{\text{valid}}<t_{\text{test}}

例如:

  • 训练:2023 年 1 月至 10 月;
  • 验证:2023 年 11 月;
  • 测试:2023 年 12 月;
  • 部署模拟:在 2024 年 1 月之后继续监控。

时间切分能暴露分布漂移,但仍需注意标签成熟期。如果标签要在事件发生后 30 天才确定,那么最近 30 天的样本可能只有部分标签,不能直接作为完整监督数据。

4. 交叉验证并非总是随机 K 折

交叉验证在训练集内部估计模型选择的不确定性。普通 K 折将数据分成 KK 份,每次用 K1K-1 份训练、剩余一份验证。

但以下情况应使用相应约束:

  • 同一实体多条样本:分组交叉验证;
  • 时间序列:滚动或扩展窗口验证;
  • 标签极少:分层交叉验证;
  • 超参数和最终指标都要无偏估计:嵌套交叉验证。

不能把测试集反复用于调参。测试集的作用是模拟“最终一次、事先未观察的结果”。


五、特征工程:让变换可训练、可服务、可追踪

1. 特征是带语义和时间边界的函数

特征不只是一个列名,而是:

xj=hj(原始数据,截止时间=t)x_j = h_j(\text{原始数据},\text{截止时间}=t)

例如:

xorders30d(u,t)=order of ut30davailable_timet1x_{\text{orders30d}}(u,t) = \sum_{\substack{\text{order of }u\\ t-30d\le \text{available\_time}\le t}}1

这里的窗口、实体、时间字段和数据可用时间都属于特征定义。若只保存生成后的数值,不保存计算逻辑,未来无法判断它是否与线上定义一致。

2. 训练阶段必须区分 fit 和 transform

很多预处理器包含需要学习的状态:

  • 均值、方差;
  • 类别词表;
  • 分箱边界;
  • 词频和词典;
  • 特征选择集合;
  • 降维矩阵;
  • 目标编码统计量。

它们都应执行:

训练集: fit + transform
验证集: transform
测试集: transform
线上数据: transform

而不是对每个集合分别 fit。分别拟合会导致训练和服务使用不同映射,且测试集统计量影响了评测。

3. 一个可运行的 scikit-learn 示例

下面示例构造一个分类任务,使用数值和类别特征,并将缺失填补、标准化、独热编码和模型放入同一条 Pipeline。这样,交叉验证的每个训练折都会独立拟合预处理器。

import numpy as np
import pandas as pd

from sklearn.compose import ColumnTransformer
from sklearn.impute import SimpleImputer
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import roc_auc_score
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder, StandardScaler

rng = np.random.default_rng(7)
n = 1000

df = pd.DataFrame({
    "age": rng.normal(40, 10, n),
    "income": rng.lognormal(10, 0.5, n),
    "channel": rng.choice(["web", "app", "store"], n),
})

# 构造一个仅用于演示的目标:真实业务中应由事件结果产生
logit = (
    0.04 * (df["age"] - 40)
    + 0.00001 * (df["income"] - df["income"].mean())
    + (df["channel"] == "web") * 0.4
)
prob = 1 / (1 + np.exp(-logit))
y = (rng.random(n) < prob).astype(int)

# 模拟缺失
df.loc[rng.choice(n, 60, replace=False), "income"] = np.nan
df.loc[rng.choice(n, 30, replace=False), "channel"] = None

X_train, X_test, y_train, y_test = train_test_split(
    df,
    y,
    test_size=0.2,
    random_state=42,
    stratify=y,
)

numeric_features = ["age", "income"]
categorical_features = ["channel"]

numeric_pipeline = Pipeline([
    ("imputer", SimpleImputer(strategy="median", add_indicator=True)),
    ("scaler", StandardScaler()),
])

categorical_pipeline = Pipeline([
    ("imputer", SimpleImputer(strategy="most_frequent")),
    ("onehot", OneHotEncoder(handle_unknown="ignore")),
])

preprocess = ColumnTransformer([
    ("num", numeric_pipeline, numeric_features),
    ("cat", categorical_pipeline, categorical_features),
])

model = Pipeline([
    ("preprocess", preprocess),
    ("classifier", LogisticRegression(max_iter=1000)),
])

model.fit(X_train, y_train)
test_prob = model.predict_proba(X_test)[:, 1]
print(f"test ROC-AUC: {roc_auc_score(y_test, test_prob):.3f}")

运行前提是安装 numpypandasscikit-learn。输出的 ROC-AUC 每次可能因数据生成方式或库版本略有变化,重点不是某个固定数字,而是以下生命周期:

  1. train_test_split 先切分原始表;
  2. model.fit 只在训练数据上拟合中位数、类别集合、缩放参数和逻辑回归系数;
  3. predict_proba 对测试数据只执行已有变换;
  4. handle_unknown="ignore" 使线上出现新类别时不会因独热列缺失而直接失败,但新类别仍可能导致预测质量下降,需要监控。

Pipeline 能减少“忘记只在训练集拟合”的错误,但它不会自动解决时间泄漏、错误标签、重复实体或错误的业务连接逻辑。高层管道封装不能替代数据语义审查。

4. 统计编码特别容易泄漏

设类别 cc 的目标均值编码为:

enc(c)=i:xi=cyii:xi=c1\text{enc}(c)= \frac{\sum_{i:x_i=c}y_i}{\sum_{i:x_i=c}1}

如果在全量数据上计算,验证样本自己的标签可能进入其特征。对训练样本还会产生过拟合,尤其当类别很少、每个类别只出现一次时。

正确做法包括:

  • 在训练折上估计编码;
  • 对验证折只应用训练折统计;
  • 使用平滑估计;
  • 对同一训练折内部使用 out-of-fold 编码。

这也是为什么目标编码不能像普通字符串替换一样直接在全表上执行。


六、数据泄漏:离线高分但上线失败的根因

1. 泄漏的正式定义

如果训练流程使用了预测时不可获得的信息,或者评测数据以任何形式影响了模型选择,就发生了数据泄漏。

可将可用信息集合写成:

I(t)={在时刻 t 之前系统可观察的信息}\mathcal{I}(t)= \{\text{在时刻 }t\text{ 之前系统可观察的信息}\}

合法特征应满足:

X(t)I(t)X(t)\subseteq \mathcal{I}(t)

泄漏特征则包含:

X(t)⊈I(t)X(t)\not\subseteq \mathcal{I}(t)

泄漏不一定直接包含标签。只要它是标签产生之后的结果、人工后处理或未来信息,就可能成为标签的代理。

2. 典型泄漏类型

标签泄漏

目标是预测“是否发生退款”,特征却包含“退款处理状态”。模型不需要学习行为规律,只需读取结果附近的字段。

时间泄漏

使用预测时点之后的订单、日志、诊断结果或用户行为。按事件时间连接,但忽略入库时间,也属于时间泄漏。

预处理泄漏

在切分前拟合:

  • 均值和方差;
  • 词表;
  • 特征选择;
  • PCA;
  • 目标编码;
  • 异常值阈值。

评测泄漏

反复查看测试集结果并调整模型、特征或阈值。即使测试标签没有直接进入训练,也已经影响了决策。

实体重叠泄漏

同一用户、患者、设备、文档或视频的相关记录同时出现在训练和测试中。

生成式 AI 的基准污染

训练语料包含公开评测集、答案、题解或其近似改写。模型在评测时可能是记忆,而不是泛化。

检索增强生成中的上下文泄漏

评测问题的答案文档被错误地放入检索库,或者构建知识库时使用了评测期之后才发布的文档。此时需要同时审查:

  • 模型参数中的训练语料;
  • 检索索引中的文档;
  • 提示模板;
  • 工具调用结果;
  • 人工反馈数据。

3. 一个反例:全量标准化

假设训练集均值为 00,测试集均值为 100100。在全量数据上标准化会估计一个包含测试集的均值。对于线性模型,这通常不会产生显眼的巨大差异,但评测过程已经不再完全独立。

更严重的是,全量数据上的特征选择。例如按每个特征与标签的相关性选前 100 个特征,测试标签直接参与了特征集合的确定,即使最终模型只在训练集上拟合,也已经泄漏。

4. 泄漏诊断方法

可以使用以下实验定位问题:

  1. 时间回放:严格模拟线上可见字段,重新生成特征;
  2. 标签置换:随机打乱标签,若仍有异常高分,说明存在重复、索引错误或泄漏;
  3. 单特征审查:逐列计算特征可用时间,查看高贡献特征;
  4. 去重后重评测:按实体和近重复内容重新切分;
  5. 空特征基线:只使用简单历史统计,比较复杂特征的增益;
  6. 删除可疑字段:若删除一个字段后指标从极高水平骤降,应检查其业务生成时机;
  7. 训练—服务一致性回放:对同一个历史时点分别用离线和线上特征计算,比较值和缺失状态。

一个有效的验证问题是:

如果在预测时刻暂停所有未来事件,这个特征还能否被独立重建?

如果不能,就必须重新定义它,或者将其从预测特征中移除。


七、从采集到上线的完整数据流

flowchart LR
    A[业务数据库/日志/API/文件] --> B[采集层]
    B --> C[原始不可变层]
    C --> D[模式与质量校验]
    D --> E[标准化数据]
    E --> F{按时间/实体切分}
    F --> G[训练快照]
    F --> H[验证与测试快照]
    G --> I[训练专用特征变换]
    I --> J[模型训练]
    H --> K[离线评测]
    J --> L[模型与特征版本]
    L --> M[在线特征/批量推理]
    M --> N[预测与监控]
    N --> O[反馈标签]
    O --> C

关键路径有三点:

  1. 原始层是恢复点:清洗逻辑错误时可以重建派生数据;
  2. 切分发生在会影响统计量的变换之前:避免评测集参与训练流程;
  3. 反馈回到原始层并保留版本:新标签不能覆盖旧标签,否则无法解释历史模型的结果。

质量校验失败时不能只有“任务失败”一种状态。常见状态至少包括:

  • accepted:通过质量规则;
  • quarantined:隔离等待人工或自动修复;
  • rejected:确定不可用;
  • late:迟到但允许补入;
  • superseded:被更新版本替代。

如果上游字段类型发生变化,任务应优先进入隔离或失败,而不是静默把异常值转成空值。静默修复会让下游模型继续运行,却可能已经改变输入分布。


八、版本治理:版本化的不只是模型文件

1. 一个训练结果由多个版本共同决定

模型可以表示为:

M=f(Dsnapshot,Ldefinition,Fcode,Cconfig,Eenvironment,Rseed)M= f( D_{\text{snapshot}}, L_{\text{definition}}, F_{\text{code}}, C_{\text{config}}, E_{\text{environment}}, R_{\text{seed}} )

其中:

  • DsnapshotD_{\text{snapshot}}:数据快照;
  • LdefinitionL_{\text{definition}}:标签定义和生成逻辑;
  • FcodeF_{\text{code}}:特征和训练代码;
  • CconfigC_{\text{config}}:超参数、阈值和采样配置;
  • EenvironmentE_{\text{environment}}:依赖版本、硬件和运行时;
  • RseedR_{\text{seed}}:随机种子及相关随机状态。

只保存 model.pkl 或权重文件,不能完整复现训练结果。

2. 数据版本的两种主要方式

内容寻址

对文件内容计算哈希:

h=SHA256(bytes)h=\text{SHA256}(\text{bytes})

同样内容得到同样哈希,适合不可变文件、对象存储和数据清单。

逻辑快照

记录查询条件或表快照,例如:

dataset_name: customer_risk
snapshot_id: 2025-03-01T00:00:00Z
source_tables:
  - warehouse.orders@2025-02-28
  - warehouse.users@2025-02-28
event_time_range:
  start: 2024-01-01T00:00:00Z
  end: 2025-01-31T23:59:59Z
availability_cutoff: 2025-02-05T00:00:00Z
label_definition: risk_label_v3
feature_code_commit: 8f31c2a
schema_version: 4

内容哈希回答“字节是否相同”,逻辑快照回答“为何包含这些数据”。生产治理通常需要两者。

3. 数据快照必须包含排除规则

以下规则都会改变训练集,不能只写在人的记忆里:

  • 过滤哪些时间段;
  • 排除哪些用户或设备;
  • 如何处理重复;
  • 如何处理缺失标签;
  • 是否按实体采样;
  • 是否限制每个实体的样本数;
  • 是否去除评测集污染;
  • 是否使用伪标签;
  • 标签成熟期是多少。

对大规模数据,保存完整副本成本很高,可以使用分区快照、增量清单、对象锁定和内容哈希。代价是恢复和查询逻辑更复杂,因此需要定期验证快照确实可读、可重建,而不是只保存了指针。

4. 特征版本必须和模型绑定

特征变化包括:

  • 公式变化;
  • 窗口变化;
  • 数据源变化;
  • 缺失值策略变化;
  • 类别映射变化;
  • 线上服务实现变化。

“特征名不变”不代表语义不变。例如 user_orders_30d 从事件时间改成入库时间,仍然叫同一个名字,但模型面对的是不同输入。模型注册信息至少应绑定:

model_id
model_version
dataset_snapshot
label_version
feature_view_version
preprocessing_artifact
training_code_commit
dependency_lock
evaluation_report
owner
approval_status

5. 版本状态和回滚

一个模型或数据版本可以经历:

draft → validated → approved → deployed → deprecated
                    ↘ rejected

状态变化必须有证据。例如从 validatedapproved,应关联评测报告、数据质量报告、权限审查和成本估算。

回滚不仅是恢复旧模型权重,还要恢复兼容的:

  • 特征计算逻辑;
  • 词表或编码器;
  • 模型输入 schema;
  • 阈值和后处理;
  • 检索索引或提示模板;
  • 运行时依赖。

如果旧模型依赖的特征版本已经被删除,所谓“回滚”可能只能恢复模型文件,不能恢复真实服务行为。


九、评测、权限和成本必须进入同一生产系统

1. 数据质量指标不等于模型指标

数据质量可以监控:

  • 行数和增量;
  • 主键唯一率;
  • 必填字段缺失率;
  • 数值范围;
  • 类别新值比例;
  • 时间延迟;
  • 重复率;
  • 特征分布变化。

模型评测则关注准确率、召回率、ROC-AUC、PR-AUC、对数损失、校准、分组公平性或生成质量等。两者关系是:

数据质量异常模型输入变化的可能性增加\text{数据质量异常} \Rightarrow \text{模型输入变化的可能性增加}

但反过来不一定成立。数据质量通过,不代表模型一定有效;模型指标下降,也可能是标签延迟、概念漂移或阈值问题。

2. 权限边界应沿数据流传播

权限不应只控制“谁能登录数据库”,还要控制:

  • 谁能看原始个人信息;
  • 谁能使用脱敏数据训练;
  • 谁能下载模型输出;
  • 谁能访问标注内容;
  • 谁能读取检索库;
  • 谁能查看用户提示和反馈;
  • 谁能修改标签或发布数据快照。

可将数据资产标记为:

public
internal
confidential
restricted

但分类必须与实际字段和用途绑定。经过脱敏的数据也可能通过组合字段重新识别个人,不能仅因为删除了姓名就自动视为公开。

生成式 AI 还需关注提示和检索上下文中的敏感内容。模型可能不会永久记住每次提示,但日志、缓存、向量索引和人工评测系统仍可能保存这些内容。

3. 成本要按数据生命周期计算

数据成本不仅是训练 GPU:

Ctotal=Cingest+Cstorage+Cprocessing+Clabel+Ctraining+Cserving+CmonitoringC_{\text{total}}= C_{\text{ingest}}+ C_{\text{storage}}+ C_{\text{processing}}+ C_{\text{label}}+ C_{\text{training}}+ C_{\text{serving}}+ C_{\text{monitoring}}

例如:

  • 原始图像和视频会增加存储、扫描和传输成本;
  • 高频特征刷新会增加计算成本;
  • 大模型重复 token 化和训练会增加 CPU/GPU 成本;
  • 过度保留历史快照会增加存储和权限审计成本;
  • 每次实验复制完整数据会降低可追溯性并增加成本。

成本优化不能简单删除数据。应优先使用增量处理、分区裁剪、缓存确定性特征、采样和分层存储,同时保留足以复现已发布模型的快照和清单。


十、深度学习与生成式 AI 的特殊边界

1. 训练样本去重和基准污染

深度学习数据通常规模大、格式复杂。去重可以使用:

  • 精确哈希:发现完全相同文件;
  • 感知哈希:发现视觉近似图像;
  • 文本规范化后的哈希;
  • n-gram 或 MinHash:发现近似文本;
  • 向量相似度:发现语义近重复。

相似度方法有误报和漏报,阈值变化也会改变数据版本。不能把去重结果当作绝对事实,应保存规则、阈值和被移除样本清单。

对生成模型,评测集污染可能是直接复制,也可能是改写、翻译、模板变化或公开答案进入合成数据。应在训练前审查来源,在评测时使用时间隔离、私有测试集或新生成的任务,并记录污染检测方法的局限性。

2. 指令数据和偏好数据

指令微调数据通常包含:

(system,user,assistant)(\text{system},\text{user},\text{assistant})

需要确认:

  • 哪些消息参与损失计算;
  • 是否将用户输入错误地当作目标;
  • 是否存在重复对话;
  • 是否将评测答案混入助手回复;
  • 是否对个人信息和版权内容有使用权;
  • 标注者偏好是否存在系统性偏差。

偏好优化数据还需要保存候选回答、偏好标签和标注规则。如果只保留最终模型而不保留偏好数据,难以解释行为变化。

3. RAG 数据不是“训练数据”的替代品

检索增强生成系统至少有两条数据链:

文档切分嵌入索引\text{文档} \rightarrow \text{切分} \rightarrow \text{嵌入} \rightarrow \text{索引}

问题检索提示上下文生成\text{问题} \rightarrow \text{检索} \rightarrow \text{提示上下文} \rightarrow \text{生成}

文档版本、分块策略、嵌入模型、索引构建时间和权限过滤都会影响结果。重新构建索引可能在模型不变时改变答案,因此这些对象都应纳入版本记录。

权限过滤必须发生在检索结果返回之前,而不能只在最终答案阶段依赖模型“不要泄露”。模型可能复述上下文中的敏感信息。


十一、常见失败表现与定位顺序

1. 训练指标异常高,线上迅速下降

优先检查:

  1. 标签泄漏字段;
  2. 时间切分是否错误;
  3. 同一实体是否跨集合;
  4. 线上特征是否缺失或延迟;
  5. 训练和服务的编码、单位、时区是否一致;
  6. 训练样本是否来自与线上不同的筛选人群。

2. 同一代码无法复现旧模型

常见原因不是随机种子,而是:

  • 数据表被原地更新;
  • 特征查询使用了当前时间;
  • 标签规则未版本化;
  • 外部 API 内容变化;
  • 依赖库或 tokenizer 版本变化;
  • 随机抽样没有保存样本清单;
  • 训练数据只有文件路径,没有内容哈希。

解决方法是将“输入快照、代码提交、环境锁定、配置、随机状态和输出工件”作为一个实验单元保存,并定期做从快照到模型的重建演练。

3. 数据质量检查全部通过,模型仍然失效

质量检查可能只验证了类型和非空率,却没有验证语义。例如:

  • 价格单位整体改变;
  • 类别编码含义被重排;
  • 时间从本地时区改成 UTC;
  • 标签生成延迟改变;
  • 上游过滤逻辑改变;
  • 新用户群体比例大幅增加。

因此应增加分布、关联和业务不变量检查,例如金额与订单状态的关系、事件顺序约束、同一实体的时间单调性,以及关键特征与历史版本的变化解释。


十二、一条可执行的数据工程检查链

在提交一次训练任务前,可以按以下因果顺序验证:

  1. 来源:每条数据是否有来源、事件时间、可用时间和权限标记?
  2. 模式:字段类型、单位、枚举和时区是否与版本一致?
  3. 质量:重复、缺失、异常和迟到记录是否有明确处理结果?
  4. 标签:标签的观察窗口是否已成熟?定义是否固定?
  5. 切分:是否满足时间、实体和任务约束?
  6. 预处理:所有需要学习参数的变换是否只在训练集合拟合?
  7. 特征:每个特征在预测时点是否可获得?离线和线上是否使用同一语义?
  8. 泄漏:是否做过时间回放、去重和可疑字段审查?
  9. 版本:是否保存数据快照、标签、特征、代码、配置和环境?
  10. 治理:训练和推理是否符合权限、隐私、保留和成本约束?
  11. 评测:测试集是否未被用于模型选择?是否报告分组、时间段和不确定性?
  12. 恢复:数据补数、模型回滚和索引重建是否有可验证路径?

机器学习数据工程的最终产物不是一张“干净表”,而是一组能够回答以下问题的受控对象:

  • 这条样本从哪里来?
  • 在预测时刻哪些信息真正可用?
  • 标签如何产生,何时成熟?
  • 训练、验证和测试为何这样切分?
  • 每个特征如何计算,线上是否一致?
  • 指标是否被泄漏、重复或污染抬高?
  • 这个模型能否由同一份数据和代码重建?
  • 谁可以访问这些数据,保留多久,成本是多少?
  • 如果上游错误或线上退化,如何定位和恢复?

当模型、数据、评测、权限和成本被作为同一生产系统治理时,离线实验才具有可解释性,线上行为才具有可追踪性,模型迭代才不会依赖不可验证的偶然结果。


系列导航与关联阅读

官方资料

本文依据研究论文、标准组织与主流框架官方文档重新梳理;正文、示例与工程清单由 WR BLOG 编写。