LSTM时间序列预测实战:从数据预处理到模型评估完整流程
2026/9/15 4:00:12 网站建设 项目流程

简介:面向时间序列分析预测任务的完整LSTM项目,以PM2.5浓度预测为典型应用场景,适合计算机、人工智能、自动化等专业学生用于课程设计、毕业设计或大作业。项目共6个文件,包含3个Python脚本、2个CSV数据集和1个README说明文档,压缩包仅929KB;Python脚本分别覆盖主预测流程、数据预处理与序列可视化,CSV数据提供原始与整理后的输入,README则说明启动方式与项目结构,整体轻量直接、便于快速运行与二次修改。代码在作者毕设答辩中平均分达到96分,上传前已完整测试通过,可直接执行;读者既能照脚本理解LSTM时序建模的基本环节,也能利用现有数据和预处理逻辑尝试不同的预测目标。已有96人学习下载,适合作为初学者的进阶代码范本,也可作为高评分作业、课设或毕设初期的参考实现。

1. 把 LSTM 时间序列预测做成一次能跑完的大作业,先解决四个问题

一门课的期末作业如果是 LSTM 时间序列预测,通常的要求是:给一份真实数据集,提交能直接运行的 python 源码、文档说明和预测效果图。很多人卡在“老师机器上没有训练好的权重”这一步,因为模型文件太大、相对路径写错、归一化参数没保存。其实 95 分以上的作业并不追求模型创新,而是做到三件事:数据切分可复现、训练过程可控、评估指标和图表能支撑结论。常见的数据集包括径流、负荷、交通流量和股价序列,这类数据长度几百到几千条,LSTM 不比 Transformer 差,反而更容易被讲清楚。下面按数据预处理、模型调参、评估出图的顺序把直接可跑的代码路径写完整。

2. 先想清楚 LSTM 在时间序列预测里的适用边界,再写代码

2.1 门控为什么能记住“昨天的趋势”:从 RNN 到 LSTM

RNN 在时间步之间共享权重,本意是让每个历史状态都影响当前输出。问题是反向传播时梯度需要沿着时间步连乘,序列一长,梯度要么指数衰减要么爆炸。LSTM 在隐状态之外增加一条细胞状态通道,由遗忘门、输入门、输出门分别控制丢弃、写入和读取。用在水文径流预报这类场景里,遗忘门学到的往往是“昨天雨量大,今天基流要续多少”这类跨时间步的规律,这比普通 RNN 在几十步后还保留得住。

三个门里,遗忘门决定上一时刻细胞状态保留多少,输入门决定当前候选值写进去多少,输出门决定当前隐状态对外暴露多少。训练时真正学的是三组权重矩阵:连接输入到门、连接上一隐状态到门、以及输出层的投影。由于门控是逐元素操作,参数数量只和隐状态维度和输入维度相关,不会随 lookback 长度线性膨胀,这就是 LSTM 能在中等长度序列上稳定的原因。

2.2 输入形状和输出形状直接决定数据切分方式

在用 TensorFlow 或 PyTorch 写代码之前,先记住一个关键点:LSTM 的输入形状是三维的,分别是样本数、时间步数和特征数。很多直接运行失败的报告,问题不是网络写错,而是喂进去的是二维数组。

预测类型输入形状输出形状典型损失函数
单变量单步(batch, lookback, 1)(batch, 1)MSE
单变量多步(batch, lookback, 1)(batch, horizon)MSE
多变量多步(batch, lookback, n_features)(batch, horizon)MSE

这里的 lookback 是滑窗长度,horizon 是未来要预测多少步,batch 是训练时一次喂的样本数。例如一批 32 个样本,每个样本看过去 24 小时,数据里只有流量一个字段,那么 X 的形状就是 (32, 24, 1)。先用 NumPy 验证形状,再去搭网络,能省掉大半排错时间。

