3. 时间序列基础:概念、平稳性、自相关性与季节性分解

做体量异常检测,说白了就是跟时间序列打交道。你监控的 CPU、QPS、错误率,本质上都是一串按时间顺序排列的数字。我见过不少团队一上来就上模型,结果连数据的基本性质都没搞清楚——平稳不平稳、有没有周期性、噪声多大,这些都不清楚,模型效果自然好不了。

这一章,我们把时间序列的几个核心概念讲透。你理解了这些,后面讲异常检测算法时,才能知道为什么选这个算法、不选那个算法。

3.1 时间序列是什么

时间序列,就是按时间顺序记录的一组观测值。比如你每 10 秒采集一次 CPU 使用率,攒一天就是 8640 个点。这些点之间不是独立的——今天的 CPU 使用率跟昨天的、甚至跟上一分钟的都有关联。这种关联性,恰恰是异常检测能成立的前提。

时间序列三要素:趋势(Trend)、季节性(Seasonality)、残差(Residual)。

任何一个时间序列,都可以拆成这三部分。趋势是长期走向,季节性是周期性波动,残差是随机噪声。异常,往往藏在残差里。

举个例子。我负责过一个电商平台的监控系统,双十一期间 QPS 曲线有明显的日周期和小时周期。如果不做季节性分解,直接拿原始值设阈值,白天正常流量会被误报成异常,凌晨的低谷又漏报。这就是为什么必须理解时间序列的结构。

3.2 平稳性——异常检测的基石

平稳性,是时间序列分析里绕不开的概念。说白了,一个平稳序列的统计性质(均值、方差、自协方差)不随时间变化。你想想看,如果均值一直在漂移,你怎么定义“正常”?

我习惯把平稳性分成两种:

类型 定义 举例
严平稳 联合分布不随时间平移而改变 白噪声序列
弱平稳 均值恒定、方差有限、自协方差只与时间间隔有关 大部分平稳的监控指标

实际工程中,我们几乎只关心弱平稳。为什么?因为严平稳的条件太苛刻,现实数据基本达不到。弱平稳已经足够支撑大多数异常检测算法。

怎么判断平稳性?

我常用的方法有两个:

  • 肉眼观察:画个时序图,看均值是否水平、波动幅度是否一致。
  • ADF 检验:Augmented Dickey-Fuller 检验,p 值小于 0.05 就认为平稳。

如果数据不平稳怎么办?差分。一阶差分不行就二阶差分。我在处理一个数据库连接数指标时,原始序列有明显的上升趋势,一阶差分后立刻平稳了。差分后的序列再做异常检测,效果立竿见影。

注意:差分会丢失原始尺度信息。如果你需要还原到原始值做告警,记得做逆差分。我曾经踩过这个坑——差分后检测出异常,但告警阈值设在了差分值上,运维同事完全看不懂。

3.3 自相关性——时间序列的灵魂

自相关性,就是序列跟它自己过去值的相关性。今天的 CPU 使用率跟昨天的有关系吗?跟一小时前的呢?这就是自相关要回答的问题。

我习惯用两个工具看自相关:

  • ACF(自相关函数):衡量当前值与滞后 k 期的相关性。
  • PACF(偏自相关函数):剔除中间滞后项的影响后,当前值与滞后 k 期的相关性。

举个例子。你有一个小时级的 QPS 数据,ACF 图显示 lag=1 时相关性高达 0.8,lag=2 时降到 0.5,之后逐渐衰减。这说明什么?说明当前 QPS 跟上一小时高度相关,跟两小时前也有关系,但更早的影响就弱了。

自相关性对异常检测有什么意义?

  • 如果序列自相关强,说明正常行为有“惯性”。突然打破这种惯性的点,很可能是异常。
  • 如果序列自相关弱(比如白噪声),那每个点都独立,异常检测就简单了——超过 3 倍标准差就是异常。

避坑指南:我曾经处理过一个网络延迟指标,ACF 图显示 lag=24 时有个明显的尖峰。一开始没在意,后来才发现是日周期导致的。如果不做季节性分解,这个自相关结构会误导模型,让它以为 24 小时前的值对当前有直接影响。

3.4 季节性分解——把异常从周期中剥离

季节性分解,就是把时间序列拆成趋势、季节性和残差三部分。异常检测主要看残差——因为趋势和季节性都是可预测的,只有残差里的突变才是真正的异常。

最常用的分解模型有两种:

模型 适用场景 特点
加法模型 季节性波动幅度不随趋势变化 Y = Trend + Seasonal + Residual
乘法模型 季节性波动幅度随趋势变化 Y = Trend × Seasonal × Residual

怎么选?看数据。如果业务高峰期(比如白天)的波动幅度明显比低谷期大,用乘法模型。如果波动幅度一直差不多,用加法模型。我处理过一个电商网站的 UV 数据,白天 UV 高时波动也大,凌晨 UV 低时波动也小,明显是乘法模型。

Python 里做季节性分解很简单,用 statsmodels 的 seasonal_decompose 就行:

import statsmodels.api as sm

# 假设 data 是 pandas Series,索引为时间
result = sm.tsa.seasonal_decompose(data, model='additive', period=24)

# 提取三个分量
trend = result.trend
seasonal = result.seasonal
residual = result.resid

# 异常检测:残差超过 3 倍标准差
threshold = 3 * residual.std()
anomalies = residual[abs(residual) > threshold]

参数 period 怎么设?

period 就是周期长度。小时级数据日周期设 24,周周期设 168。我建议先画个时序图,肉眼观察周期,再用 ACF 图验证——ACF 在 lag=period 处有尖峰,就说明周期设对了。

3.5 知识体系总览

下面这张图,把本章的核心知识点串起来了。你可以把它当作一个思维导图,后面讲到具体算法时,随时回来对照。

时间序列基础——知识体系 时间序列分析 基本概念 趋势:长期走向 季节性:周期波动 残差:随机噪声 平稳性 严平稳 vs 弱平稳 ADF 检验 差分处理 自相关性 ACF:自相关函数 PACF:偏自相关函数 滞后阶数选择 季节性分解 加法模型 vs 乘法模型 seasonal_decompose 残差异常检测 异常检测:残差中的突变点

3.6 本章小结

时间序列分析不是理论游戏,它是异常检测的根基。你理解了平稳性,就知道什么时候该差分;你理解了自相关性,就知道为什么有些点“正常但看起来异常”;你理解了季节性分解,就能把真正的异常从周期波动中剥离出来。

我个人习惯,每次接手一个新的监控系统,第一件事就是跑一遍季节性分解,看看残差的分布。这一步花不了 5 分钟,但能帮你省下后面调参的无数时间。嗯,这就是经验。