第2章:时间序列基础概念与数学表示

2.1 到底什么是时间序列

说白了,时间序列就是按时间顺序排列的一串数据。

我刚开始接触这个领域时,总觉得这定义太简单了。不就是 x₁, x₂, x₃, … , xₜ 吗?后来在项目中吃过亏,才明白这背后的门道有多深。

给你们一个我个人习惯的定义:时间序列是按等或不等时间间隔采集的、带有时间戳的观测值序列。注意这几个关键点:

  • 有序性:顺序不能乱,乱了就全完蛋
  • 时间依赖:相邻点的关系远比随机样本复杂
  • 潜在模式:趋势、周期、波动,都是有迹可循的

你想想看,我们做Secrets异常检测,本质就是在这条时间线上找出那些“不对劲”的时刻。

时间序列基础概念与数学表示知识体系 时间序列基础概念与数学表示 时间序列定义 趋势 (Trend) 季节性 (Seasonality) 噪声 (Noise) 平稳性 (Stationarity) 均值/方差是否随时间变化 自相关性 (ACF/PACF) 当前值 vs 历史值 零均值化 (Zero-Mean) 去趋势/去季节性 ADF检验/单位根检验 判断平稳性 差分操作 (Differencing) 消除趋势/季节性 ACF/PACF图 识别模型阶数 Secrets异常检测应用 核心:从成分到特征,再到检测方法

2.2 时间序列的三大核心成分

任何时间序列都可以拆解成三部分。我在做Secrets访问日志分析时,发现这个拆解思路特别管用。

成分 含义 Secrets场景举例
趋势 (Trend) 长期上升或下降方向 泄露事件逐年递增
季节性 (Seasonality) 固定周期的重复模式 每天凌晨3点的定时密钥轮换
噪声 (Noise) 不可解释的随机波动 偶发的网络抖动导致的请求失败

我记得有次处理一个银行系统的Secrets调用数据,一开始把所有异常都揪出来报给安全团队,结果他们发现一半都是正常的定时轮换操作。这就是没做好季节性分解的坑。

2.3 数学表示:从离散到连续

严格来说,时间序列的数学定义是这样的:

X = {x₁, x₂, x₃, ..., xₜ, ...}

其中 xₜ 是在时间 t 的观测值。t 可以是离散的整数索引,也可以是连续的时间戳。

你会问:为什么要搞得这么数学?因为后续所有的异常检测算法,都是基于这个数学框架推出来的。

2.3.1 平稳性 (Stationarity)

这个概念我多讲几句。平稳性是时间序列分析的“压舱石”。

一个序列如果是严格平稳的,意味着它的统计特性不随时间改变。但现实中很难做到严格平稳,所以我们通常用弱平稳:

  • 均值恒定:E[xₜ] = μ,不依赖 t
  • 方差恒定:Var[xₜ] = σ²,不依赖 t
  • 协方差只与时滞 k 有关:Cov[xₜ, x_{t+k}] = γ(k)

我的经验之谈:在做Secrets异常检测时,90%的原始数据都是非平稳的。别慌,差分操作能帮你摆平。

2.3.2 自相关函数 (ACF)

自相关衡量的是序列与自身的延迟版本之间的相关性。数学表达就是:

ρ(k) = Cov[xₜ, x_{t+k}] / Var[xₜ]

ρ(k) 的取值范围是 [-1, 1]。值越接近1,说明相关性越强。

实际使用技巧:在我做过的项目中,ACF图是诊断异常模式最直观的工具。如果一个Secrets请求序列在滞后5阶时出现异常高峰,那很可能对应着某个定时任务。

2.3.3 偏自相关函数 (PACF)

PACF 剔除了中间变量的干扰,只保留“纯粹”的滞后相关性。举个例子:

  • ACF:xₜ 和 x_{t-2} 的相关性,可能通过 x_{t-1} 间接传递
  • PACF:xₜ 和 x_{t-2} 的相关性,排除了 x_{t-1} 的影响

这俩东西联手,基本就能判断出适合用什么模型来拟合你的时间序列。

2.4 常用数学变换

原始数据到手后,很少能直接用的。我整理了几个必须掌握的变换操作:

操作 公式 作用
一阶差分 yₜ = xₜ - x_{t-1} 消除线性趋势
季节性差分 yₜ = xₜ - x_{t-s} 消除周期为 s 的季节性
对数变换 yₜ = log(xₜ) 稳定方差,处理异方差
Box-Cox变换 yₜ = (xₜ^λ - 1)/λ 通用方差稳定法

曾经踩过的坑:我有一次对Secrets请求量做对数变换后,忘了检查变换后的序列是否真的变成平稳序列了。结果模型训练出来一塌糊涂,浪费了一周时间。记住:变换只是手段,平稳才是目的。

2.5 时间序列的 Python 基础操作

理论讲够了,来点实际的。这是我做时间序列分析时最常用的代码片段:

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

# 加载数据
df = pd.read_csv('secrets_access_log.csv', parse_dates=['timestamp'])
df.set_index('timestamp', inplace=True)

# 检查基本统计
print(df.describe())

# 检查是否平稳(ADF检验)
from statsmodels.tsa.stattools import adfuller

result = adfuller(df['request_count'])
print(f'ADF 统计量: {result[0]}')
print(f'p-value: {result[1]}')

# 如果 p-value > 0.05,说明非平稳,需要差分
df['diff_1'] = df['request_count'].diff().dropna()

# 画ACF和PACF
from statsmodels.graphics.tsaplots import plot_acf, plot_pacf

fig, axes = plt.subplots(1, 2, figsize=(12, 4))
plot_acf(df['request_count'].dropna(), lags=40, ax=axes[0])
plot_pacf(df['request_count'].dropna(), lags=40, ax=axes[1])
plt.show()

这段代码我几乎每个项目都会复用,改改列名和参数就行。

关于Secrets场景的一个建议:异常往往藏在季节性模式之外。在分解出趋势和季节成分后,留下的残差才是我们真正该关注的。别被表面上的正常节奏迷惑了。

嗯,到这里你应该对时间序列的基础概念和数学表示有了比较完整的认识。后面我们会在这些基础上,一步步构建异常检测能力。慢慢来,别急。