做预测类项目时间长了,你会发现一个规律:不管业务方是想要预测下个月的销量,还是估算设备还能跑多久,甚至要评估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, period=12).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到Prophet
3.1 ARIMA三个参数真的没那么玄
ARIMA可能是时间序列里知名度最高的模型,全称是自回归积分滑动平均模型。很多教程上来就甩公式,搞得很多人望而生畏。其实思路很简单:它假设当前时刻的值,可以由最近若干个历史值加上最近若干个预测误差组合出来。
三个参数的含义分别是:p是自回归阶数,表示用最近多少个历史值来预测当前值;d是差分次数,表示做了几次差分才让序列平稳;q是移动平均阶数,表示用最近多少个预测误差来修正当前预测。用“今天的气温”来打比方:如果只看昨天和前天的温度来推测今天,p就是2;如果天气变化还有某种惯性误差,再用过去几天的预报误差做修正,q就是对应的阶数。
手动定参确实麻烦,实践中我更推荐用pmdarima库的auto_arima做自动化搜索:
from pmdarima import auto_arima model = auto_arima( train, seasonal=False, trace=True, stepwise=True, suppress_warnings=True ) print(model.summary()) forecast = model.predict(12)这里要说一个我踩过的坑:auto_arima在小数据上很快,但数据量大、阶数范围设置宽的时候会很慢。一开始用stepwise=True做贪婪搜索,先得到一个结果,后面再逐步放宽范围。别一上来就搞全网格搜索,训练时间会非常感人。
3.2 有季节效应时再加一层:SARIMA/SARIMAX
现实业务中,大部分序列都有周期性,比如商场客流有周末周期,电费有月度周期,旅游数据有年度周期。这时候只做普通ARIMA是不够的,需要加上季节项,变成SARIMA。在参数上,除了原有的p、d、q,还需要季节部分的P、D、Q,以及周期长度m。
以经典的航空公司旅客数据为例,月度数据有明显年度周期,m=12。我的经验是,一旦发现序列中存在明显的周期性,直接考虑SARIMA,不要先跑普通ARIMA再回头补救。季节性差分的取值D也尽量取1,不要贪多。
更实用的是SARIMAX,它允许额外加入外生变量,比如节假日标记、天气数据、促销活动变量。这在业务场景里非常常见。举个例子:预测奶茶店销量,不能只看历史销量,还要知道某天是否下雨、是否在商圈搞活动。代码上就是多传一个exog参数:
from statsmodels.tsa.statespace.sarimax import SARIMAX model = SARIMAX( train, exog=train_exog, order=(1, 1, 1), seasonal_order=(0, 1, 1, 12) ) result = model.fit(disp=False) forecast = result.forecast(steps=12, exog=test_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(periods=12, freq='MS') 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倍,比如月度周期数据取window=24。实际项目中我通常会试几个候选值,用验证集决定,别拍脑袋。
数据切分上有一点必须强调:时序数据的训练集、验证集、测试集必须按时间顺序切,不能像普通分类任务那样随机打乱。否则就相当于让模型“偷看未来”,评估结果会严重虚高。代码层面,滑动窗口的造数逻辑大概是:
import numpy as np from torch.utils.data import Dataset def create_sequences(data, window=24): 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, seasonal=True, m=12, trace=True, stepwise=True, suppress_warnings=True ) print(model.summary()) forecast = model.predict(n_periods=12)跑出来的模型通常接近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, window=24): 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, dtype=torch.float32), torch.tensor(y_train, dtype=torch.float32) ) train_loader = DataLoader(train_dataset, batch_size=16, shuffle=True) # 3. 定义 LSTM 模型 class LSTMPredictor(nn.Module): def __init__(self, input_size=1, hidden=32, num_layers=1): super().__init__() self.lstm = nn.LSTM(input_size, hidden, num_layers, batch_first=True) 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(), lr=0.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, window=24): 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]), steps=12) 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里尤其常见。表现是预测值曲线和真实值曲线形状一致,但整体右移了一个时间点。本质原因是数据信噪比低,模型发现最简单有效的策略就是“把上一个观测值复制下来”,因此它并没有真正学到趋势变化。对策有:对序列做差分或季节分解后再建模、增强趋势特征、降低模型复杂度、增加正则化。如果出现这种情况,先别急着调参,回过来看数据里是否真的有可预测的信号。
翻车点四:训练集和测试集被滑窗切出重叠。比如样本量只有100,window取99,那测试集从第100个点开始时,它的输入窗口几乎覆盖了整个训练集。这不算严格的数据泄漏,但会让测试结果虚高。切分时要确保测试输入窗口完全处于训练数据之后。
6.2 排查速查表
| 现象 | 优先排查方向 | 尝试手段 |
|---|---|---|
| 训练集效果很好,测试集很差 | 归一化是否泄漏、是否过拟合 | 重新校验scaler fit范围;加正则、降模型复杂度 |
| 多步预测后期全是直线 | 误差累积、递归策略失效 | 改用seq2seq或直接多步输出;缩短预测步长 |
| 预测曲线晚了一拍 | 数据信噪比低、模型走捷径 | 差分处理、季节分解、增强特征、调整学习率 |
| 损失不下降 | 学习率不合适、梯度爆炸 | 降低学习率、加梯度裁剪、检查输入数据量纲 |
| 测试结果波动极大 | 模型随机种子未固定 | 固定torch.manual_seed,多次实验取均值 |
6.3 领域延伸:GNSS坐标时间序列预测的特别提醒
最后想用一个我接触过的专业场景来收束:GNSS测站坐标时间序列预测。这个名字听起来离AI很远,实际上它是时间序列分析在测绘和地学里的典型应用。GNSS测站每天输出一组坐标值,这些坐标序列里既有地壳运动带来的趋势项,也有周年、半周年周期项,还叠加了有色噪声,甚至偶发仪器更换、地震引起的阶跃。如果直接拿一套LSTM去套,不处理这些先验信息,结果基本是不靠谱的。
这个领域的做法通常是:先用粗差探测和阶跃处理把序列清洗干净,再估计噪声模型(往往是白噪声加闪烁噪声的组合),然后再用卡尔曼滤波或SARIMA/LSTM做短期位移预测。关键点在于,这类场景最终输出的不能只是一个均值预测,还要附带不确定度,因为滑坡监测、工程形变预警这类决策,依赖的是“预测值有多可信”,而不是一个孤立的数字。
这给我一个很深的体会:时间序列技术的分析框架是通用的,但每个行业都有自己必须尊重的数据约束和业务规则。学习模型不难,难的是把领域知识编码进数据处理流程里。这也是为什么第八章第三节会放在AI全景的中后段——它不是在讲一个孤立算法,而是在教你一套和真实世界打交道的完整方法论。
我自己现在的固定流程是:先把图画出来,做一次STL分解,跑一个ARIMA基线,然后才考虑上不上LSTM。这套流程帮我在很多项目里省下了无谓的时间。最后再分享一个小技巧:预测完之后,一定要把残差序列画出来看。残差里不应该再有明显的周期性或者趋势,如果有,说明有信号没学干净,这时候做模型融合或者加特征,往往比换一个更贵的模型更有效。