简介:面向Python数据挖掘与深度学习初学者的LSTM时间序列预测项目,以空气污染数据为实例,完整走通数据读取、清洗、归一化、滑窗构造、模型搭建、训练评估与预测可视化的全流程。压缩包共含129个文件,核心为78个Python脚本,按配置、数据预处理、模型定义、训练、测试与画图等模块拆分;26个CSV文件提供原始及清洗后的多变量时序数据;16个TXT为运行日志与说明;另有H5权重文件及TensorFlow checkpoint,训练中间结果也可直接加载或继续调优。整包约5.42MB,部署轻量,依赖环境常见。源码中关键步骤均含注释,对LSTM输入维度、时间步长、归一化与反归一化等细节有清晰标注,新手也能读懂并二次开发,适合作为期末大作业或课程设计提交。当前已有169人浏览学习,下载后按注释修改数据路径即可运行,无论是期末验收、答辩演示还是后续扩展调优,都能提供完整支撑。
1. 基于LSTM的时间序列预测项目:一份能直接跑通、能写进期末答辩的Python源码
这份资源是一个完整的期末大作业项目,核心是基于LSTM(长短期记忆网络)对空气污染时间序列数据进行预测,对应文件列表里的 pollution.csv、air_pollution.csv 以及 checkpoint 文件。它不只是散落的代码片段,而是包含训练好的模型权重、原始CSV数据、处理后的数据集和带注释的Python脚本——换句话说,下载解压、配好环境就能跑出预测结果,用自己的测试数据也能套用。
我拆完这套源码后最深的感受是:它不是那种只给你看个截图、代码却跑不通的“演示型作业”。数据预处理、LSTM模型定义、训练、预测绘图这几个环节都能直接复用,且注释密度足够,新手对照着改参数就能理解每一步在干什么。适合正在做期末大作业、课程设计,或想快速上手LSTM时间序列预测的Python开发者直接下载复现,省下从零调通网络结构的时间。接下来我会按“数据 → 模型 → 训练 → 避坑 → 验证”的顺序,把这套源码的用法和边界一次讲透。
2. 读数据与预处理:pollution.csv里到底存了什么,LSTM输入长什么样
2.1 先看数据:多变量时间序列与单变量预测的取舍
项目里出现了多个CSV文件,从文件名看是同一份数据的不同处理阶段。原始 pollution.csv 的常见结构是每小时一条记录,包含温度、气压、风速等气象特征,以及核心的污染指标列。这份资源的典型做法是:把污染指标作为预测目标,其他气象列作为辅助特征。
import pandas as pd df = pd.read_csv('pollution.csv', index_col=0) print(df.head()) print(df.columns.tolist()) print(df.isnull().sum())以上代码用来检查数据规模和缺失值分布。大多数公开的空气污染数据集都会在部分时间戳上缺值,常见做法是直接丢弃或前向填充,否则LSTM在训练时会把NaN当成一个异常输入,导致loss骤降的假象。
df = df.dropna() df = df[['pollution', 'temp', 'pressure', 'wind_speed']] # 按需选特征列这里我一般建议先跑单变量基线模型,即只用 pollution 这一列做预测,验证网络结构没问题后,再决定是否引入气象特征。原因是多变量输入会同步放大数据对齐和归一化的坑,而单变量模型更容易定位问题出在结构还是数据上。
2.2 归一化不能省:MinMaxScaler的边界与反变换
LSTM对输入尺度敏感,tanh/sigmoid 激活函数会把大数值输入压到饱和区。这套源码里通常会使用 sklearn 的 MinMaxScaler,把所有特征压缩到 [0,1] 区间,同时必须保存好 scaler 对象,因为预测时要反归一化回真实量纲。
from sklearn.preprocessing import MinMaxScaler scaler = MinMaxScaler(feature_range=(0, 1)) scaled_data = scaler.fit_transform(df) # 训练测试划分 train_size = int(len(scaled_data) * 0.8) train_data = scaled_data[:train_size] test_data = scaled_data[train_size:]注意这里有一个高频翻车点:fit_transform必须只对训练集做,测试集用transform,否则测试集的信息被提前泄露到scaler里,导致验证指标虚高,期末答辩时如果被老师追问数据泄漏问题会很难解释。
2.3 构造监督学习样本:滑动窗口与labels对齐
LSTM不是直接吃一整条时间序列的,它需要你把数据切成“用过去N个小时预测未来1小时”的监督学习格式。项目源码里的核心函数通常长这样:
def create_sequences(data, seq_len): X, y = [], [] for i in range(len(data) - seq_len): X.append(data[i:i + seq_len]) y.append(data[i + seq_len, 0]) # 预测目标取第0列 return np.array(X), np.array(y) seq_len = 24 X_train, y_train = create_sequences(train_data, seq_len) X_test, y_test = create_sequences(test_data, seq_len) print(X_train.shape) # (样本数, 24, 特征数)参数说明:seq_len=24表示用过去24小时的记录预测下1小时,这是单步预测;如果作业要求预测未来24小时,可以改成多步输出或用递归预测。np.array(X)要求生成三维数组,维度顺序是(样本数, 时间步, 特征数)。数据量不大时用这种循环切分没问题,数据量大可以改用滑动窗口View法提速。
3. LSTM模型搭建与训练细节:从keras.Sequential到fit的全参数解读
3.1 网络结构:一层还是两层LSTM,units怎么定
项目源码里最核心的网络结构一般是一个Sequential模型,典型配置是两层LSTM加一个Dense输出层。单层LSTM适合简单序列,双层的记忆表达能力更强,但训练更慢且更容易过拟合。源码用双层,说明原作者的思路是用更深的网络来拟合污染数据的周期性和趋势性。
from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout model = Sequential() model.add(LSTM(units=64, return_sequences=True, input_shape=(seq_len, X_train.shape[2]))) model.add(Dropout(0.2)) model.add(LSTM(units=32, return_sequences=False)) model.add(Dropout(0.2)) model.add(Dense(units=1)) model.compile(optimizer='adam', loss='mse', metrics=['mae'])参数说明:第一层return_sequences=True是为了把完整的时间步输出传给第二层LSTM,第二层return_sequences=False只输出最后一个时间步的隐状态,再接Dense层映射为1个预测值。units控制LSTM细胞数量,64→32是常见降维配置,数据量大可以改成128→64,小数据集用64→32不容易过拟合。Dropout(0.2)是训练时随机丢弃20%的神经元连接,用来抑制过拟合。损失函数用mse适合回归任务,如果你要输出的是一段区间预测,可以改成自定义的pinball loss。
3.2 训练流程:epochs、batch_size与early stopping
训练阶段源码里通常直接用model.fit(),但如果你的机器性能一般,或者急着交作业,盲目跑上百个epoch只会浪费时间。这套项目里应该配合回调函数控制训练提前停止。
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=100, batch_size=32, validation_split=0.1, callbacks=[early_stop], verbose=1 )参数说明:patience=10表示连续10个epoch验证集loss不下降就停止训练,这是最有用的防过拟合手段。restore_best_weights=True会在训练结束后把模型权重回滚到验证集表现最好的那个epoch,避免你拿最后一轮权重去做预测。validation_split=0.1是从训练集中再切出10%做验证,注意这个切分是在样本维度的尾部,不是随机切,否则时间序列的顺序依赖会被打破。训练结束后,建议用model.save('lstm_model.h5')保存完整模型,顺便把scaler也joblib.dump存一份,后面预测时要用。
3.3 训练过程的真实表现:loss应该怎么降
这个项目的数据量不大,通常几十个epoch内训练loss就能降到很低。如果发现train loss下降很慢,优先检查是否归一化没做;如果train loss很低但val loss炸了,优先调低LSTM层数或加大dropout。我自己重跑这类空气污染数据时,一般15到30个epoch就能看到val mae进入合理区间,上百个epoch后曲线基本走平,再多epoch纯属浪费电,所以early stopping回调是必备的。源码里带了checkpoint目录,说明项目在训练中途保存了不同阶段的模型权重,这其实是一个很好的工程习惯,下面会详细说。
4. checkpoint目录的价值:断点续训与多次实验结果回溯
4.1 checkpoint里存了什么,怎么加载
项目正文里多次出现checkpoint,这在期末大作业里很少见——说明原作者在训练过程中使用了ModelCheckpoint回调,把每个epoch或每几个epoch的模型权重存了下来。它的意义很直接:训练到一半断电或崩了,不用从头再来;同一份数据上改了几次模型结构,也可以回头对比哪个权重在测试集上表现更好。
from tensorflow.keras.callbacks import ModelCheckpoint ckpt = ModelCheckpoint( 'checkpoint/lstm_best.h5', monitor='val_loss', save_best_only=True, mode='min' ) # 恢复模型 from tensorflow.keras.models import load_model best_model = load_model('checkpoint/lstm_best.h5')逻辑说明:save_best_only=True表示只在val_loss创新低时覆盖保存,最终目录里保留的始终是历史最佳模型,不会因为最后一轮过拟合而丢掉好结果。mode='min'告诉回调“指标越小越好”。加载后可以直接复用best_model.predict(),无需重新训练。加载模型时如果遇到了自定义层、自定义损失,需要额外传custom_objects参数,但这个项目的网络结构全是keras内置层,直接load_model即可。
4.2 把训练过程和预测输出落地
我发现很多同学的期末作业只交一个ipynb,老师点开要重新跑一遍才能看到结果,一旦环境不对就完蛋。这份资源保留了checkpoint和预测结果相关的文件结构,我建议你拿到后也保留这套目录习惯。
project/ ├── pollution.csv # 原始数据 ├── air_pollution_new.csv # 预处理后可用的数据 ├── lstm_train.py # 训练脚本 ├── lstm_predict.py # 预测与可视化脚本 └── checkpoint/ └── lstm_best.h5 # 最佳模型权重我一般会在训练脚本末尾增加一个预测脚本导出预测结果为CSV,方便在报告里贴表格,也便于老师核验数值。预测脚本的核心是读入测试集最后一段历史数据,然后调用model.predict()完成反归一化输出,详细做法放在最后一章讲。
5. 避坑指南:跑LSTM时序预测最容易翻车的4个现场
5.1 训练loss到0但预测曲线是平的
亲身经历,很多新手看到train loss降到0.0001觉得稳了,结果预测曲线几乎是一条直线。原因通常是数据没有做差分或没有移除趋势,LSTM学到了“复读机”策略:把上一个值直接当成预测值,导致单步预测曲线滞后于真实值。建议先对目标列做一阶差分,或使用test_data上的滞后性指标(比如预测值和真实值的相关系数)来验证模型是否真的学到了趋势。
5.2 train loss正常但val loss持续震荡
现象:train loss稳步下降,val loss像心电图一样上下跳。原因大概率是归一化范围过窄导致数值敏感,或者validation_split默认切分把时序打乱了顺序。解决:把shuffle=False明确写进fit,验证集切分用数据尾部而不是随机抽样,同时把MinMaxScaler的范围从 [0,1] 放大到 [-1,1]。另外少调learning_rate,先固定用默认adam。
5.3 测试集结果比训练集还好,别高兴太早
如果测试集loss低于训练集loss,九成是数据泄漏。这个项目里最常见的泄漏写法是:先用全部数据做了fit_transform,再切分train/test。这样scaler已经见过了测试集的最大最小值,预测结果当然好看。解决:严格按2.2节顺序,先切分再归一化。如果资源里的代码是反着写的,你必须自己调过来。
5.4 预测未来N步时,误差越滚越大
很多作业要求预测未来24小时,默认做法是递归预测:把模型输出结果再当成输入喂回去,结果前几步还行,越往后误差越夸张。这是多步预测的累积误差问题,没有魔法能彻底消除。应对策略:改模型结构为直接多步输出,或者用教师强制方法在训练时混合真实值和预测值。期末答辩时如果能主动说出这个局限并给出分析图,反而是一个加分项。资源本身主要是单步预测结构,但如果批注里或目录里出现了多步预测相关脚本,重点看它的输入层维度是怎么定义的。
5.5 checkpoint加载时报错 shape mismatch
如果你替换了训练脚本里的seq_len或特征列数,再用旧checkpoint就会报shape不匹配。原因是LSTM层的权重维度与input_shape挂钩。遇到这个问题先别急着重训,看一下报错信息里的expected shape和actual shape,通常是把 checkpoint 里的模型结构改对即可。我的习惯是:每换一组参数就另存一个checkpoint文件名,不要覆盖同一个。
6. 预测验证与进阶技巧:用滚动预测图判断模型真假
6.1 可视化验证:多步滚动预测的写法和判定标准
项目目录里的预测脚本通常画两张图:一张是train/test的整体拟合曲线,另一张是测试集前几十个点的放大局部对比。但只看拟合曲线有个盲区——曲线平滑得像牛顿插值不代表模型有效,因为LSTM从结构上就会输出平滑结果。我习惯加一个滚动预测验证:每次只用历史数据预测未来1小时,然后把真实值追加回历史窗口,继续预测下1小时,模拟真实使用场景。
import numpy as np def rolling_forecast(model, test_X, scaler, seq_len, horizon=24): results = [] history = test_X[0].copy() for i in range(horizon): pred = model.predict(history[np.newaxis, :, :], verbose=0) results.append(pred[0, 0]) # 用真实值更新历史窗口 if i < len(test_X) - 1: next_real = test_X[i + 1][-1] history = np.vstack([history[1:], next_real[np.newaxis, :]]) return np.array(results)这段代码把history按滑动窗口更新,np.newaxis将二维数据扩展为(1, seq_len, 特征数)匹配模型输入。预测结果需要先拼接上特征维度,再做scaler.inverse_transform才能还原成真实污染值。注意这里每个时间步用的是真实值回填,所以它检验的是模型在长期滚动中的稳定性,而非多步累积误差。如果这个模式下预测值仍然能跟随真实曲线的大致走向,说明模型学到的不是噪声。
6.2 定量指标与报告素材
脚本里通常已有mean_absolute_error和root_mean_squared_error计算。写报告时除了贴指标,最好加一列“预测方向准确率”,即预测值较上一时刻是升还是降是否与真实一致。这个指标LSTM经常惨不忍睹,但它恰恰暴露了模型被均值回归主导的问题,是答辩时的深度谈资。
from sklearn.metrics import mean_absolute_error, mean_squared_error mae = mean_absolute_error(y_true, y_pred) rmse = np.sqrt(mean_squared_error(y_true, y_pred)) print(f'MAE: {mae:.3f}, RMSE: {rmse:.3f}')补充参数说明:如果污染值在200左右波动,MAE在20以内通常属于可用范围;如果你的数值单位是μg/m³,MAE超过50基本等于预测序列偏离了一个量级,应该回到归一化和差分环节重新检查。对于期末大作业,这个指标配上滚动预测图已经能撑起一个完整的验证章节。
最后说一个我自己的老毛病留作提醒:以前我拿到这类项目,总习惯直接跑model.fit等结果,翻车几次后才养成习惯——先做数据泄漏检查,再跑通一个最小的单变量单步模型,最后才加特征和多层LSTM。从那以后我每次跑LSTM时序项目都强制走一遍“先切分再归一化、先单变量再多变量、先单步再多步”的流程,再没出现过测试集指标虚高的问题。这份资源有完整数据、带注释的源码和已经训练好的checkpoint,希望这套拆解能帮你最快跑通并做出自己的期末大作业,拿到数据后建议重点对照2.2节的归一化顺序和4.1节的checkpoint加载确认一遍。希望帮到你。
本文还有配套的精品资源,点击获取