import numpy as np X_batch = np.random.randn(32, 24, 1) # 32 个样本,24 个历史时刻,1 个特征 y_batch = np.random.randn(32, 1) # 预测未来 1 个时刻的目标值 print(X_batch.shape, y_batch.shape)

这段代码的第二个维度 24 就是 lookback,第三个维度 1 是特征数量。如果改成多变量输入,比如同时用上游水位和雨量预测流量,就把第三维改成 2,y 仍然是流量这一列。Keras 的Dense输出层会自动把最后一维压到神经元数量,所以 y 的形状大多数情况下是 (样本数, 输出步数),不需要手动 reshape 成三维。

2.3 单变量、多变量与多步预测的建模差异

单变量预测直接用目标列自身的历史值做特征,适用于数据内在规律较强、外部影响不明确的序列。多变量预测需要额外准备对齐的特征列,要注意不同字段的量纲差异和缺失时间点,通常要先做插值或剔除。

多步预测有两种常见做法。一种是直接多步,即让最后一层 Dense 输出 horizon 个值,适合 3 到 7 步以内的短期预测;另一种是滚动预测,把前一步预测值当作下一步输入,缺点是误差会累积。课程大作业里用直接多步更稳妥,因为训练和预测的输入输出形状一致,代码更短,验证集指标也更直观。真正需要长序列预测时,再考虑 seq2seq 结构,但那是另一个复杂度级别了。

3. 数据读取、归一化和滑动窗口切分:LSTM 时间序列预处理直接可跑的写法

3.1 用 pandas 读取 CSV 并检查时间索引

数据准备是 LSTM 时间序列预测 Python 实现里最容易出错的部分。常见的数据集是 CSV 格式,第一列时间,第二列或若干列观测值。读取时不能让 pandas 默认把日期当成字符串,否则按时间顺序切分时会出现“看起来连续、实际乱序”的问题。

import pandas as pd import numpy as np from sklearn.preprocessing import MinMaxScaler df = pd.read_csv("data/runoff.csv", parse_dates=["date"]) df = df.sort_values("date").reset_index(drop=True) # 检查缺失值和时间间隔 print(df.isna().sum()) print(df["date"].diff().value_counts()) # 做线性插值,时间序列不建议直接删除缺失行 df["flow"] = df["flow"].interpolate(method="linear") data = df["flow"].values.reshape(-1, 1)

parse_dates让第一列变成 pandas 的 datetime 类型,sort_values保证时间从旧到新。date.diff()能看出采样间隔是否固定,比如正常情况下都是 1 小时,如果出现 2 小时说明中间缺数据。缺失值用interpolate线性填充,比dropna更适合时间序列,因为保留时间轴的连续性。reshape(-1, 1)是为了让数据变成列向量,后面喂给 MinMaxScaler 时不会报 shape 错误。

3.2 MinMaxScaler 必须在训练集上 fit,再 transform 验证集和测试集

时间序列预测的归一化有一个隐藏陷阱:如果先对整条序列做scaler.fit_transform,再划分训练集和测试集,那么测试集的信息已经参与了训练阶段的最大值和最小值计算,验证指标会偏乐观。课程作业里老师可能不深究,但被问到数据泄露时容易扣分。正确做法是先按时间顺序切段,再分别归一化。

scaler = MinMaxScaler(feature_range=(0, 1)) train_size = int(len(data) * 0.7) val_size = int(len(data) * 0.15) test_size = len(data) - train_size - val_size train_raw = data[:train_size] val_raw = data[train_size:train_size + val_size] test_raw = data[train_size + val_size:] train_scaled = scaler.fit_transform(train_raw) val_scaled = scaler.transform(val_raw) test_scaled = scaler.transform(test_raw)

这里先按 70%、15%、15% 切成三段,然后只对训练段调用fit_transformfit会记录训练集里的最小值和最大值,transform用同一组参数去缩放验证集和测试集。这种写法保证测试集的最大值不会影响归一化结果,也保证scaler.inverse_transform可以还原真实数值。

