特征工程基础:常用特征提取方法

做异常检测这么些年,我越来越觉得特征工程才是真正的“手艺活”。原始日志里全是IP、时间戳、点击流,你没法直接扔给模型说“你看着办”。得先转化成有意义的数字,让机器能听懂“正常”和“奇怪”的区别。

我个人习惯把特征分成三类:统计特征、周期性特征、上下文特征。你别看分类简单,真正落地的时候,这三类互相搭着用,效果才出得来。今天我们就一个一个说透。

统计特征:最能打基础的“老本行”

统计特征说白了就是拿数学里的描述性统计量去刻画一段窗口内的行为。你想想看,一个用户的登录频率突然从每天5次变成每天500次,均值、方差这些数字立马就拉警报了。

常用统计特征清单:

  • 均值、中位数、标准差(描述集中趋势和离散程度)
  • 最大值、最小值、极差(刻画峰值和跨度)
  • 分位数(P25 / P75 / P99 — 抗噪性强,我常用)
  • 偏度、峰度(判断分布是否对称、是否出现极端值)
  • 计数、去重计数(比如某IP访问了多少个不同用户)

我在项目里遇到过一件事:有家金融公司的告警系统天天误报,我过去一看,他们只用均值做基线。结果某天业务做营销活动,均值直接起飞,误报淹没了安全团队。后来我加了P99分位数和滑动窗口标准差,误报率降了70%。记住了,单一统计量容易翻车,多维度组合才是正道。

代码上实现也很简单,比如用Pandas提取滑动窗口特征:

import pandas as pd
import numpy as np

# 原始日志:每隔30秒采集一次访问次数
access_log = pd.Series([10, 12, 11, 30, 28, 31, 15, 14, 13],
                       index=pd.date_range('2025-01-01', periods=9, freq='30S'))

window = 5  # 5个窗口点
features = pd.DataFrame({
    'mean': access_log.rolling(window).mean(),
    'std':  access_log.rolling(window).std(),
    'max':  access_log.rolling(window).max(),
    'p99':  access_log.rolling(window).quantile(0.99)
})
print(features)

周期性特征:抓住行为中的“节律”

很多攻击行为会刻意模仿正常波峰,但真正的用户行为是有规律的。比如员工上班时间访问内部系统多,半夜几乎为0。你要是只看统计特征,攻击者选在流量高峰时段混进去,你根本察觉不到。

周期性特征的核心就是提取“重复出现的模式”。常用的方法有:

  • 傅里叶变换:把时域信号转到频域,看哪个频率成分能量高。比如24小时周期会对应一个明显的尖峰。
  • 自相关函数:测时间序列和自身滞后版本的相似度,能直观看出是否以每日/每周为周期重复。
  • 简单的方法:直接按小时、星期几、日/夜来拆分,分别算均值和标准差。我个人更倾向这种,解释性强,上线快。

避坑指南: 我曾经在处理一个海外业务时,直接用中国时区算周期性特征,结果模型在美国用户的行为上全乱套了。后来强制全部转成UTC,再按用户当地时区分组计算。记住了——时区不对,周期全废。

另外,不是所有业务都有周期。比如一台被攻破的服务器对外扫描,它的流量可能根本没周期。这时候周期性特征反而会“帮倒忙”。我建议先画个时序图看一眼,有明显波形再上周期性特征。

上下文特征:把“孤岛”连起来看

统计和周期都是“看自己”,上下文特征则是“看周围”。同一个动作,放在不同的上下文里,意义可能天差地别。

举个例子:单看某个用户在某分钟内下载了10个文件,你可能觉得没问题。但如果你知道他之前从未下载过文件,而且下载的文件名全是“backup_2025.sql”,那基本就是数据窃取了。上下文特征就是来抓这种关联的。

我常用的上下文特征维度:

维度特征示例为什么重要
用户上下文该用户过去1小时/过去7天的平均行为指标同一用户在短时间内行为突变是强信号
组/角色上下文同部门、同权限等级用户的平均访问频率一个人突然脱离群体行为,可能是账户被盗
时间上下文当前时刻是上班时间/午休/深夜/节假日非工作时间的高频访问异常性更强
空间上下文源IP所在城市、ASN、是否新IP段跨国跳转、新机房突然接入都是红牌
序列上下文上一个动作的类型、动作间的时间间隔“登录→查余额→转账”,间隔过短可能是自动化脚本

实现上我一般会构建一个“上下文聚合器”:用groupby + shift/resample把周围的数据拉过来当作新特征。比如:

# 用户上下文:过去1小时内该用户的平均访问次数
df['user_1h_mean'] = df.groupby('user_id')['count']\
                        .transform(lambda x: x.rolling('1h').mean())

# 组上下文:同部门其他用户的当前平均访问次数
dept_avg = df.groupby(['dept', 'hour'])['count'].mean()
df['dept_hour_avg'] = df.apply(lambda r: dept_avg.loc[(r['dept'], r['hour'])], axis=1)

三种特征怎么配合? 我一般这样搭配:

  1. 先用统计特征快速过滤掉明显离群点(如标准差>5倍)。
  2. 再结合周期性特征校正“虚假波峰”(营销活动日自动调高阈值)。
  3. 最后用上下文特征做精细化判断(同一时间段、同角色用户行为对比)。
特征工程三类方法协作流程 原始访问日志 统计特征(窗口滑动) 周期性特征(FFT/STL) 上下文特征(多维度) 特征融合 → 行为基线向量 异常检测判定 ⚠ 三类特征缺一不可,单用一层容易漏报/误报

嗯,说到这儿,其实特征工程没有银弹。统计特征负责“抓幅度异常”,周期性特征负责“抓节奏异常”,上下文特征负责“抓关系异常”。三管齐下,才能把攻击者从茫茫日志里翻出来。我个人建议刚开始做这个方向的朋友,先拿一个业务场景跑通这三个步骤,把特征维度控制在10~20个以内,不要一上来就怼几百维。