时序异常检测:断点、窗口模型与预测误差

FreeGuideOnline 最新 2026-06-14

什么是时序异常检测?

时序异常检测(Time Series Anomaly Detection)是从按时间顺序排列的数据点中,识别出不符合预期模式、波峰、突变或趋势中断的观测值的过程。这些异常可能代表系统故障、欺诈行为、传感器漂移或真实的事件信号。本教程聚焦三种核心思路:断点检测窗口模型预测误差,帮助初学者理解原理并能应用于实际数据。

异常类型的快速划分

在深入方法前,先建立对异常形态的基本认知。常见异常可归为两类:

  • 点异常(Point Anomaly):单个时间点显著偏离邻近值或全局分布。例如,温度传感器瞬间跳变到200°C。
  • 模式异常(Pattern/Collective Anomaly):一段子序列的整体形状与历史模式不符,但其中的单个点可能正常。例如,心电图出现一段反常的平坦曲线。

断点检测擅长发现模式的突然变化,窗口模型能捕捉局部上下文偏离,而预测误差方法则对点异常及模式偏离均有效。

断点检测:捕捉时间序列的结构性变化

断点(Change Point)是指时间序列的统计特性(均值、方差、趋势等)发生突然改变的时刻。检测断点是理解序列演化的关键,许多异常正是由结构突变引起的。

核心思想:寻找分布不一致的分割点

给定序列 ( x_1, x_2, \dots, x_T ),断点检测算法试图找到若干位置 ( \tau ),使得分割出的各段内数据服从相似的分布,而相邻段间差异最大。

常用方法:PELT 算法

PELT(Pruned Exact Linear Time)是一种高效的离线断点检测算法,时间复杂度为 ( O(n) ),基于代价函数和惩罚项:

  • 代价函数:衡量段的内部一致性,通常用负对数似然,如对均值变化采用二次代价 ( C(x_{a:b}) = (b-a+1) \log \hat{\sigma}^2 )。
  • 惩罚项:控制断点数量,防止过分割。常见惩罚为 ( \beta \times \text{(断点数)} ),( \beta ) 通常采用 BIC 或 MBIC 准则。

PELT 通过动态规划递归计算,并利用剪枝技术加速。在 Python 中可直接使用 ruptures 库:

import ruptures as rpt
model = rpt.Pelt(model="rbf").fit(signal)
breakpoints = model.predict(pen=10)

参数选择与注意事项

  • “rbf” 模型适用于均值偏移的断点,“l1” 或 “l2” 分别针对中位数和均值变化。
  • 惩罚值 pen 越大,检出的断点越少。可先参考 pen = log(n) 或根据领域知识微调。
  • 断点检测对噪声敏感,建议先进行平滑(如移动平均)或使用稳健的代价函数。

窗口模型:基于局部上下文判断异常

窗口模型不假设全局分布,而是围绕每个点取一个邻域窗口,通过与窗口内的统计特征比较来判定异常。这类方法天然适应非平稳序列中的局部异常。

滑动窗口与局部异常分数

最简单的窗口模型是滑动 Z-score:对每个点 ( x_t ),计算其相对附近窗口均值和标准差的偏差:

  • 定义窗口大小 ( w )(如前后各 ( k ) 个点,或仅用过去窗口避免未来数据泄露)。
  • 计算局部均值 ( \mu_t ) 和标准差 ( \sigma_t )。
  • Z-score = ( (x_t - \mu_t)/\sigma_t )。
  • 若 |Z-score| > 阈值(如3),则标记为异常。

但该方法易受窗口内已有异常影响(掩蔽效应)。改进方案使用**中位数和中位绝对偏差(MAD)**代替均值和标准差,更稳健:

import numpy as np
from scipy.stats import median_abs_deviation

def robust_zscore(point, window):
    med = np.median(window)
    mad = median_abs_deviation(window, scale='normal')
    return 0.6745 * (point - med) / mad

基于窗口的判别模型:Discord 发现

时序 Discord 是指与所有其他相同长度子序列最不相似的子序列。它捕获的是形状上最异常的模式,非常适合模式异常检测。算法通过计算两两子序列间的距离(如欧氏距离),并采用 SAX 或基于矩阵概要(Matrix Profile)等技术大幅加速。

