3. 数据准备与特征工程基础
在 Databricks 上做模型生命周期管理,数据准备不是“把 CSV 读进来”这么简单。训练数据的可追溯性、特征的一致性、版本的可回滚性,直接决定了模型能否被复现、能否被审计、能否安全上线。本节围绕 Delta Lake 的存储能力、特征表(Feature Table)的组织方式、数据版本控制手段,以及训练/测试集划分策略展开,目标是让读者能够独立完成一套可复现的训练数据准备流程。
一、为什么用 Delta Lake 管理训练数据
Delta Lake 是构建在 Parquet 之上的开放表格式,它在数据文件之外维护一份事务日志(_delta_log),记录每次写入的元数据、schema 变更和提交信息。对训练数据而言,这带来三个关键能力:
- ACID 事务:并发写入不会产生半成品文件,读取方要么看到旧版本,要么看到新版本,不会读到中间状态。
- 时间旅行(Time Travel):通过
VERSION AS OF或TIMESTAMP AS OF可以读取任意历史版本,这是训练数据可复现的基础。 - Schema 强制与演进:写入时校验 schema,避免上游字段类型漂移悄悄污染训练集;需要新增特征列时可用
mergeSchema显式演进。
一个典型的训练数据表可以这样建立。假设我们有一批用户行为日志,需要落成 Delta 表:
CREATE TABLE IF NOT EXISTS ml.training.user_events (
user_id STRING,
event_time TIMESTAMP,
event_type STRING,
amount DOUBLE,
label INT
)
USING DELTA
PARTITIONED BY (DATE(event_time))
LOCATION 'abfss://ml@storage.dfs.core.windows.net/training/user_events';
按日期分区的好处是:训练时若只取最近 90 天数据,Delta 的谓词下推会直接跳过无关分区,扫描量大幅下降。写入时建议使用 MERGE INTO 做幂等更新,而不是简单 INSERT,避免重复跑批导致样本重复。
二、特征表的创建与组织
特征表是把“原始数据 → 特征”的加工结果固化下来的表。它的核心价值在于:训练和推理使用同一份特征定义,避免训练/服务偏差(training-serving skew)。在 Databricks 中,特征表通常由 Feature Store 管理,但底层仍然是 Delta 表。
创建特征表时,需要明确三件事:主键(通常是 entity id + 时间戳)、特征列、以及特征的新鲜度要求。下面示例把用户近 7 天的交易聚合特征写入特征表:
CREATE OR REPLACE TABLE ml.features.user_txn_features AS
SELECT
user_id,
window_end AS feature_ts,
COUNT(*) AS txn_cnt_7d,
SUM(amount) AS txn_amt_7d,
AVG(amount) AS txn_avg_7d,
MAX(event_time) AS last_txn_time
FROM (
SELECT *,
window(event_time, '7 days').end AS window_end
FROM ml.training.user_events
WHERE event_time >= current_timestamp() - INTERVAL 7 DAYS
)
GROUP BY user_id, window_end;
这里 feature_ts 是特征的时间戳,它必须严格早于标签发生的时间,否则会造成标签泄漏。特征表建好后,建议再建一张 feature_spec 元数据表,记录每个特征的含义、计算逻辑、负责人和更新频率,方便后续排查。
三、数据版本控制
Delta Lake 的版本控制是“表级”的,每次写入产生一个新版本号。训练时固定版本号,就能保证数据可复现。例如:
SELECT * FROM ml.features.user_txn_features VERSION AS OF 12;
如果只记得时间,也可以用时间戳:
SELECT * FROM ml.features.user_txn_features
TIMESTAMP AS OF '2024-06-01T00:00:00Z';
实践中建议在训练脚本里显式记录两个信息:特征表版本号和训练数据快照版本号,写入模型注册表的描述字段。这样当线上模型出现问题时,可以快速定位到当时用的数据版本。另外,Delta 的 DESCRIBE HISTORY 可以查看版本历史,RESTORE 可以回滚到指定版本,但回滚要谨慎,因为它会改变当前表状态。
四、训练/测试集划分策略
划分策略取决于数据的时间特性。对于时序数据,随机划分会导致未来信息泄漏到训练集,必须按时间切分。常见做法是:
- 时间切分:用较早的数据训练,较晚的数据测试。例如前 80% 时间窗口训练,后 20% 测试。
- 滚动窗口验证:在训练集内部再做多个时间窗口的交叉验证,评估模型在不同时间段的稳定性。
- 分层抽样:如果标签极度不平衡,可在时间切分基础上对正负样本做分层,保证测试集有足够正例。
下面是一个按时间切分的示例,假设以 2024-05-01 为切分点:
CREATE OR REPLACE TEMP VIEW train_set AS
SELECT * FROM ml.features.user_txn_features
WHERE feature_ts < '2024-05-01';
CREATE OR REPLACE TEMP VIEW test_set AS
SELECT * FROM ml.features.user_txn_features
WHERE feature_ts >= '2024-05-01';
划分完成后,务必检查训练集和测试集的标签分布、特征分布是否一致。如果测试集正例比例远低于训练集,说明切分点选得不好,需要调整。此外,测试集应当“冻结”,在模型定稿前不要反复用它调参,否则测试集就变成了验证集。
最后强调一点:数据准备不是一次性工作。随着上游数据源变化、特征逻辑调整,训练数据表会不断产生新版本。把版本号、切分策略、特征定义都纳入模型注册表的元数据,才能让“这个模型是用什么数据训出来的”这个问题有确定答案。这也是 Databricks 模型生命周期管理区别于普通脚本训练的核心价值。