简介:一份基于深度学习的多任务空气质量预测模型设计与实现项目包,依托Python生态,面向具备一定Python基础的深度学习学习者、环境数据分析人员及毕业设计开发者,旨在解决同时预测PM2.5、PM10、二氧化硫等多项空气质量指标的建模问题。压缩包共46个文件,以36个CSV数据文件为主,包含历史监测样本与特征数据,7个Python脚本覆盖数据预处理、模型构建、训练、验证及预测等完整流程,另有3个Markdown文档提供说明与使用指引,整体体积仅5.08MB,结构清晰、目录组织贴近完整建模流程,便于快速上手。目前已有142人学习下载。通过这份项目,使用者可以获取从数据清洗、特征工程到多任务神经网络设计的全套实现思路,并可基于代码直接修改参数、扩充数据集,复现或改进空气质量的预测实验,是理解深度学习在环境科学中落地的实用参考资料。
1. 多任务空气质量预测:这份毕业设计代码到底值不值得跑
拿到这份「基于深度学习的多任务空气质量预测模型设计与实现.zip」时,我第一反应是拆开看目录结构,毕竟毕业设计资源包我见过太多——有些名字唬人,解压后全是空文件夹和README占位符。这份压缩包倒是实在:Graduation-Design-main主目录下,models.py、train.py、data_process.py、utils.py、eval.py、config.py一应俱全,还有stations_data和xy两个数据目录,明显是跑了完整流程的。
它的核心价值在于"多任务"这三个字。大多数空气质量预测项目是单任务——只预测PM2.5,但这套代码同时处理PM2.5、PM10、SO₂、NOx等多个指标。用深度学习方法做多任务预测,意味着模型要学习污染物之间的耦合关系,这不是简单的"多加几个输出头"就完了,涉及共享层和任务特定层的权重分配。如果你想在简历里写"熟悉多任务学习",或者正愁毕业设计不知道怎么把深度学习落地到环境数据上,这份代码值得花一个下午好好拆一遍。
2. 项目结构拆解:先从代码布局看懂这套系统怎么运转
2.1 文件名录:每个脚本在管线里扮演什么角色
把压缩包解压后,第一件事不是急着跑train.py,而是先建索引——搞清楚每个文件在整个训练-评估流程中的位置。我读代码的习惯是先看入口再看依赖,这套项目的依赖关系很清晰:config.py统一管理超参数,data_process.py负责把原始监测数据转成模型能吃的张量,models.py定义网络结构,utils.py放的是训练过程中的辅助函数,eval.py跑评估,train.py是总指挥。
具体对应关系是这样的:
config.py → 超参数、路径、数据配置的集中地 data_process.py → 原始CSV/表格数据 → 滑窗序列 → 训练/验证/测试集 models.py → 定义共享编码器 + 任务分支 utils.py → 记录loss、保存模型快照、指标计算 train.py → 主训练循环,调用上述所有模块 eval.py → 加载训练好的权重,输出各任务的MSE/MAE cuda_test.py → 检查CUDA可用性和显存 data/ → stations_data(站点数据),xy(特征/标签对) models/ → 训练产出的权重文件目录 models.md → 模型结构和实验记录的说明文档这个分层方式在我看来是合理的设计——数据和网络定义分离,改结构不用动数据管线。新手容易踩的坑是改了一处参数,结果发现另一处硬编码的值没同步,config.py的统一管理就是为了避免这种问题。
2.2 数据流向:从站点监测值到训练张量
这套系统的数据输入不是一张大表直接用,而是分成了stations_data和xy两个目录。我的理解是,stations_data存的是每个监测站点的原始时间序列,比如逐小时的PM2.5浓度、风速、湿度等,而xy可能是预处理后对齐好的输入输出对——x是过去N小时的特征序列,y是对应未来若干个时刻的污染物浓度。
data_process.py的核心职责是滑窗切片。假设你的原始数据是8760小时(一年),每个时刻有10个特征(三项污染物加气象参数),你要构建"用过去24小时预测未来6小时"的任务,那么窗口大小设为24,预测步长设为6,滑动步长可以设为1——这样能切出大量重叠样本。代码里通常会有类似这样的逻辑:
def create_sequences(data, window_size=24, horizon=6, step=1): """ 把原始时序数据切成监督学习格式 data: shape (n_samples, n_features) 返回: X (n_windows, window_size, n_features), Y (n_windows, horizon, n_outputs) """ X, Y = [], [] for i in range(0, len(data) - window_size - horizon, step): x_seq = data[i : i + window_size] y_seq = data[i + window_size : i + window_size + horizon, :n_outputs] X.append(x_seq) Y.append(y_seq) return np.array(X), np.array(Y)这里的三个关键参数是window_size(回看窗口)、horizon(预测步长)和step(窗口滑动步长)。step=1会大幅增加样本量,数据增强效果很好,但相邻样本高度重叠,训练时要注意验证集不能紧跟训练集切分,否则会有数据泄漏,导致评估结果虚高。
2.3 多任务模型的基本设计:共享层与分支输出
models.py定义网络时,常见做法是底部堆叠几层LSTM或GRU做序列特征提取,这个部分对四五个预测任务是共享的——污染物浓度变化受相似的气象驱动,底层特征可以复用。中间层之后分出多个独立分支,每个分支由全连接层堆叠而成,各自输出一个预测目标。
也就是说,PM2.5的分支和O₃的分支在编码阶段共享参数,在解码阶段各自独立。这样设计的好处有两点:一是因为5个任务的训练样本相同,共享编码器让小样本任务能借力大样本任务;二是计算开销和单任务模型几乎一样,推理时多分支并行,一次前向算出所有指标,这对后续做实时预测很关键。
3. 训练流程实战:从配置到跑通train.py
3.1 环境准备和CUDA检查
先跑cuda_test.py确认GPU可用。很多同学拿到代码先把train.py跑起来,结果在服务器上等了一个小时发现CPU在硬扛,才回头查CUDA。正确顺序是验证环境:torch.cuda.is_available()返回True,还要看显存够不够——batch size设64时模型占用多少显存是决定训练速度的关键。如果是像GTX 1060这种6GB显存的老卡,batch size可能要降到32甚至16。
环境依赖通常在README或models.md里有说明。以这套项目的技术栈推断,大概率是Python 3.8+、PyTorch 1.9及以上,数据预处理用到Pandas和NumPy。装环境的命令一般是:
pip install torch pandas numpy scikit-learn matplotlib装好之后跑一下cuda_test.py,检查是否能正常调用GPU:
import torch print("CUDA available:", torch.cuda.is_available()) print("GPU count:", torch.cuda.device_count()) if torch.cuda.is_available(): print("GPU name:", torch.cuda.get_device_name(0))需要注意的是CUDA版本要和PyTorch匹配,PyTorch 2.x通常要求CUDA 11.8以上。如果你用的显卡比较新,比如RTX 40系列,对应的CUDA驱动版本得够高,否则会出现"no kernel image available"的报错。这是新手最容易卡住的地方。
3.2 config.py参数详解:哪些值影响训练结果
config.py是整个训练的控制面板,我建议逐项过一遍。它一般会包含这样几类参数:
第一类数据参数:数据目录路径、窗口大小window_size、预测步长horizon、训练集和验证集的比例划分。其中window_size和horizon直接影响模型能看多远、要预测多远,经验值是window_size取24小时(一天),horizon取6小时——太短的窗口抓不到日周期变化,太长的窗口对LSTM来说序列过长反而难训练。
第二类模型参数:隐藏层维度hidden_size、LSTM层数num_layers、dropout比例、学习率learning_rate。hidden_size设64-128比较常见,设太大容易过拟合;dropout一般取0.2-0.5,这个值不能太小,因为多任务模型参数多,小数据集上很容易过拟合。
第三类训练参数:batch_size、epochs、early_stopping的耐心值。前面说过显存会限制batch_size,epochs建议设为50-100,配合early_stopping——也就是验证集loss连续多少个epoch不降就提前终止,能省很多时间。
实际训练时可以开头先用一个小数据集跑通流程,比如把训练数据量乘个0.1,确认代码没问题,再用全量数据训练。我在跑很多深度学习项目时都坚持这个习惯,先小规模冒烟测试再全量,能避免白等两小时发现代码有bug。
3.3 训练启动与日志观察
一切都配置好后,启动训练就是一条命令:
python train.py训练过程中要观察的关键指标有两个:训练loss和验证loss之间的差距,如果训练loss持续下降但验证loss不降反升,八成是过拟合——这时应该增大dropout、减小模型尺寸或者调大正则化权重;另一个是各任务分支的loss下降情况,你的项目里有4-5个任务,如果某个分支的loss下降特别慢或一直不降反升,可能不是整体问题,而是那个预测任务本身太难,需要检查该任务的标签列是否有大量缺失值或异常值。
3.4 检查点与模型保存
训练过程中要注意模型保存的策略。好的做法不是只在训练结束保存一次,而是验证集loss每刷新最优就保存一次快照——通常代码会写成:
if val_loss < best_val_loss: best_val_loss = val_loss torch.save(model.state_dict(), os.path.join(config.save_dir, "best_model.pth"))这段代码会在验证集loss刷新纪录时覆盖保存最优模型。后续如果训练崩了或者超参数调爆炸了,你还能从best_model.pth加载一个境内较好的权重继续调。
4. 多任务预测的实现细节:为什么共享编码器能提升精度
4.1 多任务学习在空气质量预测中的适用逻辑
空气质量指标之间的相关性很强:PM2.5和PM10来源相似,都受燃煤和扬尘影响;SO₂和NOx都来自工业排放和机动车尾气;气象条件(风速、湿度、温度)对以上所有污染物都有影响。既然输入特征几乎重叠,那么让它们共享一个特征提取器,可以让模型学到更通用的"污染-气象"规律——这就是多任务学习中"硬参数共享"的基本思路。
和单任务模型相比,多任务模型更容易学到稳定表示。因为多个任务的梯度会在共享层叠加,相当于正则化效果——极端情况下,如果单个任务的数据里刚好有一段噪声,共享层会因为多任务梯度互补而更容易绕过这个噪声。从工程角度看,一次前向推断能同时出多个指标,省去部署多个模型的资源开销。
4.2 多分支输出结构与loss加权策略
如果你打开models.py,大概率会看到类似这样的结构:
class MultiTaskLSTM(nn.Module): def __init__(self, input_size, hidden_size, num_layers, n_tasks, dropout=0.3): super().__init__() self.encoder = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True, dropout=dropout) self.task_heads = nn.ModuleList([ nn.Sequential( nn.Linear(hidden_size, 64), nn.ReLU(), nn.Dropout(dropout), nn.Linear(64, 1) ) for _ in range(n_tasks) ]) def forward(self, x): out, _ = self.encoder(x) # out: (batch, seq_len, hidden_size) last_out = out[:, -1, :] # 取最后一个时间步的隐状态 return [head(last_out) for head in self.task_heads]forward里通过out[:, -1, :]取最后时间步的隐状态,每个任务分支独立输出一个标量。如果你要预测未来多个时刻而不只是下一时刻,输出维度就不是1,而是horizon——每个分支的输出改成Linear(hidden_size, horizon)。
多任务学习的关键坑在于loss怎么加。不同任务的量纲不一样,PM2.5的数值在0-500 μg/m³之间波动,而O₃可能是0-200 ppb,直接相加会导致大数值任务主导梯度。常见解法是给每个任务的loss配权重,比如调成均值——这部分在train.py中通常会出现类似这样的代码:
losses = [] for i, head_output in enumerate(model_outputs): task_loss = criterion(head_output, y[:, i:i+1]) losses.append(task_loss) total_loss = sum(losses) / len(losses) # 等权重,也可按任务难度调 total_loss.backward()我的经验是先用等权重跑一轮,看每个任务的loss量级差异,再调整权重——那种loss最大的任务往往是模型最需要关注的任务。如果希望模型更关注主要污染物(比如PM2.5),可以在加权时给对应的任务分支乘以一个更大的系数。
4.3 时间序列预测的坑:数据泄漏与归一化
在空气质量预测场景里,必须强调的坑是时间序列的数据泄漏。很多做过图像分类的同学切分数据集时用的是随机采样,但时序数据乱切会带来两个问题:一是时间上靠后的数据会在训练集和验证集中同时出现,导致模型"见过"未来信息,验证集loss虚低;二是如果按站点切分——同一个站点的数据出现在训练和验证两侧,模型可能把站点ID当作特征来记。正确做法是按时间顺序切:前70%作为训练集,后30%作为验证集,而且要确保验证集的最后一段时间在训练集之后。
归一化也要小心。标准做法是用训练集的均值方差做归一化,验证预测时要用同一组统计参数再归一化——如果你把全部数据的均值方差都拿来归一化,验证集的信息就提前泄露了。常规处理代码长这样:
train_mean = X_train.mean(axis=0) train_std = X_train.std(axis=0) X_train_norm = (X_train - train_mean) / train_std X_val_norm = (X_val - train_mean) / train_std注意第二行必须在X_train上计算统计值,用这套参数来转换验证集——但实际项目里我看到很多人都是fit_transform一把梭全量数据后再切分,这是个需要特别注意的细节。
5. 多任务空气质量预测的避坑手册:现象、原因、解决
近两年陆续接触了不少复现这套代码的开发者,他们的疑问高度集中在几个地方:训练loss正常下降但验证集表现差、多任务里只有某个任务学不动、程序跑着跑着显存直接OOM。我把这些高频问题整理成了排查清单,每条都先看现象再找原因,最后给出解决办法。
问题一:训练loss下降很快,但验证集loss完全不动,甚至训练集准确率高于验证集一大截
- 现象:train.py打出来的train loss从0.8降到0.1,eval.py跑出的val loss停在0.7左右不降。
- 原因:大概率是数据泄露和归一化处理不当——特征和标签里包含了未来信息,比如做滑窗切数据时,窗口内包含了预测时刻的数据,或者数据归一化用了全量数据的均值和标准差,验证集信息被偷看了。
- 解决:检查create_sequences的逻辑,确认X窗口结束的索引和Y起始索引之间至少有1步间隔;再确认归一化先fit训练集再transform验证集,用统一的scaler对象处理两组数据。
问题二:同是多任务模型,一个任务分支loss降得飞快,另一个任务loss下降几乎停滞
- 现象:PM2.5的MAE降到了20,但NOx分支的MAE掉了半天还在80上下。
- 原因:通常是标签缺失率太高或量纲太大。比如NOx监测站点少导致大量缺失值,用简单均值填充后相当于加入了大量噪声;也可能是该任务的label数值范围远大于其他任务。
- 解决:先检查该任务的缺失值比例,超过20%时建议改成masked loss技术,让缺失值不参与梯度计算;再看量纲,把各任务的标签做了标准化再放入loss计算,比如每个任务单独除以自身训练集的标准差。
问题三:训练到一半显存溢出,报CUDA out of memory
- 现象:epoch 1跑得好好的,跑到epoch 3在for循环某一batch时直接中断。
- 原因:最常见的是batch size设太大,而LSTM展开的序列又长,中间状态占用了大量显存;还有一种情况是梯度累积时没有释放batch间的计算图——不是每步都做zero_grad,导致图一直累积。
- 解决:batch_size从64降到32或16;如果时间步长很长,可以考虑用梯度裁剪(gradient clipping)并检查是否在每步做了optimizer.zero_grad();如果显存还是很紧张,把模型转移到CPU上跑也不是不行。另外检查一下PyTorch版本,新版Python对显存释放更高效。
问题四:长时间训练却没有任何进度条或日志输出
- 现象:python train.py运行后,终端黑屏几个小时,不知道是不是死机了。
- 原因:训练循环里没有打印loss,或者数据加载(DataLoader)的prefetch_factor和num_workers设置不合理,卡在数据IO上。
- 解决:首选在每epoch或每N个batch打印一个训练日志。如果确认卡在数据加载上,把DataLoader的num_workers调低到4或2,过多workers在Windows上反而慢。
问题五:eval.py跑出的验证loss远高于train.py里的val loss
- 现象:训练过程中认为验证集loss是0.3,训练结束后单独跑eval.py却输出0.6。
- 原因:训练时打印的loss是平滑过的或用了训练集,而多任务模型最终保存的是一个整体的state_dict。如果是best_model保存的是所有任务分支最后的loss之和,而eval时单独评估某个指标,会出现口径不一致。
- 解决:核对eval.py里load的权重是否和train.py保存的一致,是否加载了best_model而不是last_model;如果训练过程做了数据增强或dropout,一致的模式也必须在eval时关闭——用model.eval()停在推演模式。
6. 从跑通到改进:验证模型的落地价值与进阶实验设计
代码跑通只是开始,工作流里真正有价值的环节是验证模型到底学到了什么、以及怎么让模型更接近实际场景的需求。
run这个模型并查看eval.py的输出后,下一步值得做的事情是模型的可解释性分析。如果污染物预测不准,往往不是因为模型不够深,而是输入特征里缺少关键信息。一个初版的模型只用污染物浓度历史数据,但现实场景中风速、风向、湿度、气压才是驱动污染物扩散的根本因素。你可以把气象变量拼进特征向量里,输入维度从原来的P个拉长到P+M个,看验证MAE是不是大幅下降——如果下降明显,说明模型确实是在学气象-污染的耦合关系,而不是死记硬背历史曲线。
进阶方向上可以考虑三个实验设计。
第一个改预测输出分布:把单值输出改成预测概率分布——比如用负对数似然损失让模型输出均值和方差,这样模型不仅告诉你"明天PM2.5是35",还告诉你"这个预测的置信度在±8之间"。在环境决策场景中,这种不确定性估计比单一数值更有实用价值。
第二个改任务结构:把任务头从独立的全连接换成带注意力机制的共享结构——让不同任务分支能从共享层动态抽取自己需要的信息,比如PM2.5任务可能更关注共享层的低层特征,而O₃任务可能需要更长时间窗口的特征。
第三个是迁移实验:用A城市的训练权重,只微调B城市的一两层,看模型跨地域迁移能力如何。多任务模型因为学的是通用的污染-气象耦合规律,迁移性通常优于单任务模型。这个实验做出来,在论文里或面试中展示都很有说服力。
从有效性角度讲,跑通这套代码只是第一步——我自己的习惯是,任何一个深度学习项目都必须完整走完三次迭代:第一次先确保代码能在小数据集上跑通;第二次用全量数据跑出基准结果;第三次加入一两个针对性改动观察指标变化。这套流程能保证你不是在盲目调参,而是理解模型的每一个改动在数据上产生的影响。
我拿到新模型代码的习惯是:先跑通小批量训练,确认loss有意义地从大值降下来;接着训练到收敛看baseline;然后加改进点看变化;最后再用eval.py做严格的时间切分评估,而不是用训练时的验证集结果充数。这套"三遍法"陪我把好多毕业设计的代码都排过雷,希望也能帮你在复现这套多任务空气质量预测模型时少走弯路。
本文还有配套的精品资源,点击获取