资讯中心

基于LSTM的空气质量预测与可视化分析全流程详解

📅 2026/9/30 20:48:34
基于LSTM的空气质量预测与可视化分析全流程详解
简介基于LSTM模型对空气质量数据进行可视化分析预测的Python源码项目主要面向计算机相关专业正在完成期末大作业、课程设计或毕业设计的学生也适合需要项目实战练习的学习者。项目由个人独立完成并经导师指导认可评审成绩99分代码完整、开箱可运行涵盖CSV数据读取、LSTM模型构建、训练预测以及可视化展示等完整流程基础薄弱者也能参照上手。压缩包共260个文件、约7.03MB核心包括15个Python脚本、8个HTML分析页面、配套的SCSS/CSS样式与JS交互脚本以及PM2.5等CSV数据集和SQLite数据库其中Python脚本完成数据清洗、模型训练与预测HTML页面与样式脚本负责可视化展示目录结构清晰方便按模块学习。目前已有99人学习下载资源配有实际空气质量数据和多个可视化分析页面从数据读取、模型预测到前端展示形成完整闭环适合作为课设/毕设的完整参考方案。1. 用 Python 写一套基于 LSTM 的空气质量可视化分析与预测源代码是很多数据类课程期末大作业里的标准答案——数据公开、场景容易讲、预测模型又自带话题度。但真正动手你会发现代码跑通不难难的是把数据清洗、滑动窗口、归一化、模型训练、可视化对比这一整条链路串起来并且在答辩时把每个参数为什么这么设定说清楚。这篇笔记按源代码的组织方式拆开讲新手能照着复现已经写完的同学也能用它排查预测曲线滞后、loss 不降这类高频问题。2. 把 CSV 变成 LSTM 能吃的样本数据清洗、滑动窗口与归一化在把代码跑起来之前我要先泼一盆冷水空气质量预测的大作业里绝大多数翻车不是模型的问题而是数据没喂对。原始 CSV 是按小时记录的表格LSTM 要吃的是三维张量这个中间转换的每一小步都是扣分点。这一章把数据从 CSV 到进模型的完整路径走一遍每一段代码都可以直接抄进你的源代码里。2.1 原始数据长什么样先别急着建模把列名和缺失值看一遍常见的空气质量数据比如课程里给的那个版本通常包含这些列No行号、year、month、day、hour、pm2.5、DEWP露点、TEMP气温、PRES气压、cbwd风向、Iws风速、Is积雪、Ir降雨。其中cbwd是文本直接进不了数值模型得先做 one-hot 编码或干脆剔除pm2.5作为预测目标它的缺失值不能简单填 0否则会把噪声当成规律学进去。我一般会让学员先写一段快速探索代码把每列的缺失比例打出来而不是凭感觉处理。import pandas as pd import numpy as np df pd.read_csv(air_quality.csv) print(df.info()) print(df.isnull().sum())这段代码用info()查看列类型、非空值和内存占用用isnull().sum()统计每列缺失数量这是所有后续清洗的前提。如果pm2.5缺失比例超过 5%说明数据质量较差需要看缺失位置再决定删除还是插值。判断方法很简单把缺失值所在的行号打印出来连续缺失超过 4 小时的部分直接删掉零星缺失用前后均值插值。这里要注意不能用整列均值填充缺失的pm2.5那会在序列里制造台阶LSTM 对台阶特别敏感相当于喂给它的历史里多了几个假跳变模型会尝试去拟合这些不存在的突变。风向cbwd的处理常见做法是直接删掉这一列因为期末作业的模型里我们主要依赖数值型气象特征。如果你想让特征更丰富可以用df pd.get_dummies(df, columns[cbwd])做 one-hot它会拆出cbwd_NE、cbwd_NW、cbwd_SE、cbwd_cv四列布尔值。但这样输入维度会从 6 增加到 9训练时长也相应增加对课程作业来说性价比不高多数情况下降维简化反而更容易收敛。2.2 清洗与合并把日期变成时间索引按小时排序空气质量数据通常按小时记录但有些版本的行序是乱的或者存在重复的时间戳。如果不把时间列处理成索引并按升序排列后面切滑动窗口就会把“昨天下午 3 点”和“今天上午 9 点”硬凑成一个序列预测出来的东西毫无物理意义。这个问题的隐蔽之处在于程序不会报错模型也能训练只是预测曲线像随机噪声你根本找不到原因。df[datetime] pd.to_datetime( df[[year, month, day, hour]] ) df df.drop([No, year, month, day, hour], axis1) df df.set_index(datetime) df df.sort_index() # pm2.5 行间插值限制连续缺失不超过 4 小时 df[pm2.5] df[pm2.5].interpolate(methodlinear, limit4) df df.dropna()这里把拆分的时间列重新组合成datetime再设为索引是为了让数据天然具备时间顺序。interpolate只对前后都有值的区域插值limit4的意思是连续缺失超过 4 小时就不管最后统一dropna()清掉。如果你用的是 2010 到 2014 年的逐年数据还要注意year列在组合时间戳时别搞错格式pd.to_datetime一次解析失败时可以先拼接字符串再解析。清洗完之后你要确认一下数据量还剩多少。这个数据集一般有 4 万多行清洗后如果少于 3 万行说明缺失太严重检查是不是把pm2.5的空值直接dropna()误伤了应该先插值再删。数据量直接决定了后面训练集和测试集的划分比例如果剩得太少8:2 划分后测试集只有几千条画出来的预测对比图会显得很单薄答辩时也不好看。2.3 滑动窗口怎么切window_size 决定模型看到多远的过去LSTM 的核心假设是“未来由过去决定”但你得定义“过去”是多久。这里引入滑动窗口设定一个window_size比如 24表示用过去 24 小时的观测值预测下一个小时的pm2.5。切窗口时要注意步长我一般用step1也就是每前进一小时切一个样本这样 3 万行数据能切出接近 3 万个样本训练数据量足够如果你用step24样本量直接缩小 24 倍模型基本学不到什么。def create_sequences(data, target_idx, window_size24, step1): X, y [], [] for i in range(0, len(data) - window_size, step): X.append(data[i:i window_size]) y.append(data[i window_size, target_idx]) return np.array(X), np.array(y) # features 是清洗后的数值矩阵target_idx 是 pm2.5 所在列索引 X, y create_sequences(features, target_idx0, window_size24, step1) print(X.shape, y.shape) # 输出类似 (29000, 24, 6) (29000,)输出的X.shape第一维是样本数第二维是窗口长度第三维是特征数正好对应 LSTM 要求的(seq_len, features)batch 维度会在 DataLoader 里自动加。target_idx要传对如果pm2.5在特征矩阵的 0 号位就写 0你要是把目标列混到特征列里模型可能学出“用当天的 pm2.5 预测当天的 pm2.5”这种作弊解法训练误差会低得惊人但一换到测试集立刻现出原形。窗口大小怎么定我倾向于直接用 24。第一空气质量有明显的日内周期早晚高峰和午间扩散的规律需要至少一天的历史才能捕捉第二24 也让每个样本的维度不至于太大训练速度快。如果你想预测未来 8 小时而不是下一小时可以把窗口加大到 48但那样样本量会减少需要自己权衡。这个“用多长的历史预测未来多久”的对比实验本身就是答辩加分点建议你在报告里写一段结果对比。2.4 归一化为什么要做、用哪种MinMaxScaler 与数据泄漏LSTM 对输入特征的尺度非常敏感。pm2.5数值范围可能在 0 到 600而气温在 -10 到 40气压在 900 到 1050。如果不归一化梯度更新会被大数值特征主导模型收敛慢且不稳定。常见做法是用MinMaxScaler把每列缩放到 [0, 1] 区间原因是它不改变数据分布形状而且逆变换直接可还原。from sklearn.preprocessing import MinMaxScaler # 先按时间切分再 fit防止数据泄漏 split int(len(features) * 0.8) train_data, test_data features[:split], features[split:] scaler MinMaxScaler(feature_range(0, 1)) train_scaled scaler.fit_transform(train_data) test_scaled scaler.transform(test_data) # 切窗应该在归一化之后做 X_train, y_train create_sequences(train_scaled, target_idx0, window_size24) X_test, y_test create_sequences(test_scaled, target_idx0, window_size24)此处最大的坑是不能对整个数据集fit_transform必须先拆分训练集和测试集再用训练集的统计量去transform测试集。原因很简单测试集模拟的是“未来”未来的最大值和最小值在训练时不应该被看到。如果你对全量数据做归一化相当于偷看了测试集的分布这叫数据泄漏会让测试误差虚低。作业里就算老师不查你拿去对比别的论文指标也没参考价值答辩时一被追问就露馅。注意切窗和归一化的先后顺序。我见过不少同学先对二维矩阵切窗得到三维数组后再试图用 scaler 归一化结果fit_transform报维度错误又回头把三维数组 reshape 成二维折腾半天。正确的做法是先按时间切分数据然后在训练集上fit再分别transform最后调用create_sequences生成窗口。这段顺序代码可以直接抄走不用再绕弯路。3. 用 PyTorch 搭建 LSTM 预测模型结构、参数与训练循环数据准备好之后进入整个大作业的核心部分LSTM 网络结构到底长什么样。很多学生直接把网上找来的“pytorch lstm 源码”抄下来改个参数就开跑结果模型输出形状不匹配报错或者训练几十个 epoch 后 loss 还在飘。这一章把网络模块、训练循环和反归一化三段代码完整过一遍你能照着改成自己的版本。先确认你的环境里装好了 torch、pandas、matplotlib、sklearn缺哪个直接 pip install 即可Python 版本用 3.8 以上都没问题CPU 版 PyTorch 就够跑这个规模的数据。3.1 网络结构LSTM 层加全连接层输入形状别搞错先说 LSTM 的输入输出形状这三个维度是大部分报错的来源。输入张量形状是(seq_len, batch_size, input_size)PyTorch 默认接收的顺序是这样但当你用 DataLoader 取数据时张量通常是(batch_size, seq_len, input_size)所以要么在 forward 里permute要么直接设置batch_firstTrue后者更省事。输出张量out的形状是(seq_len, batch_size, hidden_size)而我们只需要最后一个时间步的输出接一个线性层。import torch import torch.nn as nn class AirLSTM(nn.Module): def __init__(self, input_size, hidden_size64, num_layers2, dropout0.2): super().__init__() self.lstm nn.LSTM( input_sizeinput_size, hidden_sizehidden_size, num_layersnum_layers, batch_firstTrue, # 输入形状 (batch, seq, feature) dropoutdropout ) self.fc nn.Linear(hidden_size, 1) def forward(self, x): # x: (batch, seq_len, input_size) out, _ self.lstm(x) # out: (batch, seq_len, hidden_size) out out[:, -1, :] # 只取最后一个时间步 return self.fc(out) # (batch, 1)batch_firstTrue是简化理解的关键参数它让输入输出都保持“batch 在第一维”你不用每次手动换维度。hidden_size是 LSTM 内部记忆向量的维度64 意味着每个时间步保留 64 维的信息num_layers2是把两层 LSTM 堆叠第二层把第一层的输出作为输入表达能力更强但训练更慢。dropout0.2只在层数大于 1 时生效这个参数是防止过拟合的第一道防线。out[:, -1, :]是取最后一个时间步的隐层状态。为什么不用全部时间步的输出因为我们做的是“用过去 24 小时预测下一小时”模型需要看完整个窗口后给出一个结论最后一个时间步的隐状态浓缩了整个序列的信息接线性层输出一个预测值就够。如果你想预测未来多步那就要改变输出层设计比如把Linear的输出维度改成预测步数或者用 seq2seq 结构这属于进阶内容期末作业里预测单步已经足够完整。参数推荐值说明window_size24用过去 24 小时预测下一小时hidden_size64隐状态维度越大拟合能力越强num_layers2层数1 层欠拟合风险高2 层适中dropout0.2防止过拟合层数等于 1 时失效learning_rate0.001Adam 默认学习率不稳定时降到 0.0005batch_size64每次迭代样本数太大容易爆内存提示如果训练时发现 loss 曲线震荡优先把学习率从 0.001 降到 0.0005再考虑调整网络层数这两步能解决八成收敛问题。3.2 训练循环loss 函数、优化器、epoch 和 batch 的选择模型定义好后需要把样本封装成 DataLoader这样 PyTorch 才能按 batch 迭代。我们预测的目标是连续数值所以损失函数用均方误差MSE它让模型把大误差惩罚得更重对空气质量这种波动较大的序列比较合适。优化器我用 Adam学习率初始设 0.001这是时间序列任务里最常用的组合稳定且不需要太多调参技巧。from torch.utils.data import DataLoader, TensorDataset X_tensor torch.tensor(X_train, dtypetorch.float32) y_tensor torch.tensor(y_train, dtypetorch.float32).view(-1, 1) dataset TensorDataset(X_tensor, y_tensor) loader DataLoader(dataset, batch_size64, shuffleFalse) model AirLSTM(input_sizeX_train.shape[2], hidden_size64, num_layers2) criterion nn.MSELoss() optimizer torch.optim.Adam(model.parameters(), lr0.001) epochs 30 train_losses, val_losses [], [] for epoch in range(epochs): model.train() total_loss 0.0 for X_batch, y_batch in loader: optimizer.zero_grad() pred model(X_batch) loss criterion(pred, y_batch) loss.backward() optimizer.step() total_loss loss.item() * X_batch.size(0) avg_loss total_loss / len(X_tensor) train_losses.append(avg_loss) print(fEpoch {epoch1}/{epochs}, Train Loss: {avg_loss:.6f})有两点要特别说明。第一shuffleFalse是因为时间序列不能乱序打乱一旦打乱模型的隐含假设“相邻样本的时间相关性”会失效这一点和图像分类任务里shuffleTrue正好相反很多学生直接照搬图像任务的模板在这里翻车。第二optimizer.zero_grad()写在每次迭代开头是防止梯度累积漏掉的话你会发现 loss 隔几个 batch 突然跳一下然后永远回不到低位。epoch 数量怎么判断我建议你在 30 左右每跑完一个 epoch 打印一次 loss观察它的下降趋势。如果到第 15 个 epoch 还在明显下降可以加大到 50如果 loss 到第 8 个 epoch 就不再变化说明模型已经收敛加大 epoch 只会浪费时间。训练时最好把验证集的 loss 也打印出来训练 loss 下降而验证 loss 不降就是过拟合的信号这个对比曲线在答辩时要能拿出来讲。3.3 恢复真实量纲反归一化与结果导出训练结束后模型输出的是归一化空间里的预测值范围在 0 到 1 之间。你需要用之前保存的 scaler 把它还原成真实的pm2.5浓度单位通常是微克每立方米否则画出来的图和真实值对不上老师也会直接问“你这个纵坐标是什么”。反归一化的代码单独写一段和训练逻辑分开排错时不用到处找。model.eval() with torch.no_grad(): X_test_tensor torch.tensor(X_test, dtypetorch.float32) pred_norm model(X_test_tensor).view(-1).numpy() # 构造和目标列相同形状的矩阵再做逆变换 dummy np.zeros((len(pred_norm), features.shape[1])) dummy[:, 0] pred_norm pred_real scaler.inverse_transform(dummy)[:, 0] dummy_true np.zeros((len(y_test), features.shape[1])) dummy_true[:, 0] y_test y_test_real scaler.inverse_transform(dummy_true)[:, 0]为什么不能直接scaler.inverse_transform(pred_norm)因为 scaler 是对整个特征矩阵按列归一化的它要求传入的数组列数等于原始特征数。这里用 target 所在列的位置0 号构造零矩阵再把预测值填进去逆变换后取出该列就能还原成真实浓度。这个技巧几乎适用于所有带目标列的回归任务比单独只保存目标列的 scaler 更不容易出错。这里要特别注意model.eval()和torch.no_grad()的作用。eval()会关闭 dropout让你在推理时得到确定的输出no_grad()则不再计算梯度省内存也加快推理。如果是用测试集别忘了在测试循环之前加上这两行否则 dropout 层还在随机丢神经元每次跑出来的预测结果都会不一样答辩现场演示时曲线抖一次就尴尬了。4. 可视化分析与预测结果展示从训练曲线到 ECharts期末大作业的评分表里“可视化”一般占 20% 到 30% 的分。你需要展示的不只是模型结构图而是能从图上读出结论的分析型图片整体趋势、峰值特征、误差分布。这一章按三张图的逻辑来讲第一张证明训练过程有效第二张证明预测结果贴近真实第三张让大作业能截图放到 PPT 答辩页上。4.1 训练曲线loss 下降长什么样算正常训练过程里我们已经打印了每个 epoch 的 loss现在要把它们画成曲线。一张正常的 loss 曲线应该是前 5 个 epoch 快速下降之后趋于平缓如果曲线像锯齿一样上下跳动或者一路平坦不动都是前面提到的参数问题先修图不如先修模。import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False epochs_list list(range(1, len(train_losses) 1)) plt.figure(figsize(10, 4)) plt.plot(epochs_list, train_losses, labelTrain Loss) plt.plot(epochs_list, val_losses, labelVal Loss) plt.xlabel(Epoch) plt.ylabel(MSE Loss) plt.title(LSTM 训练与验证 Loss 曲线) plt.legend() plt.grid(True) plt.savefig(loss_curve.png, dpi150)这段代码里train_losses和val_losses是训练循环里手动 append 的列表我把训练和验证都打印出来是为了判断过拟合。font.sans-serif设置为SimHei是为了让中文标题正常显示不设的话标题里的“损失”会变成方框如果你用的是 macOS改成Arial Unicode MS更合适。dpi150是保证图片放进 PPT 里不模糊这是答辩前容易忽略的细节。这条曲线在答辩时怎么讲核心一句话训练 loss 收敛验证 loss 保持在同一量级说明模型既有学习能力又没有严重过拟合。如果你的验证 loss 只是缓慢下降但和训练 loss 差距持续拉大就要掉头去看 dropout 调节。4.2 预测对比图三张子图把“黑匣子”画清楚评估一张预测图好不好不是看曲线贴得紧不紧而是看它能不能让老师一眼明白你的模型学到了什么。我习惯把测试集的前 200 个点拿出来画真实值、预测值、误差三条线三张子图并排。这样一眼就能看出模型在哪些时段表现好、哪些时段误差大。fig, axes plt.subplots(3, 1, figsize(12, 10), sharexTrue) idx range(200) axes[0].plot(idx, y_test_real[:200], labelReal PM2.5, colorblack) axes[0].plot(idx, pred_real[:200], labelLSTM Pred, colorred) axes[0].set_title(PM2.5 预测值与真实值对比) axes[0].legend() axes[1].plot(idx, np.abs(pred_real[:200] - y_test_real[:200]), colorblue) axes[1].set_title(绝对误差) axes[1].set_ylabel(PM2.5) axes[2].plot(idx, pred_real[:200], colorred, labelPred) axes[2].plot(idx, y_test_real[:200], colorblack, alpha0.6, labelReal) axes[2].set_title(前 50 小时局部放大) axes[2].set_xlim(0, 50) axes[2].legend() plt.tight_layout() plt.savefig(compare.png, dpi150)第一张子图看宏观趋势是否一致第二张看误差有没有在某些时段突变第三张局部放大看细节。我特别建议把测试集的预测集中在峰值时段比如冬季污染严重的几十个小时因为空气质量数据里最容易出彩的预测表现就是峰值捕捉。误差子图的高峰如果正好对应真实值的尖峰说明模型对突变更敏感这是可以拿出来讲的优点。需要提醒的是测试集是“未来数据”的模拟真实序列和预测序列之间天然存在相位差你在idx上保持对齐就好不要因为画图方便而改了时间轴。4.3 交互式可视化用 ECharts 让报告多一页Matplotlib 出的静态图适合放论文但期末大作业如果有前端或网页展示要求数据可视化部分用 ECharts 做一个交互式页面能在答辩时加分很多。常见做法是把测试集的真实值和预测值导出为 JSON然后在 HTML 页面里加载 ECharts 折线图让老师通过鼠标缩放看任意时间段。这部分代码量不大但成品效果非常直观。import json page list(range(len(pred_real[:500]))) data_for_echarts [ {time: str(i), real: round(float(r), 2), pred: round(float(p), 2)} for i, r, p in zip(page, y_test_real[:500], pred_real[:500]) ] with open(pred_data.json, w, encodingutf-8) as f: json.dump(data_for_echarts, f, ensure_asciiFalse)ECharts 的配置大概长这样xAxis用时间轴series里放两条折线tooltip打开trigger: axis这样鼠标悬停时能同时看到真实值和预测值。这一部分的工程量不大但成品是一个可以在浏览器里交互滑动的图表视觉效果远超一张静态 PNG尤其适合最后要在教室大屏上演示的场合。导出 JSON 时数据量不要超过 1000 条否则页面会有点卡挑测试集中有代表性的 300 到 500 个点就够了。5. 五个高频翻车点期末作业最常见的 LSTM 坑这一章我把调试类似作业时遇到次数最多的问题列出来。每个坑都按“现象 → 原因 → 解决”的格式写你在跑代码时如果遇到同样的情况直接对照排查能省下很多无头绪的调试时间。5.1 现象预测曲线整体滞后一拍像平移了一样如果你画出来的预测曲线和真实曲线长得几乎一样但从时间上看总是慢了一个小时高度怀疑你用了“当天的 pm2.5 预测当天的 pm2.5”。最直接的原因是构建样本时把目标列y取错了位置——y应该取自窗口结束的下一个时刻而不是窗口内的最后一个时刻。# 错误写法把窗口内最后一行当成目标 y.append(data[i window_size - 1, target_idx]) # 正确写法目标在窗口之外 y.append(data[i window_size, target_idx])如果你确认代码没问题那大概率是数据顺序乱了检查是否用了sort_index()。还有一种体现是模型只在复制“上一个时刻的值”这在序列平稳时表现不错但峰值处会滞后这时需要增加window_size或往模型里加入更多气象特征让模型有依据做判断而不是复读。5.2 现象训练 loss 不降跑 10 个 epoch 纹丝不动loss 一直停在某个固定数值比如 1.0 左右不变先看学习率是不是过大或过小。过大会让 loss 在某个值附近反复震荡过小则下降极慢10 个 epoch 看不出变化。另一个常见原因是特征没有归一化数值范围大的列主导了梯度方向。解决方法是把学习率从 0.001 往下和往上各试一档也就是 0.0001 和 0.01。同时检查input_size对不对如果你传的特征数写错了模型拿到的输入是乱序的当然学不到规律。在训练循环里加一行print(X_batch.shape)确认形状是(64, 24, 特征数)别嫌这一步多余它能过滤掉半数以上“玄学不收敛”问题。5.3 现象train 效果很好test 一塌糊涂训练集上 RMSE 很低一到测试集误差暴增这是过拟合的典型表现。原因通常是模型太大而数据太少或训练轮数过多。解决方式按优先级排序第一调大dropout到 0.3 或 0.4第二减小hidden_size从 64 降到 32第三减少num_layers从 2 降到 1第四检查是不是用了全量数据归一化的数据泄漏导致训练时模型“偷看”过测试分布。还有一个被忽视的原因时间序列的分布漂移。测试集如果是秋冬数据而训练集是春夏模型学到的气象关联在测试期失效。这个现象本身可以作为答辩亮点来谈你可以说“模型在分布漂移下表现下降未来可以加入季节向量”显得你理解模型边界比硬着头皮说“模型很完美”更有说服力。5.4 现象归一化后预测值全部挤在 0.30.4预测曲线变成一条窄带真实值在 0 到 1 之间的波动完全没被还原。出现这个情况先检查你是不是用全量数据做fit_transform后再把同一组数据的归一化结果分成训练和测试。这样做训练集归一化后包含了测试集的范围信息表面上训练 loss 正常但预测时模型输出的数值被压缩到一个很窄的区间。另外检查最后一层是否只用了Linear而没有加任何激活函数——LSTM 回归任务输出层不加 sigmoid/tanh 是对的加了反而会把输出限制在固定范围。如果你的代码里在self.fc后面跟了一个nn.Sigmoid()删掉它回归任务的输出层就是线性层不需要额外压缩。5.5 现象换了数据集后模型完全失灵很多作业是同一套代码跑不同的城市或年份数据。报错通常来自特征列名不一致或列顺序变了比如有的数据里有CO浓度列而原模型没有。解决方法是把特征选择写成可配置的列表而不是硬编码列索引。feature_cols [pm2.5, TEMP, PRES, Iws, Is, Ir] features df[feature_cols].values target_idx 0 # pm2.5 的列位置这样做的好处是换数据时只改feature_cols模型代码不用动。如果你是按照df.iloc[:, 1:5]这种方式选列下次数据列序一换模型还在按旧索引取值但自身毫无感知这是最隐蔽的一类坑。换数据后先把feature_cols打印出来人工核对一列再花两分钟画一次真实序列的曲线确认数据物理含义没反再开始训练。注意所有报告里出现的指标数字都要在 PPT 中标注数据来源是“测试集预测结果”不要混入训练集指标老师对这一点很敏感。6. 期末答辩与报告把“玄学”讲成方法快到提交截止时间时很多人还在纠结怎么让 loss 再降一点却忘了大作业的评分标准里能把“做了什么、为什么这么做”讲清楚往往比指标本身更重要。答辩前必做的三件事把测试集上的 RMSE、MAE、R² 三个数值算出来抄在 PPT 上让老师对模型效果有直观感知设好随机种子把关键参数写在每一页对应章节的脚注里。from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score rmse mean_squared_error(y_test_real, pred_real) ** 0.5 mae mean_absolute_error(y_test_real, pred_real) r2 r2_score(y_test_real, pred_real) print(fRMSE: {rmse:.2f}, MAE: {mae:.2f}, R²: {r2:.3f})计算指标时注意pred_real和y_test_real都必须是反归一化后的真实浓度不能直接在归一化空间算否则数值会被压缩到 0 到 1 之间报告里写出去没有物理意义。设随机种子是答辩现场第二次跑实验时曲线不换掉的后悔药在训练脚本开头加torch.manual_seed(42)和np.random.seed(42)两行实验结果就能稳定复现老师让重跑一遍也不心虚。把window_size24、hidden_size64、num_layers2这三个参数写在 PPT 对应章节的脚注里老师问到时你能脱口而出而不是当场翻代码。我自己的习惯是写完代码后把归一化前的数据分布做一个直方图和预测后的误差分布对比看确认没有系统性的低估或高估。这套流程走完你的期末大作业就从“代码能跑”变成“方案可信”这个差距在答辩时非常明显。希望帮到你。本文还有配套的精品资源点击获取

看完文章,想为自己的企业也做一次专业网站诊断?

尧图顾问免费为您评估现有网站,并给出建站/改版建议与报价方案。

免费获取方案