3.3 用滑动窗口把序列拼成有监督样本

LSTM 不能直接吃一维序列,需要把序列切成 (样本, 时间步, 特征) 的三维数据。切窗口时还要注意一个顺序问题:验证集和测试集应该各自连续切,不能从整段归一化序列里统一切,否则验证集窗口会包含训练集尾部的数据,造成信息穿越。

def make_sequences(input_data, lookback=24, horizon=1): X, y = [], [] for i in range(len(input_data) - lookback - horizon + 1): X.append(input_data[i:i + lookback]) y.append(input_data[i + lookback:i + lookback + horizon]) return np.array(X), np.array(y) lookback = 24 horizon = 1 X_train, y_train = make_sequences(train_scaled, lookback, horizon) X_val, y_val = make_sequences(val_scaled, lookback, horizon) X_test, y_test = make_sequences(test_scaled, lookback, horizon) print(X_train.shape, y_train.shape) print(X_val.shape, y_val.shape) print(X_test.shape, y_test.shape)

循环里,X取当前位置往后 lookback 个点,y取后面 horizon 个点。因为 LSTM 只看过去不看未来,所以窗口严格保持时间先后。三个集合分开切,验证集和测试集的开头会各损失 lookback 个点,这是正常现象,不需要额外处理。如果数据量很大,可以用sliding_window_view替代循环加速,但中小型课程数据集用循环更直观,也不会慢到哪去。

4. 构建 LSTM 模型并调出“95 分”的四个关键参数

4.1 用 TensorFlow/Keras 搭一个可复现的 LSTM 模型

环境建议是 Python 3.10 以上,TensorFlow 2.13 或 2.15 都能直接跑,安装时用pip install tensorflow。如果不想让首次运行等太久,可以先用 CPU 训练,数据量在几千条时单轮通常只要几秒到十几秒。下面这段代码是课程作业里最常见、也最容易解释清楚的两层 LSTM 结构。

from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dropout, Dense from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau 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", loss="mse", metrics=["mae"]) model.summary() history = model.fit( X_train, y_train, validation_data=(X_val, y_val), epochs=100, batch_size=32, callbacks=[ EarlyStopping(monitor="val_loss", patience=15, restore_best_weights=True), ReduceLROnPlateau(monitor="val_loss", factor=0.5, patience=5, min_lr=1e-5) ], verbose=1 )

第一层LSTM(64, return_sequences=True)输出的是每个时间步的隐状态,给第二层 LSTM 继续处理;第二层return_sequences=False只返回最后一个时间步的输出,再接一个Dense(1)产生预测值。input_shape不写 batch 维度,只写(lookback, 1),和前面切出来的 X 形状对齐。

EarlyStopping是拿到高分的关键。它监控验证集 loss,如果连续 15 轮没有下降,就停止训练并恢复到验证集 loss 最低时的权重。ReduceLROnPlateau在验证集 loss 连续 5 轮不降时把学习率减半,通常能避免 loss 卡在高位震荡。训练结束后,history对象里保存了每一轮的训练和验证 loss,可以直接拿来画曲线。

4.2 units、lookback、dropout、batch_size 的取舍

这四个参数是 LSTM 时间序列预测大作业里最常被问到的调参项,直接决定了模型是欠拟合还是过拟合。

参数建议范围效果偏小效果偏大
units32 到 128学不到复杂周期,训练 loss 高过拟合,验证 loss 升高
lookback12 到 48看不到日周期或周周期输入噪声太多,训练变慢
dropout0.1 到 0.3正则化不足模型欠拟合,训练 loss 降不下去
batch_size16 到 64梯度抖动,收敛慢内存占用大,收敛不稳定

