简介:这是一份面向高校课程设计与期末大作业的LSTM时间序列预测Python项目源码,适合具备Python基础、希望快速上手深度学习预测任务的在校学生参考。项目已获高分通过,包含完整可运行代码、公开数据集与说明文档,覆盖数据预处理、模型训练、评估与结果可视化等关键环节。压缩包共31个文件,以Python脚本、CSV数据文件、PNG图表及工程配置文件为主,整体大小28.48MB,目录结构清晰,便于直接部署和二次修改;其中14张PNG结果图可直观查看预测曲线与误差情况,3个CSV数据集支持多场景验证。代码中不仅实现了LSTM模型,还额外提供RNN与Transformer对比实现,方便分析不同循环结构在预测任务上的表现差异;数据集涵盖电力负载、污染物等多类时间序列,有助于验证模型的泛化能力。目前已有1026人学习该资源,说明其在实际作业和课程设计中具有较高参考价值。下载后按说明配置环境即可运行,既可作为期末作业的完整模板,也能用于答辩演示或进一步研究的基础。
1. 拿到这个期末大作业压缩包,先搞懂 LSTM 时间序列预测到底在做什么
如果你下载过一个名字叫「时间序列预测(LSTM模型)python代码实现源码(期末大作业).zip」的压缩包,多半是要交一门机器学习的课程设计。里面通常是一段 Python 脚本、一组历史数据、几张预测曲线图,外加一份实验报告模板。你要做的不是把代码跑通就交,而是能讲清每一步在干什么、参数为什么这么设、预测结果为什么长这样。LSTM 时间序列预测的核心,是用过去一段连续观测值去预测未来若干个值——比如用过去 30 天的客流量预测明天的数值。它和普通回归最大的区别是:数据有前后依赖,顺序不能乱,时间窗口决定模型能看到多远的“记忆”。这篇文章按照我实际做过的期末大作业和工业小项目的经验,把从数据处理、模型搭建到最终出图的完整路径拆开讲,新手能照着复现,熟手也能对照检查自己的参数和边界。
2. 读懂 LSTM 预测代码前,先把数据形态和时间窗口这两个地基打牢
很多同学拿到的源码第一眼是懵逼的:reshape、look_back、MinMaxScaler满天飞。其实这些都是在解决同一个问题——把普通表格数据变成 LSTM 能吃进去的“序列样本”。先把这个弄明白,后面所有代码都顺了。
2.1 单变量还是多变量:先看你的 CSV 长什么样
打开压缩包里的数据文件,最常见的两种格式:
- 单变量:只有一列数值,比如某日客流量、某设备温度、某股票收盘价。你要预测的就是这一列的未来值。
- 多变量:有多列,比如天气、湿度、节假日标记,外加一个目标列。模型要用这些特征一起预测目标。
LSTM 的输入张量形状是(样本数, 时间步长, 特征数)。单变量时特征数是 1,多变量时特征数是列数。源码里如果有data[:, 0]这种切片,说明它只取了第一列,属于单变量预测。如果你手里的数据有多列,却只喂一列,等于白白扔掉了辅助信息。
处理时我一般先打印数据概览,确认有没有缺失值和异常值:
import pandas as pd df = pd.read_csv('data.csv', encoding='utf-8') print(df.head()) print(df.info()) print(df.isnull().sum())这里的head()看前五行结构,info()看列类型和数量,isnull().sum()检查空值。如果有缺失,常见做法是前一行的值填充,或者线性插值,千万不要直接删行——时间序列一旦删行,时间间隔就乱了。如果数据里有日期列,建议先解析成datetime类型并设为索引,后面画图时 x 轴才正常。
2.2 时间窗口(look_back)怎么选:3 个必调参数和一组经验值
时间窗口,也叫look_back或seq_len,是指用过去几个时刻的数据预测当前时刻。源码里通常有一个全局变量:
look_back = 10 # 用前10个时刻预测当前时刻这个值直接决定了模型能看到的“记忆长度”。选太大,模型参数量增加、训练变慢,而且可能引入过多噪声;选太小,模型看不到周期性。三个相关参数:
look_back:时间步长。对日级别数据,如果存在周周期,至少取 7;月周期取 30。n_features:输入特征数,单变量填 1。n_outputs:预测目标数的另一种写法,常见于多步预测,源码里可能没有。
经验值:数据点只有几百条时,look_back取 5~15 比较稳;数据量上千,可以取 20~60。别一上来就搞 100,样本数会被压得很少——因为每个样本要连续look_back个点,能构造出的样本总数是len(data) - look_back。窗口越大,可训练样本越少,二者是矛盾的。
2.3 数据归一化为什么必须做:MinMaxScaler 的用法与反归一化
LSTM 用的是 tanh 和 sigmoid 激活函数,对输入数值范围很敏感。如果原始数据是几千的大数,梯度会爆炸或消失,loss 根本降不下去。源码里出现MinMaxScaler就是干这个的:
from sklearn.preprocessing import MinMaxScaler scaler = MinMaxScaler(feature_range=(0, 1)) scaled_data = scaler.fit_transform(df['value'].values.reshape(-1, 1))注意两件事。第一,fit_transform只能用在训练集上,测试集要单独transform,不能重新fit,否则测试集的信息会泄露到训练过程里。第二,预测出来的结果也是 0~1 之间的缩放值,要看到真实数值,必须做反归一化:
predicted = model.predict(X_test) predicted = scaler.inverse_transform(predicted)我见过不少大作业就是忘了最后这一步,画出来的预测曲线全在 0 到 1 之间,和真实曲线差一个数量级,直接扣分。另外,如果时序数值波动剧烈,也可以考虑StandardScaler,但 LSTM 配 MinMax 是最常见、最不容易出错的组合。
3. 用 PyTorch 还是 TensorFlow/Keras 实现:以 Keras 为例把模型搭起来
压缩包里的模型实现,大概率是 Keras 或 PyTorch 二选一。先说结论:期末大作业图省事,Keras 的Sequential最直接;如果想在报告里多写点自定义结构,PyTorch 更灵活。但无论哪种,核心的 LSTM 层定义思路是一致的。
3.1 二选一之前,先看期末大作业最常见的打分点
一般老师评分的点无非这几个:能跑通、预测曲线和真实曲线贴合、有 loss 曲线、有模型结构说明、有参数分析。用 Keras 写十几行就能搭完,不容易在框架语法上翻车。PyTorch 需要自己写训练循环,代码量多一倍,但如果你之前用过 PyTorch,或者要展示nn.LSTM的细节,也不难。
我一般建议:压缩包里如果已经带了 Keras 代码,就顺着它改;如果让你自己选,优先 Keras。环境配置上,pip install tensorflow keras pandas matplotlib scikit-learn就够了。注意新版 Keras 可能内置在 TensorFlow 里,导入方式为:
from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense3.2 搭一个能跑的 LSTM 模型:核心代码与逐段说明
以单变量、单步预测(用前look_back个点预测后 1 个点)为例,典型模型如下:
from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense model = Sequential() model.add(LSTM(units=50, return_sequences=True, input_shape=(look_back, 1))) model.add(LSTM(units=50, return_sequences=False)) model.add(Dense(units=1)) model.compile(optimizer='adam', loss='mse')逻辑说明:第一个LSTM层设置return_sequences=True,原因是它后面还要接一个 LSTM 层,必须输出完整序列给下一层;第二个LSTM层设置return_sequences=False,只输出最后一个时间步的结果,然后接一个Dense层输出预测值。units=50表示这一层有 50 个 LSTM 记忆单元,单元数越多,模型表达能力越强,但也越容易过拟合。loss='mse'因为预测连续数值,均方误差是最自然的损失函数。
这里有个常见改法:如果你是多变量预测,input_shape=(look_back, n_features)里的第二维要改成特征数,n_features对应你的列数。还有,如果不想堆两层 LSTM,完全可以只留一层,return_sequences就不用了:
model.add(LSTM(units=50, input_shape=(look_back, 1)))两层 LSTM 能捕捉更抽象的时序特征,但数据量小的时候一层反而更稳。什么意思?数据长度只有几百时,两层 LSTM 的参数量几乎翻倍,训练集又少,很容易在训练集上拟合得好、测试集上一塌糊涂。数据量少于 1000 个点,我建议先用一层试跑。
3.3 训练参数怎么定:epochs、batch_size、optimizer 的取舍
模型编译好之后,fit里三个参数最常被反复调:
history = model.fit( X_train, y_train, epochs=100, batch_size=32, validation_split=0.2, verbose=1 )epochs=100:训练轮数。不是越大越好——loss 降到一定程度后就不再下降,甚至开始反弹。我会先跑 50 轮看 loss 曲线,如果还在明显下降,再往上加。
batch_size=32:每批喂多少个样本。它影响收敛速度和显存占用。数值越小,梯度更新越频繁,训练震荡越厉害;越大越稳定,但对小数据集可能陷入局部最优。32 和 64 是大多数场景的默认选择。
validation_split=0.2:从训练集里切出 20% 当验证集,用来观察有没有过拟合。如果验证 loss 先降后升,而训练 loss 一直降,那就是过拟合,需要减少units、增加Dropout或减小look_back。
optimizer='adam'基本不用换,它自适应学习率,比 SGD 省心得多。如果你发现 loss 抖动剧烈,可以试试把学习率调到0.001以下,但用adam默认值通常就够。
4. 训练到预测的完整流程:从 train_test_split 到 inverse_transform
数据处理好了,模型也搭起来了,接下来是串联。这一章看的是源码里从train_test_split到predict再到画图的整条链路,也是期末报告里流程图最常照抄的部分。
4.1 数据切分:千万不能把时间序列随机打乱
很多人写机器学习代码习惯了train_test_split(X, y, test_size=0.2, shuffle=True),轮到时间序列也这么干,结果模型“偷看”了未来,测试集上准确率奇高,一换新数据就被打回原形。时间序列的切分必须按时间顺序:
train_size = int(len(scaled_data) * 0.8) train_data = scaled_data[:train_size] test_data = scaled_data[train_size:]这里不shuffle,保持原始时间顺序。train_size取前 80%,剩下的 20% 作为测试集。还有一种更符合时间序列的做法是用TimeSeriesSplit,但期末作业里手动切片足以说明问题。
接下来构造样本对。假设原始序列是[x0, x1, x2, ..., xn],用前look_back个点预测下一个点,生成方式如下:
import numpy as np def create_dataset(data, look_back=10): 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]) return np.array(X), np.array(y)这段代码的逻辑:遍历序列,每次取i到i+look_back-1共look_back个值作为输入,第i+look_back个值作为目标。最终X的形状是(样本数, look_back),y是(样本数,)。注意,训练集和测试集要分别调用这个函数,不能把整个序列先构造好再切——否则测试集样本可能混进训练集前面的点,造成信息重叠。
给 LSTM 输入前,还得把Xreshape 成三维:
X_train = X_train.reshape(X_train.shape[0], X_train.shape[1], 1) X_test = X_test.reshape(X_test.shape[0], X_test.shape[1], 1)这里第三维是特征数 1。如果你多变量,这一维对应列数。很多人reshape报错,基本都是因为X_train的 shape 和input_shape对不上。打印一下X_train.shape,把(样本数, look_back)变成(样本数, look_back, 1),模型就能吃了。
4.2 训练过程与 loss 曲线的判读
训练结束后,源码里一般会画 loss 曲线,代码大致是:
import matplotlib.pyplot as plt plt.plot(history.history['loss'], label='train_loss') plt.plot(history.history['val_loss'], label='val_loss') plt.legend() plt.show()判断标准很简单:两条曲线都下降并趋稳,说明训练正常。如果val_loss明显高于train_loss且还在上升,是过拟合。如果两条曲线都在高位波动不降,可能是数据没归一化、学习率太大、或者look_back太小。这里最容易翻车的点是:只贴训练集上的预测图,不贴测试集。期末报告里,测试集上的预测曲线才最有说服力。
4.3 用训练好的模型做多步预测的两种常见写法
部分大作业要求预测未来 7 天或 30 天,这时单步预测就不够了。两种常见方案:
方案一:递归预测。每预测出一个新值,把它拼到输入序列末尾,丢掉最前面的旧值,再预测下一个。代码类似:
last_sequence = X_test[-1] # 形状 (look_back, 1) predicted = [] for _ in range(7): pred = model.predict(last_sequence.reshape(1, look_back, 1))[0, 0] predicted.append(pred) last_sequence = np.roll(last_sequence, -1, axis=0) last_sequence[-1, 0] = prednp.roll把所有值往前移一位,最后一位填入新预测值。注意,误差会随步数累积——第一步预测的小偏差,会成为第二步的输入,然后被放大。所以递归预测只适合短期几步,7 步以内还能看,30 步以上基本飘了。
方案二:多输出预测。把Dense的输出改成目标步数,比如预测未来 7 天,Dense(7),训练集的y也要相应改成连续 7 个未来值。这需要重新构造数据集,复杂一些,但每一步预测不依赖前面步的结果,误差不会累积。期末大作业如果只要求“预测趋势”,递归预测就够用;如果要求“预测未来 N 天数值”,建议用多输出。
5. 期末大作业避坑指南:5 个最容易让你翻车的细节
这一章是血泪经验汇总,每一条都是我在跑 LSTM 预测代码时真实遇到过的。现象、原因、解决,按这个顺序写清楚。
5.1 现象:预测曲线比真实值“平移”了一段
你画出来的预测曲线和真实曲线形状几乎一样,但整体向右(或向左)偏移了一个时间点,看起来像“抄了一段过去”。原因:很多人构造训练样本时把y取错了位置。比如应该用data[i+look_back]作为预测目标,结果写成了data[i+look_back-1],相当于模型学到的是“把上一个时间点的值搬过来”。解决:核对create_dataset里y.append(data[i+look_back, 0]),确保预测目标是窗口之后的第一个点。如果代码没错,还有一种可能——数据本身做了一阶差分,但预测完忘了把差分还原。
5.2 现象:训练 loss 降了,但预测结果全是一条直线
这是 LSTM 最常见的病。原因通常是两个:一是损失函数用mae或mse,模型发现“输出所有样本的平均值”就能让 loss 降到很低,尤其是当序列变化不大、预测步长又短的时候。二是look_back设得过大,模型过度依赖最近几个点的平均值,学不到波动规律。解决:改用loss='mse'并加入early stopping;减小look_back;或者检查数据是否被错误地排序了。如果是多步预测,递归预测时直线问题更明显,因为第一步预测的误差会把后续预测压平。
5.3 现象:用测试集整体预测时,结果好得离谱
有些源码喜欢这样操作:把整个测试集的输入一次性喂给模型,得到一串预测,然后画图和真实测试序列对比。如果这串预测和真实值贴合得太完美,多半有问题——因为X_test里的每个样本和y_test的对应位置,存在窗口重叠:第 i 个样本的输入包含第 i+1 个样本的输入的一部分,导致测试集的样本不是独立的。更隐蔽的是,如果你的数据预处理用了全局MinMaxScaler的fit_transform(在整个原始序列上做的),测试集的信息已经被模型间接见过了。解决:先切分训练测试,再在训练集上fit缩放器,测试集只transform。这也是为什么前面强调,scaler.fit_transform不能用在全部数据上。
5.4 现象:中文路径导致读取数据报错
压缩包解压后,数据文件或代码路径带中文,比如C:\Users\张三\期末作业\data.csv,在 Windows 上用 pandas 读取时可能报UnicodeDecodeError或路径找不到。原因不是代码错了,而是 Python 的默认编码策略在中文路径上容易出问题。解决:一是把整个项目移到纯英文路径下,例如D:\workspace\lstm_project,这是最快的方法;二是如果有中文读文件需求,用pd.read_csv(file_path, encoding='utf-8'),文件本身是 GBK 编码时换成encoding='gbk'。期末交作业前,最好把压缩包的解压路径和文件夹名都改成英文,避免老师那边打开直接报错。
5.5 现象:LSTM 单元数设太大,训练慢且过拟合
看到别人的模型用 128 个单元,自己也照抄,结果几百条数据训练了十分钟还没收敛,测试集预测结果更是惨不忍睹。原因:单元数决定了模型容量,小数据集配上大容量模型,必然过拟合。解决:小数据量(<1000 条)用 32~50 个单元足够;如果加了Dropout(0.2),可以适当加大到 64。再一个参数是return_sequences=True的两层结构,数据量小的时候改成单层,参数量直接减半,训练速度明显提升。
6. 把大作业从“能跑”做到“高分”:3 个验证技巧和一份报告结构
到这里代码基本能跑通,但期末大作业的分数往往不在代码本身,而在你怎么验证它、怎么把结论写进报告。分享三个我常用的验证技巧。
第一个技巧是残差图。预测值与真实值相减,画一条残差曲线。如果残差随机分布在零轴附近,说明模型捕捉到了主要规律;如果残差有明显周期性或趋势性,说明还有信息没学到,可以增加look_back或增加 LSTM 层数。这个图老师最爱看,因为它比预测曲线更能说明问题。
第二个技巧是基准对比。拿一个简单模型当参照——比如“用昨天的值预测今天”或者“用过去 7 天平均值预测今天”。如果 LSTM 连这个基准都打不过,那说明要么数据没有强时序依赖,要么你的模型调参有问题。对比结果直接写进报告,能证明你做了充分的实验分析。
第三个技巧是多组参数实验。把look_back分别设为 5、10、20,记录每种配置下的测试集 MSE,做成小表格。这比单纯贴一张最终预测图有说服力得多,也正好回应了老师最爱问的“为什么选这个窗口”。
报告结构我建议按这个顺序写:问题定义与数据描述 → 数据预处理方法(归一化、窗口构造) → 模型结构(LSTM 层数、单元数、为什么选这个结构) → 训练与调参过程(loss 曲线、参数对比表) → 测试集验证结果(预测图、残差图、MSE/RMSE 指标) → 结论与不足。每一段都要配图和代码片段,代码要用英文注释。
说一个我自己的习惯:每次跑完实验,我一定把模型结构和关键参数用三行话记录在报告开头,防止第二天自己都忘了look_back为什么设成 10。很多同学在答辩时被问“你的look_back怎么确定的”,支支吾吾半天。其实只要你做过那组对比实验,任何回答都比“我试出来的”强。
希望这篇笔记能帮你的 LSTM 时间序列预测大作业少走弯路,把“能跑”变成“能讲清楚”,这是期末拿高分最实在的一条路。
本文还有配套的精品资源,点击获取