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 知识体系总览
下面这张图,把本章的核心知识点串起来了。你可以把它当作一个思维导图,后面讲到具体算法时,随时回来对照。
3.6 本章小结
时间序列分析不是理论游戏,它是异常检测的根基。你理解了平稳性,就知道什么时候该差分;你理解了自相关性,就知道为什么有些点“正常但看起来异常”;你理解了季节性分解,就能把真正的异常从周期波动中剥离出来。
我个人习惯,每次接手一个新的监控系统,第一件事就是跑一遍季节性分解,看看残差的分布。这一步花不了 5 分钟,但能帮你省下后面调参的无数时间。嗯,这就是经验。