units 决定 LSTM 的“记忆容量”。流量预测这类数据用 32 和 64 通常已经足够,超过 128 反而容易把噪声也记住。lookback 应该结合数据本身的周期来定:小时数据至少看 24 步,如果做周预测就再看 168 步,但 lookback 过大时序列样本数量会减少,需要根据数据集长度权衡。dropout 加在 LSTM 层之间,而不是输入层;不要一开始就加到 0.5,否则模型很难收敛。batch_size 一般取 32,如果训练集只有几百条,可以降到 16。

4.3 训练曲线和早停怎么看

训练结束后,用plt.plot(history.history["loss"])plt.plot(history.history["val_loss"])画两条曲线。正常情况是两者都下降,然后趋于平缓。如果训练 loss 一直降但验证 loss 先降后升,说明在第 10 到 20 轮左右开始过拟合,这时把EarlyStopping的 patience 调小,或者加大 dropout。

一种容易被忽视的情况是验证 loss 一开始就是nan。常见原因有两个:一是训练数据里出现无穷大值,归一化前没有做np.isinf排查;二是学习率太大,导致梯度爆炸。可以把ReduceLROnPlateau的初始学习率设成1e-3,再在 compile 时传入optimizer=tf.keras.optimizers.Adam(learning_rate=1e-3)。如果 loss 还是震荡,就把clipnorm=1.0加进 Adam 优化器,梯度范数超过 1 就会被缩放,这是 LSTM 训练里稳定性的最后一道保障。

5. 预测结果还原、误差指标和 LSTM 时间序列大作业文档说明的加分写法

5.1 预测结果逆归一化与误差指标

模型输出的是 0 到 1 之间的归一化值,要还原成原始流量单位,必须用训练时保存的scaler做逆变换。注意scaler是在整个train_raw上拟合的,所以逆变换时可以传入整列预测值。

pred = model.predict(X_test, verbose=0) pred_flat = pred.reshape(-1, 1) y_test_flat = y_test.reshape(-1, 1) pred_inv = scaler.inverse_transform(pred_flat) y_test_inv = scaler.inverse_transform(y_test_flat) mae = np.mean(np.abs(pred_inv - y_test_inv)) rmse = np.sqrt(np.mean((pred_inv - y_test_inv) ** 2)) ss_res = np.sum((y_test_inv - pred_inv) ** 2) ss_tot = np.sum((y_test_inv - np.mean(y_test_inv)) ** 2) r2 = 1 - ss_res / ss_tot print(f"MAE: {mae:.3f}, RMSE: {rmse:.3f}, R2: {r2:.3f}")

MSE在训练时作为损失函数,但汇报时用MAERMSE更直观,数值单位与原数据相同。越接近 1 说明预测越接近真值,但要注意如果测试集本身波动很小,R² 会虚高,所以最好同时报告 MAE。绘图时只画测试集前 200 个真实值与预测值对比,避免序列太长导致曲线糊成一团。

5.2 文档说明里写清四个部分

课程大作业的文档说明决定最终分数上限,代码之外最值得花时间的是一张数据划分表和一段参数实验记录。按顺序写四个部分:

第一,问题定义。说明预测对象是什么、时间粒度是多少、为什么选 LSTM 而不是 ARIMA。第二,数据与预处理。写清楚原始数据集来源字段含义、缺失值处理方式、训练验证测试的划分比例,以及划分时是否打乱顺序。第三,模型结构。给出网络层数和每层参数数量,贴出model.summary()的输出,再列一张超参数表。第四,实验结果。放预测对比图、loss 曲线、MAE/RMSE/R² 三个指标,最后写一句“在当前数据集上,模型在汛期峰值处误差偏大,可能原因是训练样本中高流量样本较少”,这种老实的局限分析比一味说“模型效果很好”更拿分。

交付时把源码、文档说明、数据集和已训练权重放在同一个目录,使用相对路径读取数据,并把随机种子固定为np.random.seed(42)。这样老师按顺序运行 ipynb 单元格就能得到完全一致的图,整份作业的可信度会明显超过只贴一段训练代码的提交。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询