老周在上海经营一家快递分拣仓库,每个小时他都会在笔记本上记下“进港包裹量”。把这一串数字按时间画成曲线,大多数日子像潮汐一样有规律:凌晨清冷、午后爬升、晚间平稳。可某一天凌晨 2 点,包裹量突然冲到平时的三倍——不是闹鬼,是传送带卡住后又一起涌进来。老周要在几万条记录里快速找出这种“不对劲”,这就是时间序列异常检测。本文要讲的两类方法都不需要提前贴好“异常标签”:孤立森林像“随机拉几道帘子,把怪东西最快隔开”,频谱残差像“把日常节奏从波形里抽走,剩下的尖刺就是异常”。这两种方法在运维监控、金融风控中都非常实用。

岗位关联

掌握时间序列异常检测后,你可以在没有标注样本的前提下搭建运维监控和风控告警系统,从“事后救火”变成“主动发现”,这是 AI 工程落地中性价比最高的技能之一。

目录
  1. 可视化演示
  2. 异常检测入门
  3. 孤立森林原理
  4. 孤立森林实战
  5. 频谱残差原理
  6. 频谱残差实战
  7. 模型评估调优
  8. 风控运维落地
  9. 易错点与工程化
  10. AI 应用场景
  11. Python 演示
  12. 常见误区
  13. 面试题

可视化演示

本节配有交互动效(进入视口后自动加载,约 3KB)。

时间序列数据 (红点=真实异常) 异常分数:异常分数:异常分数:异常分数: 异常分数:异常分数:异常分数:异常分数:

异常检测入门

老周的仓库有一本运行日志:每小时记录一次进港包裹量,按时间排成一条曲线。这条曲线就是“时间序列”。正常日子里,它像潮汐一样有固定节奏;但如果传送带卡了、大客户突然爆单、或者系统统计出错,曲线上就会出现少数“不合群”的点。异常检测的任务,就是从这些按时间排列的数据里,找出真正需要关注的特殊时刻。

异常可以分成三类。第一类是点异常:某个单点数值突然大幅偏离正常区间,比如凌晨 2 点包裹量飙升到 3000 件。第二类是上下文异常:数值本身还在正常范围,但出现在错误的时间上下文里,比如深夜本该安静的时段出现了午高峰级别的流量。第三类是群体异常:连续多个点组成一段不正常形态,比如传送带卡住三小时,曲线变成一条诡异的水平线。这三类异常在运维监控、金融风控中都很常见。

为什么不用传统的“超过三倍标准差就算异常”呢?因为真实数据有趋势、有周期、有噪声,简单阈值会把节假日的正常峰值误判为故障,也会漏掉隐藏在正常数值中的上下文异常。于是我们需要能“看形状”和“看频率”的算法,这正是孤立森林和频谱残差登场的理由。

回到老周的仓库:他不想给每个小时的数据都贴标签,只想让算法自己去发现“奇怪的小时”。这不就是无监督学习最舒服的场景吗?接下来我们先把视角拉到“拉帘子”的孤立森林上。

孤立森林原理

老周有块空场地,里面堆着许多包裹样本。正常包裹长得都差不多,堆在一起;异常包裹又小又偏,藏在角落。新来的员工想找出怪包裹,既不称重也不看面单,而是随手拉几道帘子把场地切成小块。奇怪的是,普通包裹要经过很多道帘子才能被隔离,而那个角落里的怪包裹几下就被单独隔出来了。孤立森林用的正是这个“少数且不同”的直觉。

算法会随机选一个特征和切分值,把数据空间一切两半,不断重复直到每个样本都被单独分到叶子节点。每棵随机切分树里,样本从根到叶子经过的边数叫路径长度。异常点因为位置偏僻,路径长度往往很短。把所有树上的路径长度取平均,就得到稳定的异常度量。为了让分数好解释,我们把它归一化到 0 到 1 之间。异常分数公式如下:

当 \(\mathbb{E}[h(x)]\) 接近 \(c(n)\) 时,分数接近 0.5,表示普通样本;当路径长度明显短于平均水平时,分数接近 1,老周就会让这个包裹进入复查区。孤立森林不依赖任何标签,也不需要假设数据服从什么分布,这是它最可爱的地方。

