ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

LSTM温度预测实战:从数据预处理到PyTorch模型训练与避坑指南

LSTM温度预测实战:从数据预处理到PyTorch模型训练与避坑指南 简介一套用于温度预测的Python期末大作业项目基于LSTM神经网络实现面向计算机相关专业正在完成课程设计或期末大作业的学生也适合需要时间序列预测实战经验的开发者。项目经导师指导并以98分评审通过完整呈现数据探索、季节性分解、模型训练与结果可视化等环节内容可靠。资源压缩包约1.51MB共15个文件涵盖5个Python脚本负责模型搭建、训练、预测与保存、6张PNG可视化图表展示温度走势、真实值对比、训练与验证损失等、1份Excel温度数据集、1个预训练h5模型文件以及1个Markdown说明文档目录结构清晰便于按流程学习。目前已有154人学习下载读者可借此掌握LSTM在温度预测中的完整流程包括数据预处理、序列构造、模型训练与评估还能直接复用或改造代码快速产出自己的期末项目。1. 基于LSTM神经网络的温度预测这道期末大作业为什么值得做把LSTM神经网络用在温度预测上是Python期末大作业选题里的一个常见选择但它绝不是那种只为了凑代码量的题目。温度是一个典型的连续时间序列历史温度之间有强相关性今天的气温会深刻地影响明天和后天的气温这种结构天然适合用LSTM来建模。相比用普通线性回归或者BP神经网络硬拟合LSTM的循环结构能把时间维度的依赖关系真正学进去这让它既能体现深度学习模型的威力又不会像图像分类那样对训练资源要求过高。对期末这个场景来说LSTM温度预测具备三个讨好老师的特征数据公开易获得、代码量适中可控、结果能用曲线和误差指标直观展示做出来既有技术深度又有交付感。这篇笔记就把项目从数据清洗到模型训练的完整流程和你需要提前规避的坑讲一遍让新手能照着复现也能让已经写完代码的同学检查边界和参数是否合理。2. 数据准备与预处理温度序列从CSV到Tensor的全部操作2.1 数据长什么样温度序列的采样频率与时间跨度温度预测的第一个坑往往不在模型而在数据本身。公开历史气象数据里最常见的温度格式是每小时一条或每天一条记录字段包含时间戳、温度、湿度、风速、气压等。做LSTM温度预测时你首先要确认的是采样频率和连续时长因为这两个参数直接决定滑窗长度、序列步数和训练样本数量。常见做法是选择小时级数据或日级数据。小时级数据的优点是样本量充足一天有24个连续观测点序列长度为7天时能切出168步的窗口缺点是数据噪声偏大夜间温度波动、冷空气过境都会让曲线出现毛刺。日级数据则平滑得多但样本量骤减一年只有365条序列长度为30天时窗口里的步数并不算多而且一天之中最高温最低温的差异被抹掉了。做期末项目我一般建议优先选小时级数据因为滑窗能切出的样本更多模型训练效果更好讲。拿到原始数据后要做三件基础设施工作检查时间戳是否连续、处理缺失值、剔除异常值。时间戳不连续最常见比如某些气象站夜间不更新或采集设备掉线导致时间序列出现空洞。对LSTM来说空洞是致命问题模型默认相邻两步间隔相同中间缺一条数据会让邻接关系失真。处理办法一般是按照标准频率重采样空值填充用前一天同时刻的值或前后线性插值不要直接删掉那行否则序列断裂。2.2 滑窗切分用过去多少步预测下一步LSTM本身不会自动产生预测它收到的是一个长度为seq_len的序列输出是这个序列之后下一个时刻的值。所以数据预处理的核心操作是构造“输入窗口-目标标签”对。滑窗长度就是seq_len它决定了模型能往前看多远。这里先给出一个通用的滑窗构造代码后面再解释参数选择。import numpy as np import pandas as pd # data_series: pd.Series, 按时间升序排列的温度序列 def create_sequences(data, seq_len72): 将一维温度序列切分为 [输入窗口, 目标值] 的监督学习格式 seq_len: 用过去多少个小时/多少天的数据来预测下一点 xs, ys [], [] for i in range(len(data) - seq_len): x data[i:i seq_len] # 输入窗口连续seq_len个点 y data[i seq_len] # 下一个时刻的温度 xs.append(x) ys.append(y) return np.array(xs).reshape(-1, seq_len, 1), np.array(ys).reshape(-1, 1)这段代码的逻辑很直观对每个位置i取i到iseq_len之间的所有温度值作为输入第iseq_len个温度值作为要预测的目标。循环遍历完整个序列后就得到了一组时间窗口样本。注意reshape成二维数组后第三步的维度1代表特征数量这里暂时只有一个温度特征。seq_len的选择直接决定训练样本量和模型能捕捉的周期长度。如果你的数据是小时级的一天有24个点seq_len72就是看三天的温度做预测这比较合理既保留了昼夜周期样本也没有被过多压缩。如果seq_len720看整月对期末项目的计算资源来说训练速度慢而且序列太长梯度在传播过程中更容易消失效果反而可能变差。如果数据是日级的seq_len一般取30或60对应一个月或两个月的趋势记忆。初始训练我建议从seq_len72开始跑通后再尝试24和168对比效果。2.3 归一化与数据集划分先fit训练集再transform测试集温度序列的数值通常在零下十几度到零上四十度之间直接喂给LSTM会带来两个副作用第一LSTM内部的sigmoid和tanh激活函数对输入范围敏感输入数值过大会让梯度偏小甚至饱和第二不同地区的数据量纲差异大模型对量纲产生依赖后泛化能力差。常见的处理是使用MinMaxScaler或StandardScaler把数据压缩到0到1之间或变成均值为0、方差为1的标准正态分布。from sklearn.preprocessing import MinMaxScaler # 原始温度二维数组 (n_samples, 1) scaler MinMaxScaler(feature_range(0, 1)) # 注意: 只对训练集做fit再用同一个scaler转换测试集 train_len int(len(temperature_data) * 0.8) scaled_train scaler.fit_transform(temperature_data[:train_len]) scaled_test scaler.transform(temperature_data[train_len:])上面这段代码有一点至关重要fit_transform只在训练集上执行一次测试集只调用transform。原因很直接scaler如果看到全量数据的最大值和最小值等于把未来信息泄漏到了标准化参数里。训练集看到的min和max和测试集不同测试集还沿用训练集的标准这才是真实的预测场景。另一点要注意的是scaler保存后要保留到模型预测阶段因为模型输出的是0到1之间的值要还原成真实温度就必需调用scaler.inverse_transform。这个步骤常被漏掉导致预测图和真实值对不上后面避坑章节会重点展开。数据切好窗口后还需要按时间顺序划分训练、验证、测试三份数据集。时间序列和普通分类任务不同不能随机打乱否则模型看到了未来的数据测试集就失去了意义。一般用8:1:1的比例切分严格保持时间顺序。验证集用来做early stopping和调超参数测试集只在最终评估时用一次。3. 模型搭建基于PyTorch定义LSTM网络的完整代码与逐参数说明3.1 PyTorch里定义LSTMinput_size、hidden_size、num_layers到底怎么设PyTorch是目前实现LSTM最主流的框架比手写循环单元可靠也比较容易在答辩时讲清楚结构。我们来定义一个新类把LSTM层和输出层封装在一起。import torch import torch.nn as nn class TemperatureLSTM(nn.Module): def __init__(self, input_size1, hidden_size64, num_layers2, seq_len72, dropout0.2): 用于温度预测的LSTM回归模型 :param input_size: 每个时间步的特征数量只用温度特征时为1 :param hidden_size: 隐藏层神经元个数控制模型容量 :param num_layers: LSTM堆叠层数2层比较稳妥 :param seq_len: 输入序列长度 :param dropout: 多层LSTM之间的dropout比例防止过拟合 super(TemperatureLSTM, self).__init__() self.hidden_size hidden_size self.num_layers num_layers self.seq_len seq_len # LSTM层 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 shape: (batch_size, seq_len, input_size) out, _ self.lstm(x) # 取最后一个时间步的输出 last_out out[:, -1, :] # 接全连接层压缩成单个预测值 result self.fc(last_out) return result这段代码里最值得解释的是out[:, -1, :]这行。LSTM会为输入序列的每个时间步都输出一个hidden_stateout的形状是(batch_size, seq_len, hidden_size)。我们要预测的是seq_len之后那一个时刻的温度相当于把输入序列最后一个时间步的输出交给全连接层所以取索引为-1的那个时间步。很多初学者会把整个out展平后再接全连接层这是不必要的还会引入大量冗余参数。hidden_size取64是一个比较均衡的默认值。太小比如16会让模型表达能力不足温度这种连续的强相关序列学得粗糙太大比如256会让参数量剧增训练需要更多数据和更多轮数期末项目的数据量很难喂饱它。num_layers选2层时模型能学到更高层的组合特征一层的效果会偏弱。如果电脑跑得动也可以尝试3层但注意层数增加的同时参数量快速增长训练速度和过拟合风险都同步上升。3.2 训练循环损失函数、优化器与early stopping的配合方式模型定义好之后训练循环是比较模板化的。温度预测是回归任务损失函数用MSELoss即可。优化器常见的是Adam不需要手动调整太多参数。训练过程中最需要设计的机制是early stopping也就是当验证集loss连续多个epoch不下降时提前终止训练防止模型在训练集上过度拟合。import torch.optim as optim from torch.utils.data import DataLoader, TensorDataset # model放在GPU或CPU上 device torch.device(cuda if torch.cuda.is_available() else cpu) model TemperatureLSTM(input_size1, hidden_size64, num_layers2, seq_len72).to(device) criterion nn.MSELoss() optimizer optim.Adam(model.parameters(), lr0.001) # 把训练数据转换成Tensor的Dataset train_dataset TensorDataset(x_train_tensor, y_train_tensor) train_loader DataLoader(train_dataset, batch_size64, shuffleTrue) val_dataset TensorDataset(x_val_tensor, y_val_tensor) val_loader DataLoader(val_dataset, batch_size64) def valid_loss(model, loader): model.eval() total_loss, total_cnt 0.0, 0 with torch.no_grad(): for xb, yb in loader: xb, yb xb.to(device), yb.to(device) pred model(xb) loss criterion(pred, yb) total_loss loss.item() * xb.size(0) total_cnt xb.size(0) return total_loss / total_cnt best_val float(inf) patience, trigger 20, 0 epochs 100 for epoch in range(epochs): model.train() train_epoch_loss 0.0 train_cnt 0 for xb, yb in train_loader: xb, yb xb.to(device), yb.to(device) optimizer.zero_grad() output model(xb) loss criterion(output, yb) loss.backward() optimizer.step() train_epoch_loss loss.item() * xb.size(0) train_cnt xb.size(0) val_loss valid_loss(model, val_loader) train_avg train_epoch_loss / train_cnt if (epoch 1) % 10 0 or epoch 0: print(fEpoch {epoch1:3d}/{epochs} | train_loss: {train_avg:.5f} | val_loss: {val_loss:.5f}) # early stopping: 连续patience个epoch val_loss不创新低就停 if val_loss best_val: best_val val_loss trigger 0 torch.save(model.state_dict(), best_model.pt) else: trigger 1 if trigger patience: print(fEarly stopping at epoch {epoch1}) break训练循环里的核心机制有三个。第一个是DataLoader的shuffle只在训练集打开验证集不开保证验证时的数据顺序不干扰loss计算。第二个是每个epoch都做一次验证用验证集判断模型当前状态而不是等训练全部结束才评估。第三个是best_model.pt保存的是验证集loss最低的那一版参数如果训练过程中发生过拟合最终加载这个文件而非最后一轮的参数能得到一个更稳定的模型。关于优化器的细节也值得说两句learning_rate用0.001起步是行业默认值LSTM对学习率比卷积网络更敏感lr大于0.01时loss非常容易震荡。如果训练100轮还没有下降到预期水平可以试试把lr减半再从头训练这比盲目调网络结构更直接。3.3 预测与反归一化把模型的输出还原成真实温度模型训练完成后预测流程包含三个层次加载最优权重、对测试集做前向推理、把归一化后的预测值还原成真实温度。反归一化这步被漏掉的案例非常多切了窗口后直接inverse_transform会报形状错误忘记存scaler甚至无法还原测试集预测结果和真实值画出来图形差了一个均值偏移——这些现象本质上都是反归一化环节出错。model.load_state_dict(torch.load(best_model.pt, map_locationdevice)) model.eval() test_pred_list [] test_true_list [] with torch.no_grad(): for xb, yb in test_loader: xb xb.to(device) pred model(xb).cpu().numpy() test_pred_list.append(pred) test_true_list.append(yb.numpy()) pred_all np.concatenate(test_pred_list, axis0) true_all np.concatenate(test_true_list, axis0) # 还原为真实温度值这里必须用和训练时同一个scaler pred_temp scaler.inverse_transform(pred_all).flatten() true_temp scaler.inverse_transform(true_all).flatten()这里用scaler.inverse_transform把预测结果和真实值同时还原回真实温度刻度。有人只还原预测值而不还原真实值对比图就是一条0到1的线和一条几十度的线叠在一起。虽然最后你可能发现模型没毛病但图表上无法直观呈现给老师看。数据形状也要留个心眼scaler接受二维输入所以pred_all和true_all必须是(n_samples, 1)的形状再传入。4. 训练与评估超参数设置、指标计算与源码包文档结构4.1 必调参数清单seq_len、hidden_size、batch_size、lrLSTM温度预测项目的最终效果80%取决于数据预处理和超参数设置而不是模型本身的高深改动。这里给一张参数清单覆盖我跑这个项目时的起点和调整方向。参数常用起点调整方向原因seq_len72小时级24 / 168 对比过短学不到昼夜周期过长梯度消失hidden_size6432 / 128偏小欠拟合偏大训练慢且过拟合num_layers21 / 31层欠拟合3层训练耗时上涨batch_size6432 / 128影响收敛速度64是均衡点learning_rate0.0010.005 / 0.0005过大大loss震荡过小收敛慢patience2010 / 30决定早停快慢过小容易早停过早dropout0.20.1 / 0.3防止多隐藏层过拟合调参的推荐顺序是固定seq_len和hidden_size之后先调lrlr稳定了再调batch_size最后才去动网络结构。原因很简单lr问题导致的loss震荡会被误以为是模型结构问题白花很多时间。训练时观察epoch的train_loss和val_loss两条曲线是最直接的诊断方式train降而val不降就是在过拟合train和loss都不降可能是lr太小或数据泄漏train剧烈震荡是lr太大或者梯度异常。4.2 回归任务指标MAE、RMSE、R²分别代表什么期末答辩时老师一定会问你的模型效果到底怎么样不能用一句loss是0.01糊弄过去。loss是归一化空间里的误差老师没有直观感受你需要转换到真实温度刻度计算回归指标这组数字才是交付结论。from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score mae mean_absolute_error(true_temp, pred_temp) rmse np.sqrt(mean_squared_error(true_temp, pred_temp)) r2 r2_score(true_temp, pred_temp) print(fMAE : {mae:.3f} ℃) print(fRMSE: {rmse:.3f} ℃) print(fR² : {r2:.3f})MAE是平均绝对误差单位是摄氏度直观含义是平均每个预测点偏离真实值多少度2度以内算是能用的模型超过3度需要检查数据和模型结构。RMSE因为是平方项对极大误差更敏感如果RMSE明显大于MAE说明存在少数预测点误差非常大这往往是局部天气突变或者数据异常导致的。R²则代表模型解释了多少方差通常0.95以上说明曲线拟合得很好但对于温度这种平滑序列R²高是正常的不代表模型一定有强泛化能力。这三个指标配合测试集的预测曲线图是答辩时最有说服力的材料。4.3 源码包的文档说明怎么写README、代码结构和答辩思路标题里的文档说明对期末大作业来说不是装饰品而是评分的一部分。一份能让老师快速理解项目结构的文档通常包含以下六个部分用Markdown编写即可项目简介用150字说清楚干了什么地址是什么用LSTM预测温度。环境依赖列python版本和所需的torch、numpy、pandas、sklearn、matplotlib依赖建议附requirements.txt。项目结构用tree命令输出的文件层级把data、model、train.py、utils.py、README.md标出来。数据来源与预处理描述数据集的时间跨度、采样频率、缺失值处理方式、滑窗参数。模型结构简要说明LSTM层数、hidden_size、全连接层并附一张手绘网络结构图。训练结果放train/val loss曲线、预测对比图、MAE/RMSE/R²指标。源码包的组织方式一般建议拆成四个文件data_loader.py负责数据读取和滑窗model.py负责模型定义train.py负责训练和验证predict.py负责加载最优模型并出预测图。不要把全部代码堆进一个脚本里虽然能跑但答辩时老师追问代码结构会很被动。5. 避坑指南LSTM温度预测的五个常见翻车点与解决办法5.1 现象预测曲线整体向上或向下偏移真实趋势对不上原因归一化时对整个数据集执行了fit_transform而不是分训练测试两步导致测试集合的min/max参与计算模型输出的归一化数值和真实值之间产生系统性偏移。解决严格按照先切分再归一化的顺序训练集做fit_transform测试集只做transform保存scaler到本地文件预测结束后用同一个scaler做inverse_transform。5.2 现象模型训练时loss平滑下降测试集误差却大得离谱原因数据做了随机打乱或者序列切分时跨了时间段边界。模型的记忆里既有历史信息又有未来信息训练时看起来收敛正常一旦面对没见过的真实预测区间表现就会雪崩。解决切分数据集前保持原始时间顺序先按8:1:1比例分割再切窗口。如果要从不同位置单独构造窗口也必须在同一个时间段内部进行。5.3 现象训练中loss突然跳高甚至出现NaN原因学习率设置过高会导致梯度更新幅度过大plus温度序列的值经过LSTM内部多层tanh叠加可能出现梯度爆炸。另一常见原因是输入数据里混入了极大或极小的异常值归一化后仍然有离群点。解决把lr降到0.0005或0.0001检查数据集中是否有填充失败遗留的负无穷、空值等脏数据或对loss做梯度裁剪torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0)可以兜底防止某个batch把参数冲开。5.4 现象train_loss一路下降val_loss在某个epoch后反而连续上升原因典型的过拟合模型把训练集里噪声也学进去了对于温度这种本身有气候噪声和时间波动的序列数据量不大的情况下很容易出现过拟合。解决早停机制在val_loss连续上升后停止训练把dropout从0.2调到0.3增加seq_len可以提升模型视野的同时削弱噪声影响。观察过拟合时刻的epoch数如果太早说明模型容量过剩可以减小hidden_size。5.5 现象训练结果每次复现都不一样同一模型跑多轮指标差距很大原因torch在没有设置随机种子时模型参数的初始值、dropout失效模式、batch顺序都是随机的这些因素叠加会让结果出现波动。这不算bug但期末答辩时如果两次运行结果差异明显很容易留下代码不稳定的印象。解决在训练脚本开头固定随机种子。import random import numpy as np import torch SEED 42 random.seed(SEED) np.random.seed(SEED) torch.manual_seed(SEED) torch.cuda.manual_seed_all(SEED)这五行代码加上后相同的数据和相同环境下的训练结果是可复现的。不过注意在GPU上运行时某些操作仍然有极小概率产生浮点误差CPU环境复现性更好。答辩前先跑一次再把结果图保存好不要现场现跑。6. 答辩加分技巧特征扩展、多步预测与验证方法模型跑通之后下一步是让这个项目在答辩时有增量亮点。温度预测入门版只用温度本身做输入但气象学和工程实践里湿度、气压、风速、以及时间特征星期几、几点钟、是否节假日都对温度有影响。给序列加特征是性价比很高的改进方向把时间戳转成hour_of_day和day_of_year两个特征风压湿温四个气象变量一起喂给模型input_size从1变成6代码改动很少但指标通常能明显改善。这类改动在答辩时非常容易讲清楚因为你是在给模型加信息量。另一个值得尝试的是多步预测而不是单步递归滚动。单步预测只能看到24小时后的下一个小时模型无法直接给出未来24个小时的曲线。常见做法是递归预测把预测值拼到输入序列末尾删掉最早的那个点这样滚动构造出未来24小时或168小时的预测序列。这个方案实现不复杂但误差会随步数累积预测得越远曲线越平滑、越偏离真值。答辩时如果能把这个误差累积的现象画出来配上未来24小时预测-滚动过程导致误差增大的解释会对评委的冲击力很强。验证方法上除了分测试集还有一个值得做的实验切换数据集验证泛化能力。从另一个城市或另一个季节拉一段温度数据直接用之前保存的模型参数预测如果MAE依然在2度左右说明模型学到的是温度变化规律而不是死记了一条曲线。这是期末答辩最容易出彩的部分因为绝大多数同学的模型只在自己划分的测试集上验证过换了城市和季节还能保持稳定已经超出课程要求了。最后分享一个我自己最深刻的教训整个流程从数据清洗到最终出图最耗费时间的永远是第一版参数调通不要指望一次定死seq_len和hidden_size就能拿到理想结果。先跑通最小规模seq_len24hidden_size32确认训练流程没问题后再把参数调大每一步只动一个变量记录对应指标变化。保持这样的习惯最终指标不会太差你也能在答辩时从容地回答为什么这么设置。希望这篇笔记里的代码和参数能帮你在期末阶段少走几条弯路把时间留给真正值得打磨的结果分析上。本文还有配套的精品资源点击获取
返回列表