基于Python的深度学习时间序列预测实战:LSTM/ARIMA/SVM对比与避坑指南
发布时间:2026/9/28 5:48:14来源:尧图网络
简介一套基于Python的深度学习时间序列预测研究与实现代码包面向希望入门或进阶时间序列分析的学习者也适合用于毕设、课程设计、大作业、工程实训或初期项目立项。压缩包共19个文件包含12个Python脚本、4个CSV数据集、2个Markdown说明文档及1个数据集目录整体仅177KB轻量易部署其中Python脚本按功能划分为数据加载、模型构建、预测与评估模块CSV文件提供实验原始数据Markdown文档辅助理解项目结构。项目围绕标普500指数与太阳黑子两个典型实验场景覆盖从数据预处理到模型训练、预测与误差评估的完整流程并提供了LSTM、ARIMA、SVM、ARMA等多种算法实现便于横向对比不同模型的时间序列预测效果配套的数据加载、预测与误差评估脚本让读者能直接运行并查看各环节输出。目前已有158人学习下载既能帮助快速上手时间序列预测的代码实践也可作为课程项目或初期立项的模块化基础方便继续扩展与二次开发。1. 时间序列预测实操这份基于 Python 的深度学习项目到底能让你学到什么如果你刷到过“用 LSTM 预测股票”或者“ARIMA 预测太阳黑子”之类的视频大概率会卡在同一个地方代码能跑通但不知道自己调的是什么参数数据是怎么处理的换一个数据集就完全懵。这份“基于 Python 的深度学习时间序列预测的研究与实现”项目源码包解决的问题就是这个——它不是你常见的那种只给你一个训练好的模型文件而是一个把完整流程拆开的毕设级代码包里面有 ARIMA、ARMA、SVM、LSTM 四类模型的独立实现配上标普 500SP500和太阳黑子两类特征差异极大的数据让你能从数据加载、差分处理、滑动窗口构造一路写到模型评估。适合正在做时间序列相关毕设、课程设计或大作业或者就是想搞懂“深度学习怎么用在时序预测上而不是只会跑 MNIST”的开发者。2. 先分清项目里的两条主线SP500 和太阳黑子的数据流与模型选型2.1 项目目录里到底有什么按功能拆开看拿到GraduationProject-code.zip之后解压出来的第一层目录是dataset、sp500、sunspot三个文件夹。dataset里是原始数据spv2.csv和sp2005-2015.csv是标普 500 的行情数据sunspot_ms.csv和sunspot_ms_diff.csv是太阳黑子月均数据及其差分版本。两个模型文件夹里各自带着 loader、predict、模型实现和 readme区别只在sp500/下是lstm.py arima.py svm.py model.py而sunspot/下是lstm.py arma.py svm.py model.py——太阳黑子用的是 ARMA 而不是 ARIMA原因是它没有明显的趋势项不需要差分。这个结构本身就是时间序列入门的最佳学习路径先拿有趋势、有波动、受外部因素影响的金融数据练手理解为什么单纯用 ARIMA 很难拟合再换成相对平稳、有周期性规律的太阳黑子数据体会 ARMA 和 LSTM 在不同数据特征下的表现差异。如果你只想跑通代码验证功能sp500/是更合适的第一站如果你想理解“平稳性”这个概念的实战意义先去sunspot/看差分前后的数据对比会更直观。2.2 数据加载与预处理loader.py 里的隐藏逻辑所有模型的起点都是loader.py。以sp500/loader.py为例常见做法是读取 csv 后先做日期解析和排序防止源数据时间乱序然后把 close 价格列单独提出来作为预测目标。这里需要注意的一个重要细节是差分的实现位置——dataset下已经有sunspot_ms_diff.csv这个差分文件说明作者是在数据准备阶段就完成了差分而不是在模型内部处理。import pandas as pd import numpy as np from sklearn.preprocessing import MinMaxScaler def load_sp500_data(path../dataset/spv2.csv, diffFalse, seq_len7): df pd.read_csv(path, parse_dates[Date], index_colDate) # 确认数据按时间升序时间序列建模最怕顺序错乱 df df.sort_index() close df[Close].values.reshape(-1, 1) if diff: # 一阶差分消除趋势得到平稳序列 close np.diff(close, axis0) scaler MinMaxScaler(feature_range(-1, 1)) scaled scaler.fit_transform(close) # 构造滑动窗口样本用前 seq_len 天预测下一天 X, y [], [] for i in range(len(scaled) - seq_len): X.append(scaled[i:iseq_len]) y.append(scaled[iseq_len]) return np.array(X), np.array(y), scaler这段代码的核心有三个。排序那行很容易被新手漏掉——真实拿到的 csv 不一定按日期排好序如果不做sort_index()LSTM 看到的序列是乱的损失函数会异常但模型不报错。MinMaxScaler(feature_range(-1, 1))把数据缩放到 -1 到 1 之间这是 LSTM 的常见配置因为 tanh 激活函数的输出范围是 (-1, 1)。滑动窗口的seq_len7表示用过去 7 天的价格预测明天的价格这个值不是固定的后面讲调参时会展开。如果是 sunspot 的 loader唯一的差异是不需要选择 close 列太阳黑子数据只有一列数值且差分文件已经存在直接读sunspot_ms_diff.csv即可逻辑是一致的。2.3 模型对比的基准为什么同时写 ARIMA、SVM 和 LSTM这个项目最有价值的部分不是某个单独的模型而是把三种模型放在同一个数据集上做横向对比。sp500/arima.py里用的statsmodels库做 ARIMA 拟合svm.py用支持向量回归做时序预测lstm.py用 PyTorch 搭循环神经网络。model.py是公共的评估入口EvaluationIndex.py则是计算 MAE、RMSE、R² 这些指标的统一脚本。这样设计的学习路径非常清晰先用 ARIMA/ARMA 理解线性模型的边界再用 SVM 理解核方法在非线性拟合上的能力最后用 LSTM 理解深度学习如何处理序列依赖。对毕设来说这三种模型天然构成“传统线性模型—机器学习模型—深度模型”的对比体系答辩时能讲的东西会丰富很多。如果你只是想快速验证代码能跑通先跳过 ARIMA 也没问题因为 ARIMA 调参order(p,d,q)相对费时而 LSTM 和 SVM 更容易快速看到结果。3. 把 ARMA 和 ARIMA 跑出可信度从定阶到残差检验3.1 为什么要先用 ARMA 处理太阳黑子数据太阳黑子数据是科学界标准的时序数据集具有明显的周期性约 11 年太阳活动周期且均值基本稳定。对这种数据用 ARMA 是合理的ARMA 描述的是平稳序列的自回归和移动平均关系不像 ARIMA 那样需要对非平稳序列做差分。项目里把sunspot/下的模型命名为arma.py对应的正是这个逻辑。ARMA 模型记作 ARMA(p,q)p 是自回归阶数q 是移动平均阶数。定阶最稳妥的方式不是只看 ACF/PACF 图拍脑袋而是用 AIC 或 BIC 信息准则做网格搜索选出信息量最小的组合。import itertools import warnings import statsmodels.api as sm warnings.filterwarnings(ignore) def select_arma_order(y, max_p5, max_q5): best_aic float(inf) best_order (0, 0) results [] # 在 p、q 的 0~5 范围内全组合搜索共 36 组 for p, q in itertools.product(range(max_p 1), range(max_q 1)): if p 0 and q 0: continue try: model sm.tsa.ARMA(y, order(p, q)).fit(methodmle) aic model.aic results.append((aic, p, q)) if aic best_aic: best_aic aic best_order (p, q) except Exception: continue # 输出前5名AIC最低的排最前 results.sort(keylambda x: x[0]) print(Top5 AIC:, results[:5]) return best_order这段代码把 p 和 q 都限制在 0-5 之间用methodmle极大似然估计拟合参数。直接ARMA(y, order(p,q))然后打印.aic是最省事的方式但要注意statsmodels从 0.12 之后 ARMA 类的 API 有变动这个项目如果是用比较早的版本写的在新环境下可能需要把sm.tsa.ARMA换成sm.tsa.ARIMA(y, order(p,0,q))来调用。选定阶数后要做的下一步是残差白噪声检验。Ljung-Box 检验是最常用的一种方式如果残差序列的 p-value 大于 0.05说明残差没有显著的自相关性模型已经提取了数据中的有效信息。from statsmodels.stats.diagnostic import acorr_ljungbox def check_residual(model, y, lags10): resid model.resid # 检验残差是否为白噪声p值0.05则不能拒绝“无自相关”的原假设 lb_test acorr_ljungbox(resid, lags[lags], return_dfTrue) p_value lb_test[lb_pvalue].iloc[0] if p_value 0.05: print(f残差通过白噪声检验 (p{p_value:.3f})模型可用) else: print(f残差仍存在自相关 (p{p_value:.3f})需要增大 p 或 q) return p_value很多人做完 ARMA 拟合就直接拿预测结果画图跳过残差检验这一步。这是时间序列建模里典型的“虚假拟合”——模型看似拟合得很好实际只是记住了训练集的形状。我一般会保留这个check_residual函数每次定完阶就跑一遍p 值小于 0.05 的时候绝不多想直接调大 p 或 q 重新搜索。3.2 ARIMA 在标普 500 数据上的翻车点与补救把同样的思路套到 SP500 上就要小心了。标普 500 价格序列有明显的上涨趋势是非平稳序列所以sp500/arima.py用了 ARIMA 而不是纯 ARMA。核心逻辑是先做一阶差分差分后的序列近似平稳再对差分序列建立 ARMA最后预测结果要反差分回原始价格尺度。from statsmodels.tsa.arima.model import ARIMA import numpy as np def arima_predict(series, order(1, 1, 1), steps10): # order(p,d,q)d1 表示做了一阶差分 model ARIMA(series, orderorder, trendc) fitted model.fit() forecast fitted.forecast(stepssteps) # 如果原始序列有差分这里返回的是差分后尺度上的预测 # 需要用最后一个观测值做累积还原 last_value series.iloc[-1] restored [] acc last_value for diff_val in forecast: acc acc diff_val restored.append(acc) return np.array(restored)关键的坑在于预测结果的尺度还原。fitted.forecast()返回的是差分后序列上的预测值不是原始价格。要做还原必须从最后一个真实观测值开始把每个差分预测值逐项累加起来。新手最容易在这里翻车——画图时发现预测曲线和真实价格差了一个数量级其实不是模型错了是忘了做逆变换。另外ARIMA 在金融数据上的效果通常一般。这类数据有波动聚集效应大涨大跌往往扎堆出现而这正是 GARCH 类模型擅长建模的对象。这个项目没引入 GARCH算是合理的取舍——毕设论文里写明“传统线性模型在波动率聚集数据上的局限”就已经能撑起一段分析了。4. LSTM 时序预测的完整搭建从滑动窗口到训练收敛4.1 为什么用滑动窗口而不是直接喂整条序列深度学习模型处理时间序列时通常是把一个固定长度的历史窗口作为输入预测下一个时间点的值。这个窗口长度是超参数项目里 loader 的默认值是 7。选 7 的解释在技术上没有严格对错但对日频数据来说“最近 7 个交易日”代表过去一周的走势属于经验性选择。值得你自己试一试的取值是 5、10、20、30——窗口缩短容易欠拟合趋势窗口拉长会引入过多噪声且增加训练量。4.2 看一眼 lstm.py 里 LSTM 网络的核心结构sp500/lstm.py的模型定义部分不会太长核心是nn.LSTM搭配一个全连接输出层。代码结构是一个典型的单层或多层 LSTM 回归头的模型。import torch import torch.nn as nn class LSTMPredictor(nn.Module): def __init__(self, input_size1, hidden_size64, num_layers2, output_size1, dropout0.2): super().__init__() # batch_firstTrue 时输入形状为 (batch, seq_len, input_size) self.lstm nn.LSTM(input_size, hidden_size, num_layers, batch_firstTrue, dropoutdropout) self.reg nn.Linear(hidden_size, output_size) def forward(self, x): # 输入 x: (batch, seq_len, 1)对应 loader 构造的滑动窗口 out, _ self.lstm(x) # out: (batch, seq_len, hidden_size) # 只取最后一个时间步的隐状态接全连接层 last out[:, -1, :] # (batch, hidden_size) return self.reg(last)参数逻辑是hidden_size64决定记忆容量num_layers2加深网络非线性表达能力dropout0.2做正则化防过拟合。out[:, -1, :]这一行的含义要理解——LSTM 输出的每个时间步都有一个隐状态预测“明天”只应该用“今天”这个时间步的隐状态而不是把所有时间步混在一起。如果你强行接out.mean(dim1)做平均池化通常效果是变差的。训练循环里常见的坑是没有做早停。时间序列的验证损失常常会在某个 epoch 之后反弹最好的做法是记录每个 epoch 的验证损失连续 10 个 epoch 不下降就提前终止训练并恢复最优参数。def train_model(model, X_train, y_train, X_val, y_val, epochs100, lr0.001): optimizer torch.optim.Adam(model.parameters(), lrlr) loss_fn nn.MSELoss() best_val_loss float(inf) best_state None patience 10 no_improve 0 for epoch in range(epochs): model.train() # 训练循环里要记得每次反向传播前清空梯度 optimizer.zero_grad() output model(X_train) loss loss_fn(output, y_train) loss.backward() optimizer.step() model.eval() with torch.no_grad(): val_loss loss_fn(model(X_val), y_val) # 早停逻辑验证损失连续10轮不降就停 if val_loss best_val_loss: best_val_loss val_loss best_state {k: v.clone() for k, v in model.state_dict().items()} no_improve 0 else: no_improve 1 if no_improve patience: print(fEarly stop at epoch {epoch}) break model.load_state_dict(best_state) return model这个训练模板可以直接替换进项目里原本的lstm.py。唯一的注意点是数据顺序时序数据不能随机打乱后再切训练验证集否则未来信息会泄露进训练集验证结果会虚高。要按时间顺序切分比如前 80% 训练、后 20% 验证。4.3 预测结果的逆缩放与评价指标对齐模型输出的是归一化后的预测值因为 loader 里做了 MinMaxScaler要把预测还原成真实价格才能计算有意义的误差指标。EvaluationIndex.py里的 MAE、RMSE 等指标要求在原始尺度上计算否则数值看起来很小但没有实际意义。def inverse_transform_predictions(scaler, pred_norm, y_norm): # scaler 是 MinMaxScaler 实例pred_norm 是归一化预测值 pred_real scaler.inverse_transform(pred_norm) y_real scaler.inverse_transform(y_norm) return pred_real, y_real如果 loader 里做了差分逆变换会更复杂一些要先用scaler.inverse_transform还原到差分尺度再做累积还原。这一步在代码调试里最容易出错——我自己就在一次实验中把预测值静置在差分尺度上画图结果曲线形态是对的但偏了多少也说不上来后来也是做了还原才发现源头。5. SVM 做回归预测选核函数与调参的常见误区5.1 SVM 怎么用于时间序列预测SVM 做分类多见但做回归SVR在时间序列上也能用。核心做法和 LSTM 类似构造滑动窗口样本把前 seq_len 天的值作为特征向量下一天的值作为目标。svm.py里通常会使用 scikit-learn 的SVR常见配置是 RBF 核。from sklearn.svm import SVR def svm_predict(X_train, y_train, X_test, C1.0, epsilon0.1, gammascale, kernelrbf): # C: 惩罚系数越大越容易过拟合epsilon: 不敏感带的宽度 model SVR(kernelkernel, CC, epsilonepsilon, gammagamma) model.fit(X_train, y_train) pred model.predict(X_test) return pred这段代码本身不复杂。需要理解的是 SVR 与 LSTM 的本质区别SVR 把前 7 天的值当成一个 7 维向量不考虑其内部的时间顺序LSTM 会按时间顺序逐步处理这 7 个值。也就是说SVR 对“第 1 天和第 5 天谁先谁后”不敏感这在某些场景下会损失信息。5.2 参数让人发疯C、epsilon、gamma 怎么调svm 的三个核心参数是互相牵制的网上说的“网格搜索一把梭”在这类小数据集上确实是够用的但不建议用完整网格不加限制地搜索否则时间会受不了。应设定一个合理的参数范围再搜一圈然后根据验证集上的误差再收窄范围。from sklearn.model_selection import GridSearchCV from sklearn.svm import SVR import numpy as np param_grid { C: [0.5, 1, 5, 10], # 先粗再细 epsilon: [0.05, 0.1, 0.2], # 对误差的容忍度 gamma: [0.01, 0.05, 0.1, scale] } # 用时间序列交叉验证更合理这里先用标准CV演示 model SVR(kernelrbf) grid GridSearchCV(model, param_grid, cv5, scoringneg_mean_squared_error) grid.fit(X_train, y_train) print(grid.best_params_)关于参数选择的经验说一下C越大模型越复杂越容易过拟合gamma越大模型越偏向于记住局部模式容易把训练集拟合得过于精细epsilon越大模型对误差越宽容预测曲线越平滑。金融数据噪声大一般epsilon要适当加大一点否则 SVR 会疯狂追逐每根小毛刺。一个常见的误用是把 SVR 的输入直接做成原始价格的滑动窗口而不做归一化。SVR 对特征尺度很敏感RBF 核内有距离度量价格从几百到几千如果不归一化gamma 和 C 的步长几乎没法设。正确做法是用 scaler 先缩放到 -1~1同 loader 里给 LSTM 做的处理一致。6. 避坑指南时间序列项目里我踩过的八个典型坑这一章的每一条都是我自己在复现类似项目时实际遇到过的按“现象 → 原因 → 解决”来写逐个排查能省下大量时间。坑 1预测曲线整体滞后一天现象LSTM 和 SVR 的预测图看着和真实值长得几乎一样但是整条线向右偏了一天。 原因用第 t 天的值预测第 t1 天在日频数据上最优解就是“记住昨天的价格”模型偷懒了。 解决去看评估指标MAE/RMSE而不是只看拟合曲线如果滞后十分明显说明序列的可预测性弱模型没学到有效模式。这时候减少滑窗长度或改用多步预测直接预测 t5 而不是递归预测 5 步会更有意义。坑 2反归一化后预测值变成一条水平线现象loss 降得很好但还原后画图预测值几乎是一条平线。 原因scaler 在差分后序列上做归一化预测出来后反归一化之前忘记了累积求和还原。 解决按第 3 章那个arima_predict里的累积还原逻辑先逆变换差分尺度再逐项累加。坑 3数据集切分时用了 random split现象验证集指标漂亮得不像话但一到测试集或新数据就崩。 原因随机切分让模型在训练时偷偷见了未来的数据时间序列是“过去预测未来”不是“随机样本分类”。 解决按时间顺序切分。用train_test_split(..., shuffleFalse)或者直接用df.iloc[:int(len*0.8)]做切分。坑 4用原始价格直接算 RMSE 和用归一化后的值算 RMSE 混着比较现象模型 A 的 RMSE 是 0.02模型 B 是 12.5无法判断谁好。 原因一个在归一化尺度上算、一个在原始尺度上算。 解决所有模型统一用原始尺度计算指标。Example 里EvaluationIndex.py应该是在原始尺度上算的要确认传入的是逆变换后的值。坑 5LSTM 训练 loss 不降现象loss 卡在某个数值附近死活降不下来。 原因学习率太高导致震荡或数据没归一化或权重初始值不合适。 解决先用 0.001 的 lr配合 Adam如果 loss 一直在 0.05 以上跳动试着把 lr 降到 0.0005 或 0.0001再确认输入数据经过 MinMaxScaler 处理过。坑 6SVM 在小数据集上训练得太久现象数据量不大但svm_fit跑了几分钟还在转。 原因SVR 的复杂度随样本量平方级上升如果还做了完整网格搜索组合大得离谱。 解决先做归一化再把参数网格分段搜索先粗搜定范围再细搜定精度。数据量在 2000 个样本以上时SVR 的耗时已经非常明显。坑 7读 csv 时日期变成 object 类型现象df.plot()的 x 轴乱序或直接报错。 原因没有parse_dates[Date]Pandas 按字符串处理了。 解决pd.read_csv(path, parse_dates[Date], index_colDate)。坑 8SP500 数据里存在停牌日或缺失日现象序列中间突然出现明显的跳跃或断点。 原因美股交易日和自然日不同周末、节假日没有数据而 csv 里可能用空行或 NaN 表示。 解决检查df.isna().sum()用前向填充df.ffill()或直接删掉空行。这八条里前三条是项目隐藏最深的问题你读代码时如果不细看很容易漏掉。建议把这份清单复制到你自己的项目 README 里每次实验跑完一条条对照。7. 把普通代码变成毕设亮点用可视化和多模型对比体现工作量这个项目已经给了你 ARIMA/ARMA、SVM、LSTM 三种模型但你自己要做的关键一步是把结果画成有说服力的对比图。这是你这套代码最终价值能否被看见的分水岭。一个合格的对比图应该包含三条曲线真实值、LSTM 预测、SVR 预测再加上用浅色区分训练集和测试集的背景区域。import matplotlib.pyplot as plt import matplotlib.dates as mdates def plot_forecast(dates, y_true, y_pred_dict, test_start_idxNone): # y_pred_dict: {LSTM: pred_lstm, SVR: pred_svr} fig, ax plt.subplots(figsize(12, 5)) ax.plot(dates, y_true, labelTrue, colorblack, linewidth1.2) colors {LSTM: #e41a1c, SVR: #377eb8, ARIMA: #4daf4a} for name, pred in y_pred_dict.items(): ax.plot(dates, pred, labelname, colorcolors[name], linewidth1, alpha0.8) # 用纵向虚线标记测试集起点比纯靠颜色区分直观得多 if test_start_idx is not None: ax.axvline(dates[test_start_idx], colorgray, linestyle--, alpha0.7) ax.legend(locupper left) ax.set_xlabel(Date) ax.set_ylabel(Value) # 自动优化日期刻度避免x轴标签挤成一坨 locator mdates.AutoDateLocator() formatter mdates.ConciseDateFormatter(locator) ax.xaxis.set_major_locator(locator) ax.xaxis.set_major_formatter(formatter) fig.tight_layout() fig.savefig(comparison.png, dpi150)具体执行思路是这样的先用EvaluationIndex.py算出统一的表格按 MAE、RMSE、R²、训练时间四列把三个模型排成一张表然后跑这段绘图脚本生成一张对比图。图能放进论文表格能放进答辩 PPT——毕设最需要的“工作量展示”基础就成型了。指标计算逻辑上MAE 用来看平均偏差大小RMSE 对离群点更敏感R² 结合业务背景使用即可金融时序经常出现很低的 R²这是正常的不用紧张。我自己的经验是完成这套工作之后把 SP500 的滑动窗口从 7 调到 14 再跑一遍记录指标变化——这个前后对比在论文里是很有力的实验内容。从那以后我每次拿到时间序列项目都会强制走一遍“差分、归一化、时序切分、逆变换还原、统一指标尺度”这条完整流程少一步都会在某个不起眼的地方翻车。希望这份拆解能帮你把项目真正跑起来也跑明白。本文还有配套的精品资源点击获取
网站建设高端定制企业官网