s(x,n)=2−E[h(x)]c(n)\displaystyle s(x,n)=2^{-\frac{\mathbb{E}[h(x)]}{c(n)}}
  1. 路径长度\(h(x)\) 表示样本 \(x\) 在一棵隔离树中被切分出来所需要的切分次数。
    h(x)\displaystyle h(x)
  2. 期望长度\(\mathbb{E}[h(x)]\) 是 \(x\) 在所有树上的平均路径长度,代表“平均要拉几道帘子才能隔离它”。
    E[h(x)]=1T∑t=1Tht(x)\displaystyle \mathbb{E}[h(x)] = \frac{1}{T}\sum_{t=1}^{T}h_t(x)
  3. 归一化常数\(c(n)\) 是 \(n\) 个样本构建二叉搜索树的平均路径长度,用来消除样本量带来的影响。
    c(n)=2H(n−1)−2(n−1)n\displaystyle c(n)=2H(n-1)-\frac{2(n-1)}{n}
  4. 异常分数当平均路径长度越短,指数部分越小,分数越接近 1,表示越可能是异常。
    s(x,n)=2−E[h(x)]c(n)\displaystyle s(x,n)=2^{-\frac{\mathbb{E}[h(x)]}{c(n)}}

孤立森林实战

老周发现,只看单个小时的包裹量有时不够。比如“深夜 2 点出现 800 件”既可能因为爆单,也可能因为传送带积压后集中吐出。要识别这种上下文异常,就得把“最近一小时的走势”也告诉模型。由于孤立森林不关心时间顺序,我们通常用滑动窗口把最近 \(m\) 个时间点拼成一个特征向量,就像老周判断员工状态时,不只看一秒钟的表情,而是看连续十分钟的动作。

在 Python 的 sklearn.ensemble.IsolationForest 中,三个参数最值得关注。n_estimators 控制树的数量,一般 100 到 200 就够,太多只会增加计算量;max_samples 控制每棵树最多使用的样本数,常用 256 或 512;contamination 是“预期异常比例”,它会在训练时帮助模型确定切分边界,相当于告诉老周“你预计 3% 的包裹需要复查”。

阈值如何定?如果数据里没有标签,可以把所有样本的异常分数排序,取 \(1-\text{contamination}\) 分位数作为阈值;如果有一些历史标注,则可以直接在验证集上选一个让 F1 最高的分数边界。需要注意,contamination 是预期比例而不是真实结果,业务变化时它也要跟着调整。

老周把边界定在“最怪异的 2% 包裹必须复查”,果然抓到了几单被系统漏掉的问题件。孤立森林把一个复杂问题变成了随机切分的概率游戏:树多、样本足、窗口合适,异常常常无处可藏。

频谱残差原理

老周还有一顶神奇的“频谱眼镜”。戴上它看包裹曲线,原本上下起伏的潮汐被拆成一堆不同频率的波浪:有每天一次的日周期,有每周一次的周周期,还有一些高频小抖动。正常数据在频率图里往往呈现出平滑的“常见节奏”,而突然出现的异常,会在很多频率上留下不协调的尖刺。频谱残差方法就是把这些“不协调”单独抠出来。

具体来说,把时间序列做傅里叶变换,得到幅度谱 \(A(f)\)。对幅度谱取对数,可以压缩动态范围,让大周期和小细节都能被看清。正常的时间序列,其对数幅度谱在局部应该是平滑的;我们用移动平均 \(h_n(f)\) 去拟合这个“平滑常态”,再把它从原始对数谱里减掉,剩下的就是异常带来的频谱残差。公式如下:

得到残差谱后,把它与原始相位结合,再做逆傅里叶变换,就能把频率上的“不协调”翻译回时间轴上的每个点,形成一条异常显著度曲线。曲线越高的地方,越可能是老周要找的异常时刻。

