4. 时间序列基础:构成、平稳性与自相关

做异常检测,说白了就是跟时间序列打交道。你拿到的遥测数据,比如 CPU 使用率、磁盘 IO、网络延迟,本质上都是一串按时间顺序排列的数值。这串数值背后藏着什么规律?怎么把它拆开看明白?这一章我们就来聊聊时间序列的底子。

我个人习惯,拿到任何遥测数据,第一件事不是直接上模型,而是先做一次“拆解”。就像修表一样,你得先知道齿轮怎么转的,才能判断哪个零件坏了。

4.1 时间序列的构成:趋势、季节、残差

任何一个时间序列,都可以拆成三个部分:趋势、季节性、残差。用公式表达就是:

Y(t) = Trend(t) + Seasonal(t) + Residual(t)

或者在某些乘法模型里:

Y(t) = Trend(t) × Seasonal(t) × Residual(t)

我一般在遥测场景下用加法模型居多,因为指标波动幅度相对稳定。下面我们逐个拆开看。

趋势(Trend)

趋势是时间序列长期的变化方向。比如服务器 CPU 使用率随着业务增长,半年内从 30% 慢慢爬到 60%,这就是趋势。它反映的是系统整体的“老化”或“增长”。

季节性(Seasonal)

季节性是指固定周期内的重复模式。注意,这里的“季节”不一定是春夏秋冬。在运维场景里,最常见的周期是 24 小时(日周期)和 7 天(周周期)。

举个例子,我做过一个电商平台的监控。每天晚上 8 点到 10 点是流量高峰,凌晨 4 点到 6 点是低谷。这个规律每天重复,就是日季节性。周末的流量模式和工作日不一样,那就是周季节性。

残差(Residual)

残差是去掉趋势和季节性之后剩下的部分。它应该是随机波动的,没有明显规律。如果残差里还有结构性的模式,说明你的模型没拆干净,或者数据里有异常。

核心观点: 异常检测的本质,就是在残差里找“不寻常”的点。如果趋势和季节没拆干净,残差里就会混入正常波动,导致误报。

下面这张图展示了时间序列拆解的整体流程:

时间序列拆解流程 原始序列 Y(t) 趋势 Trend(t) 季节 Seasonal(t) 残差 Residual(t) 异常检测 → 残差中的离群点 说明: 拆解后,残差应近似白噪声。若残差出现明显模式或离群点,说明存在异常或模型未充分拟合。

4.2 平稳性概念

平稳性,是时间序列分析里绕不开的一个概念。很多模型(比如 ARIMA)都要求数据是平稳的。那什么是平稳?

简单说,平稳时间序列的统计性质不随时间变化。具体来说,均值、方差、自协方差都是常数。

你想想看,如果一段 CPU 数据,前半段均值 30%,后半段均值 80%,那它肯定不平稳。因为均值在变。

平稳性分两种:

类型 定义 要求 实际场景
严平稳 所有统计性质完全不变 极其严格,几乎不存在 理论概念,实际很少用
弱平稳 均值、方差为常数,自相关只与滞后有关 较宽松,实用 大多数运维指标经处理后可达

我们平时说的平稳,默认指弱平稳。怎么判断数据是否平稳?我常用的方法有两个:

  • 肉眼观察法:画个时序图,看均值是否上下漂移,方差是否忽大忽小。
  • 单位根检验:比如 ADF 检验(Augmented Dickey-Fuller Test)。p 值小于 0.05 就认为平稳。
我的经验: 遥测数据大多不平稳。比如磁盘使用率长期增长,网络流量有日周期。别慌,差分一次或两次,大部分都能变平稳。我曾经处理过一个数据库连接数指标,差分 3 次才平稳——这种情况就要考虑是不是周期没拆干净。

4.3 自相关与偏自相关

这两个概念,是理解时间序列“记忆性”的关键。说白了,就是看当前时刻的值,跟过去某个时刻的值有没有关系。

自相关函数(ACF)

