2026/9/18 15:56:13

时间序列预测全攻略:从ARIMA到LSTM的实战指南

时间序列预测全攻略:从ARIMA到LSTM的实战指南 做预测类项目时间长了你会发现一个规律不管业务方是想要预测下个月的销量还是估算设备还能跑多久甚至要评估GNSS监测站的位移趋势最后都会归结到同一个技术动作——把一串带时间戳的历史数据吃透再用统计模型或神经网络的方式把未来的值算出来。这套动作学名就叫时间序列分析与预测技术。在AI全景的知识体系里这一节处在非常微妙的位置它既要用到前面的特征工程和传统统计方法又要用到后面才展开的深度神经网络是一条连接经典算法与现代AI的枢纽线。这篇文章我就按自己实际做项目的顺序来讲先拆数据再选模型最后落地评估中间穿插一些踩坑经验希望对正在啃这一章内容的你有帮助。需要说明的是这里不会只讲“调库预测”的层面而是把每一步背后的判断逻辑也讲清楚。时间序列项目最大的坑往往不是模型选得不够高级而是数据没看透、指标没定对、评估过程泄露了未来信息。这些问题如果不提前规避后面所有结果都是自欺欺人。1. 知识定位与技术全景时间序列预测为什么值得单开一章1.1 为什么几乎所有领域都绕不开时间序列在真实业务里能拿到的数据大概率是带时间顺序的电商后台的每日订单量、服务器每分钟的CPU使用率、工厂传感器的振动波形、电网的负荷曲线、金融产品的日收益率、GNSS测站每天输出的坐标序列……这些数据的共同点是相邻时间点的值彼此相关未来会以某种方式延续历史的规律。把这种“延续性”识别出来并加以利用就是时间序列分析要做的事。而在AI全景的知识体系里时间序列这个能力模块是典型的“承上启下”。承上是因为它大量用到回归、特征工程、模型评估这些基础技术启下是因为预测结果最终会流入业务决策——库存备多少货、资源怎么调度、设备要不要停机检修。换句话说它是把“计算”变成“判断”的关键一环。如果你将来做AI产品经理、算法工程师或者数据分析师几乎避不开这个方向。时序任务本身也不只是“预测未来”。异常检测突然出现的峰值或跌落、缺失值填补传感器短暂离线、归因分析哪个因素导致了指标波动都属于时间序列分析的范畴。第八章这一节之所以值得单独拿出来是因为它把“时间维度”这个额外结构放进了建模框架你不能再用普通回归的思路随手处理了。1.2 三条技术路线并行先定基线再上一层楼接触到时间序列问题时你可能会看到三类做法它们不是替代关系更多是适用条件不同技术路线代表方法适用条件主要优势主要限制经典统计ARIMA、SARIMA、ETS、Prophet单变量或少量外生变量样本量几百到几千可解释性强、训练快、对小样本友好非线性、多变量耦合建模困难机器学习LightGBM、XGBoost配合滞后特征有多维特征、数据量较大、需要自动特征交互能吸纳外部变量、效果稳定滞后特征构造需要领域经验时间顺序必须严格处理深度学习LSTM、GRU、TCN、Transformer数据量大、序列长、模式复杂能学习非线性依赖和长距离关联训练成本高、小样本容易过拟合、可解释性差我个人的经验是拿到任何时间序列任务先别急着调深度学习框架。很多业务场景的样本量就几百条统计模型或树模型都能打出不错的底子。先用“最便宜的办法”跑出一个可上线的基线再去判断是否有必要上更复杂的模型。后面第五章我会用一个具体案例演示这条路径怎么走。2. 动手前先拆数据趋势、季节与平稳性检验2.1 数据形态拆解趋势、季节、周期、噪声与异常点许多新手拿到时间序列就开始套模型这是最要命的做法。时间序列分析的第一步永远是“先看懂数据长什么样”。一个序列通常由四部分叠加而成假设你开了一家餐饮店记录每天的营业额。你会发现营业额里至少包含四种成分第一整体走高因为店铺知名度在提升这叫趋势第二周末比工作日高这叫季节性第三偶尔有大额团购订单这叫事件或异常点第四还有一些说不清原因的随机波动这叫噪声。在代码里可以用统计库快速把序列拆开。以statsmodels为例import statsmodels.api as sm # series 是 pandas Series索引为时间 stl sm.tsa.STL(series, period12).fit() trend stl.trend seasonal stl.seasonal resid stl.resid拆开之后很多信息一眼就能看出来序列是否有明显的上升或下降趋势季节性是否在不同年份保持稳定残差里是否还有周期性结构。这里有一个容易被忽略的细节如果序列的波动幅度随着整体水平上升而变大通常意味着乘法效应比如营业额涨到1倍波动也放大到1倍。这时候只做加法分解可能不准更好的是先对数据取对数把乘法关系转成加法关系再做STL分解。2.2 平稳性检验与差分处理ADF检验的实操解读“平稳性”是时间序列里绕不开的概念但也是最容易被一句话带过的概念。简单说平稳序列指的是它的均值和方差不会随时间发生系统性变化。比如一条水平线上的随机波动就是平稳的而一条持续上涨的曲线均值一直在变就是不平稳的。为什么不平稳不能直接建模因为绝大多数统计模型都假定“历史规律在未来仍然成立”。如果均值一直在变模型学到的“平均规律”落到某个具体时间点上就会严重失真。实际操作中我们通常用ADF检验Augmented Dickey-Fuller test来判断from statsmodels.tsa.stattools import adfuller p_value adfuller(series)[1] print(p_value) # 小于 0.05 通常认为平稳如果用原始数据检验不平稳最常见的手段是差分用今天的值减去昨天的值得到“增量序列”。很多金融价格序列本身不平稳但收益率一阶差分就平稳了。差分一次不够就差分两次但这里要拉住自己差分次数一般不要超过2。过差分会把有效的长期信息也差分掉导致模型拟合噪声预测反而变差。你可以把差分想象成给数据做“去趋势手术”手术能治病但做多了会伤元气。2.3 自相关图ACF与偏自相关图PACF传统定阶的直觉来源在看完了趋势和季节之后再进一步观察序列内部的自相关结构。自相关函数ACF衡量的是相距k个时间点的两个值之间的相关性偏自相关函数PACF衡量的是剔除了中间变量影响后相距k个时间点的两个值的直接相关性。这两个图是传统ARIMA定阶的重要工具。经验法则大致是如果PACF在阶数p之后突然截尾ACF呈拖尾状那么可以考虑AR(p)过程如果ACF在阶数q之后截尾PACF拖尾那么可以考虑MA(q)过程。不过说实话靠肉眼看图定阶需要比较丰富的经验新手看几轮很容易看岔。我的建议是这类图作为“理解数据”的辅助工具是很好的但最终定参交给auto_arima这类自动搜索工具更稳。图的价值更多在于帮你确认序列里是否还存在未被提取的季节成分或者是否存在过长周期的依赖这些信息往往是自动搜索工具不容易把握的。3. 传统统计模型选型实战ARIMA、SARIMA到Prophet3.1 ARIMA三个参数真的没那么玄ARIMA可能是时间序列里知名度最高的模型全称是自回归积分滑动平均模型。很多教程上来就甩公式搞得很多人望而生畏。其实思路很简单它假设当前时刻的值可以由最近若干个历史值加上最近若干个预测误差组合出来。三个参数的含义分别是p是自回归阶数表示用最近多少个历史值来预测当前值d是差分次数表示做了几次差分才让序列平稳q是移动平均阶数表示用最近多少个预测误差来修正当前预测。用“今天的气温”来打比方如果只看昨天和前天的温度来推测今天p就是2如果天气变化还有某种惯性误差再用过去几天的预报误差做修正q就是对应的阶数。手动定参确实麻烦实践中我更推荐用pmdarima库的auto_arima做自动化搜索from pmdarima import auto_arima model auto_arima( train, seasonalFalse, traceTrue, stepwiseTrue, suppress_warningsTrue ) print(model.summary()) forecast model.predict(12)这里要说一个我踩过的坑auto_arima在小数据上很快但数据量大、阶数范围设置宽的时候会很慢。一开始用stepwiseTrue做贪婪搜索先得到一个结果后面再逐步放宽范围。别一上来就搞全网格搜索训练时间会非常感人。3.2 有季节效应时再加一层SARIMA/SARIMAX现实业务中大部分序列都有周期性比如商场客流有周末周期电费有月度周期旅游数据有年度周期。这时候只做普通ARIMA是不够的需要加上季节项变成SARIMA。在参数上除了原有的p、d、q还需要季节部分的P、D、Q以及周期长度m。以经典的航空公司旅客数据为例月度数据有明显年度周期m12。我的经验是一旦发现序列中存在明显的周期性直接考虑SARIMA不要先跑普通ARIMA再回头补救。季节性差分的取值D也尽量取1不要贪多。更实用的是SARIMAX它允许额外加入外生变量比如节假日标记、天气数据、促销活动变量。这在业务场景里非常常见。举个例子预测奶茶店销量不能只看历史销量还要知道某天是否下雨、是否在商圈搞活动。代码上就是多传一个exog参数from statsmodels.tsa.statespace.sarimax import SARIMAX model SARIMAX( train, exogtrain_exog, order(1, 1, 1), seasonal_order(0, 1, 1, 12) ) result model.fit(dispFalse) forecast result.forecast(steps12, exogtest_exog)这里有个非常容易踩的坑SARIMAX预测未来时必须提供未来各期的外生变量真实值或估计值。很多人在训练时传了外生变量预测时却忘记传直接报错或者得到奇葩结果。在业务落地中未来外生变量怎么来是一个要提前确认的问题。3.3 Prophet适合“只有时间戳和数值”的快速基线Meta开源的Prophet也是一个不可忽视的工具。它把时间序列分解为趋势、季节性和节假日效应并对趋势突变点有自适应能力。它的最大优势是使用门槛极低只需要两列数据一列是时间ds一列是数值y不需要用户做太多特征工程对缺失值和异常值也相当容忍。from prophet import Prophet model Prophet() model.fit(df[[ds, y]]) future model.make_future_dataframe(periods12, freqMS) forecast model.predict(future)我通常把Prophet当作“业务方只给一个Excel表没有时间做复杂特征工程”时的首选基线。它对日粒度、周粒度数据非常好用而且画出来的置信区间很直观方便向非技术同事解释。但要注意Prophet不太适合高频数据比如秒级、分钟级吞吐量也一般如果序列存在多变量强耦合它很难表达清楚。另外趋势突变点参数如果调得太过拟合曲线会跟着噪声乱扭看着好看预测一塌糊涂。4. 深度学习时序模型选型LSTM、TCN与Transformer怎么挑4.1 从LSTM入门记忆机制如何解决长期依赖当序列变长、变量变多、模式非线性时传统统计模型就容易力不从心这时候轮到深度学习登场。而在深度学习时序模型里最经典且最值得先学的是LSTM。LSTM长短期记忆网络的特别之处在于引入了“门控机制”遗忘门决定之前的记忆保留多少输入门决定新信息有多少写入记忆输出门决定当前时刻输出什么。可以把它想象成一条工厂传送带上面流动着历史信息而每个LSTM单元是传送带旁的工人工人判断哪个历史信息已经没用、该丢弃哪个信息重要、要留下最后根据留下的信息给出当前输出。这样一搞模型就有能力记住几十个时间步之前的有效信息而不像普通RNN那样一遇到稍长的序列就“失忆”。在动手写代码之前我建议你先有一个认知LSTM并不是“更高级所以一定更好”。它在样本量小、信号简单的任务上经常打不过调好参的SARIMA。深度学习真正能发挥优势的场景是多维输入、变量之间关系复杂、数据量达到几千条以上、序列中存在长距离依赖。如果样本只有一两百条老老实实跑统计模型更划算。4.2 滑动窗口与数据切分深度学习预测的关键前置步骤用LSTM做时间序列预测第一步是把原始序列构造成“监督学习”样本也就是滑窗。一个长度为window的历史窗口作为输入窗口后面的值作为标签。比如用过去30天的销量预测第31天的销量那么每个样本就是一组“30天输入1天标签”。窗口大小怎么选是新手最容易纠结的问题。没有绝对标准但有一条经验法则窗口至少覆盖一个完整周期如果数据以周为周期窗口至少7以年为周期但只有日数据窗口也至少365起步就比较难搞了。第一种做法是先用ACF图看自相关在多少阶后衰减为0窗口取那个范围附近第二种做法是直接把窗口设成周期长度的2~3倍比如月度周期数据取window24。实际项目中我通常会试几个候选值用验证集决定别拍脑袋。数据切分上有一点必须强调时序数据的训练集、验证集、测试集必须按时间顺序切不能像普通分类任务那样随机打乱。否则就相当于让模型“偷看未来”评估结果会严重虚高。代码层面滑动窗口的造数逻辑大概是import numpy as np from torch.utils.data import Dataset def create_sequences(data, window24): X, y [], [] for i in range(len(data) - window): X.append(data[i:i window]) y.append(data[i window]) return np.array(X), np.array(y) class TimeSeriesDataset(Dataset): def __init__(self, X, y): self.X X self.y y def __len__(self): return len(self.X) def __getitem__(self, idx): return self.X[idx], self.y[idx]有关归一化我再啰嗦一句。深度学习模型几乎都需要把数据缩放到0~1或-1~1之间但缩放器只能用训练集去fit再应用到训练集、验证集和测试集上。要是对整个序列先做了fit测试集的统计信息就跑进了训练流程这在评估上属于作弊。4.3 TCN与Transformer什么情况下值得升级LSTM虽然是入门首选但它有两个让人头疼的问题训练无法并行序列一长就慢标准RNN结构对特别长的依赖关系仍然不够稳。于是有了两类重要替代。TCN时间卷积网络使用因果卷积和空洞卷积。因果卷积保证了在预测t时刻时只用t及之前的信息不会看到未来空洞卷积则通过不断增大的间隔扩大感受野让模型能用更少的层数看到更远的过去。TCN的优势是训练可以并行速度快在某些长序列任务上效果往往不比LSTM差甚至更好。Transformer系列则是目前大模型时代的主流选择。它用自注意力机制直接建模序列中任意两个时间点之间的关系让长距离依赖不再是问题。但这里要泼一盆冷水标准Transformer是为自然语言设计的直接搬到时间序列上不一定有优势。时序领域为此发展出Informer、Autoformer、PatchTST等变体专门处理长序列预测。我的建议很直白如果你的数据只有几千条别一上来就上Transformer很容易过拟合先跑LSTM或TCN效果不足以支撑业务时再来考虑这些重型武器。5. 完整实操流程从旅客数据到ARIMA与LSTM对比预测5.1 数据集选择与评估指标设定为了演示一整套流程我选了经典的国际航班旅客数据。这份数据几乎出现在所有时间序列教材里特点是只有144个月度观测值但包含明显的上升趋势和年周期非常适合用来做教学对比。数据规模小跑起来很块也便于观察从统计模型到深度学习的性能差异。评估指标方面我在这类项目里最常用两个RMSE和MAPE。RMSE就是均方根误差它会放大大的误差适合关注“严重偏差”的业务MAPE是平均绝对百分比误差直接给出了误差占真实值的百分比非技术背景的同事也容易理解。不过要注意MAPE对真实值为0或接近0的数据非常敏感如果序列里存在低频零值最好改用SMAPE或MASE。流程上我会把数据按时间切分前132个月做训练最后12个月做测试模拟“用过去数据预测未来一年”的真实场景。这里有个细节验证集不是必须的但如果你要调超参数就要从训练集尾部再切一段出来不能把测试集用于调参。5.2 统计基线用auto_arima快速定参并拟合SARIMA先跑统计基线。这份数据有明显年周期所以直接上SARIMA用auto_arima搜索参数from pmdarima import auto_arima train data[:132] test data[132:] model auto_arima( train, seasonalTrue, m12, traceTrue, stepwiseTrue, suppress_warningsTrue ) print(model.summary()) forecast model.predict(n_periods12)跑出来的模型通常接近SARIMA阶数不大比较简洁。把这个简单模型的预测画成折线图放在真实值旁边我自己的经验是最后12个月的MAPE往往能落在3%到6%之间。这个结果可能超出很多人的预期一个一百多年的老模型在这样的小样本数据上目前大多数深度学习模型都不容易稳定地超越它。所以如果你做时序预测第一件事永远是先跑统计基线它既是精度参考也是复杂度参考。5.3 用PyTorch训练一个可用的LSTM预测模型接下来上LSTM。这里我给出一个相对精简但完整的实现重点在于把框架搭对后面替换成GRU、TCN都只是改模型类而已。import torch import torch.nn as nn from torch.utils.data import DataLoader, TensorDataset from sklearn.preprocessing import MinMaxScaler # 1. 归一化scaler 只能用训练集 fit scaler MinMaxScaler() train_scaled scaler.fit_transform(train.reshape(-1, 1)).flatten() all_scaled scaler.transform(data.reshape(-1, 1)).flatten() # 2. 构造滑窗样本 def create_sequences(data, window24): X, y [], [] for i in range(len(data) - window): X.append(data[i:i window]) y.append(data[i window]) return np.array(X), np.array(y) window 24 X_all, y_all create_sequences(all_scaled, window) split len(X_all) - 12 # 保留最后12个作为测试 X_train, y_train X_all[:split], y_all[:split] X_test, y_test X_all[split:], y_all[split:] train_dataset TensorDataset( torch.tensor(X_train, dtypetorch.float32), torch.tensor(y_train, dtypetorch.float32) ) train_loader DataLoader(train_dataset, batch_size16, shuffleTrue) # 3. 定义 LSTM 模型 class LSTMPredictor(nn.Module): def __init__(self, input_size1, hidden32, num_layers1): super().__init__() self.lstm nn.LSTM(input_size, hidden, num_layers, batch_firstTrue) self.fc nn.Linear(hidden, 1) def forward(self, x): out, _ self.lstm(x) return self.fc(out[:, -1, :]) model LSTMPredictor() optimizer torch.optim.Adam(model.parameters(), lr0.001) loss_fn nn.MSELoss() # 4. 训练 for epoch in range(100): for xb, yb in train_loader: pred model(xb.unsqueeze(-1)) loss loss_fn(pred, yb.unsqueeze(-1)) optimizer.zero_grad() loss.backward() optimizer.step() # 5. 递归多步预测 def recursive_predict(model, history, steps, window24): model.eval() preds [] history list(history) with torch.no_grad(): for _ in range(steps): x torch.tensor(history[-window:]).float().view(1, window, 1) y model(x).item() preds.append(y) history.append(y) return np.array(preds) pred_scaled recursive_predict(model, list(all_scaled[:split window]), steps12) pred scaler.inverse_transform(pred_scaled.reshape(-1, 1)).flatten()这段代码里有几个点想特别强调。第一归一化的scaler只在训练集上fit但转换时覆盖了全部数据这样才能保证测试输入处于模型见过的数值范围。第二window选择了24覆盖两个年度周期让模型有足够上下文感知季节位置。第三多步预测采用“递归预测”策略每预测出一步就把它拼到历史输入末尾再用这个包含了预测值的窗口去预测下一步。这种做法很直观缺点是误差会逐步累积预测步数越多越明显。我不建议在小样本上把LSTM训练轮数调得过多。100个epoch在这个数据集上通常已经足够再多就容易过拟合出现“训练集损失很低、测试集一塌糊涂”的情况。5.4 结果对比与场景化模型选择建议把两种方法的结果放在一起看基本是这种对比感觉模型训练耗时MAPE典型范围可解释性部署复杂度SARIMA秒级3%~6%高低LSTM小样本分钟级5%~10%低中这不是说深度学习不行而是说在“样本少、模式相对固定”的数据上统计模型确实更有优势。LSTM真正的用武之地在于输入变量不止一个、变量之间有复杂的非线性交互、样本量至少几千、长期依赖关系明显。在这些条件下LSTM往往能做出统计模型做不到的预测。所以我的建议是先跑基线再判断复杂度别为了用新模型而用新模型。6. 高频踩坑记录与排查速查表6.1 四个容易翻车的环节翻车点一归一化时偷看全局数据。这是最隐蔽也最严重的错误。很多人在处理时序数据时顺手对整个序列做了MinMaxScaler.fit_transform然后才切训练集和测试集。表面上看模型在训练集上跑得很好实际上测试集的统计信息已经被模型“偷看”到了上线后表现立刻现出原形。正确做法是像上面代码那样只对训练集调fit。翻车点二多步预测变成“复读机”。递归预测预测到后面结果往往趋于平滑甚至接近最近观测值的重复。这是因为误差累积后模型只能依赖最近的信息失去对远期变化的敏感度。对策一是检查预测步数是否合理二是考虑用seq2seq结构或直接多步输出三是主动在训练时加入噪声扰动增强模型稳定性。翻车点三预测曲线比真实曲线“晚了一拍”。这个问题在LSTM里尤其常见。表现是预测值曲线和真实值曲线形状一致但整体右移了一个时间点。本质原因是数据信噪比低模型发现最简单有效的策略就是“把上一个观测值复制下来”因此它并没有真正学到趋势变化。对策有对序列做差分或季节分解后再建模、增强趋势特征、降低模型复杂度、增加正则化。如果出现这种情况先别急着调参回过来看数据里是否真的有可预测的信号。翻车点四训练集和测试集被滑窗切出重叠。比如样本量只有100window取99那测试集从第100个点开始时它的输入窗口几乎覆盖了整个训练集。这不算严格的数据泄漏但会让测试结果虚高。切分时要确保测试输入窗口完全处于训练数据之后。6.2 排查速查表现象优先排查方向尝试手段训练集效果很好测试集很差归一化是否泄漏、是否过拟合重新校验scaler fit范围加正则、降模型复杂度多步预测后期全是直线误差累积、递归策略失效改用seq2seq或直接多步输出缩短预测步长预测曲线晚了一拍数据信噪比低、模型走捷径差分处理、季节分解、增强特征、调整学习率损失不下降学习率不合适、梯度爆炸降低学习率、加梯度裁剪、检查输入数据量纲测试结果波动极大模型随机种子未固定固定torch.manual_seed多次实验取均值6.3 领域延伸GNSS坐标时间序列预测的特别提醒最后想用一个我接触过的专业场景来收束GNSS测站坐标时间序列预测。这个名字听起来离AI很远实际上它是时间序列分析在测绘和地学里的典型应用。GNSS测站每天输出一组坐标值这些坐标序列里既有地壳运动带来的趋势项也有周年、半周年周期项还叠加了有色噪声甚至偶发仪器更换、地震引起的阶跃。如果直接拿一套LSTM去套不处理这些先验信息结果基本是不靠谱的。这个领域的做法通常是先用粗差探测和阶跃处理把序列清洗干净再估计噪声模型往往是白噪声加闪烁噪声的组合然后再用卡尔曼滤波或SARIMA/LSTM做短期位移预测。关键点在于这类场景最终输出的不能只是一个均值预测还要附带不确定度因为滑坡监测、工程形变预警这类决策依赖的是“预测值有多可信”而不是一个孤立的数字。这给我一个很深的体会时间序列技术的分析框架是通用的但每个行业都有自己必须尊重的数据约束和业务规则。学习模型不难难的是把领域知识编码进数据处理流程里。这也是为什么第八章第三节会放在AI全景的中后段——它不是在讲一个孤立算法而是在教你一套和真实世界打交道的完整方法论。我自己现在的固定流程是先把图画出来做一次STL分解跑一个ARIMA基线然后才考虑上不上LSTM。这套流程帮我在很多项目里省下了无谓的时间。最后再分享一个小技巧预测完之后一定要把残差序列画出来看。残差里不应该再有明显的周期性或者趋势如果有说明有信号没学干净这时候做模型融合或者加特征往往比换一个更贵的模型更有效。