R(f)=log⁡A(f)−hn(f)∗log⁡A(f)\displaystyle R(f)=\log A(f)-h_n(f)*\log A(f)
  1. 傅里叶变换把时间信号 \(x(t)\) 变成频率信号,得到每个频率的幅度。
    A(f)=∣F[x(t)]∣\displaystyle A(f)=|F[x(t)]|
  2. 对数幅度谱对幅度取对数,避免大周期淹没了小尖刺。
    log⁡A(f)\displaystyle \log A(f)
  3. 平滑常态\(h_n(f)\) 是一个长度为 \(n\) 的平均滤波器,卷积后得到局部平均谱,代表“正常节奏”。
    hn(f)∗log⁡A(f)\displaystyle h_n(f)*\log A(f)
  4. 频谱残差用原始对数谱减去平滑谱,残差越大,说明该频率越“不属于常态”。
    R(f)=log⁡A(f)−hn(f)∗log⁡A(f)\displaystyle R(f)=\log A(f)-h_n(f)*\log A(f)

频谱残差实战

频谱残差的实战流程像老周查监控一样清晰,总共五步。第一步,对整段序列做 FFT,得到幅度谱和相位谱;第二步,对幅度谱取对数,得到对数幅度谱;第三步,用一个长度为 \(n\) 的滑动平均窗口拟合常态谱;第四步,用对数谱减去常态谱得到残差谱;第五步,将残差谱配合原始相位做 IFFT,还原出每个时间点的显著度分数。

参数选择上,滑动平均窗口 \(n\) 是关键。窗口太小,平滑谱会跟着原始谱抖动,异常尖刺容易被当成常态;窗口太大,平滑谱过于迟钝,小异常会被抹平。实践中一般从 3 到 10 之间尝试,再结合业务周期调整。如果数据有明确的日周期,可以先按 24 小时切片,再对每一列做检测,效果往往更好。

阈值设定可以用“均值加三倍标准差”这种保守策略,也可以用训练集上的分位数。老周对一天 1440 个分钟级数据做检测,把显著度超过 \(\mu + 3\sigma\) 的点标红,结果正好对应传送带卡住的那几分钟。

频谱残差最大的优势是计算快、无需训练标签,适合作为第一道“粗筛”;但它对缓慢漂移不敏感,所以工程上常让它和孤立森林形成互补。

模型评估调优

老周不能只凭感觉说“抓得准”,他需要量化评估。最基础的工具是混淆矩阵:把真实情况和预测结果交叉统计成四种格子:真正例 \(TP\)、假正例 \(FP\)、假负例 \(FN\)、真负例 \(TN\)。有了这四个数,就能计算精确率和召回率。

精确率回答“报警里有多少是真异常”,召回率回答“所有真异常里我们抓到了多少”。两者常常此消彼长:阈值调高,报警变少,误报少了但漏报变多;阈值调低,异常更容易被发现,但噪音也会跟着进来。为了综合权衡,常用 F1 分数:

老周把报警阈值想象成一个“灵敏度旋钮”:往左拧,更多异常被暴露,但半夜误报也多了;往右拧,群聊安静了,但可能漏掉大故障。调优不是让某个指标无限高,而是在业务容忍度下找到平衡点。运维场景更在意少漏报,故意把阈值调低;风控场景更在意误报成本,通常把阈值调高。

P=TPTP+FP,R=TPTP+FN,F1=2PRP+R\displaystyle P=\frac{TP}{TP+FP},\quad R=\frac{TP}{TP+FN},\quad F1=\frac{2PR}{P+R}
  1. 精确率\(TP\) 除以预测为正例的总数,衡量报警的纯度。
    P=TPTP+FP\displaystyle P=\frac{TP}{TP+FP}
  2. 召回率\(TP\) 除以真实为正例的总数,衡量异常被找到的比例。
    R=TPTP+FN\displaystyle R=\frac{TP}{TP+FN}
  3. 调和平均\(F1\) 是精确率和召回率的调和平均,只有当两者都高时,F1 才会高。
    F1=2PRP+R\displaystyle F1=\frac{2PR}{P+R}

风控运维落地

