简介:这份资源面向希望系统入门时间序列预测的开发者与学习者,围绕同一类预测任务,横向对比BP神经网络、CNN、LSTM、GRU、RNN、SVM以及CNN+LSTM混合模型等多种建模思路,帮助读者理解不同网络结构在时序数据上的适用场景与效果差异。压缩包共25个文件,以9个Python脚本为核心,配合10个CSV、3个XLSX数据文件及TXT、JSON、PKL等辅助文件,整体约5.08MB,脚本与数据按模型分目录组织,便于逐个运行与对照实验。内容覆盖上车测试与训练数据、月度太阳黑子等典型时序数据集,既有单模型实现,也有CNN与LSTM的融合结构,可用来练习数据读取、特征构造、模型搭建与预测评估的完整流程。目前已有1177人学习,适合作为课程设计、入门练手或算法对比实验的参考素材。
1. 从一份时间序列预测学习包说起:七种模型同台,到底该怎么跑
很多人做时间序列预测,第一步就卡在“模型选型”上——业务方丢过来一份带时间戳的销量表或者传感器数据,要求下周给个预测结果,你打开搜索引擎,满屏都是 LSTM、GRU、Transformer,到底用哪个?这份「时间序列预测学习」资源包的价值就在这:它把 BP、CNN、LSTM、GRU、RNN、SVM 以及 cnn+lstm 混合结构这七种常见方案,各自配了一份可运行的数据和脚本,放在同一个压缩包里。你不用再东拼西凑找 demo,解压之后每个文件夹就是一个独立的最小可复现单元。适合谁?刚接触时序预测、想快速把各模型跑通对比一遍的开发者;也适合已经用过 LSTM 但没试过 cnn+lstm 混合结构、想看看特征提取层加进去到底有没有提升的熟手。包里数据分两类:一类是上车测试/训练数据(xlsx 格式,偏业务场景),一类是 Monthly Sunspots.csv(经典太阳黑子数据集,偏教学验证)。两条线并行,方便你既看业务数据表现,也看标准数据集上的收敛情况。
2. 拆包看结构:七套脚本各自吃什么数据、吐什么结果
2.1 目录映射与数据格式对照
解压之后不要急着跑,先把目录和数据格式对一遍。这个包的结构不是统一框架,而是按模型分文件夹各自为政,所以每个子目录的入口脚本、数据路径、依赖都不一样。下面这张表是我实际拆包后整理的对应关系,跑之前先确认你手上有没有对应的数据文件。
| 模型/结构 | 入口脚本 | 配套数据 | 数据格式 | 任务类型 |
|---|---|---|---|---|
| BP/NN | NN_model.py | 需自行指定或复用 xlsx | xlsx/csv | 回归预测 |
| RNN | elman_rnn.py | 内置序列构造或 csv | csv | 单步/多步预测 |
| LSTM | LSTM模型.py | 上车训练数据.xlsx、上车测试数据.xlsx | xlsx | 业务时序预测 |
| GRU | GRU_Sunspots.py | Monthly Sunspots.csv | csv | 标准数据集验证 |
| SVM | svmprediction.py + read_data.py | 20180829.xlsx | xlsx | 回归预测 |
| cnn+lstm | cnn_lstm.py + data_helper.py | mini_data 目录 | 自定义 | 混合结构预测 |
这张表的核心信息是:数据格式不统一。LSTM 和 SVM 走 xlsx,GRU 走 csv,cnn+lstm 走 mini_data 自定义格式。这意味着你不能指望一个数据加载函数通吃所有脚本,每个文件夹都得单独看它的 read 逻辑。常见做法是先把 xlsx 用 pandas 读进来,确认列名和时间列,再决定要不要转成 csv 统一管理。
2.2 各脚本的输入输出约定
以 LSTM 为例,LSTM模型.py默认读取同目录下的上车训练数据.xlsx和上车测试数据.xlsx。你需要确认两件事:时间列是哪一列、预测目标列是哪一列。很多新手直接跑,报KeyError,就是因为脚本里写死的列名和你数据里的列名对不上。
import pandas as pd # 读取训练数据,先看列名和形状 train_df = pd.read_excel('上车训练数据.xlsx') print(train_df.columns.tolist()) print(train_df.shape) print(train_df.head()) # 常见坑:时间列可能是字符串,需要转成 datetime # train_df['时间列名'] = pd.to_datetime(train_df['时间列名']) # train_df = train_df.sort_values('时间列名').reset_index(drop=True)这段代码的作用是“先侦察再动手”。参数说明:pd.read_excel的sheet_name默认是第一个 sheet,如果你的数据在第二个 sheet,要显式指定。sort_values那行注释很关键——时序数据必须按时间排序,否则 LSTM 学到的顺序是乱的,loss 会莫名其妙地震荡,这种玄学问题十有八九是排序没做。
GRU 那边走的是Monthly Sunspots.csv,这个数据集列少、干净,适合先跑通流程再换业务数据。GRU_Sunspots.py里通常会做归一化和滑动窗口构造,窗口大小(lookback)是个关键参数,后面单独讲。
SVM 这套稍微特殊,read_data.py负责读20180829.xlsx并做特征工程,svmprediction.py负责建模预测。SVM 做时序预测的思路是把历史若干步拼成特征向量,然后当回归问题解。它的优势是小样本下不容易过拟合,劣势是窗口一大特征维度爆炸,训练时间直线上升。
cnn+lstm 这套是包里结构最完整的:mini_data放数据,data_helper.py做批处理和窗口构造,cnn_lstm.py定义网络。CNN 层负责从局部窗口里提特征,LSTM 层负责学时序依赖,这个组合在不少时序任务上确实比纯 LSTM 稳。但它的数据管道是自定义的,你得先读懂data_helper.py里的__init__.py和mini_build相关逻辑,才能把自有数据塞进去。
3. 把 LSTM 和 GRU 跑起来:窗口构造、归一化与训练参数
3.1 滑动窗口构造:lookback 到底设多少
时序预测和普通回归最大的区别在于,样本不是独立同分布的,而是用过去 N 步预测未来 M 步。这个 N 就是 lookback(回看窗口)。设太小,模型看不到足够的历史模式;设太大,噪声被放大,训练也慢。我一般会先看数据的周期性:如果数据有明显的 7 天周期,lookback 至少设 14;如果是月度数据,lookback 设 12 或 24。
import numpy as np def make_windows(series, lookback=24, horizon=1): """ series: 一维归一化后的序列 lookback: 回看步数 horizon: 预测未来第几步 返回: X shape (样本数, lookback, 1), y shape (样本数,) """ X, y = [], [] for i in range(len(series) - lookback - horizon + 1): X.append(series[i:i+lookback]) y.append(series[i+lookback+horizon-1]) X = np.array(X).reshape(-1, lookback, 1) y = np.array(y) return X, y逻辑说明:循环从 0 到len(series)-lookback-horizon+1,每次取 lookback 长度的窗口作为输入,取窗口后第 horizon 个点作为标签。reshape(-1, lookback, 1)是为了适配 LSTM 的输入格式(batch, timesteps, features)。参数怎么改:horizon=1 是单步预测,改成 3 就是预测未来第三步;lookback 根据数据周期调整。注意归一化要在构造窗口之前做,而且要用训练集的最大最小值去归一化测试集,不能各自归一化,否则信息泄露,测试集表现会虚高。
3.2 LSTM 模型搭建与训练参数
LSTM模型.py里的网络结构一般不会太复杂,常见是两层 LSTM 加一个全连接输出。但有几个参数直接决定你能不能跑出合理结果。
from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout from tensorflow.keras.optimizers import Adam model = Sequential([ LSTM(64, return_sequences=True, input_shape=(lookback, 1)), Dropout(0.2), LSTM(32, return_sequences=False), Dropout(0.2), Dense(1) ]) model.compile(optimizer=Adam(learning_rate=0.001), loss='mse') history = model.fit(X_train, y_train, epochs=100, batch_size=32, validation_split=0.1, verbose=1)参数说明:第一层 LSTM 的return_sequences=True是为了把序列传给第二层 LSTM,如果只有一层 LSTM,这行可以去掉。Dropout(0.2)是防过拟合的,数据量小的时候可以加到 0.3。learning_rate=0.001是 Adam 的常用起点,如果 loss 下降太慢可以调到 0.005,如果震荡就降到 0.0005。batch_size=32不是固定的,样本少就设 16 甚至 8。validation_split=0.1是从训练集尾部切 10% 做验证,注意时序数据不能随机切验证集,必须按时间顺序切,否则又是信息泄露。
GRU 的脚本结构类似,把LSTM换成GRU即可,参数量会少一些,训练速度通常快 20% 到 30%。在 Monthly Sunspots 这种数据量不大的场景下,GRU 和 LSTM 的最终误差往往差不多,但 GRU 收敛更快。我的习惯是:先跑 GRU 快速验证数据管道和窗口设置对不对,确认没问题再换 LSTM 精调。
3.3 训练过程怎么看:loss 曲线与早停
跑起来之后不要只盯着最后那个 MSE 数字。训练过程中要看两个东西:训练 loss 和验证 loss 的差距、验证 loss 是否开始上升。如果训练 loss 一直降但验证 loss 在某个 epoch 后开始涨,说明过拟合了,这时候要么加 Dropout,要么减模型容量,要么早点停。
from tensorflow.keras.callbacks import EarlyStopping early_stop = EarlyStopping(monitor='val_loss', patience=10, restore_best_weights=True) history = model.fit(X_train, y_train, epochs=200, batch_size=32, validation_split=0.1, callbacks=[early_stop], verbose=1)patience=10的意思是验证 loss 连续 10 个 epoch 没改善就停,restore_best_weights=True会恢复到验证 loss 最低那个 epoch 的权重。这个回调能帮你省掉手动调 epoch 数的麻烦,也是避免过拟合的后悔药。注意monitor默认是val_loss,如果你自己切了验证集,要改成对应的 key。
4. cnn+lstm 混合结构:CNN 提特征、LSTM 学依赖,怎么接才不翻车
4.1 混合结构的连接方式与维度对齐
cnn+lstm 的核心思路是:先用一维卷积在时间轴上滑动,提取局部模式(比如连续几天的突增突降),再把卷积输出的特征序列喂给 LSTM 学长期依赖。这个结构在不少时序任务上比纯 LSTM 效果好,因为 CNN 层相当于自动做了特征工程。
但连接的时候最容易翻车的地方是维度。Conv1D 的输出是(batch, new_timesteps, filters),LSTM 需要的是(batch, timesteps, features)。如果卷积的 stride 和 padding 没设好,new_timesteps会变,LSTM 的input_shape就对不上。
from tensorflow.keras.layers import Conv1D, MaxPooling1D, LSTM, Dense, Flatten from tensorflow.keras.models import Sequential model = Sequential([ Conv1D(filters=32, kernel_size=3, activation='relu', input_shape=(lookback, 1)), MaxPooling1D(pool_size=2), Conv1D(filters=64, kernel_size=3, activation='relu'), LSTM(50, return_sequences=False), Dense(1) ]) model.compile(optimizer='adam', loss='mse') model.summary()逻辑说明:第一层 Conv1D 的input_shape=(lookback, 1),1 是特征维度,单变量时序就是 1。kernel_size=3表示每次看 3 个连续时间步。MaxPooling1D(pool_size=2)把时间步减半,减少计算量。第二层 Conv1D 的 filters 加到 64,提取更丰富的特征。最后 LSTM 的return_sequences=False,只取最后一个时间步的输出,接 Dense 出预测值。
参数怎么调:如果model.summary()报维度错误,先看 MaxPooling 之后的时间步是多少,再确认 LSTM 能不能接上。常见做法是池化之后时间步还太长,可以再加一层池化,或者把 LSTM 的input_shape改成(None, filters)让它自动适配。data_helper.py里的mini_build相关逻辑就是干这个的——把原始数据切成适合卷积和循环层的形状。
4.2 数据管道对接:把自有数据塞进 mini_data
mini_data目录和data_helper.py是配套的。你要用自己的数据跑 cnn+lstm,最稳妥的方式是照着mini_data里的格式准备一份,然后改data_helper.py里的读取路径。
# data_helper.py 里常见的读取和窗口构造逻辑 import pandas as pd import numpy as np def load_mini_data(path, lookback=24, horizon=1): df = pd.read_csv(path) # 假设第一列是时间,第二列是数值 series = df.iloc[:, 1].values.astype(float) # 归一化 series = (series - series.min()) / (series.max() - series.min() + 1e-8) X, y = [], [] for i in range(len(series) - lookback - horizon + 1): X.append(series[i:i+lookback]) y.append(series[i+lookback+horizon-1]) return np.array(X).reshape(-1, lookback, 1), np.array(y)这段代码和前面 LSTM 的窗口构造逻辑一致,区别在于它从 csv 读数据,并且假设了列的位置。如果你的数据列顺序不同,改df.iloc[:, 1]里的索引。1e-8是防止除以零。注意归一化用的是全局最大最小值,实际项目中应该只用训练集的最大最小值,这里为了简化没做区分,你自己改的时候要补上。
4.3 训练与验证:混合结构到底有没有提升
跑完 cnn+lstm 之后,和纯 LSTM 的结果对比一下。对比的时候不能只看一个随机划分的测试集,因为时序数据波动大,单次划分的结果可能只是运气。我的做法是:用同样的 lookback 和 horizon,分别跑 LSTM 和 cnn+lstm,各跑 3 次取平均 MSE。如果 cnn+lstm 的平均 MSE 比 LSTM 低 10% 以上,那这个提升大概率是真实的;如果只低 2% 到 3%,可能是随机波动,不值得增加复杂度。
另外要注意,cnn+lstm 的参数量通常比纯 LSTM 大,训练时间也更长。如果数据量很小(比如只有几百个点),CNN 层可能学不到有意义的局部模式,反而更容易过拟合。这种情况下纯 GRU 或 SVM 可能是更务实的选择。
5. 避坑与排查:七套脚本跑下来最容易翻车的五个地方
5.1 现象:loss 是 nan,训练一开始就崩
原因:学习率太大,或者数据里有 nan/无穷大值,或者归一化的时候除以了零。时序数据里经常有缺失值,pandas 读进来是 NaN,直接送进模型就会导致 loss 变 nan。
解决:训练前先做df.isnull().sum()检查缺失值,用df.fillna(method='ffill')前向填充或者插值补上。归一化时分母加1e-8。学习率从 0.001 起步,不要一上来就 0.01。
5.2 现象:预测结果是一条直线,完全不跟着数据波动
原因:模型没学到东西,可能是 lookback 太小、归一化把数据压得太扁、或者训练 epoch 不够。另一个常见原因是输出层用了 sigmoid 但目标值没归一化到 0 到 1 之间。
解决:先确认归一化范围,如果目标值在 0 到 1 之外,输出层不要用 sigmoid。把 lookback 调大试试,比如从 12 调到 24。训练 epoch 加到 200 以上,配合 EarlyStopping。如果还是直线,检查数据本身是不是方差太小。
5.3 现象:SVM 跑得特别慢,内存爆了
原因:SVM 做时序预测时,如果 lookback 设得大,特征维度就是 lookback,样本数乘以特征维度很容易撑爆内存。而且 SVM 的训练复杂度是 O(n²) 到 O(n³),样本一多就慢得离谱。
解决:控制 lookback 在 10 到 20 之间,不要超过 30。样本数超过几千条时,SVM 不是好选择,换 LSTM 或 GRU。如果一定要用 SVM,可以先用 PCA 降维,或者用LinearSVC替代SVC。
5.4 现象:cnn+lstm 报维度不匹配,改了半天改不对
原因:Conv1D 和 MaxPooling1D 会改变时间步长度,LSTM 的input_shape如果写死了具体数字,池化之后就对不上。另外data_helper.py里 reshape 的形状和模型输入不一致也会报错。
解决:用model.summary()逐层看输出形状,确认池化后的时间步。LSTM 的input_shape可以写成(None, filters)让 Keras 自动推断。数据管道那边,在 reshape 之后打印X.shape,和模型第一层的输入要求对一遍。
5.5 现象:GRU 在 Sunspots 上表现很好,换到业务数据就崩
原因:Sunspots 是平滑的周期性数据,业务数据往往有噪声、缺失、突变。在干净数据上调的窗口大小和归一化方式,换到脏数据上不适用。
解决:换数据后重新做探索性分析,看缺失值比例、异常值分布、周期性是否明显。归一化方式可能要从 min-max 改成 z-score,因为业务数据可能有极端值,min-max 会被拉偏。lookback 也要重新试,不要直接套用。
6. 进阶技巧:用同一套数据把七个模型串起来对比
跑通单个脚本只是第一步,这份资源真正的用法是:用同一份数据、同一个窗口设置,把七个模型跑一遍,横向对比。这样你才能知道在你的数据上,到底哪个模型性价比最高。
具体做法:选一份你手头的数据(比如上车训练数据.xlsx),统一做归一化和窗口构造,导出成X_train.npy、y_train.npy、X_test.npy、y_test.npy。然后每个模型脚本只改数据加载部分,都读这四个 npy 文件。这样模型之间的差异就只来自网络结构本身,而不是数据预处理。
# 统一数据准备脚本 prepare_data.py import pandas as pd import numpy as np df = pd.read_excel('上车训练数据.xlsx') series = df.iloc[:, 1].values.astype(float) series = (series - series.min()) / (series.max() - series.min() + 1e-8) lookback, horizon = 24, 1 X, y = [], [] for i in range(len(series) - lookback - horizon + 1): X.append(series[i:i+lookback]) y.append(series[i+lookback+horizon-1]) X, y = np.array(X).reshape(-1, lookback, 1), np.array(y) split = int(len(X) * 0.8) np.save('X_train.npy', X[:split]) np.save('y_train.npy', y[:split]) np.save('X_test.npy', X[split:]) np.save('y_test.npy', y[split:]) print('train:', X[:split].shape, 'test:', X[split:].shape)这个脚本跑完,你就有了统一的数据集。接下来每个模型脚本开头加两行np.load就能用同一份数据。对比的时候记录每个模型的测试集 MSE 和训练时间,做成一张表。
| 模型 | 测试 MSE | 训练时间 | 参数量级 | 适用场景 |
|---|---|---|---|---|
| BP/NN | 待填 | 快 | 小 | 基线对比 |
| RNN | 待填 | 中 | 小 | 短序列 |
| LSTM | 待填 | 慢 | 中 | 长依赖 |
| GRU | 待填 | 中 | 中 | 长依赖、追求速度 |
| SVM | 待填 | 小样本快 | 不适用 | 小样本 |
| cnn+lstm | 待填 | 最慢 | 大 | 局部模式明显 |
这张表填完,你对自己数据该用哪个模型就有数了。我的血泪经验是:不要迷信复杂模型,很多时候 GRU 和 SVM 的性价比比 cnn+lstm 高得多,尤其是在数据量不大、上线时间紧的情况下。从那以后我每次拿到新的时序数据,都强制先跑一遍统一对比,再决定用哪个模型,而不是上来就堆 LSTM。希望帮到你。
本文还有配套的精品资源,点击获取