
简介这份资源是一套基于LSTM的时间序列预测Python程序面向需要完成课程设计、期末大作业或入门深度学习预测任务的学生与开发者尤其适合Python基础薄弱、希望快速跑通完整项目的新手。程序围绕LSTM模型构建预测流程代码注释详尽并配有使用说明手册下载后按注释与文档即可部署运行无需从零搭建。压缩包共27个文件约8.85MB包含3个py源码文件、6个pyc缓存、5个xml配置、5个png与1个jpg示意图、2个xlsx数据表、1个md说明、1个pdf使用手册以及LSTM权重文件覆盖源码、数据、模型权重与文档等关键环节。目前已有666人学习下载说明其可参考性较强。读者可获得一套可直接运行的LSTM预测代码、配套数据与权重、图文说明及排错思路便于快速理解模型训练与预测流程也能作为大作业或课程设计的完整参考方案。1. 从一份「无脑代码」说起LSTM 时间序列预测到底能解决什么很多人第一次接触时间序列预测都是被一份号称「简单又好用」的 LSTM Python 程序带进门的。把历史数据丢进去跑几十行代码就能吐出未来一段时间的预测曲线看起来像玄学其实背后是一套相当成熟的建模流程。问题在于大部分「无脑代码」只给了能跑通的最小骨架一旦换成自己的数据要么预测出一条直线要么误差大到没法看于是开始怀疑 LSTM 是不是被吹过头了。这篇笔记想做的事情很具体把一份基于 LSTM 的时间序列预测 Python 程序从数据准备、窗口切分、模型搭建、训练调参到预测还原完整拆一遍让你拿到手能改、改完能跑、跑完知道结果为什么长这样。适合两类人一类是刚学完 Python 基础语法想找一个能落地的深度学习项目练手的新手另一类是手里有设备运行数据、销量数据、传感器读数想用 LSTM 做预测但被各种参数和维度劝退的工程师。核心词 LSTM、时间序列预测、Python 会贯穿始终但重点不是背概念而是把每一步的参数和坑讲清楚。2. 数据准备与窗口切分LSTM 时间序列预测的第一道坎2.1 为什么 LSTM 需要「滑动窗口」而不是直接喂原始序列时间序列预测的本质是用过去一段时间的值去推断未来的值。LSTM 作为循环神经网络虽然理论上能记住任意长的历史但实际训练时不可能把整条序列一次性塞进去一是显存扛不住二是梯度传播太长反而学不好。所以常见做法是把长序列切成一个个固定长度的窗口每个窗口作为一个样本窗口后面的那个值作为标签。举个例子假设你有一列按天记录的销量数据长度 1000。设定窗口长度 look_back 为 30意思是用前 30 天的数据预测第 31 天。那么第一个样本的输入是第 1 到第 30 天标签是第 31 天第二个样本的输入是第 2 到第 31 天标签是第 32 天依此类推。这样一条序列就能生成几百个训练样本既保留了时间顺序又满足了批量训练的需求。这里有个容易被忽略的点窗口长度不是随便定的。它决定了模型能「看到」多长的历史。窗口太短模型捕捉不到周期性窗口太长参数量和学习难度都会上升还容易过拟合。我一般会先画一下自相关图ACF看数据在滞后多少阶之后相关性明显下降把那个阶数作为窗口长度的起点再上下浮动试试。2.2 用 Pandas 和 NumPy 完成归一化与窗口切分下面这段代码是整套流程里最基础也最关键的一步。它做了三件事读取数据、归一化、切窗口。归一化用 MinMaxScaler 把数据缩放到 0 到 1 之间这是 LSTM 训练的常规操作因为 LSTM 内部的激活函数对输入范围敏感原始数据量纲差异大会导致收敛慢甚至不收敛。import numpy as np import pandas as pd from sklearn.preprocessing import MinMaxScaler # 读取数据假设只有一列目标值列名为 value df pd.read_csv(data.csv, parse_dates[date], index_coldate) values df[value].values.reshape(-1, 1) # 归一化fit 只在训练集上做避免数据泄露 scaler MinMaxScaler(feature_range(0, 1)) scaled scaler.fit_transform(values) def create_dataset(data, look_back30): 把序列切成 (样本数, look_back, 1) 的输入和 (样本数, 1) 的标签 X, y [], [] for i in range(len(data) - look_back): X.append(data[i:i look_back, 0]) y.append(data[i look_back, 0]) X np.array(X).reshape(-1, look_back, 1) y np.array(y).reshape(-1, 1) return X, y look_back 30 X, y create_dataset(scaled, look_back) print(X.shape, y.shape) # 例如 (970, 30, 1) (970, 1)逻辑说明create_dataset里的循环是滑动窗口的核心每次取look_back个连续点作为输入紧跟着的下一个点作为标签。reshape(-1, look_back, 1)这一步不能省LSTM 的输入要求是三维张量形状为样本数时间步长特征数。这里特征数是 1因为只用了单变量如果你有多个相关变量比如温度、湿度、压力一起预测某个指标特征数就变成对应的列数。参数说明look_back是最重要的超参数之一30 只是一个常见起点。数据周期性强就调大比如有明显月度周期可以设 30 或 60数据变化剧烈、噪声大就调小比如 10 到 15。feature_range默认是 (0, 1)如果数据里有明显异常值可以考虑改成 (-1, 1)但大多数场景 (0, 1) 够用。注意归一化的 scaler 一定要在训练集上 fit然后用同一个 scaler 去 transform 测试集和未来数据。如果全量数据一起 fit测试集的信息就泄露到训练过程里了评估结果会虚高。2.3 训练集、验证集、测试集怎么分才不翻车时间序列和普通机器学习最大的区别是不能随机打乱。因为打乱之后未来的数据可能跑到训练集里模型等于提前看到了答案。正确做法是按时间顺序切分比如前 70% 做训练中间 15% 做验证最后 15% 做测试。n len(X) train_end int(n * 0.7) val_end int(n * 0.85) X_train, y_train X[:train_end], y[:train_end] X_val, y_val X[train_end:val_end], y[train_end:val_end] X_test, y_test X[val_end:], y[val_end:] print(X_train.shape, X_val.shape, X_test.shape)验证集的作用是调参和早停测试集只在最后评估一次不要反复用测试集调参否则测试集就变成了验证集评估结果失去意义。如果数据量本身就不大比如只有几百个点可以只分训练和测试用交叉验证的思路在训练集内部做验证。3. 用 PyTorch 搭一个能跑的 LSTM 模型层数、隐藏单元和 Dropout 怎么定3.1 LSTM 层的关键参数input_size、hidden_size、num_layersPyTorch 里nn.LSTM的几个参数决定了模型的容量。input_size是每个时间步的特征数单变量预测就是 1。hidden_size是隐藏状态的维度也就是 LSTM 内部记忆的宽度这个值越大模型表达能力越强但参数量和过拟合风险也越高。num_layers是堆叠的 LSTM 层数一层通常够用两层可以捕捉更抽象的时序模式但超过两层在中小规模数据上收益很小反而容易训不动。我一般会从hidden_size64、num_layers1开始试如果欠拟合就加到 128如果过拟合就加 Dropout 或者减小 hidden_size。batch_firstTrue这个参数建议打开这样输入张量的形状是batch, seq, feature符合大多数人的直觉不然默认是seq, batch, feature调试的时候容易搞混。import torch import torch.nn as nn class LSTMModel(nn.Module): def __init__(self, input_size1, hidden_size64, num_layers1, dropout0.2): super().__init__() self.hidden_size hidden_size self.num_layers num_layers self.lstm nn.LSTM( input_sizeinput_size, hidden_sizehidden_size, num_layersnum_layers, batch_firstTrue, dropoutdropout if num_layers 1 else 0 ) self.fc nn.Linear(hidden_size, 1) def forward(self, x): # x: (batch, seq_len, input_size) out, _ self.lstm(x) # 取最后一个时间步的输出接全连接层 out self.fc(out[:, -1, :]) return out逻辑说明forward里只取了 LSTM 输出序列的最后一个时间步out[:, -1, :]因为预测目标是窗口之后的那一个值最后一个时间步已经聚合了整个窗口的信息。如果你要做多步预测比如一次预测未来 7 天可以把fc的输出维度改成 7或者用 Seq2Seq 的结构但那是另一个话题了。参数说明dropout只在num_layers 1时生效这是 PyTorch 的设计单层 LSTM 加 dropout 不会报错但也不起作用。hidden_size和num_layers是最值得花时间调的两个参数建议用网格搜索或者手动试几组记录验证集损失。3.2 训练循环损失函数、优化器和早停训练部分看起来模板化但有几个细节决定了模型能不能收敛。损失函数用 MSELoss因为回归任务优化器用 Adam学习率从 1e-3 开始早停Early Stopping是防止过拟合的后悔药验证集损失连续若干轮不下降就停止训练。from torch.utils.data import DataLoader, TensorDataset # 转成 Tensor 并构建 DataLoader train_ds TensorDataset(torch.tensor(X_train, dtypetorch.float32), torch.tensor(y_train, dtypetorch.float32)) val_ds TensorDataset(torch.tensor(X_val, dtypetorch.float32), torch.tensor(y_val, dtypetorch.float32)) train_loader DataLoader(train_ds, batch_size32, shuffleFalse) val_loader DataLoader(val_ds, batch_size32, shuffleFalse) device torch.device(cuda if torch.cuda.is_available() else cpu) model LSTMModel(input_size1, hidden_size64, num_layers1).to(device) criterion nn.MSELoss() optimizer torch.optim.Adam(model.parameters(), lr1e-3) best_val_loss float(inf) patience, wait 10, 0 for epoch in range(200): model.train() for xb, yb in train_loader: xb, yb xb.to(device), yb.to(device) optimizer.zero_grad() pred model(xb) loss criterion(pred, yb) loss.backward() optimizer.step() model.eval() val_loss 0 with torch.no_grad(): for xb, yb in val_loader: xb, yb xb.to(device), yb.to(device) val_loss criterion(model(xb), yb).item() val_loss / len(val_loader) if val_loss best_val_loss: best_val_loss val_loss torch.save(model.state_dict(), best_model.pth) wait 0 else: wait 1 if wait patience: print(fEarly stop at epoch {epoch}) break逻辑说明shuffleFalse在时间序列里很重要因为 DataLoader 的 shuffle 会打乱样本顺序虽然窗口之间本身有重叠但打乱后 batch 内的时序结构就没了。早停的逻辑是每次验证损失创新低就保存模型连续patience轮没进步就停。这样最终用的是验证集上最好的模型而不是最后一轮的模型。参数说明batch_size一般 16 到 64 之间数据量小就用小 batch。lr从 1e-3 开始如果损失震荡就降到 1e-4如果收敛太慢就升到 3e-3 试试。patience设 10 到 20 比较稳妥太小容易早停太大浪费训练时间。3.3 预测与反归一化把 0 到 1 的数字还原成真实值模型输出的是归一化后的值必须用之前 fit 好的 scaler 做逆变换才能和真实值对比。这一步经常被忽略导致预测结果看起来「小得离谱」。model.load_state_dict(torch.load(best_model.pth)) model.eval() with torch.no_grad(): X_test_tensor torch.tensor(X_test, dtypetorch.float32).to(device) pred_scaled model(X_test_tensor).cpu().numpy() # 反归一化 pred scaler.inverse_transform(pred_scaled) true scaler.inverse_transform(y_test) # 计算误差 from sklearn.metrics import mean_absolute_error, mean_squared_error mae mean_absolute_error(true, pred) rmse np.sqrt(mean_squared_error(true, pred)) print(fMAE: {mae:.4f}, RMSE: {rmse:.4f})逻辑说明inverse_transform要求输入形状和 fit 时一致所以pred_scaled是 (n, 1) 的二维数组。如果模型输出是一维的需要先 reshape。MAE 和 RMSE 是最常用的两个指标MAE 反映平均绝对偏差RMSE 对大误差更敏感。两个一起看如果 RMSE 远大于 MAE说明存在个别预测偏差很大的点需要检查数据里有没有异常值。4. 调参与排错LSTM 时间序列预测最常见的五个坑4.1 预测结果是一条直线现象模型训练损失下降但预测出来的曲线几乎是一条水平线完全没有波动。原因最常见的是归一化之后数据范围太小或者学习率太低导致模型学不到变化另一个可能是窗口长度太短模型看不到足够的上下文。还有一种情况是数据本身噪声极大模型学到的均值就是最优解。解决先检查归一化前后的数据分布确认没有把有效信号压扁。然后尝试增大look_back比如从 30 加到 60 或 90。如果还不行把学习率调大一个数量级试试。最后检查数据本身如果信噪比极低LSTM 也救不了需要先做滤波或特征工程。4.2 训练损失下降但验证损失上升现象训练集上的损失一路走低验证集损失先降后升典型的过拟合。原因模型容量相对于数据量太大或者训练轮数太多。hidden_size设得过大、num_layers堆得太多都会加剧这个问题。解决先加 Dropout单层 LSTM 可以在全连接层前加nn.Dropout(0.2)。然后减小hidden_size比如从 128 降到 64 或 32。再不行就减少num_layers一层往往够用。早停也是必须的不要等到训练损失降到接近零才停。4.3 输入维度报错expected 3D input现象运行时报错RuntimeError: input must have 3 dimensions, got 2。原因LSTM 要求输入是三维张量batch, seq, feature但create_dataset里 reshape 写错或者 DataLoader 取出的 batch 被压成了二维。解决在create_dataset里确认reshape(-1, look_back, 1)这一步。如果用了DataLoader检查TensorDataset里的张量形状。调试时可以在forward里打印x.shape一眼就能看出问题。4.4 预测值整体偏移现象预测曲线的形状和真实曲线很像但整体偏高或偏低一个固定量。原因归一化时用的scaler和反归一化时用的不是同一个或者训练集和测试集分别 fit 了不同的 scaler。解决全局只创建一个 scaler在训练集上 fit然后 transform 所有数据。反归一化时用同一个 scaler 的inverse_transform。如果数据有明显的趋势MinMaxScaler 会把趋势也压缩可以考虑先做差分再归一化。4.5 多变量输入时特征顺序搞混现象模型能跑但效果比单变量还差。原因多变量输入时input_size要等于特征数且每个特征的顺序在训练和预测时必须一致。如果训练时是 [温度, 湿度, 压力]预测时变成 [压力, 温度, 湿度]模型就懵了。解决把特征列的顺序固定下来写成一个列表训练和预测都按这个列表取数。归一化时对每一列分别做或者用MinMaxScaler的二维模式整体做但要注意反归一化时只取目标列。5. 让预测更稳的几个进阶技巧从单步到多步、从单变量到多变量5.1 多步预测的两种做法直接法和递归法单步预测只能预测下一个点实际场景往往需要预测未来一段。直接法是让模型输出维度等于预测步数比如fc nn.Linear(hidden_size, 7)一次吐出 7 个值。递归法是用预测出的第一个值拼到输入窗口末尾再预测第二个循环下去。直接法训练稳定但步数固定递归法灵活但误差会累积。我一般先用直接法步数不多于 10 步时效果通常更好。5.2 多变量输入把相关特征拼进窗口如果你有多个相关变量比如预测电力负荷时同时有温度、湿度、节假日标记可以把它们作为额外特征拼进输入。create_dataset里把data[i:ilook_back, 0]改成data[i:ilook_back, :]input_size改成特征数。注意目标列也要在特征里但预测时只取目标列的反归一化结果。5.3 用验证集曲线判断该调哪个参数训练时把训练损失和验证损失画在同一张图上是最直观的调参依据。两条线都高且下降慢说明欠拟合加hidden_size或num_layers。训练线低验证线高且分叉早说明过拟合加 Dropout 或减容量。两条线都震荡说明学习率太大或 batch_size 太小。这个习惯帮我省了很多瞎试的时间。5.4 一个我常用的基线对比习惯每次跑 LSTM 之前我会先跑一个最简单的基线用窗口内最后一个值直接作为预测值或者用窗口均值。如果 LSTM 连这个基线都打不过说明要么数据本身没有可学的模式要么预处理出了问题。这个习惯听起来笨但能避免在无效方向上浪费大量时间。希望帮到你。本文还有配套的精品资源点击获取