矩阵概要方法能同时给出每个子序列与其最近邻居的距离,距离峰值对应 discord。stumpy 库实现了高效计算:

import stumpy
mp = stumpy.stump(ts, m=window_size)
anomaly_idx = np.argmax(mp[:, 0])

预测误差方法:当预测值与实际值分道扬镳

基于预测的方法遵循一个直观原则:若时间序列在某一时刻的预测误差突然增大,说明该点偏离了正常演化轨迹,很可能是一个异常。

构建预测模型作为“正常行为”基准

任何能够合理拟合历史数据的模型都可用作正常行为的生成器。常用模型包括:

  • 简单模型:移动平均、指数平滑(Holt-Winters)、季节性分解(STL)。
  • 传统时序模型:ARIMA、SARIMA。
  • 机器学习模型:随机森林回归、XGBoost(需谨慎处理时间依赖)。
  • 深度学习模型:LSTM、CNN、Transformer——擅长捕获复杂非线性模式。

无论选择哪种模型,核心流程均为:

  1. 训练:用“干净”或包含少量异常的历史数据训练模型。
  2. 预测:在时间 ( t ),基于可用历史预测 ( \hat{x}_t )。
  3. 计算残差:( e_t = x_t - \hat{x}_t )。
  4. 阈值判定:对残差序列应用异常检测(如固定阈值、残差的 Z-score 或动态阈值)。

动态阈值与残差建模

简单的3-sigma方法在残差非正态时失效。更好的做法是对残差本身建模:

  • 残差的滑动统计:对残差同样使用滑动 Z-score 或移动中位数+MAD,自适应变化。
  • 残差的预测区间:使用分位数回归或 bootstrap 生成预测区间,实际值落在区间外即异常。例如,Facebook Prophet 的 yhat_loweryhat_upper 直接提供不确定性区间。

示例(Prophet):

from fbprophet import Prophet
df['cap'] = df['y'].max() + 1  # logistic 增长需设上限
model = Prophet(interval_width=0.99, changepoint_prior_scale=0.05)
model.fit(df)
forecast = model.predict(future)
anomalies = df[df['y'] > forecast['yhat_upper']]

预测误差方法的陷阱

  • 异常污染训练集:若训练数据包含大量未标注的异常,模型会学到错误的模式。解决方法:先用简单阈值过滤极端值,或采用稳健回归。
  • 错误累积:在回归模型中,若每一步用实际值作为输入进行下一步预测,会掩盖异常。应采用一步预测滚动的模式。
  • 概念漂移:当序列的正常模式缓慢变化时,需定期重新训练模型或使用在线学习方案。

三种方法的对比与组合策略

方法 擅长场景 典型输出 局限
断点检测 检测整体统计特性突变,如工艺参数调整、市场模式变化 断点位置集合 无法识别短暂的点异常
窗口模型 局部上下文异常,模式反常,且对非平稳序列友好 每个点的异常分数或标签 窗口大小敏感,计算代价可能较高
预测误差 点异常、微小偏离,可结合趋势与季节 残差与阈值边界 依赖模型假设,对训练数据质量敏感

实际工作中,常组合使用以获得更完善的检测效果:

  • 先用断点检测将序列切分为相对同质的区间,然后在各区间内应用窗口模型或预测误差方法,避免跨模式误报。
  • 使用预测误差方法提供的残差作为窗口模型的输入,叠加异常判定规则。
  • 对多模型结果进行投票或加权,提升鲁棒性。

动手实践建议与工具链

  1. 数据探索:画图,观察趋势、周期、噪声水平。标记肉眼可见的异常作为评估基线。
  2. 库选择
    • ruptures:丰富的断点检测算法。
    • stumpy:高性能的模式异常检测(Matrix Profile)。
    • prophet:带趋势、季节和假期效应的预测模型,天然输出不确定性区间。
    • PyODscikit-learn 提供通用异常检测器,可结合时间特征使用。
  3. 评估:若缺乏标签,可使用领域反馈迭代验证;有标签时采用时序交叉验证(避免未来信息泄露),并关注 precision、recall 与时序上的延迟容忍度。

时序异常检测没有万能银弹,理解数据特性、结合多种视角进行判断,是通向可靠检测的必经之路。