时间序列基础
时间序列分析,说白了就是跟带时间戳的数据打交道。我这几年处理过的异常监控场景,不管是服务器指标还是业务流量,几乎全是时间序列。你想想看,一个系统跑得好好的,突然某个时间点指标变了——那这个"变"是怎么来的?是趋势本身在涨,还是周期波动,还是真的出了故障?
不把时间序列拆明白了,后面做异常检测就是盲人摸象。所以这一章,咱们把地基打牢。
什么是时间序列?
时间序列,就是按时间顺序采集的一系列观测值。比如每分钟的CPU使用率、每小时的订单量,都是典型的时间序列。关键特征:数据点之间有顺序依赖,前后的值不是独立抽签,而是有关联的。
我曾经接手过一个数据库连接池的监控,发现连接数每隔几小时就莫名飙升。光看原始折线图,根本分不清是业务高峰还是泄漏。这就是典型的——不拆解,你就被表象骗了。
时间序列的四个成分
一个标准的时间序列,可以分解成四部分:
| 成分 | 含义 | 例子 |
|---|---|---|
| 趋势 (Trend) | 长期上升或下降的走向 | 用户量随产品推广逐月增长 |
| 季节性 (Seasonal) | 固定周期(如天、周、月)的重复波动 | 每天凌晨低峰,白天高峰 |
| 周期 (Cyclic) | 非固定长度的起伏,通常跟经济或业务周期有关 | 每年双十一前后的流量波动 |
| 残差 (Residual / Noise) | 除去趋势、季节、周期后剩下的随机波动 | 网络抖动、临时任务调度引发的毛刺 |
我习惯把残差叫作"真正的异常信号"。为什么?因为趋势和季节是"可预料的",真正值得关注的,恰恰是那个"说不清楚"的残差部分。
做异常检测之前,先做一次时间序列分解。把趋势和季节剥离掉,剩下的残差做阈值检测,误报率能降低一半以上。
加法模型 vs 乘法模型
分解的时候有两种模型:
- 加法模型: 序列 = 趋势 + 季节 + 残差。适合波动幅度不随水平变化的数据(比如CPU温度)。
- 乘法模型: 序列 = 趋势 × 季节 × 残差。适合波动幅度随水平变化的数据(比如电商销售额,旺季波动大)。
我记得有一次帮朋友分析App日活,用了加法模型,结果残差里总带着明显的季节性尾巴。换成乘法模型后,残差就干净了。选择错了,后面全白费。
平稳性 —— 时间序列分析的"入场券"
很多时间序列模型(尤其是ARIMA)要求数据必须是平稳的。什么是平稳?简单说:统计性质(均值、方差、自协方差)不随时间变化。
你把平稳性想象成一个"脾气稳定"的人——他今天的性格跟去年差不多,不会突然炸毛。如果序列不平稳,模型预测就像给一个情绪不稳定的人算命,根本不准。
如何判断平稳性?
我一般用两个方法:
- 肉眼观察: 画个图,看均值是否大致水平,波动幅度是否一致。
- 统计检验: 最常用的是 ADF检验 (Augmented Dickey-Fuller Test)。
ADF检验的原假设H0是"序列存在单位根,即非平稳"。p值小于0.05,就拒绝H0,认为序列平稳。反之,p值大于0.05,序列不平稳。
千万别只看图!有些序列均值稳定但方差在变,图上是看不太出来的。一定用ADF检验确认。我早年踩过这个坑,调了好几天的模型都不收敛,最后发现是序列方差非平稳。
差分 —— 让不平稳变平稳的"手术刀"
如果序列不平稳,最常用的方法就是差分。一阶差分:x_t - x_(t-1)。差一次不行就两次,直到平稳。
# Python示例:ADF检验 + 差分
from statsmodels.tsa.stattools import adfuller
import numpy as np
# 原始序列
series = [100, 102, 101, 105, 103, ...] # 实际数据
result = adfuller(series)
print('ADF Statistic:', result[0])
print('p-value:', result[1])
if result[1] < 0.05:
print("序列平稳,可以建模")
else:
# 一阶差分
diff1 = np.diff(series)
result2 = adfuller(diff1)
if result2[1] < 0.05:
print("一阶差分后平稳,d=1")
else:
print("需要二阶差分或更复杂的处理")
差分虽然能让序列变平稳,但会损失一部分信息(趋势信息被抹掉了)。所以差分次数要控制在必要的最小值。我见过有人对着一个本来平稳的序列差分了三次,结果模型预测完全失真。
时间序列分解的实践案例
让我给你看一个真实的例子。这是某个Web服务的响应延迟数据(每分钟采样)。我们来做分解:
# 使用 statsmodels 的 seasonal_decompose
from statsmodels.tsa.seasonal import seasonal_decompose
import matplotlib.pyplot as plt
# 假设 data 是Pandas Series,时间索引
decomp = seasonal_decompose(data, model='additive', period=1440) # 周期设为一天(1440分钟)
decomp.plot()
plt.show()
# 提取残差
residual = decomp.resid.dropna()
# 残差就是异常检测的原材料
我在生产环境里,经常把残差画出来,加个3σ或者动态阈值。一旦残差超过阈值,就直接触发告警。这种方法在监控CPU使用率和内存占用时特别管用。
本章核心知识图谱
下面这张图我把时间序列分析的核心逻辑串起来了,你做异常方案设计的时候可以照着这个思路走:
整个流程很清晰:拿到原始序列,先跑ADF检验。平稳了,就拆成趋势、季节、残差,再针对残差做检测。不平稳,就先差分再回去检验。别看不起这个流程——我99%的线上异常检测方案都是这条路跑通的。
总结几个要记住的点
- 时间序列分解是异常检测的前置步骤,不是可选项。
- 加性 vs 乘性模型,要根据数据的波动特性选,试一下就知道了。
- 平稳性检验别省,ADF p值就是你的"绿卡"。
- 差分次数越少越好,d=1够用就别d=2。
- 残差才是异常检测的真正战场,把趋势和季节剥离干净,阈值才好设。
后面你写检测算法的时候,一定先走一遍分解+平稳性检验。磨刀不误砍柴工,这一步省了,后面的工作可能全是徒劳。