在运维监控里,老周把两种算法装进监控室,形成一条完整链路:采集指标、滑窗预处理、并行打分、融合判断、告警通知。对于服务器 CPU、QPS、响应时间这类指标,频谱残差擅长发现“频率上的突变”,孤立森林擅长发现“特征组合上的离群”。两个模型都报警时,基本可以确认是真实故障;只有一个报警时,再结合业务规则决定是否升级。

金融风控是另一个典型场景。支付平台每分钟的交易笔数或金额,天然是时间序列。盗刷、营销事件、接口故障都会让序列出现尖峰或骤降。传统规则引擎只能识别已知模式,而异常检测不需要先验知识,能抓住从未见过的异常形态。发现异常后,风控系统会自动生成工单,让审核人员介入,形成“检测—预警—复核—处置”的业务闭环。

告警本身也要降噪。老周最讨厌半夜被同一故障连续轰炸,所以系统会做去重和聚合:同一个异常源在 10 分钟内只发一次告警,等确认恢复后再重置状态。这样,告警不再是噪音,而是一份高质量的“问题清单”。

易错点与工程化

老周踩过不少坑,第一个就是未来数据泄漏。离线分析时可以用整段序列的均值做平滑,但在线检测时,当前时刻只能用历史数据,不能拿未来数据帮忙。比如频谱残差的滑动平均,在线模式要用因果窗口或滚动 FFT,否则模型会“偷看答案”,上线后性能暴跌。

第二个坑是低频高幅误区。很多指标有强烈的周期性,比如每天早上八点准时上涨。如果不先消除趋势或周期,算法会把这种正常峰值当成异常。解决方法是先做差分、按周期切分,或者把“同期对比值”作为一个特征送入模型,让模型知道这个点只是“今天的正常早高峰”。

第三个坑是模型“过期”。业务会漂移,节假日的模式与工作日不同,老周的仓库旺季和淡季也完全不同。因此需要定期用最近数据重训模型,或者让基线随着滑动窗口自动更新。孤立森林和频谱残差的融合也是工程化的关键:前者擅长高维空间中的离群样本,后者擅长频率域上的突变,两者投票可以让误报率明显下降。

最后,老周把检测结果沉淀成一张“异常事件日历”,每个季度复盘哪些异常是故障、哪些是误报、哪些是业务变化。这不仅是模型优化,更是组织和流程的持续改进。

AI 应用场景

运维监控告警

阶段:模型推理与告警触发

解决的问题:传统固定阈值在业务高峰时段频繁误报,故障发生后又容易漏报。AI 方法无需人工标注,通过孤立森林和频谱残差自主学习指标的正常节奏,能提前发现抖动和突变。

具体案例:某电商平台对网关 QPS 按分钟采集,算法在故障前 5 分钟捕捉到高频抖动和局部尖峰,系统自动向值班群推送告警,运维团队在用户投诉前完成了限流处理。

金融交易反欺诈

阶段:特征工程与在线检测

解决的问题:盗刷、机器交易等行为会让交易量在短时间内出现极端偏离,传统规则只能命中已知作弊模式。异常检测可以发现从未出现过的异常形态。

具体案例:第三方支付公司对每个商户每 10 分钟的交易额做时间序列分析,当频谱残差显著度与孤立森林分数同时超过阈值时,自动触发人工复核,成功拦截了一批新型养卡套现交易。

Python 演示

