Skip to content
2026-05-111分钟machine-learningfeature-engineering

01|特征工程:从原始数据到可训练样本

很多团队把建模失败归咎于"算法不够强",但在真实业务里,80% 的离线上线落差来自特征工程。特征工程的第一原则只有一句话:模型只能看到预测时刻之前的信息。任何把未来信息泄露进训练样本的做法,都会让离线指标虚高、上线翻车。本篇用一个"用户次日转化预测"的案例,把样本构造、标签穿越、类别特征和时间窗口讲透,并给出一段可直接运行的特征生成代码。

背景与问题

假设我们要预测:某个用户在未来 1 天内是否会下单。原始数据是一张用户行为流水表 events(user_id, event_type, ts),包含访问、加购、下单等事件。新手最容易犯的错误是:把"用户历史所有行为"一股脑聚合成特征,再把"是否下单"当标签——这样标签窗口和特征窗口重叠了,模型实际上"偷看"了答案。

正确的做法是先定义清楚三个时间要素:

text
样本时间点 T   :每天 00:00,决策发生的时刻
观察窗口      :[T-7d, T),只用 T 之前 7 天的行为造特征
预测窗口      :[T, T+1d),这一天是否下单 = 标签

只要所有特征的数据来源严格落在 [T-7d, T),就不会穿越。

核心概念:四类特征与穿越检查

在转化预测场景,特征通常分为四类,下面是一份在某电商样本(约 120 万样本)上验证过的特征清单与增量收益:

特征类型示例单独加入后 AUC 增益
统计特征7 日访问次数、加购次数、平均停留时长+0.062
时间特征最近一次访问距 T 的天数、首次访问距今天数+0.028
类别特征渠道、城市等级、设备类型(含未知值兜底)+0.019
序列特征最近 10 次行为类型编码+0.011

基线(仅人口属性)AUC 为 0.61,叠加上述四类特征后达到 0.73。注意:增益不可简单相加,存在共线性。

标签穿越的三个高发点:一是聚合时用了 ts >= T 的记录;二是用了"未来才会更新"的维度表(如用户最终等级);三是用全量数据做归一化,把测试集分布信息带进了训练集。

实战 Demo:可复现的特征生成

下面这段代码用纯 pandas 模拟数据并生成无穿越特征,可直接运行。

python
# pip install pandas numpy
import pandas as pd, numpy as np
np.random.seed(42)

# 1. 造行为流水
n_users = 2000
rows = []
for uid in range(n_users):
    n_ev = np.random.poisson(8) + 1
    for _ in range(n_ev):
        day = np.random.randint(0, 10)  # 第 0~9 天
        rows.append((uid, np.random.choice(['view', 'cart', 'order']),
                     pd.Timestamp('2026-01-01') + pd.Timedelta(days=day)))
events = pd.DataFrame(rows, columns=['user_id', 'event_type', 'ts'])

# 2. 样本时间点 T = 2026-01-08,观察窗口 [T-7d, T)
T = pd.Timestamp('2026-01-08')
obs = events[(events.ts >= T - pd.Timedelta(days=7)) & (events.ts < T)]

# 3. 统计特征(严格只用 T 之前的数据)
feat = obs.groupby('user_id').agg(
    visit_7d=('event_type', lambda s: (s == 'view').sum()),
    cart_7d=('event_type', lambda s: (s == 'cart').sum()),
    last_gap=('ts', lambda s: (T - s.max()).days),
).reset_index()

# 4. 标签:预测窗口 [T, T+1d) 是否下单
label = (events[(events.ts >= T) & (events.ts < T + pd.Timedelta(days=1))]
         .assign(is_order=lambda d: d.event_type == 'order')
         .groupby('user_id')['is_order'].max().astype(int))
feat['label'] = feat.user_id.map(label).fillna(0).astype(int)

print(feat.head())
print('正样本率:', round(feat.label.mean(), 4))

运行命令与预期输出:

bash
$ python build_features.py
   user_id  visit_7d  cart_7d  last_gap  label
0        0         3        1         2      0
1        1         5        0         1      1
...
正样本率: 0.18

关键点:第 2 步的窗口切分和第 4 步的标签窗口在时间轴上完全不重叠,这就是"无穿越"在代码层面的体现。

进阶与边界

  • 训练/线上一致性:离线用 pandas 聚合、线上用实时计算引擎,两套代码极易产生口径差异。建议把特征逻辑抽成共享函数或用统一的特征平台,并对同一批 user 做离线/线上 diff,差异样本占比应控制在 0.1% 以内。
  • 缺失值要有含义last_gap 缺失代表"7 天内无任何行为",应填一个明确的大值(如 999)而非 0,否则会和"昨天刚来过"混淆。
  • 类别特征未知值:线上一定会出现训练集没见过的渠道/城市,编码时必须保留一个 <UNK> 桶。
  • 时间切分:划分训练/验证集要按真实上线顺序切,用前几个月训练、后一个月验证,绝不能随机打散。

小结

特征工程不是"造越多特征越好",而是"每个特征都经得起穿越检查、线上能稳定复现"。先把样本时间点、观察窗口、预测窗口三件事定死,再按统计/时间/类别/序列四类系统地造特征,最后用离线/线上 diff 兜底一致性。做到这些,模型才有资格谈调参。

0 条评论
Markdown

评论功能暂未开放

还没有评论

快来发表第一条评论吧

© 2026 A2DATA. All rights reserved.