时序异常检测算法 Isolation Forest
FreeGuideOnline
最新
2026-07-08
s(x, n) = 2^(-E(h(x)) / c(n))
其中 `c(n)` 是给定样本数 `n` 时二叉搜索树的平均路径长度(用于归一化)。
分数解读:
- 分数接近 1,判定为异常;
- 分数远小于 0.5,判定为正常;
- 所有样本分数都在 0.5 左右时,整个数据集可能没有明显异常。
## 在时序数据中的应用方法
### 特征工程:将时序转化为特征矩阵
Isolation Forest 本质上是基于特征的算法,不能直接处理时间依赖关系。应用前需要将时序观测转换为特征向量。常见方法:
1. **滑动窗口**
将连续 `k` 个时间点的原始值作为特征。例如滞后 1 到 lag 的序列值。
2. **统计衍生特征**
对每个滑动窗口计算均值、标准差、最大值、最小值、一阶差分等。
3. **频率特征**
通过短时傅里叶变换或小波变换提取频域特征,捕捉周期性异常。
### 训练流程
1. 选择无异常或少量异常的历史数据作为训练集;
2. 构建特征矩阵 `X`,每一行对应一个时间点的特征向量;
3. 设定 Isolation Forest 参数(树木数量、子采样大小、污染率等);
4. 训练模型,计算每个训练样本的异常分数;
5. 设定阈值(如基于分数分位数),对未来新数据点进行实时检测。
### 在线/增量检测
单棵隔离树不支持增量更新,但可以通过滑动窗口定期重新训练模型,或采用扩展算法如 **Half-Space Trees** 实现增量异常检测。对于流式时序,通常每小时或每日批量重训。
## 关键参数调优
| 参数 | 说明 | 经验建议 |
|------|------|----------|
| `n_estimators` | 隔离树的数量 | 通常 100 以上即可稳定,过大增加计算负担 |
| `max_samples` | 构建每棵树的子采样大小 | 默认 256,大数据集可适当增大,过大会降低对局部异常的敏感度 |
| `contamination` | 训练数据中异常点的预期比例 | 根据领域知识设置,如 0.01 表示期望 1% 为异常,影响阈值自动确定 |
| `max_features` | 每次分割时考虑的特征数 | 默认 1.0(全部特征),高维时可减少以增加随机性 |
时间序列场景下,`contamination` 的设定十分关键。如果历史数据纯度很高,可设为较小的值(如 0.001);若有标注数据,可通过验证集调整阈值。
## 实战示例(Python)
```python
import numpy as np
import pandas as pd
from sklearn.ensemble import IsolationForest
import matplotlib.pyplot as plt
# 生成模拟时序数据:正弦波 + 少量尖峰异常
np.random.seed(42)
n = 500
time = np.arange(n)
signal = 10 * np.sin(2 * np.pi * time / 50) + np.random.normal(0, 1, n)
# 注入异常
anomaly_idx = [100, 200, 300, 400]
signal[anomaly_idx] += np.random.normal(15, 3, len(anomaly_idx))
# 滑动窗口构造特征(lag=3)
lag = 3
X = np.zeros((n - lag, lag))
for i in range(lag):
X[:, i] = signal[i:n - lag + i]
# 训练 Isolation Forest
model = IsolationForest(n_estimators=100, contamination=0.02, random_state=42)
model.fit(X)
# 预测:-1 表示异常,1 表示正常
pred = model.predict(X)
# 可视化
plt.figure(figsize=(12, 5))
plt.plot(time[lag:], signal[lag:], label='Original Series', color='blue')
plt.scatter(time[lag:][pred == -1], signal[lag:][pred == -1],
color='red', label='Detected Anomaly')
plt.legend()
plt.title('Isolation Forest on Time Series (Sliding Window)')
plt.show()