python40 行
import numpy as npfrom sklearn.ensemble import IsolationForest def make_series():    rng = np.random.default_rng(42)    t = np.linspace(0, 6 * np.pi, 720)    series = 10 * np.sin(t) + 3 * np.sin(12 * t) + rng.normal(0, 0.8, 720)    series[180] = 32    series[481] = -28    return series def spectral_residual_score(series, smooth=7):    fft = np.fft.fft(series)    log_amp = np.log(np.abs(fft) + 1e-8)    kernel = np.ones(smooth) / smooth    avg_log_amp = np.convolve(log_amp, kernel, mode='same')    residual = log_amp - avg_log_amp    restored = np.exp(residual + 1j * np.angle(fft))    score = np.abs(np.fft.ifft(restored))    return score def detect_by_score(score, k=3):    thr = score.mean() + k * score.std()    return score > thr, thr def main():    series = make_series()    sr_score = spectral_residual_score(series)    sr_anom, sr_thr = detect_by_score(sr_score)    print("频谱残差异常点:", np.where(sr_anom)[0].tolist())    print("频谱残差阈值:", round(sr_thr, 4))     X = np.vstack([series[i:i + 10] for i in range(len(series) - 10)])    model = IsolationForest(n_estimators=200, contamination=0.03, random_state=42)    labels = model.fit_predict(X)    if_anom = (labels == -1)    print("孤立森林异常样本数:", int(if_anom.sum())) if __name__ == "__main__":    main()
目的:演示两种无监督异常检测方法——频谱残差先对序列做 FFT 和谱平滑,再用逆变换得到显著度;孤立森林用滑动窗口把时间上下文变成特征,输出异常标签。
  1. rng = np.random.default_rng(42)固定随机种子,让实验结果可复现。
  2. series[180] = 32在序列中人为埋入两个异常点,模拟真实突发。
  3. np.fft.fft(series)把时域序列变换到频域,得到幅度谱和相位谱。
  4. np.convolve(log_amp, kernel, mode='same')对对数幅度谱做滑动平均,得到“常态谱”。
  5. np.exp(residual + 1j * np.angle(fft))用残差谱和原始相位重构信号,再逆变换得到显著度。
  6. IsolationForest(n_estimators=200, contamination=0.03)构建 200 棵孤立树,并假设约 3% 的样本为异常。
  7. model.fit_predict(X)无监督训练并返回每个样本的标签,-1 表示异常。

常见误区

误区初学者认为既然要识别异常,就应该把大量异常样本标出来喂给模型,觉得样本越丰富模型越聪明。

正解孤立森林的核心假设是“异常是少数且不同的”。如果训练数据里满是异常,随机切分就很难把某个样本快速隔离出来,模型会把这些异常误认成常态。它不需要大量异常标签,而是需要保证异常在数据中的“稀有性”。

为何常见:监督学习越多样本越准的思维惯性,让人误以为无监督算法也遵循同样逻辑。

误区学会了 FFT 就觉得它万能,认为频谱残差可以覆盖点异常、上下文异常、群体异常甚至缓慢漂移。

正解频谱残差对“频率成分突变”最敏感,适合捕捉突然出现又消失的尖峰。长期缓慢的趋势漂移在频率谱里会被平滑谱吸收,往往无法被有效检出,需要与其他方法配合。

为何常见:算法名字听起来很底层,容易被误认为“数学变换解决一切”。

误区看到 `fit(X)` 只要输入一列数就行,以为模型会自动理解时间先后顺序。

正解孤立森林是“集合模型”,不感知时间顺序。如果只看单点数值,凌晨的高峰值和下午的高峰值会被一视同仁,识别不出“错误时间出现的高峰”。必须用滑动窗口把最近几个点拼成特征向量,模型才能感知上下文。

为何常见:时间序列的“顺序感”来自人的直觉,机器不会自动获得。

误区上线时选了效果最好的阈值,就以为一劳永逸,不再更新。

正解业务会漂移,节假日、促销、版本更新都会改变数据分布。原来 3% 的异常比例可能变成 0.5%,原来合适的阈值开始狂刷告警。需要通过周期性评估和自适应分位数持续校准。

为何常见:阈值看起来是静态超参数,实际上高度依赖数据分布的时效性。

面试题

Q1. 孤立森林判断样本是否为异常的核心依据是什么?

单选题 · easy

  • A.样本在数据集中出现的频次
  • B.随机切分将样本隔离所需路径长度← 正确答案
  • C.样本到均值的欧氏距离
  • D.样本被采集的时间先后

答案:B。孤立森林用随机切分构造二叉树,样本被隔离得越快,路径长度越短,异常分数越高。出现频次、欧氏距离和时间先后都不是其核心依据。

Q2. 频谱残差方法的基础是下列哪种数学变换?

单选题 · easy

  • A.傅里叶变换← 正确答案
  • B.小波变换
  • C.拉普拉斯变换
  • D.Z 变换

