3. 特征工程入门:实时特征与离线特征的区别、滑动窗口特征、时间衰减特征
在实时风控系统中,特征工程是连接原始数据与模型决策的桥梁。与传统的离线风控不同,实时风控对特征的时效性、计算延迟和存储方式提出了严苛要求。本章将系统讲解实时特征与离线特征的核心区别,并深入剖析两种最常用的实时特征构造方法:滑动窗口特征与时间衰减特征。
3.1 实时特征 vs 离线特征:核心差异
离线特征通常基于全量历史数据批量计算,适用于模型训练和周期性评估;而实时特征必须在毫秒级延迟内完成计算,服务于在线推理。两者的差异贯穿数据源、计算模式、存储与更新机制。
| 对比维度 | 离线特征 | 实时特征 |
|---|---|---|
| 数据源 | 全量历史表(Hive / 离线数仓) | 实时流(Kafka / 实时数仓) |
| 计算模式 | 批量调度(T+1 / 小时级) | 增量计算(毫秒级) |
| 存储介质 | HDFS / 离线表 | Redis / 内存状态存储 |
| 更新频率 | 固定周期(每日/每小时) | 事件驱动(每次请求/每笔交易) |
| 延迟容忍 | 分钟级 ~ 小时级 | < 100ms |
| 典型用途 | 模型训练、离线回溯、报表 | 在线推理、实时决策 |
关键原则: 训练集切分时,必须保证离线特征与实时特征在时间戳上严格对齐。使用离线特征训练模型,再用实时特征上线推理,会导致特征分布偏移(Feature Drift),直接影响模型效果。
3.2 滑动窗口特征
滑动窗口特征是最基础的实时特征构造方法。它统计过去一段时间窗口内(如过去 1 小时、过去 24 小时)用户或设备的行为聚合值,如计数、求和、均值、标准差等。窗口可以是固定长度(如最近 30 分钟)或会话长度(如当前登录会话)。
3.2.1 滑动窗口的三种实现方式
- 跳跃窗口(Tumbling Window): 窗口之间不重叠,按固定时间间隔切分。适合离线批处理,实时场景下容易丢失边界事件。
- 滑动窗口(Sliding Window): 窗口之间部分重叠,每次滑动一个步长。实时风控中最常用,能捕捉连续行为模式。
- 会话窗口(Session Window): 基于事件间隔动态划分窗口,超过静默阈值则关闭当前窗口。适合用户行为序列分析。
3.2.2 滑动窗口特征实战示例
以下代码展示在 Flink SQL 中定义 1 小时滑动窗口特征,统计用户过去 1 小时内的交易次数与金额总和:
-- 实时风控滑动窗口特征(Flink SQL)
CREATE VIEW user_1h_features AS
SELECT
user_id,
COUNT(*) AS trans_cnt_1h,
SUM(amount) AS trans_amt_sum_1h,
AVG(amount) AS trans_amt_avg_1h,
STDDEV(amount) AS trans_amt_std_1h,
COUNT(DISTINCT device_id) AS distinct_device_cnt_1h
FROM transaction_stream
WHERE event_time BETWEEN CURRENT_TIMESTAMP - INTERVAL '1' HOUR AND CURRENT_TIMESTAMP
GROUP BY user_id;
工程注意: 滑动窗口特征在实时计算中需要维护窗口状态。建议将窗口大小控制在 24 小时以内,避免状态膨胀。对于超长窗口(如 7 天),考虑使用时间衰减特征替代。
3.3 时间衰减特征
时间衰减特征解决了滑动窗口的“硬边界”问题——窗口边界处的事件被突然丢弃,导致特征值跳变。衰减特征通过给每个事件赋予一个随时间递减的权重,使得越近的事件贡献越大,越远的事件逐渐被遗忘,形成平滑的衰减曲线。
3.3.1 常用衰减函数
| 衰减函数 | 公式 | 特点 | 适用场景 |
|---|---|---|---|
| 指数衰减 | w(t) = e-λ·Δt | 衰减速度由 λ 控制,λ 越大衰减越快 | 短期行为模式(如秒级/分钟级) |
| 线性衰减 | w(t) = max(0, 1 - α·Δt) | 简单直观,权重线性下降至 0 | 固定时间窗口的平滑版本 |
| 对数衰减 | w(t) = 1 / log(e + β·Δt) | 衰减速度先快后慢,长尾保留 | 长期行为画像(如天级/周级) |
3.3.2 指数衰减特征计算流程
以指数衰减为例,实时计算用户历史交易金额的衰减加权和:
- 初始化: 在 Redis 中存储用户当前衰减总值
decay_sum和最后更新时间last_update。 - 事件到达: 读取
decay_sum,先按时间差Δt进行衰减:decay_sum *= e-λ·Δt。 - 叠加新值: 将当前事件金额
amount累加到衰减后的总值上。 - 更新状态: 写回
decay_sum和当前时间戳last_update。
# 指数衰减特征实时更新(Python 伪代码)
import time
import math
LAMBDA = 0.001 # 衰减系数,控制遗忘速度
def update_decay_feature(user_id, amount, redis_client):
key = f"decay_sum:{user_id}"
last_sum, last_time = redis_client.hmget(key, ["sum", "timestamp"])
now = time.time()
if last_sum is None:
# 首次初始化
decay_sum = amount
else:
delta_t = now - float(last_time)
decay_sum = float(last_sum) * math.exp(-LAMBDA * delta_t) + amount
redis_client.hset(key, mapping={"sum": decay_sum, "timestamp": now})
return decay_sum
衰减系数调参: λ 的选择直接影响特征敏感性。λ 过大,历史信息被快速遗忘,特征波动剧烈;λ 过小,衰减接近无状态,失去时间敏感性。建议通过离线回溯实验确定最优 λ,通常取值范围为 0.0001 ~ 0.01。
3.4 滑动窗口 vs 时间衰减:选型对比
两种特征构造方法各有优劣,实际风控系统中常混合使用。以下对比帮助你在不同场景下做出选择:
| 对比维度 | 滑动窗口特征 | 时间衰减特征 |
|---|---|---|
| 边界效应 | 窗口边界处特征跳变 | 平滑连续,无跳变 |
| 计算复杂度 | 低(仅需窗口内聚合) | 中(需维护衰减状态) |
| 状态存储 | 窗口内所有事件(可能较大) | 仅存储聚合值与时间戳(极小) |
| 可解释性 | 高(“过去 1 小时交易次数”) | 中(“衰减加权和”需额外说明) |
| 长周期支持 | 窗口过长时状态膨胀 | 天然支持长周期,状态恒定 |
| 推荐场景 | 短周期行为(分钟~小时) | 长周期画像(天~周) |
3.5 本章小结
- 实时特征与离线特征在数据源、计算模式、存储和延迟要求上存在本质差异,训练集切分时必须保持特征对齐。
- 滑动窗口特征适用于短周期行为统计,实现简单、可解释性强,但存在边界跳变和状态膨胀问题。
- 时间衰减特征通过平滑权重解决边界效应,状态存储极小,适合长周期画像,但需要调参衰减系数。
- 实际生产环境中,建议混合使用两种特征:短周期用滑动窗口,长周期用时间衰减,兼顾实时性与稳定性。