ARTICLE DETAIL

资讯详情

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

LSTM空气质量预测实战:数据清洗、模型训练与评估全解析

LSTM空气质量预测实战:数据清洗、模型训练与评估全解析 简介这是一份面向期末大作业、毕设与课程设计的 Python 空气质量指数预测项目源码基于 LSTM 实现包含完整可运行的模型、数据预处理和训练预测流程代码注释较多新手也能看懂适合想快速搭建预测系统或参考高分作品写法完成课程任务的学生。资源包为 zip 压缩格式共两千个文件、二十九点三三 MB主体是北京地区空气质量 CSV 历史数据另有 PY 预测脚本、XML 配置和说明文档。目前已有 222 人学习/下载。项目界面完善、操作简单经过严格调试可直接部署使用包含数据读取、模型构建、训练、评估与可视化等环节CSV 数据集覆盖多个时间点可替换其他城市数据或扩展特征后重新训练。整体代码结构、注释风格和 LSTM 建模调参思路对期末大作业、毕业设计或课程设计都有较高参考价值。1. 一篇LSTM空气质量预测源码凭什么能当高分期末大作业期末大作业选“基于LSTM的空气质量指数预测”十个人里有八个是在复现别人的教程最后提交的代码却往往卡在同一个地方训练完画个图loss曲线还行但预测值和真实值对不上。真正能拿高分的那种源码不是模型花哨而是把数据清洗、归一化、滑动窗口构造、模型训练、误差评估、可视化这一整条链路做扎实了。这篇笔记会顺着这套PythonLSTM预测源码的关键步骤拆开讲每一步都给出能直接跑的代码和参数说明也会把常见翻车点提前标出来。适合正在做期末大作业、想交出一份结构完整可复现项目的Python学习者也适合开始用PyTorch做时序回归、但还没形成工程化习惯的人。2. 把AQI数据喂进LSTM之前清洗、归一化与滑动窗口构造2.1 用pandas读入空气质量CSV最容易被扣分的一步LSTM本身不认CSV只认数值张量。期末项目里最常见的翻车不是模型写错而是数据读进来之后日期列没解析、空值没处理直接把NaN喂给了神经网络。这里的前提是你已经装好了Python 3.8或更高版本并且装全了pandas、numpy、torch、matplotlib和scikit-learn这些常用库——装numpy库的时候注意用pip install numpy不要用系统自带的旧版Python。我的做法是先用pandas读原始文件确认列名和缺失值情况再统一转成float32。空气质量监测数据的常见CSV格式会包含日期时间、各项污染物浓度PM2.5、PM10、SO2、NO2、CO、O3和计算出的AQI。代码按这个结构来写你的字段名不一样就改一下列名列表。import pandas as pd import numpy as np # 读取原始监测数据假设CSV包含datetime, PM2.5, PM10, SO2, NO2, CO, O3, AQI df pd.read_csv(air_quality.csv, parse_dates[datetime]) print(df.info()) # 检查列类型和缺失值 print(df.isnull().sum()) # 统计每列缺失数量 # 删除全空行对单列缺失用前向填充 df df.dropna(howall) df df.ffill().bfill() # 选取建模用特征列按时间排序 feature_cols [PM2.5, PM10, SO2, NO2, CO, O3] data df[feature_cols].astype(np.float32).values print(data.shape) # 例如 (43800, 6)这里用parse_dates把时间列变成datetime类型后续排序和画图都依赖它。ffill()是多数空气质量监测数据的常规处理方式——污染物浓度在短时间内的变化是连续的用前向填充比直接删除更能保留时间序列结构。如果某一列开头就是空值再补一个bfill()兜底。astype(np.float32)是为了防止Windows上double和float混用导致的训练速度下降。注意不要用df.dropna()暴力删行那会打断时间连续性。如果你的数据里空值超过10%就需要回到数据源重新检查常见原因是传感器离线这类情况在答辩时可以直接作为数据局限性讲出来反而显得你认真做过数据探索。2.2 MinMax归一化LSTM对输入尺度有多敏感参数怎么设LSTM内部用的是tanh和sigmoid激活函数输入动辄几百的PM2.5浓度值会直接把激活函数推到饱和区梯度更新变得非常慢。这就是很多人发现LSTM神经网络预测结果像一条直线的原因之一。常见的做法是用sklearn的MinMaxScaler把每个特征缩放到[0,1]而不是用StandardScaler做标准化——LSTM对输出的有界性更友好且逆变换时不容易出现负值。from sklearn.preprocessing import MinMaxScaler scaler MinMaxScaler(feature_range(0, 1)) scaled_data scaler.fit_transform(data) # data形状: (n_samples, 6) # 保存scaler供预测结束后反归一化 import joblib joblib.dump(scaler, scaler.save) print(归一化后最小值:, scaled_data.min(), 最大值:, scaled_data.max())feature_range(0,1)是LSTM预测任务的默认配置。千万别用StandardScaler它会把数据变成均值0方差1的分布虽然也能训练但逆变换后得到的预测值可能超出合理范围比如PM2.5变成负数。另外fit_transform只能用在训练集上验证集和测试集要用已经拟合好的scaler.transform否则会引入未来数据信息这在答辩时很容易被老师问出漏洞。保存scaler是一个特别容易被忽略的工程细节。很多期末项目只保存模型权重忘了保存归一化参数导致评估阶段无法把预测值还原成真实浓度单位。joblib.dump保存的是整个scaler对象加载时joblib.load(scaler.save)就能直接inverse_transform。我在构建模型时也习惯直接把它写进checkpoint第3章会演示。2.3 构造滑动窗口样本look_back参数的选择逻辑LSTM一次看一段序列而不是单个时间点。所以要把一维时间序列切成“用过去24小时预测下一小时”的样本。这个窗口长度就是look_back。取值没有标准答案常见做法是24一天、48两天或168一周。期末数据通常只有几个月窗口太大样本量会急剧减少所以24最稳妥。如果你的数据呈明显周期性可以观察自相关曲线再决定但期末项目里24通常不会错。def create_sequences(data, look_back24): X, y [], [] for i in range(len(data) - look_back): X.append(data[i:ilook_back, :]) # 过去look_back小时的所有特征 y.append(data[ilook_back, 0]) # 预测下一小时的PM2.5第一个特征 return np.array(X), np.array(y) look_back 24 X, y create_sequences(scaled_data, look_back) print(X shape:, X.shape, y shape:, y.shape) # 按时间顺序切分前80%训练后20%验证 split_idx int(len(X) * 0.8) X_train, X_valid X[:split_idx], X[split_idx:] y_train, y_valid y[:split_idx], y[split_idx:]这里X的形状是(样本数, look_back, 特征数)正是PyTorch中LSTM需要的(seq_len, batch, input_size)的转置版本后面训练时要做维度调整。y取的是data[ilook_back, 0]也就是预测PM2.5这一列。如果你想把AQI作为预测目标就把特征列里的AQI放到第一个或者改成y.append(data[ilook_back, 特征列索引])。用多个特征预测单一PM2.5是空气质量预测的常规设定——PM10、SO2等与PM2.5有物理相关性LSTM可以从中学到协同变化效果通常比单变量自回归好。时间序列切分不能使用随机打乱。如果像普通分类任务那样用train_test_split(X, y, test_size0.2, shuffleTrue)验证集里会混入训练集时段的信息模型相当于提前看到了未来验证指标会虚高。这个点如果能在文档里写明答辩会非常加分。提示create_sequences生成的是重叠窗口相邻两个样本共享look_back-1个时间点所以有效样本数约等于总长度减look_back不是总长度。3. 用PyTorch手写LSTM预测模型网络结构、训练循环与模型保存3.1 定义LSTM回归网络输入维度、隐藏层数与dropout的取舍很多教程会引导你用现成的LSTM封装库但期末项目里我建议直接用PyTorch的nn.LSTM手写几十行代码就能说清原理答辩时也能讲明白每个张量是怎么流转的。LSTM模型不需要很复杂一个单层LSTM加一个全连接层足以拟合空气质量数据。隐藏层大小一般取32~128之间数据量不大时取64比较稳妥。我见过不少人一上来就写两层LSTM加attention结果训练时间翻倍精度却没提高答辩还得解释复杂结构里每个张量的shape纯属给自己挖坑。import torch import torch.nn as nn class LSTMPredictor(nn.Module): def __init__(self, input_size6, hidden_size64, num_layers1, dropout0.0): super().__init__() self.lstm nn.LSTM( input_sizeinput_size, hidden_sizehidden_size, num_layersnum_layers, batch_firstTrue, dropoutdropout ) self.regressor nn.Sequential( nn.Linear(hidden_size, 32), nn.ReLU(), nn.Linear(32, 1) ) def forward(self, x): # x: (batch, seq_len, input_size) out, (h_n, c_n) self.lstm(x) # 取最后一个时间步的输出也可以直接使用h_n[-1] last_output out[:, -1, :] # (batch, hidden_size) return self.regressor(last_output) model LSTMPredictor(input_size6, hidden_size64, num_layers1) print(model)batch_firstTrue让输入张量的形状是(batch, seq_len, input_size)写数据处理和调试都更直观。取out[:, -1, :]是LSTM序列建模的常规操作——含义是“看完整个窗口后最后时刻的隐状态携带了整个序列的压缩信息”。如果你用h_n[-1]效果类似但out更灵活后期想做多步预测时可以直接取中间时刻。num_layers1在多数情况下足够除非你的数据有明显多小时尺度的周期叠加再考虑加到2层并给中间的LSTM层加上dropout0.2防止过拟合。为什么是6个输入特征因为data里有6列污染物浓度没有把AQI放进去。AQI是六项污染物浓度的综合指数把它作为输入特征会造成信息冗余而且会导致目标变量与输入高度共线模型学到的只是“用AQI预测PM2.5”这种伪关系。期末项目里想体现对特征的理解可以在报告里写一句“未使用AQI作为特征避免目标泄漏”这是很加分的。3.2 训练循环损失函数、优化器、epoch和batch_size的典型配置损失函数选MSE因为预测目标是连续浓度值MSE对大的偏差惩罚更重符合空气污染预测“宁可偏高不能低估”的场景直觉。优化器用Adam学习率设1e-3比较安全如果数据量少想稳定收敛可以用1e-4。epoch在50~200之间期末项目不需要跑到上千轮跑太多反而过拟合。from torch.utils.data import DataLoader, TensorDataset X_train_t torch.tensor(X_train, dtypetorch.float32) y_train_t torch.tensor(y_train, dtypetorch.float32).view(-1, 1) X_valid_t torch.tensor(X_valid, dtypetorch.float32) y_valid_t torch.tensor(y_valid, dtypetorch.float32).view(-1, 1) train_dataset TensorDataset(X_train_t, y_train_t) train_loader DataLoader(train_dataset, batch_size64, shuffleFalse) criterion nn.MSELoss() optimizer torch.optim.Adam(model.parameters(), lr1e-3) epochs 100 for epoch in range(epochs): model.train() total_loss 0.0 for batch_X, batch_y in train_loader: optimizer.zero_grad() pred model(batch_X) loss criterion(pred, batch_y) loss.backward() optimizer.step() total_loss loss.item() * batch_X.size(0) if (epoch 1) % 10 0: avg_loss total_loss / len(X_train_t) print(fepoch {epoch1:3d}, train MSE: {avg_loss:.6f})注意这里的shuffleFalse。时间序列样本有重叠相邻窗口来自相邻时刻打乱虽然不影响训练收敛但会让梯度更新在局部时间上反复摆动也容易让人误以为模型学到了全局模式。如果显存有限batch_size64合适如果你的数据总共只有几千条batch_size可以降到16或32效果往往更好因为小batch的梯度噪声有助于逃离局部极小值。每个epoch打印一次训练MSE是为了观察收敛速度。如果10个epoch后loss还在原地踏步先不要加模型复杂度去查学习率和归一化。Loss曲线要是完全不动十有八九是把原始数据直接喂进去了。还有一个血泪经验PyTorch版本不同默认初始化略有差异如果你复现别人的代码发现loss表现不一样先固定torch.manual_seed(42)再谈对比。3.3 保存与加载模型答辩现场可复现的关键期末项目答辩最尴尬的时刻是老师让你现场再预测一段数据而你只保存了model.state_dict()却忘了保存scaler导致预测结果根本没法还原成真实浓度。正确做法是把模型权重、scaler和配置参数一起打包。# 保存 torch.save({ model_state_dict: model.state_dict(), input_size: 6, hidden_size: 64, num_layers: 1, look_back: 24, scaler: scaler, # 可以直接存对象或单独joblib }, lstm_aqi_model.pt) # 加载 checkpoint torch.load(lstm_aqi_model.pt, map_locationcpu) model LSTMPredictor( input_sizecheckpoint[input_size], hidden_sizecheckpoint[hidden_size], num_layerscheckpoint[num_layers] ) model.load_state_dict(checkpoint[model_state_dict]) model.eval()把scaler直接放进同一个字典是后期写预测脚本时最省事的方案不用再管两个文件之间的路径依赖。map_locationcpu是为了防止在GPU上训练、CPU上推理时出现设备不匹配的报错。model.eval()一定要在预测前调用它会关闭dropout和batch norm的随机行为避免同一条数据每次预测结果不一样。这一点是很多人忽略却在答辩时被发现的雷老师多跑两次预测输出完全不一样你就说不清了。如果你在GPU上训练记得把训练数据也to(cuda)但期末项目数据量通常不大CPU跑完全够。真用GPU时注意保存checkpoint之前把模型model.cpu()否则加载时会因为键名带cuda而报错。用torch.save保存整个模型对象虽然省事但会携带类的代码路径换机器或改项目目录后容易出问题所以这里只推荐保存state_dict加配置。4. 预测效果怎么量化误差指标、对比图与24小时滚动预测4.1 计算RMSE、MAE和R²三个指标代码与学术解读模型训练完不能只说“loss很低”期末报告里必须给出测试集上的回归指标。RMSE和MAE的单位都是µg/m³R²是无量纲的拟合优度。RMSE对大误差更敏感MAE反映平均偏差R²则用来向答辩老师说明模型解释了多大气象变化。这三个指标在scikit-learn里都有现成函数不自己造轮子。from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score model.eval() with torch.no_grad(): pred_valid model(X_valid_t).numpy().flatten() # 反归一化还原为真实PM2.5浓度 other_features X_valid[:, -1, 1:6] # 每个样本窗口最后时刻的其余特征 pred_real scaler.inverse_transform( np.hstack([pred_valid.reshape(-1, 1), other_features]) )[:, 0] y_valid_real scaler.inverse_transform( np.hstack([y_valid.reshape(-1, 1), other_features]) )[:, 0] rmse np.sqrt(mean_squared_error(y_valid_real, pred_real)) mae mean_absolute_error(y_valid_real, pred_real) r2 r2_score(y_valid_real, pred_real) print(fRMSE: {rmse:.2f} µg/m³, MAE: {mae:.2f} µg/m³, R²: {r2:.3f})这里反归一化的写法容易把人绕晕。MinMaxScaler是对6列特征一起做的逆变换时需要把预测的PM2.5和其他5列的真实值拼回一个6维向量。X_valid[:, -1, 1:6]取的是每个样本最后一个时刻的除PM2.5外其余特征因为预测目标对应窗口最后时刻之后的那个值所以用最后时刻的特征来辅助还原维度。如果你嫌麻烦更优雅的做法是单独用一个scaler只对PM2.5目标列做归一化其余特征用另一个scaler这样预测值直接scaler_y.inverse_transform(pred_valid)即可。期末项目强烈建议这个分离式方案避免拼接维度错误。指标怎么解读如果R²大于0.8说明模型解释了大部分波动0.6~0.8属于可用但预测曲线可能滞后或振幅偏小低于0.5就要回到数据或模型层找问题。空气质量预测本质上受气象因素影响数据里没有气温、风速、湿度这些外部变量时R²不高不代表模型失败。在报告里写明“由于缺少气象特征R²在0.6附近是可以接受的”比藏着掖着更专业。4.2 画真实值与预测值曲线matplotlib可视化代码可视化是高分项目的门面。只给loss曲线会让老师觉得你只是把代码跑通了而不是真正理解预测结果。要画这样一张图横轴是时间步纵轴是PM2.5浓度蓝色真实值橙色预测值窗口取最近200个点就够了全部画上去会糊成一片。import matplotlib.pyplot as plt plt.figure(figsize(12, 5)) plt.plot(y_valid_real[-200:], labelTrue PM2.5, colorblue, alpha0.7) plt.plot(pred_real[-200:], labelPredicted PM2.5, colororange, alpha0.7) plt.xlabel(Time step (hour)) plt.ylabel(PM2.5 (µg/m³)) plt.title(LSTM Air Quality Prediction on Validation Set) plt.legend() plt.grid(alpha0.3) plt.tight_layout() plt.savefig(prediction_curve.png, dpi150) plt.show()记住把图保存成文件再放进报告。dpi150足够清晰图例和坐标轴标签是基本要素。答辩时老师一眼能看到预测曲线是否贴合真实曲线的相位如果你的预测曲线明显滞后整体向右平移说明模型把上一个时刻的真实值“背”下来了这个问题在第5章会重点讲。这里一个小技巧把预测误差的阴影带也画出来例如用plt.fill_between填充预测值±MAE的区间能直观展示不确定性这是超出一般学生作业的加分项。4.3 往后预测24小时滚动预测实现与需要注意的误差累积期末项目要拿“优秀”光做历史验证不够。很多现成源码只停留在“预测验证集”但题目要求“预测未来”所以需要一个滚动预测函数用最后一个已知窗口作为输入预测出下一小时然后把预测值当作新的已知值拼进窗口再预测下下一小时循环24次。def predict_future(model, last_window, scaler, steps24): model.eval() future [] current_window last_window.copy() # shape: (1, look_back, input_size) with torch.no_grad(): for _ in range(steps): input_tensor torch.tensor(current_window, dtypetorch.float32) pred model(input_tensor).item() # 预测归一化后的值 future.append(pred) # 用预测值替换窗口最后一个时间步的PM2.5位置 # 其他特征在滚动时用真实最新值近似或保持最后状态 new_step current_window[0, -1, :].copy() new_step[0] pred current_window np.roll(current_window, shift-1, axis1) current_window[0, -1, :] new_step # 反归一化PM2.5列 future np.array(future).reshape(-1, 1) future_real scaler.inverse_transform( np.hstack([future, np.repeat(current_window[0, -1, 1:6].reshape(1, -1), steps, axis0)]) )[:, 0] return future_real # 取验证集最后一个样本的窗口 last_window X_valid[-1:].copy() future_24 predict_future(model, last_window, scaler, steps24) print(未来24小时PM2.5预测:, future_24)滚动预测的误差会累积第2小时的预测基于第1小时的预测第24小时的预测误差通常比第1小时大一截。这是LSTM预测的固有局限不是bug。写报告时主动提出来反而显得你理解时序预测的本质。比如你可以画一张“不同提前量下RMSE变化”的小图展示误差随预测步长增长这是很有说服力的分析。另外这里滚动时只更新PM2.5列其他5个特征在短期预测中通常变化不大用最近时刻的真实值近似是可接受的工程简化。如果你有更完整的天气数据把天气预测值也接进来会更好但期末项目里这个简化完全够用。5. LSTM空气质量预测的常见问题排查5个翻车现场与修复方法下面这五个问题是我从好几个学生的期末项目里反复看到的。每个都按现象、原因、解决的顺序讲你可以直接对照自己的打印日志来排查。这些问题不解决改再多的LSTM层数都是白费。5.1 loss不下降或下降极慢先查学习率和归一化现象训练10个epoch后MSE仍然在0.2以上不动或者一直在某个值震荡。原因最常见的有两个。一是把原始浓度成百上千直接送进LSTMtanh和sigmoid饱和梯度消失二是学习率设置过大比如0.1让loss在一个大值附近打转。还有一种玄学情况数据和标签都归一化到了[0,1]但是batch_first设置错误导致输入shape变成(seq_len, batch, features)模型根本没有读到完整序列loss也降不下去。解决确认数据已经用MinMaxScaler缩放到[0,1]把学习率降到1e-3以下检查batch_firstTrue后输入是否形如(batch, seq, feature)。再用torch.manual_seed(42)固定随机种子排除初始化随机性。如果还不行把batch_size从64降到16试试小batch带来的梯度噪声有时能帮模型跳出局部平坦区。我一般会先跑3个epoch把loss打出来如果前3个epoch的loss没有任何下降那问题大概率不在模型而在数据或优化器。5.2 预测曲线永远比真实值“慢半拍”现象验证集曲线整体向右平移了1~2个小时RMSE看着不高但相位明显滞后真实值上升时预测值还在低位。原因这几乎都是因为输入窗口里包含了与目标同刻的当前时刻真实值。比如构造样本时用了data[i: ilook_back]作为特征ydata[ilook_back]理论上是没问题的但如果你在预测阶段输入的最后一个时间点恰好是当前时刻的真实浓度模型学到的最强信号就是“把上一个值拷贝成下一个值”因为污染物浓度短时间自相关很强拷贝的loss比任何非线性拟合都低。模型因此退化成一个带延迟的恒等映射看起来就是“慢半拍”。解决检查预测阶段的输入是否包含目标时刻之后的信息。正确做法是预测下一小时时输入窗口必须截止到上一小时目标是从该时刻之后的那一个点。训练阶段没有泄漏但预测阶段不能把当前真实浓度作为已知值再去预测当前浓度。更彻底的做法是使用多步滚动预测并报告不同提前量的误差。如果条件允许把窗口去掉最近一小时重训一次延迟会明显改善。如果你想在报告里量化这个滞后现象可以计算预测序列与真实序列的互相关把最大相关对应的平移量写出来这比画一条线更有说服力。5.3 预测出负的PM2.5浓度现象反归一化后预测值出现负值或者超过600 µg/m³这种离谱区间。原因网络输出层用的是线性激活输出本身是无界的。如果训练数据里浓度刚好分布在低值区模型在边界外外推时就可能给出负值。另一个原因是逆归一化时用错了scaler比如验证集样本对应的时间点没有正确对齐导致用了某个特征列的最大最小值去还原另一列的预测值。解决最简单的是在逆变换之后做一次np.clip(pred, 0, None)把低于0的值截断为0。根本做法是改用对数变换先对浓度取log1p再归一化预测后再expm1还原。这种方法能抑制负值且在污染浓度跨越几个数量级的数据集上效果更好。如果模型在低值区仍给出负数就说明训练数据里低浓度样本占比太高可以稍微调整训练集的时间范围把浓度较高、规律较强的时段纳入训练。你也可以统计一下负数在预测结果中的占比如果超过5%那基本可以确定是模型或归一化的问题而不是随机噪声。5.4 训练集R²0.98、验证集却崩了现象训练集上RMSE很小R²接近1验证集上R²只有0.2甚至为负。原因除了过拟合更隐蔽的原因是数据划分泄漏——时间序列数据被随机打乱了。如果使用train_test_split(X, y, test_size0.2, shuffleTrue)验证集里包含训练集期间的数据模型见过未来片段验证指标毫无参考价值。另一种泄漏是把scaler在全量数据上fit_transform再划分数据集归一化时已经偷看了验证集的统计量严格来说也算泄漏。解决严格按时间切分训练集在前、验证集在后。scaler只对训练集调用fit然后transform验证集。如果确认划分没问题但验证集仍然崩那就是过拟合适当增加LSTM的dropout或把num_layers从2降回1减少参数规模。还可以在训练循环中每10个epoch计算一次验证集loss一旦验证loss连续上升就提前停止这个早停技巧在期末报告里可以写成一个带patience参数的简单实现。最简单的自查方法打印验证集的时间范围确认它晚于训练集如果重叠立刻改划分方式。5.5 内存暴涨或训练龟速现象小数据量训练要等十几分钟一个epoch或者跑着跑着内存占用翻倍最后程序被杀。原因最常见的是在循环里重复调用torch.tensor(X)创建临时张量没有做with torch.no_grad():计算图不断累积。另一个原因是look_back太大比如168且特征有6列生成的样本数接近(总长度-168)每个样本是(168,6)的浮点数组几万样本会轻松超过1GB内存。解决用DataLoader分batch迭代避免一次性把所有样本都加载成tensor训练循环里确保每次迭代都optimizer.zero_grad()否则梯度会累加到下一次计算图不断堆积。如果look_back168后内存超限把batch_size降到16或把特征列减少到PM2.5、PM10、CO、O3四个主要组分。在Windows上注意别给DataLoader设置num_workers0否则容易触发内存泄漏和假死这是PyTorch在Windows上的老问题降低num_workers0就能解决。想定位内存问题可以用psutil.Process().memory_info().rss在每个epoch后打印显存或内存占用观察是否存在持续上涨。6. 给源码加一个命令行入口让期末作业从“能跑”变成“能用”6.1 用argparse封装predict.py支持--input、--hours和--checkpoint训练和评估做完后再写一个predict.py让老师和同学能从命令行直接调用这是把项目从“我的实验脚本”提升成“可复现工程”的关键一步。期末报告里写“项目支持命令行预测”是实打实的亮点而且实现起来只需要十几行。import argparse import numpy as np import torch import pandas as pd from sklearn.preprocessing import MinMaxScaler parser argparse.ArgumentParser(descriptionLSTM AQI Forecast Tool) parser.add_argument(--input, typestr, defaultair_quality.csv, helppath to input CSV) parser.add_argument(--hours, typeint, default24, helphow many hours to forecast) parser.add_argument(--checkpoint, typestr, defaultlstm_aqi_model.pt, helppath to model checkpoint) args parser.parse_args() # 加载checkpoint、构造最近窗口、调用predict_future输出结果到CSV然后按第3章的加载方式重建模型读入args.input的最后一段数据构造窗口调用predict_future把结果保存成forecast_result.csv。注意兼容检查这个脚本要能处理checkpoint里没有scaler的旧模型如果scaler not in checkpoint就用某个默认路径加载scaler.save并给出警告。这体现了防御性编程老师抽查时不会翻车。我自己的教训是第一版预测脚本只保存了模型权重答辩现场演示时输出全是0到1之间的归一化值老师在旁边看着场面很尴尬。后来我养成了习惯——凡是做时序预测项目一定把scaler和模型绑在一起保存并且写一个能从命令行独立运行的小工具。这个习惯后来在好几个项目里都派上用场。如果你正卡在LSTM空气质量预测这个方向别急着堆模型结构先把数据、评估、封装这三件事做扎实项目自然就有高分的样子。希望帮到你。本文还有配套的精品资源点击获取
返回列表