答案:A。频谱残差通过 FFT 将时间序列变换到频率域,在对数幅度谱上做平滑与相减,再逆变换回时间域。小波、拉普拉斯和 Z 变换不是该方法的标准基础。

Q3. 在时间序列中,某个单点数值突然大幅偏离正常区间,这种异常属于?

单选题 · easy

  • A.群体异常
  • B.上下文异常
  • C.点异常← 正确答案
  • D.长期趋势

答案:C。单个时间点独立且显著偏离正常区间,属于典型的点异常。如果数值本身正常但出现在错误的时间上下文,才是上下文异常;连续多点的异常形态属于群体异常。

Q4. 孤立森林中 c(n) 的作用是什么?

单选题 · mid

  • A.控制树的深度上限
  • B.表示随机切分尝试次数
  • C.对路径长度做归一化← 正确答案
  • D.缩放样本特征区间

答案:C。c(n) 是 n 个样本构建二叉搜索树的平均路径长度,用来消除样本量差异对路径长度的影响。它不控制树深度,也不做特征缩放。

Q5. 频谱残差中,对数幅度谱的平滑窗口设置过大,可能导致?

单选题 · mid

  • A.异常点更加突出
  • B.细微异常被抹平← 正确答案
  • C.计算时间呈指数增加
  • D.无法进行逆变换

答案:B。平滑窗口过大时,常态谱会过度平滑,异常带来的细微残差被当成常态吸收,导致小异常无法被检出。窗口过小则容易让噪声混入残差,产生大量误报。

Q6. 检测阈值调高以后,通常会出现什么情况?

单选题 · mid

  • A.精确率和召回率都升高
  • B.精确率升高且召回率下降← 正确答案
  • C.精确率和召回率都降低
  • D.召回率升高且精确率下降

答案:B。阈值调高意味着只有显著度很高的样本才被判定为异常,告警变少、误报降低,所以精确率通常升高;但一些较弱但仍真实的异常会被漏掉,召回率下降。这正是精确率与召回率的典型权衡。

Q7. 以下哪些方法能提升时间序列异常检测的可靠性?

多选题 · mid

  • A.融合孤立森林与频谱残差← 正确答案
  • B.定期重训练更新正常基线← 正确答案
  • C.用未来数据平滑当前样本
  • D.仅用异常样本做交叉验证

答案:AB。多模型融合可以互补各自盲区,定期重训练能让模型适应业务漂移。用未来数据会引入泄漏,仅用异常样本则无法验证误报率,两者都是错误做法。

Q8. 对时间序列使用孤立森林时,滑动窗口的主要目的是?

单选题 · mid

  • A.让特征包含时间上下文← 正确答案
  • B.降低数据采样频率
  • C.减少模型训练耗时
  • D.消除数据中的周期性

答案:A。孤立森林本身不感知先后顺序,通过滑动窗口把最近几个时间点拼成特征,模型才能识别“这个峰值出现在错误时间点”这类上下文异常。

Q9. 频谱残差最适合检测哪一类时间序列异常?

单选题 · hard

  • A.频谱中突变的局部尖峰← 正确答案
  • B.长期缓慢的趋势漂移
  • C.样本间距离均匀的聚类异常
  • D.高维稀疏表格中的离群点

答案:A。频谱残差把正常周期成分平滑后减掉,留下的残差谱对应频率域的突变,逆变换后表现为时间轴上的尖峰。缓慢漂移会被平滑谱吸收,聚类异常和高维表格离群点更适合孤立森林等方法。

Q10. 关于时间序列异常检测的工程化落地,正确的做法包括?

多选题 · extreme

  • A.在线检测使用滚动窗口← 正确答案
  • B.采样率变化后沿用原参数
  • C.告警系统加入去重和聚合← 正确答案
  • D.强季节性指标先做差分← 正确答案

答案:ACD。在线场景必须用滚动窗口避免未来数据泄漏;告警去重聚合可以防止告警风暴;强季节指标先差分或周期消除,可以避免正常峰值被误判。采样率变化会改变数据尺度、周期结构,参数必须重新校准。