7.1 时间点正确性与特征泄漏:预测时看不到的信息不能入模
回归系数会随量纲、遗漏变量和样本人群改变。特征工程还要再问一层:需要预测时,这个输入究竟拿得到吗?把未来值编码得再精确,它也还是未来值。
特征工程的第一步不是造列,而是规定预测现场。只有时间点、实体和标签都对齐,后面的编码与模型才有意义。
本课目标
- 定义预测时点、观察窗口和标签窗口;
- 识别结果泄漏、未来泄漏、切分泄漏与代理泄漏;
- 用 point-in-time join 构造可复现的历史特征;
- 为特征记录可用时间、刷新周期和默认行为。
1. 先写预测契约
一个监督学习样本至少要回答四个问题:
- 实体是谁:是服务、部署、主机还是作业;
- 预测时点是什么:例如计划部署前 30 分钟;
- 可见历史到哪里为止:哪些记录在此时已产生且已到达系统;
- 标签在哪个窗口发生:例如部署后 24 小时内是否发生服务事故。
可把第
2. 发生时间不等于可用时间
事件有两个常被混淆的时钟:
event_time:事情在现实中何时发生;available_time:记录何时进入预测系统并可被读取。
现场报告可能上午 9 点发生、下午 2 点才同步。中午的预测不能使用它。只按 event_time <= cutoff_time 过滤,仍可能造成回填泄漏。
可用时间指预测链路已能读取,不只是上游原始表已落盘。排队、特征计算和发布都可能继续增加延迟;修订也要保留版本化可用时间,不能用最终修正版覆盖掉历史。
可靠的特征定义要同时说明:
实体键: team_id
预测时点: departure_time - 30min
事件条件: event_time <= cutoff_time
可用条件: available_time <= cutoff_time
观察窗口: [cutoff_time - 30d, cutoff_time]
聚合: 已完成任务的资源消耗中位数
缺省值: 无历史时标记为 missing,不用全局未来中位数3. 五类常见泄漏
结果泄漏
把结果或结果的直接产物放进特征,如用“最终消耗量”预测是否资源不足。
未来泄漏
用完整月份统计预测月中事件,或用任务结束后的复盘字段预测出发前风险。
预处理泄漏
在全数据上拟合填充值、缩放参数、类别词表或特征选择器,再切训练集与验证集。
代理泄漏
字段表面不是标签,却由处理结果生成。例如“升级给业务负责人的工单编号”几乎等同于严重事故已被确认。
实体泄漏
同一任务的多行记录跨训练集和验证集,模型记住任务而不是学到可泛化规律。
泄漏不是只靠相关系数就能发现。必须结合字段来源、生成时点和业务流程审查。
4. Point-in-time join
这里的快照特征应在“当时已可用”的版本中,先选事件时间最新的状态;同一事件时间再取当时可见的最新修订,最后用唯一快照 ID 打破剩余并列。只按到达时间排序,会让迟到的旧快照盖过较新状态。以下独立查询使用 DuckDB 1.5,时间值均约定为 UTC:
WITH prediction_samples(sample_id, warehouse_id, cutoff_time) AS (
VALUES ('p1','W1',TIMESTAMP '2026-08-01 12:00:00'),
('p2','W1',TIMESTAMP '2026-08-01 09:05:00'),
('p3','W2',TIMESTAMP '2026-08-01 12:00:00')
),
inventory_snapshots(snapshot_id, warehouse_id, event_time, available_time, inventory_level) AS (
VALUES
('s1','W1',TIMESTAMP '2026-08-01 09:00:00',TIMESTAMP '2026-08-01 09:10:00',5),
('s2','W1',TIMESTAMP '2026-08-01 08:00:00',TIMESTAMP '2026-08-01 11:00:00',100),
('s3','W1',TIMESTAMP '2026-08-01 10:00:00',TIMESTAMP '2026-08-01 10:05:00',7),
('s4','W1',TIMESTAMP '2026-08-01 10:00:00',TIMESTAMP '2026-08-01 10:15:00',8),
('s5','W1',TIMESTAMP '2026-08-01 11:00:00',TIMESTAMP '2026-08-01 14:00:00',0)
),
candidates AS (
SELECT p.sample_id, s.inventory_level,
ROW_NUMBER() OVER (
PARTITION BY p.sample_id
ORDER BY s.event_time DESC, s.available_time DESC, s.snapshot_id DESC
) AS rn
FROM prediction_samples AS p
LEFT JOIN inventory_snapshots AS s
ON s.warehouse_id = p.warehouse_id
AND s.event_time <= p.cutoff_time
AND s.available_time <= p.cutoff_time
)
SELECT sample_id, inventory_level
FROM candidates WHERE rn = 1
ORDER BY sample_id;实际系统还要规定同一时间戳的确定性排序、迟到数据的回补策略、时区以及快照是否代表更新前或更新后的状态。
输出为 p1=8、p2=NULL、p3=NULL。中午 s5 尚未可用;s2 虽比 s4 到得晚,描述的却是更早状态。p2 当时还没有可用快照,p3 则没有匹配仓库。ID 与时间必须非空,排序也必须符合约定的修订规则;库存不可过旧时,还需限定快照最大年龄。
Feast 时间点连接文档区分了事件时间检索与创建时间过滤。依赖特征仓库接口前,应核对安装版本、存储支持范围以及该时间列是否真代表线上可用。
5. 窗口特征必须封住右边界
“最近 7 天告警数”不是一个完整定义。它需要说明:
- 左右边界是否包含;
- 按自然日还是连续 168 小时;
- 使用事件发生时间还是可用时间;
- 同一告警的修订和撤销怎样处理;
- 没有历史时返回 0 还是缺失。
“距上次确认补货的天数”应在截止时间前已可用的记录中找最近事件时间,再与 cutoff 相减。按到达时间排序后 shift() 找到的只是上次到达,迟到的旧事件也可能排在后面;排序和移位都不能替代两个时钟的条件。
6. 切分应模拟上线
若上线目标是预测未来任务,训练必须早于验证;若目标是泛化到新队伍,验证集必须留出完整队伍。时间和实体条件可能同时存在,需要组合切分。
时间序列交叉验证中的 gap 可以隔开训练窗口与验证窗口,但它不会自动修复错误特征。特征查询仍必须以每个样本自己的 cutoff 为边界。
7. 特征说明书
每个进入模型的特征至少记录:
名称与业务含义
实体键、单位和数据类型
来源表与负责人
事件时间、可用时间、观察窗口
计算逻辑与版本
缺失、迟到和未知类别策略
刷新频率与最大可接受延迟
允许使用的预测场景说明书的价值不在文档数量,而在于让训练、回放和在线预测回答同一个问题。
常见误区
- 字段在训练表里,所以能用:训练表可能由事后宽表生成。
- 只删掉标签列就没有泄漏:标签的代理字段和后处理字段仍会泄漏。
- 按事件时间过滤就够了:迟到数据还要检查可用时间。
- 先算全局聚合再切分更高效:全局统计可能已经看过验证期。
练习
- 为“提前 24 小时预测服务事故”定义实体、cutoff time、观察窗口和标签窗口。
- 审查一张事后宽表,标出五个预测时不可用的字段并解释原因。
- 为最近库存快照写 point-in-time join,并测试迟到记录不会进入历史样本。
- 比较随机切分与按时间、队伍联合切分的验证分数。
小结
特征首先是一份关于时间和可见性的契约,然后才是一列数值。把预测现场定义清楚,才能判断哪些历史允许进入样本、哪些高相关字段只是偷看了答案。
下一课处理合法历史的表示方式:缩放、类别编码和时间变换的参数也属于模型,必须在训练数据中学习。