ACF 衡量的是 Y(t) 和 Y(t-k) 之间的相关性,不考虑中间变量的影响。k 叫滞后阶数。

举个例子,今天的 CPU 使用率和昨天的 CPU 使用率相关吗?如果相关,ACF 在 lag=1 处就会显著非零。

但 ACF 有个问题:它会把间接相关也算进去。比如今天和前天相关,可能是因为今天和昨天相关,昨天和前天相关。这种“传递”效应,ACF 分不清。

偏自相关函数(PACF)

PACF 解决了这个问题。它衡量的是 Y(t) 和 Y(t-k) 之间的直接相关性,剔除了中间滞后项的影响。

我打个比方:ACF 就像问“你和你的祖父长得像吗?”——可能像,但中间隔了你父亲。PACF 则问“剔除你父亲的影响后,你和祖父还像吗?”

在模型选择时,ACF 和 PACF 的形态能帮你判断用 AR 还是 MA 模型:

模型类型 ACF 形态 PACF 形态
AR(p) 自回归 拖尾(逐渐衰减) p 阶后截尾(突然变零)
MA(q) 滑动平均 q 阶后截尾 拖尾
ARMA(p,q) 拖尾 拖尾
注意: 实际数据往往没这么理想。ACF 和 PACF 的“截尾”是统计意义上的,不是绝对归零。我建议结合信息准则(AIC/BIC)一起判断,别光看图。

下面这张图展示了 ACF 和 PACF 的典型形态对比:

ACF 与 PACF 典型形态(AR(1) 示例) ACF(拖尾) PACF(1阶截尾) 解读: ACF 缓慢衰减(拖尾),PACF 在 lag=1 后骤降至置信区间内(截尾)。 这种形态提示我们:数据适合用 AR(1) 模型。

4.4 在异常检测中的实际应用

讲完理论,我们回到异常检测这个主题。这些概念到底怎么用?

我总结了一个三步走的流程:

  1. 拆解:对原始序列做 STL 或 X13-ARIMA 分解,得到趋势、季节、残差。
  2. 平稳化:对残差做平稳性检验。如果不平稳,说明趋势或季节没拆干净,需要调整分解参数。
  3. 建模:用 ACF/PACF 判断残差的自相关结构,选择合适的模型(如 ARIMA)拟合正常行为,然后检测偏离。
避坑指南: 我曾经犯过一个错误——直接对原始数据做 ACF 分析,结果看到强烈的自相关,以为是 AR(24) 模型。后来才发现,那只是日周期没去掉。记住:先拆解,后分析,顺序不能乱。

最后,给你一个简单的 Python 代码片段,演示如何做时间序列拆解和 ACF 计算:

import numpy as np
import pandas as pd
from statsmodels.tsa.seasonal import STL
from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
from statsmodels.tsa.stattools import adfuller

# 模拟遥测数据:趋势 + 日周期 + 噪声
np.random.seed(42)
t = np.arange(0, 720)  # 30天,每小时一个点
trend = 0.01 * t
seasonal = 10 * np.sin(2 * np.pi * t / 24)
noise = np.random.normal(0, 2, len(t))
data = trend + seasonal + noise

# 1. 时间序列拆解
stl = STL(data, period=24)
result = stl.fit()
residual = result.resid

# 2. 平稳性检验(ADF)
adf_stat, p_value, _, _, critical_values, _ = adfuller(residual)
print(f"ADF p-value: {p_value:.4f}")
if p_value < 0.05:
    print("残差平稳,可以继续建模")
else:
    print("残差不平稳,需要调整分解参数")

# 3. 查看残差的 ACF/PACF
# plot_acf(residual, lags=48)
# plot_pacf(residual, lags=48)

嗯,这一章的内容就到这里。时间序列的构成、平稳性、自相关,这三块是后续所有模型的基础。你把这些概念吃透了,后面讲 ARIMA、季节性分解、异常检测阈值设定,都会顺很多。