基于LSTM的气温预测与可视化:数据预处理、模型训练与避坑指南

📅 发布时间:2026/10/12 18:48:30
基于LSTM的气温预测与可视化:数据预处理、模型训练与避坑指南
简介基于LSTM的气温预测与可视化Python源码配套文档说明是一份适合期末大作业、课程设计的高分参考实现同时适合Python学习者作为时序预测项目的实战练手材料。资源压缩包共16个文件以8个Python脚本为主体覆盖天气数据爬取、数据预处理、LSTM模型定义与训练、气温预测及可视化展示等完整流程另有4个编译缓存文件、2个Excel数据文件和2个Markdown说明文档整包约723KB结构简洁、便于部署。所有代码均含详细注释模块划分清晰配套文档对项目背景、运行环境、参数配置和实验过程做了说明使用说明文档还提供快速开始指引新手也能轻松复现。不仅可作为期末大作业、课程设计的直接模板也可用于扩展新气象数据、对比不同时序模型或调整网络结构省去从零搭建的时间。已有179人学习下载是兼顾完整性与易用性的气温预测入门示例。1. 气温预测大作业选型为什么是 LSTM而不是 ARIMA 或者普通神经网络如果你正盯着“基于LSTM的气温预测及可视化python源码文档说明高分期末大作业”这个标题找现成代码先停一下。我的建议是不要把下载来的源码直接改名交上去评阅老师一眼就能看出是不是你写的。但把这个标题当成一个完整的技术方案来做你完全可以在一个周末内跑通一条可靠的气温预测流水线——数据清洗、滑窗切分、LSTM 模型训练、预测曲线可视化、误差分析最后整理成一份能拿高分的大作业文档。LSTM 在气温这类单变量时间序列上确实有用不是因为它在所有指标上碾压 ARIMA而是因为它能让你把“序列建模”的完整流程走一遍——数据处理、网络结构、训练调参、结果评估全都能在同一个项目里展示这才是课程设计真正想考察的东西。这套笔记就是按这个目标来写的适合你正在准备期末大作业同时也想把 LSTM 的细节真正弄明白。2. 气温序列预处理从原始 CSV 到 LSTM 能吃的样本2.1 为什么原始气温序列不能直接喂进 LSTM气温预测是一个标准的时间序列任务。它的特殊性在于序列本身有强烈的周期性和自相关性——今天 30 度明天大概率 29 度而不是突然 15 度。LSTM 能捕捉这种时间依赖但它并不理解“日期”它只认识数值矩阵。所以你要做的第一件事是把一维的气温序列改造成监督学习所需的二维样本即[样本数, 时间步长, 特征数]这样的张量。常见做法是“滑窗”sliding window用过去 24 小时的气温去预测未来 1 小时的气温。这里的 24 就是time_step也就是 LSTM 展开的步数。选择多大的窗口直接决定模型能看到多长的历史。气温序列的周期是 24 小时所以窗口至少应该覆盖一个完整周期如果你预测的是未来 6 小时窗口可以适当扩大到 48 或 72。窗口太小模型看不到周期窗口太大训练成本涨、收益递减这个后面避坑章会细讲。2.2 数据清洗中的三个常见坑缺失值、时间戳、异常点先从你拿到的数据说起。NOAA 的公开气象站数据、国内气象数据网导出的 CSV或者老师给的数据集格式上做的事情是一样的日期列 气温列。但原始数据几乎必然有缺失值比如某个小时的记录为空或者出现明显异常值——夏季某天突然蹦出一个零下 20 度这大概率不是真实气温而是传感器故障。缺失值处理我一般不会用均值填充而是用前后时刻的线性插值气温变化是连续的邻近时刻的值远比全局均值有参考意义。异常值则用滚动中位数检测如果某个点偏离其前后 12 小时的中位数超过 3 倍标准差就把它替换成插值结果。时间戳处理也很关键如果你用 pandas 读取 CSV一定要把日期列解析成datetime类型并按它排序否则数据一旦乱序整个序列的自相关性就会被破坏。import pandas as pd import numpy as np # 读取原始数据假设有两列timestamp、temperature df pd.read_csv(temperature_data.csv, parse_dates[timestamp]) df df.sort_values(timestamp).reset_index(dropTrue) # 缺失值处理按时间顺序线性插值 df[temperature] df[temperature].interpolate(methodlinear, limit_directionboth) # 异常值检测滚动中位数 3倍标准差 rolling_median df[temperature].rolling(window24, centerTrue).median() rolling_std df[temperature].rolling(window24, centerTrue).std() mask (df[temperature] - rolling_median).abs() 3 * rolling_std df.loc[mask, temperature] rolling_median[mask] print(f总样本数: {len(df)}, 异常点替换数: {mask.sum()})这里有两个参数你可以根据自己的数据改window24表示用前后 24 小时的中位数作为局部基准如果你的数据是分钟级的就要改成 14403 * rolling_std是异常阈值数据本身波动大就放宽到 4波动小可以收紧到 2.5。原则是宁可少替换也不要误伤真实的气温突变。2.3 归一化为什么 MinMaxScaler 是 LSTM 气温预测的默认选择LSTM 内部用的是 tanh 和 sigmoid 激活函数它们的输出范围分别是 (-1, 1) 和 (0, 1)。如果输入是 35 度这样的原始数值经过激活函数后梯度很容易饱和训练会变得非常慢甚至不收敛。所以归一化不是可选项是必选项。对气温序列我通常用MinMaxScaler缩放到 [0, 1] 区间。你可能会问为什么不用StandardScaler标准化到均值 0 方差 1两者都行但气温数据通常没有严重的重尾分布MinMax 的 [0, 1] 区间和 LSTM 的输出范围更匹配反归一化的时候也更直观。归一化有一个极易踩的坑必须用训练集的统计量来转换验证集和测试集。如果你对全部数据一起 fit再切分训练测试测试集的信息就泄漏进了训练过程最终评估结果会虚高答辩时一问就露馅。from sklearn.preprocessing import MinMaxScaler # 注意先切分再归一化避免数据泄漏 train_size int(len(df) * 0.8) train_df df.iloc[:train_size] test_df df.iloc[train_size:] scaler MinMaxScaler(feature_range(0, 1)) train_scaled scaler.fit_transform(train_df[[temperature]]) test_scaled scaler.transform(test_df[[temperature]]) # 保存 scaler预测完要反归一化回真实温度 import joblib joblib.dump(scaler, scaler.save)这里train_size用 80% 做训练、20% 做测试是时间序列任务里最常见的比例但要注意必须按时间顺序切不能随机打乱。scaler.save是你预测阶段的“后悔药”没有它你预测出来的结果永远停留在 [0, 1] 区间根本没法跟真实气温对比。2.4 滑窗切分构造监督学习样本的核心代码归一化之后的气温序列还是个一维数组LSTM 需要的是三维张量。写一个滑窗函数把序列切成长度为time_step的输入片段和对应的预测目标。这里有个隐藏逻辑你要想清楚每个样本的x是过去time_step个时刻的气温y是未来某个时刻的气温。如果你做单步预测y就是下一个时刻如果做多步预测y可以是一段序列但这样做模型的输出维度会变复杂度上升一个等级。作为大作业我建议先用单步预测把流程跑通拿到可靠结果后再尝试多步。def create_sequences(data, time_step24, pred_step1): X, y [], [] for i in range(len(data) - time_step - pred_step 1): X.append(data[i:i time_step, 0]) y.append(data[i time_step:i time_step pred_step, 0]) return np.array(X), np.array(y) # 假设气温是小时级数据用过去24小时预测未来1小时 time_step 24 pred_step 1 X_train, y_train create_sequences(train_scaled, time_step, pred_step) X_test, y_test create_sequences(test_scaled, time_step, pred_step) # 重塑为 LSTM 输入格式(样本数, 时间步, 特征数) 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) print(f训练集形状: {X_train.shape}, 测试集形状: {X_test.shape})time_step24是小时级气温数据的经验起点值一天有 24 个小时窗口正好掠过两个相邻的日周期模型能同时看到今天的这个时刻和昨天的同一时刻。如果你的数据是 10 分钟一条24 就只覆盖 4 小时远不够同理如果是天级数据一个周期是 365 天24 天的窗口对年周期来说太短这种情况建议用 30 或 60 天窗口加多变量特征。pred_step1表示前向预测 1 步想预测未来 6 小时就改成 6但输出层维度也要同步调整。3. 搭建 LSTM 气温预测模型网络结构、损失函数与训练策略3.1 单变量 LSTM 还是多变量 LSTM期末大作业怎么选在这个大作业的语境里最常见的方案是用单一的气温序列作为输入也就是特征数1。但如果你想让模型效果更好、论文文档里有更多可写的内容可以升级为多变量输入把湿度、气压、风速、降水量一起作为特征喂进去。气温不是孤立变化的湿度高往往意味着体感温度高气压骤降通常预示降温。多变量输入时X_train.shape变成(样本数, 时间步, 特征数)特征数从 1 变成 4 或 5模型结构不需要改但训练收敛会更慢数据对齐也更麻烦。我给你的建议是如果你只有气温数据就直接做单变量这一点不丢人单变量做扎实了照样高分如果老师给了附带的气象要素数据就做多变量同时在文档里加一段“特征相关性分析”——用 pandas 的corr()算一下各个特征与气温的相关系数选相关系数高的几个作为输入这个细节非常加分。3.2 网络结构设计两层 LSTM 加 Dropout 是稳妥起点LSTM 模型的结构我一般会从“一层 LSTM 一层 Dense”起步但作为期末大作业强烈建议你至少做成两层 LSTM。理由有两个一是两层结构能让模型学到不同时间尺度上的依赖关系——第一层捕捉局部波动第二层捕捉周期性趋势二是两层结构在文档里能写出更多的设计思考和实验对比。这正好回应了“LSTM神经网络 模型代码”这类搜索诉求不是堆层数而是有理由地加深。第一层 LSTM 用return_sequencesTrue把完整的隐状态序列传给第二层第二层不返回序列只返回最后时间步的隐状态然后接一个全连接层输出预测值。Dropout 加在两层 LSTM 之间取值 0.2 左右防止过拟合。损失函数用mse均方误差气温预测是回归任务不用交叉熵。from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau model Sequential() model.add(LSTM(units64, return_sequencesTrue, input_shape(time_step, X_train.shape[2]))) model.add(Dropout(0.2)) model.add(LSTM(units32, return_sequencesFalse)) model.add(Dropout(0.2)) model.add(Dense(unitspred_step)) model.compile(optimizeradam, lossmse, metrics[mae]) model.summary()units64是第一层 LSTM 的记忆单元数它决定了模型内部状态向量的维度。64 对于小时级气温数据来说是一个够用但不浪费的起点数据量大超过 5 万条可以提到 128数据少几千条就降到 32不然容易过拟合。第二层units32负责把第一层提取到的抽象特征压缩成更紧凑的表示再交给输出层。两层 unit 数量从大到小像漏斗一样这是时序模型最常见的设计。input_shape里的X_train.shape[2]就是特征数单变量为 1多变量为几就填几。3.3 训练配置epochs、batch_size、早停与学习率衰减训练 LSTM 有四个超参数几乎决定了你最终结果的上下限epochs、batch_size、EarlyStopping和ReduceLROnPlateau。先说不推荐的写法model.fit(X_train, y_train, epochs100, batch_size32)硬跑 100 轮。LSTM 训练到第 20 轮可能就已经收敛剩下 80 轮全在过拟合验证集 loss 一路飙升。正确做法是搭配早停。EarlyStopping监控验证集 losspatience10表示连续 10 轮没有改善就停止训练这样你既不用反复试 epochs又能拿到那个“刚好收敛”的模型。ReduceLROnPlateau监控验证集 loss 连续 5 轮不降时自动把学习率乘以 0.5这能在模型进入平台期后继续往下探一点。early_stop EarlyStopping(monitorval_loss, patience10, restore_best_weightsTrue) reduce_lr ReduceLROnPlateau(monitorval_loss, factor0.5, patience5, min_lr1e-5) history model.fit( X_train, y_train, validation_data(X_test, y_test), epochs100, batch_size32, callbacks[early_stop, reduce_lr], verbose1 )batch_size32是内存和梯度稳定性的平衡点。调大能加快训练但梯度的随机性减小模型更容易陷入局部最优调小则收敛慢但不稳定。如果你在 CPU 上跑batch_size保持 32 就好如果是 GPU 且显存充足可以提到 64 或 128 以充分利用并行计算。restore_best_weightsTrue这个参数一定要加它表示训练结束后自动回退到验证集表现最好的那一轮权重而不是用最后一轮——这是很多人容易忽略的细节。3.4 预测与反归一化把数值变回“温度”模型训练完成后predict输出的结果是在 [0, 1] 区间内的归一化值要用之前保存的scaler反变换回真实的摄氏度才能跟原始数据画在同一张图上比较。这里要注意scaler.inverse_transform期望的输入形状是(样本数, 1)而model.predict输出形状是(样本数, pred_step)当pred_step1时形状刚好匹配但多步预测时要做 reshape。from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score # 模型预测 y_pred_scaled model.predict(X_test) # 反归一化 y_pred scaler.inverse_transform(y_pred_scaled.reshape(-1, 1)).flatten() y_true scaler.inverse_transform(y_test.reshape(-1, 1)).flatten() # 评估指标 mae mean_absolute_error(y_true, y_pred) rmse np.sqrt(mean_squared_error(y_true, y_pred)) r2 r2_score(y_true, y_pred) print(fMAE: {mae:.2f}°C, RMSE: {rmse:.2f}°C, R²: {r2:.4f})三个指标各有侧重MAE平均绝对误差比较直观就是你平均预测偏差几度3 度以内算不错RMSE均方根误差对大的偏差更敏感如果某些时刻预测严重偏离RMSE 会明显大于 MAER² 是拟合优度越接近 1 越好但气温预测的 R² 通常不会特别高0.85 以上就算是相当好的结果了。这三个指标要同时写进大作业文档比单独贴一个 loss 曲线有说服力得多。4. 可视化用 Matplotlib 画出预测曲线、误差分布与训练过程4.1 可视化在期末大作业里扮演的角色很多同学把可视化理解为“画一张图交差”但在 LSTM 气温预测这个大作业里可视化其实是你的调试工具和打分依据。一个训练好的模型loss 曲线能告诉你有没有收敛预测对比图能告诉你模型是不是在认真预测还是偷懒直接复制前一天的数据残差分布能告诉你模型在哪些时段系统性偏差。这些图放到文档里既展示了你的工程能力也暴露了你的思考深度——评阅老师看重的恰恰是后者。所以这个章节我不会只教你“画图”而是告诉你每张图应该怎么看、看出什么问题。4.2 第一张图真实值 vs 预测值时序对比画这张图有一个关键决策画多少条数据。如果把测试集全部 2000 条数据都画出来曲线会挤成一团肉眼看不出预测质量。我一般只画最近 200300 个时间步的对比或者每隔几个点采样一次让曲线的波峰波谷清晰可辨。注意把真实值画成黑色实线、预测值画成红色虚线图例、轴标签、标题一个都不能少这张图直接决定评委对你的第一印象。import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei] # 处理中文显示 plt.rcParams[axes.unicode_minus] False # 正常显示负号 # 只展示最近200个时间步 plot_range 200 plt.figure(figsize(12, 5)) plt.plot(y_true[-plot_range:], label真实气温, colorblack, linewidth1) plt.plot(y_pred[-plot_range:], labelLSTM预测, colorred, linestyle--, linewidth1) plt.xlabel(时间步小时) plt.ylabel(气温°C) plt.title(LSTM气温预测结果对比) plt.legend() plt.grid(True, alpha0.3) plt.tight_layout() plt.savefig(prediction_result.png, dpi150) plt.show()这里有个小知识点要提醒你plot_range200不是随便选的它应该恰好覆盖几个完整的气温周期。小时级数据画 200 个点就是 200 小时约 8 天足够展现多个昼夜循环评阅老师能直观看到预测曲线在白天和夜间的跟随能力。dpi150是论文插图的最低清晰度要求如果是要打印出来的文档建议调到 300。4.3 第二张图训练与验证 Loss 曲线这张图的解读价值常被忽略。合格的 loss 曲线应该是训练 loss 和验证 loss 都单调下降并且两者最终靠拢如果验证 loss 在下降一段时间后反弹升高说明过拟合开始早停应该触发。画这张图的代码直接复用history对象就行不需要重新训练。plt.figure(figsize(10, 4)) plt.plot(history.history[loss], label训练损失, color#1f77b4) plt.plot(history.history[val_loss], label验证损失, color#ff7f0e) plt.xlabel(Epoch) plt.ylabel(MSE Loss) plt.title(LSTM训练过程损失变化) plt.legend() plt.grid(True, alpha0.3) plt.tight_layout() plt.savefig(loss_curve.png, dpi150) plt.show()你训练完拿到这张图后先别急着放文档。先检查两点第一训练损失有没有在最后几轮还在明显下降如果有说明epochs100不够或者早停的patience设小了训练被提前截断第二验证损失曲线是不是比训练损失低如果是多半是你的验证集划分出了泄漏——回顾一下你有没有在归一化之前就先切分数据。这个自查习惯能帮你避免答辩现场的尴尬。4.4 第三张图预测残差分布与自相关性分析第三张图是大作业拿高分的“隐藏加分项”因为大部分同学只会画前两张图。残差就是y_true - y_pred画成散点图或直方图可以直观判断模型是否有系统性偏差。比如残差直方图如果明显偏移 0说明模型整体偏高或偏低如果残差在某个时段如中午集中变大说明模型对高温时段捕捉能力不足。更进一步你还可以算残差的自相关系数残差如果有明显的周期性说明模型没有把气温的周期信息学完这时应该增大time_step或增加 LSTM 层数。residuals y_true - y_pred plt.figure(figsize(12, 4)) plt.subplot(1, 2, 1) plt.hist(residuals, bins50, edgecolorblack, alpha0.7, color#2ca02c) plt.xlabel(预测误差°C) plt.ylabel(频次) plt.title(预测残差分布直方图) plt.subplot(1, 2, 2) plt.scatter(y_true, y_pred, s8, alpha0.5, color#d62728) plt.plot([y_true.min(), y_true.max()], [y_true.min(), y_true.max()], k--, linewidth1) plt.xlabel(真实气温°C) plt.ylabel(预测气温°C) plt.title(真实值 vs 预测值散点图) plt.tight_layout() plt.savefig(residual_analysis.png, dpi150) plt.show()残差直方图里bins50对几千条预测结果足够细分散点图里的黑色虚线是 yx 的理想预测线点越贴近这条线说明预测越准。注意散点图如果发现高温段比如 30 度以上的点整体偏离理想线而且偏向下方说明模型对极端高温存在低估——这是 LSTM 处理气温数据的一个典型行为因为高温样本在数据集中占比少模型学会了“平均化”。5. 避坑LSTM 气温预测的五个经典翻车现场5.1 数据泄漏归一化在切分之前做测试集指标虚高现象训练完成后测试集 MAE 只有 0.5°C你觉得这模型神了结果提交答辩时老师现场换了另一段时间的数据一跑误差直接翻倍。原因你用了scaler.fit_transform(全部数据)让归一化统计量包含了测试集的信息。模型在训练时等于提前“看到”了测试集数值的大致分布这叫数据泄漏data leakage。这在 LSTM 时间序列项目里是最容易被忽略的错误因为代码不报错loss 正常下降指标反而更好看。解决严格按时间顺序先df.iloc[:80%]切分再对训练集fit然后用训练好的 scaler 分别transform训练集和测试集。逻辑上就是一句话任何从数据中学到的统计量都只能从训练集里学。5.2 预测曲线比真实值滞后一拍看起来像“复制昨天的温度”现象画出来的预测曲线和真实曲线形态几乎一致但有肉眼可见的时间滞后——真实温度已经升高了预测值还停留在低点。原因这是单步 LSTM 最经典的失败模式学术上叫“预测均值回归”。模型发现最优策略是“预测值等于上一个时间步的真实值”——因为气温是一个强自相关过程上一时刻的温度本来就是对下一时刻最好的估计。模型偷懒了它学到了这个捷径没有真正学到昼夜周期的内在规律。解决先看你的time_step是不是太小24 以下的话先加大到 48其次检查数据中是否有趋势项如果有可以先做一阶差分diff()把趋势去掉再喂给模型预测完再累加回去另外报告指标时除了 MAE 还可以看“预测方向准确率”——即预测值的变化方向和真实变化方向是否一致这个指标能暴露滞后问题。5.3 训练 loss 不降或者直接 NaN模型根本没学起来现象model.fit跑了几十个 epochloss 纹丝不动或者 loss 在第几个 epoch 突然变成nan然后一路飘红。原因lossnan的最常见原因是学习率过大导致梯度爆炸Adam 优化器虽然自带自适应学习率但初始学习率设成 0.1 还是会翻车第二个原因是输入数据里有无穷大或缺失值interpolate之后如果时间戳有重复或者有空值没有被处理NaN 就会混进训练数据第三个原因是MinMaxScaler在某个特征的方差为 0 时比如冬天整月都是 0 度会除零。解决给model.compile的优化器显式设置学习率optimizertf.keras.optimizers.Adam(learning_rate0.001)训练前加一行断言np.isnan(X_train).sum()确保输入没有 NaN检查 scaler 是否单独保存并且测试集的transform没有重新fit。这三点排查顺序从高频到低频命中率很高。5.4 训练集 R² 0.95测试集 R² 0.6过拟合到没边现象训练集上的评估指标非常好但测试集一塌糊涂。原因LSTM 参数量远大于普通全连接网络两层 6432 个单元加上各种门控权重参数量轻松破万。如果你的训练数据只有几千条模型完全有能力“背下来”训练集而不是“理解”规律。解决优先级从高到低第一把 Dropout 从 0.2 提到 0.30.4第二把第一层 LSTM 的 units 从 64 降到 32第三早停的patience从 10 降到 5减少过拟合训练轮次第四如果数据量实在太小少于 3000 条考虑把两层 LSTM 减为一层效果反而更好。5.5 多步预测结果发散预测值一路衰减到 0现象用模型做未来 48 小时的多步预测时预测值越来越平最终趋近于某个常数根本看不出昼夜波动。原因这是递归多步预测的误差累积问题。每一步预测都有误差误差进入下一步作为输入误差被不断放大模型输出逐渐收敛到训练集中气温的均值。LSTM 没有外部反馈机制来纠正这个漂移。解决两个方向。第一个是改用“直接多步预测”即输出层节点数直接设为pred_step一次预测出未来 48 小时的完整曲线不要用上一步的输出作为下一步的输入。第二个是引入外部特征比如把“一天中的第几个小时”作 a 周期性编码特征加到输入里模型就能持续感知昼夜相位。作为期末大作业我建议先做单步预测把主流程跑通把多步当作扩展实验写进文档的“未来工作”部分既能展示设计能力又不会把自己卡死在误差累积上。6. 让结果经得起复现滑动预测验证与实验记录技巧验证一个 LSTM 气温预测模型是否真的可靠只用静态测试集是不够的。因为气温序列是非平稳的模型可能在夏季数据上表现优秀、秋季数据上表现平平。常用的方法是“滚动预测验证”rolling forecast validation每天只预测未来 1 个时间步然后当天真实值出来后又把它拼进历史窗口继续预测下一天。这样虽然每一步都是单步预测但模型始终在使用最新的观测数据和真实业务里“每天早上预测当天最高气温”完全一致。滚动验证的代码并不复杂但完成这个验证后你要在文档里留下三样东西第一滚动预测整个测试集期间的 MAE 和 RMSE第二按月份或季度分组的误差表看看模型在哪类天气下最不稳定第三记录模型预测的滞后现象有没有在滚动模式下减轻。这三样东西组合起来比任何单张图都更能证明你的模型不是“调参调出来的巧合”。最后的文档整理有个我自己的血泪建议不要等到代码全部写完才开始写文档边写代码边记录每个超参数的实验结论。比如time_step24试过、time_step48也试过两者对比误差差多少写进文档里就是“参数敏感性分析”归一化泄漏的坑踩过、修好之后误差下降了多少写进文档里就是“问题与改进”。这些真实过程比任何编造的理论分析都更有说服力也更能撑起“文档说明”这个标题。评阅老师想看到的不是完美的模型而是你能把模型从坏到好的过程讲清楚。希望这篇笔记能帮你把整套流程走通少踩几个我当年踩过的坑。本文还有配套的精品资源点击获取