4、特征工程实战:滑动窗口统计、实时特征计算、特征存储与更新、特征重要性评估

好,咱们进入第四章。特征工程,说白了就是给模型喂“好饭”。

我见过太多团队,模型结构调得飞起,最后效果不行,一查,特征全是垃圾。你想想看,模型再牛,输入的是噪声,输出能好到哪去?

这一章,咱们就聚焦在实时异常交易检测里,特征到底怎么造、怎么算、怎么存、怎么挑。全是实战干货,没有废话。

4.1 滑动窗口统计:捕捉交易行为的“短期记忆”

实时检测里,单笔交易的特征往往不够。比如一笔1000元的消费,单独看可能正常。但如果这个账户过去5分钟里已经刷了10笔,每笔都接近1000元,那就不对劲了。

滑动窗口统计,就是用来捕捉这种“短期行为模式”的。

核心思想: 对每个用户,维护一个时间窗口(比如5分钟、1小时),统计窗口内的交易次数、金额总和、最大金额、标准差等。

我个人习惯用 deque 来实现滑动窗口,效率高,内存占用也小。看代码:

from collections import deque
import time

class SlidingWindowStats:
    def __init__(self, window_seconds=300):
        self.window = deque()
        self.window_seconds = window_seconds

    def add_transaction(self, amount, timestamp=None):
        if timestamp is None:
            timestamp = time.time()
        self.window.append((timestamp, amount))
        # 清理过期数据
        while self.window and self.window[0][0] < timestamp - self.window_seconds:
            self.window.popleft()

    def get_stats(self):
        if not self.window:
            return {'count': 0, 'sum': 0.0, 'max': 0.0, 'mean': 0.0}
        amounts = [amt for _, amt in self.window]
        return {
            'count': len(amounts),
            'sum': sum(amounts),
            'max': max(amounts),
            'mean': sum(amounts) / len(amounts)
        }

避坑指南: 我曾经在项目里直接用列表存所有交易,结果用户量一上来,内存直接爆了。后来换成 deque,问题解决。记住,实时系统里,内存管理是第一位的。

4.2 实时特征计算:从数据流到特征向量的“流水线”

数据是流式进来的,特征也得流式算。你不能等所有数据到齐了再算,那就不是实时了。

实时特征计算,我一般把它拆成三步:

  1. 数据接入: 从 Kafka 或 Redis 流里拿到原始交易事件。
  2. 特征提取: 对每个事件,提取基础特征(金额、时间、地点、设备等)。
  3. 聚合计算: 结合滑动窗口,计算统计特征。

举个例子,一个典型的实时特征向量可能是这样的:

{
  "user_id": "u12345",
  "amount": 1500.00,
  "hour_of_day": 14,
  "is_weekend": 0,
  "device_type": "mobile",
  "window_5min_count": 3,
  "window_5min_sum": 4200.00,
  "window_1hour_count": 12,
  "window_1hour_sum": 18500.00,
  "amount_ratio_to_avg": 1.2  # 当前金额 / 过去1小时平均金额
}

嗯,这里要注意,amount_ratio_to_avg 这个特征特别有用。如果突然一笔交易金额远超用户平时的水平,那基本就是异常信号。

4.3 特征存储与更新:别让特征“过期”

特征算出来了,存哪?怎么更新?

我个人推荐用 Redis。原因很简单:快,支持 TTL(过期时间),天然适合实时场景。

看一个简单的特征存储设计:

import redis

r = redis.Redis(host='localhost', port=6379, db=0)

def store_user_features(user_id, features, ttl=3600):
    key = f"features:{user_id}"
    # 用 hset 存储多个特征字段
    r.hset(key, mapping=features)
    # 设置过期时间,避免僵尸数据
    r.expire(key, ttl)

def get_user_features(user_id):
    key = f"features:{user_id}"
    return r.hgetall(key)

注意: 特征一定要设置 TTL!我见过有人忘了设,结果 Redis 里堆了几百万个用户的特征,查询越来越慢。实时系统里,数据是有“保鲜期”的。

更新策略上,我一般用“增量更新”。比如滑动窗口统计,每次来新交易,只更新窗口尾部,而不是重新算全部。这样计算量小,延迟低。

4.4 特征重要性评估:哪些特征真正有用?

特征造了一大堆,哪些是核心?哪些是噪声?

我常用的方法有两种:

  • 基于树模型的特征重要性: 训练一个 XGBoost 或 LightGBM,直接看 feature_importances_
  • 基于 SHAP 值的解释: 更精细,能看出每个特征对单个预测的影响。

举个例子,用 XGBoost 评估特征重要性:

import xgboost as xgb
import pandas as pd

# 假设 df 是训练数据,包含特征列和标签列
X = df.drop('is_fraud', axis=1)
y = df['is_fraud']

model = xgb.XGBClassifier(n_estimators=100, max_depth=4)
model.fit(X, y)

importance = pd.DataFrame({
    'feature': X.columns,
    'importance': model.feature_importances_
}).sort_values('importance', ascending=False)

print(importance.head(10))

输出结果大概长这样:

特征 重要性
window_5min_count 0.32
amount_ratio_to_avg 0.28
hour_of_day 0.15
device_type 0.10
is_weekend 0.05

你看,window_5min_countamount_ratio_to_avg 占了将近60%的重要性。这说明什么?短期行为模式和历史偏差,才是判断异常的关键。

我的经验: 特征重要性评估不是一次性的。模型上线后,数据分布会变,特征的重要性也会变。我建议每周跑一次特征重要性分析,及时剔除失效特征,加入新特征。

本章知识体系

下面这张图,把本章的核心逻辑串起来了:

特征工程实战:核心流程 滑动窗口统计 deque / 时间窗口 实时特征计算 流水线 / 增量更新 特征存储与更新 Redis / TTL 特征重要性 XGBoost / SHAP 流程说明: 1. 原始交易事件流入 → 滑动窗口统计模块维护每个用户的短期行为数据 2. 实时特征计算模块从窗口和事件中提取特征向量 3. 特征存储模块将特征写入 Redis,设置 TTL 保证数据新鲜 4. 特征重要性评估模块定期分析,剔除无效特征,优化特征集 持续迭代优化

特征工程不是一锤子买卖。滑动窗口、实时计算、存储更新、重要性评估,这四个环节环环相扣,缺一不可。

我见过太多人只关注模型调参,忽略了特征的质量。结果就是,模型越调越复杂,效果却原地踏步。记住,好的特征,比好的模型更重要。

一句话总结: 特征工程是实时异常交易检测的“地基”。地基不稳,楼盖得再高也得塌。


公众号:蓝海数据掘金营,微信deep3321