简介一套基于Python语言、融合LSTM深度学习模型与Django后端框架的空气质量监测及预测系统源码面向毕业设计、课程设计及期末大作业场景也适合正在入门时序预测与Web开发的读者学习。系统围绕PM2.5等空气指标完成历史数据读取、模型训练、未来趋势预测和可视化面板展示代码注释较完整部署门槛较低具备可运行、可演示、可扩展的特点。资源包共260个文件压缩后约7.07MB核心文件以Python脚本为主包含15个py源文件及对应pyc编译文件前端由HTML、CSS、JS和SCSS样式构成另有CSV历史数据、SQLite数据库、XML/Map资源配置和说明文档等整体结构按功能划分便于定位修改。已有384人学习下载项目内置当前空气质量、历史分析、省份对比等可视化页面并保留独立的数据文件和模型脚本方便替换为其他城市或污染物数据继续实验对于需要完成LSTM预测和Django整合展示的读者这份源码能直接作为项目骨架在短时间内搭建出完整系统对毕业答辩和课设验收很有帮助。1. LSTMDjango 空气监测预测系统先跑通再讲清楚为什么这么搭做毕业设计和课程设计有个很现实的落差单看 LSTM谁都能讲出一套“门结构”“长短期依赖”的理论但真要你把模型跑出曲线、再挂到网页上给导师演示很多东西没落地就翻车。这套基于 LSTM Django 的空气质量监测及预测系统价值就在结构完整——真实 PM2.5 历史 CSV 作为数据源LSTM 负责短时污染趋势预测Django 负责把当前浓度、历史分析和区域对比渲染成可视化页面。适合两类人一类是毕业设计或期末大作业需要一个能演示、能改参数、能讲清链路的完整项目另一类是刚学完深度学习想知道模型怎么和 Web 服务真正结合的中级开发者。2. 拆包看架构CSV 数据、Django 路由与 LSTM 模块的协作关系很多同学拿到资源第一件事是找 main.py结果根本没这个文件。这套系统不是单一脚本而是“数据层 模型层 Web 层”三块拼起来的组合先建立整体认知再动手后面改起来才不慌。2.1 从文件清单定位三块核心资源包的实物文件不多但角色差别很大。我习惯按“数据、模板、样式、工程配置”四堆拆开看先搞清楚哪些文件是拿来改的、哪些是拿来跑的、哪些根本不用碰。文件角色定位备注pm25.csv主数据源含时间列与 PM2.5 浓度列t_pm25.csv训练用数据集一般做过更细致的清洗current.html当前空气质量页仪表盘首页展示当前值和短期预测analysis.html历史分析页趋势曲线与统计信息provinces.html区域对比页多区域/省份横向对比material-dashboard*.css仪表板样式改配色或布局才需要动demo.css演示页辅助样式页面演示的小工具样式从这张表能看出真正要改代码的地方集中在数据处理脚本和模型训练脚本模板只是数据入口样式文件基本不用动。很多同学把大量时间花在调 CSS 上其实答辩时导师更关注数据怎么清洗、模型怎么训练、预测误差多大界面只要整洁不扎眼就够了配色换主题反而容易出乱子。2.2 关键数据流转CSV 怎么变成可预测的时间序列无论是训练还是预测第一步都是把 CSV 读进来。但直接拿来训练大概率翻车原因有两个一是源文件的时间字段未必严格升序二是 PM2.5 序列常有缺失。所以读取后要先排序、再处理缺失然后才做归一化和窗口化。# data_loader.py —— 训练与预测共用的数据读取入口 import pandas as pd from sklearn.preprocessing import MinMaxScaler def load_series(pathdata/pm25.csv, time_coldate, val_colPM25): # 用 parse_dates 解析时间列否则排序是按字符串走 df pd.read_csv(path, parse_dates[time_col], encodingutf-8) # 时间升序是滑动窗口正确性的前提 df df.sort_values(time_col).reset_index(dropTrue) # 缺失值用前向填充短段缺失比插值更稳 df[val_col] df[val_col].ffill() # 归一化到 0~1LSTM 对输入尺度敏感 scaler MinMaxScaler(feature_range(0, 1)) values scaler.fit_transform(df[[val_col]].astype(float)) return df, scaler, values.reshape(-1)这里三个点要说清楚。第一parse_dates必须指定否则你就是在按字符串排序“2024-01-02”会排在“2024-02-01”前面后面窗口全错。第二ffill()只适合缺失段较短的数据如果连续缺失超过几十行建议改成线性插值代码里加一行df[val_col] df[val_col].interpolate()就够。第三MinMaxScaler 的 fit 只能发生在训练数据上预测时对新数据只调用transform()这一点不遵守预测阶段就会引入未来数据的信息属于典型的数据泄漏会让评估结果虚高到没法答辩。2.3 先把 Django 跑起来再谈接入模型另一个容易踩的坑是上来就跑训练脚本。训练一个 LSTM 少则几分钟多则半小时报错后排查成本很高。我的习惯是先把 Django 空壳跑通分三步验证先启动开发服务器再访问三个模板页面最后才接模型。# 进入含 manage.py 的项目根目录 python manage.py runserver 0.0.0.0:8000 # 启动成功会显示 Django version x.x.x 和 Starting development server这里0.0.0.0:8000是允许局域网访问的写法。答辩时如果导师用自己的笔记本访问你的演示机这个参数就是刚需只写127.0.0.1的话外部设备根本连不上。跑起来后马上检查两处配置INSTALLED_APPS里是否注册了当前的空气质量应用以及TEMPLATES的DIRS是否指向包含三个 HTML 模板的目录。模板路径配错页面会直接 404而且报错信息很绕新手容易误判成视图函数的问题。3. 核心在 LSTM数据预处理、滑窗设计与训练参数怎么配LSTM 部分是整个系统的技术制高点答辩时导师的追问基本集中在这里。这一章把从滑窗到训练评估的完整链路拆开讲参数为什么这么设改大了会怎样都写清楚。3.1 为什么用 LSTM 而不是 ARIMA 或全连接网络空气污染浓度序列有典型的非线性特征比如早晚高峰的 PM2.5 突变、雨后骤降ARIMA 这类线性模型对这种非线性模式拟合能力有限。而普通全连接网络虽然能拟合非线性函数但输入特征之间没有先后概念PM2.5 前一小时和后一小时在它看来是并列的两个属性时间上的先后依赖被抹掉了。LSTM 有记忆单元和三个门结构能把过去若干时间步的信息选择性保留或遗忘。对空气质量这种“今天下午的污染浓度和昨天同时段强相关但也受前三天累积影响”的序列LSTM 是当前性价比最高的选择训练开销可控效果又足够说明问题。3.2 滑动窗口设计历史长度选多少预测步长怎么定LSTM 本身不接收“时间”概念它接收的是你切好的窗口。把连续 24 小时的 PM2.5 浓度作为一个窗口输入输出未来 1 小时浓度这就是最常用的单步预测结构。# make_windows.py —— 把一维序列切成监督学习需要的 (X, y) import numpy as np def make_windows(values, look_back24, ahead1): X, y [], [] for i in range(len(values) - look_back - ahead 1): X.append(values[i:i look_back]) y.append(values[i look_back:i look_back ahead]) return np.array(X), np.array(y) # usage: X shape (样本数, 24, 1)y shape (样本数, 1) values np.load(values.npy) X, y make_windows(values, look_back24, ahead1) X X.reshape((X.shape[0], X.shape[1], 1))代码里look_back24代表用前 24 小时预测ahead1代表往后看 1 小时这两个值要分开调。如果你做的是“用过去 7 天预测未来 3 小时”就把look_back设成 16824 乘 7ahead设成 3。注意ahead变大时y里的目标值要取第i look_back ahead个点而不是i look_back 1这是个很容易写错的索引边界。窗口长度有讲究太短模型看不到污染积累过程太长训练样本量大幅缩水还可能引入噪声。拿到的这套资源默认是 24 小时窗口对城市站点的 PM2.5 序列是比较合理的起点我建议你从这个值出发做对比实验而不是一上来就拍脑袋调成 168。3.3 LSTM 模型结构与参数选择这套系统的 LSTM 用的是典型的“两隐层 Dropout 全连接输出”结构不是最花哨的但稳定性好。# lstm_model.py —— LSTM 模型搭建与训练 from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout from tensorflow.keras.optimizers import Adam def build_model(look_back24, features1, units64, dropout0.2): model Sequential([ # 第一层 LSTMreturn_sequencesTrue 保留全部时间步输出 LSTM(units, return_sequencesTrue, input_shape(look_back, features)), Dropout(dropout), # 第二层 LSTM只返回最后一步压缩时间信息 LSTM(units // 2, return_sequencesFalse), Dropout(dropout), # 输出层预测未来一个点的浓度 Dense(1) ]) model.compile( optimizerAdam(learning_rate0.001), lossmse, metrics[mae] ) return model三个参数要说一下。return_sequencesTrue只在堆叠 LSTM 层时用因为第二层还需要完整的时间步序列最后一层 LSTM 改成False来压缩成单向量。dropout一般取 0.1 到 0.3取 0.5 对小数据集容易欠拟合预测结果会平成一条直线。units从 32 到 128 都算常见范围64 起步对这套数据规模是稳妥的可以往 128 加看收益别一上来就上 256训练时间翻倍但精度并不线性提升。3.4 训练策略与评估指标别只看 loss 曲线训练时用validation_split0.2留出验证集监控验证集 loss 而不是训练集 loss。空气质量数据有周期性直接随机切分会把相邻时间点同时放进训练集和验证集导致验证指标虚高所以更严格的做法是按时间顺序前 80% 训练、后 20% 验证。# train.py —— 训练入口 import numpy as np from tensorflow.keras.callbacks import EarlyStopping # X, y 已经通过 make_windows 生成 split int(len(X) * 0.8) X_train, X_val X[:split], X[split:] y_train, y_val y[:split], y[split:] model build_model() early_stop EarlyStopping(monitorval_loss, patience15, restore_best_weightsTrue) history model.fit( X_train, y_train, validation_data(X_val, y_val), epochs100, batch_size32, callbacks[early_stop], verbose1 ) # 保存模型后续 Django 推理要用 model.save(model/air_lstm.h5)评估时不要只看 MSE因为归一化后的 MSE 小到零点零零几导师没有直观感受。推荐在原始量纲上算 RMSE 和 R²# evaluate.py —— 反归一化之后算指标 from sklearn.metrics import mean_squared_error, r2_score pred model.predict(X_val) # scaler 必须是训练时 fit 过的同一个对象 pred_original scaler.inverse_transform(pred.reshape(-1, 1)) y_original scaler.inverse_transform(y_val.reshape(-1, 1)) rmse mean_squared_error(y_original, pred_original, squaredFalse) r2 r2_score(y_original, pred_original) print(fRMSE {rmse:.2f} μg/m³, R² {r2:.3f})RMSE 的单位是微克每立方米答辩时说“预测误差在 8 微克每立方米左右”远比报一个抽象的 0.003 有说服力。R² 则用来解释模型解释了百分之多少的方差0.9 以上说明拟合得很好如果低于 0.8优先怀疑数据预处理而不是模型结构。4. Django Web 层预测接口、页面渲染与模型加载策略模型训完接下来的工作是把预测能力暴露到 Web 端。这个系统的 Web 层没有用复杂框架就是 Django MTV 模式加三个模板页面。这一章讲清楚视图怎么组织、模板数据怎么喂、模型加载怎么避免卡顿。4.1 视图函数与 URL 路由把预测拆进 contextDjango 层最常见的写法是每个页面一个视图函数在函数里调用预测模块把结果塞进 context再交给模板渲染。# views.py —— 三个展示页面的视图逻辑 from django.shortcuts import render from predictor import AirQualityPredictor from django.conf import settings # 模块级初始化只加载一次模型 predictor AirQualityPredictor( model_pathsettings.MODEL_PATH, data_pathsettings.DATA_PATH, look_back24 ) def current(request): # 当前浓度取数据最后一行的真实值 now_val predictor.get_last_observed() # 下一小时预测值由 LSTM 推理给出 next_val predictor.predict_next() return render(request, current.html, { now_val: round(now_val, 1), next_val: round(next_val, 1), last_update: predictor.last_updated }) def analysis(request): # 历史分析页需要近 30 天的趋势数据 recent predictor.get_recent_series(days30) return render(request, analysis.html, {series: recent})current.html里展示当前值加预测值对应包里的日表盘卡片模板。analysis.html喂的是近 N 天的历史序列页面里用循环渲染成趋势表或图表。provinces.html需要的是多区域数据如果 CSV 里有城市列可以按城市分组聚合再塞给模板。视图函数要保持薄重逻辑都放在 predictor 模块里这样答辩时讲“视图只负责装配数据”会显得你的分层意识很清晰。4.2 模板数据绑定三个页面各自要什么 数据模板本身不复杂关键是你要清楚每个页面绑定的数据字段。以 current.html 为例它里面有个卡片区块显示当前 PM2.5、下一小时预测值和更新时间!-- current.html 中的核心卡片片段 -- div classcard div classcard-header h3 classcard-title当前空气质量/h3 /div div classcard-body p classdisplay-4{{ now_val }} smallμg/m³/small/p p下一小时预测{{ next_val }} μg/m³/p span classtext-muted数据更新时间{{ last_update }}/span /div /divanalysis.html 则是历史数据可视化一般用 JavaScript 图表库接收 Django 传来的 JSON 数组而不是直接渲染表格。provinces.html 的核心是区域对比数据要按区域分组常见做法是视图里先 groupby 聚合再组织成前端能直接用的“城市名 浓度均值”结构。改页面时记住一个原则DNS 模板里的{{ 变量 }}必须和视图 context 的键完全对应写错一个字母页面就显示空值但不报错这是新手最容易困惑的地方。4.3 模型加载策略模块级初始化避免每次请求都 load一个很容易翻车的点是每请求一次接口就load_model()一次。Keras 加载 h5 模型文件要解析完整计算图单次要一两秒但如果答辩演示时连续刷新页面内存会不断堆积最终页面无响应。这套系统在 predictor 模块里做了模块级单例# predictor.py —— 封装模型加载与预测推理 import joblib import numpy as np from tensorflow.keras.models import load_model class AirQualityPredictor: _instance None def __new__(cls, *args, **kwargs): # 单例模式整个进程生命周期只初始化一次 if cls._instance is None: cls._instance super().__new__(cls) cls._instance._initialized False return cls._instance def __init__(self, model_path, data_path, look_back24): if self._initialized: return self.model load_model(model_path) # 只加载一次 self.scaler joblib.load(model/scaler.joblib) # 训练时保存的 scaler self.look_back look_back self._load_data(data_path) self._initialized True单例模式配合模块级变量确保 Django 开发服务器和 gunicorn 进程只加载一次模型。切换 OFF 开发环境到部署环境时别把这个初始化逻辑写在视图函数里而是放在 predictor 模块 import 时就完成。这样第一个请求慢一点后面所有请求都走内存推理。5. 常见问题排查六个实操中卡住真人的坑这一章不是理论是我和几个同学复现这套系统时实际遇到过的坑。每条按现象、原因、解决三步写直接照着对号入座。5.1 预测曲线整体滞后看起来像平移了一个时间步现象预测值曲线比真实曲线晚一小时左右形状一致但错位。原因当数据自相关性极强时LSTM 学到的最优策略是“复制最近输入”相当于把上一小时的真实值直接当作下一小时预测MSE 已经很小但物理上没意义。解决增大look_back到 48 或 72同时给输入加入差分特征比如把“当前值减 24 小时前值”作为额外输入列强制模型去学变化量而不是复制实数。5.2 归一化还原后数值范围明显不对现象预测值都在 0 到 0.02 之间还原成浓度后变成负值或只有个位数。原因训练时对 scaler 做了 fit但预测时重新MinMaxScaler().fit_transform()新数据新数据的 min/max 和训练期不一致。解决训练完把 scaler 用joblib.dump()保存成文件预测时joblib.load()加载并只调用transform()永远不要在推理阶段重新 fit。5.3 Django 每请求一次页面卡 3 秒以上现象刷新页面要等很久后台日志显示反复加载同一段 TensorFlow 初始化的输出。原因模型加载代码写在了视图函数内部每次请求都执行load_model()进程内存里堆了多个模型副本。解决把初始化移到模块顶层用单例封装按 4.3 节的方式处理卡顿直接消失。5.4 读取 CSV 中文乱码现象时间列或城市列显示成一堆乱码训练数据根本没法用。原因文件可能是 GBK 或 GB2312 编码而read_csv默认按 UTF-8 解析。解决先把编码试出来常见做法是读文件二进制前几个字节判断或者直接用代码兜底# 尝试 3 种常见编码直到不抛异常 for enc in [utf-8, gbk, latin1]: try: df pd.read_csv(path, encodingenc) break except UnicodeDecodeError: continue5.5 预测值几乎恒为常数曲线是平线现象训练 loss 下降很快但预测输出永远是同一个值。原因最常见的是 Dropout 取 0.5 导致模型欠拟合或者归一化后数据方差太小被模型忽略。解决把 Dropout 降到 0.1检查归一化后的序列方差是否过低若原始数据本身波动就小考虑换更细粒度的时间粒度小时改成 15 分钟。5.6 换机器后模型文件加载失败现象在自己电脑上跑得好好的换到答辩机器上load_model直接抛错。原因TensorFlow 版本不一致Keras 保存的权重结构对版本敏感。解决换机器后先看pip list里 TensorFlow 版本建议固定 2.10 左右同时确认训练和加载用的 Python 大版本一致。最稳的兜底是训练完同时导出模型权重为.h5和.keras两种格式哪个能加载用哪个。6. 进阶玩法把单点预测改成多步滚动预报加一个阈值告警如果答辩时间充裕把单点预测升级成多步滚动预报效果立刻不一样——导师看到你能连续预测未来 12 小时而不是只吐一个点项目的完整度上一个台阶。6.1 多步滚动预测的实现思路多步预测的思路很朴素先预测未来第 1 小时然后把预测值拼到窗口尾部、丢掉窗口头部继续预测第 2 小时如此滚动。# rolling_forecast.py —— 多步滚动预测 def rolling_predict(predictor, hours12): # 用最近 look_back 个真实观测初始化窗口 window predictor.get_recent_history(predictor.look_back) preds [] for _ in range(hours): # window shape: (look_back, 1) x window.reshape(1, predictor.look_back, 1) y_hat predictor.model.predict(x, verbose0)[0, 0] preds.append(float(y_hat)) # 滚动去掉最老的点拼上刚预测出的值 window np.roll(window, shift-1, axis0) window[-1, 0] y_hat return predsnp.roll是这里的关键操作把窗口整体前移然后最后一个位置放新预测值。误差会随着滚动步数累积所以在模板里同时展示预测区间而不是单点值比如在第 6 小时后标注“预测误差随步长累积仅供参考”。6.2 阈值告警把预测值变成可行动的信息预测值不落地就是数字落地的方法是接一个告警函数。比如 PM2.5 超过 75 微克每立方米时模板卡片显示红色警告条# alerts.py —— 简单阈值判断 def alert_level(pm25_value): if pm25_value 150: return 重度污染, danger if pm25_value 75: return 轻度污染, warning return 良, success把 6.1 的滚动预测输出接上这个判断页面就能显示“未来 12 小时预测中度污染峰值出现在 14:00”。这三个小功能滚动预测、峰值检测、颜色卡片加起来代码量不到 50 行但答辩演示时非常抓眼球。我第一次做这类系统时单点预测做完就急着去装饰页面结果导师一问“下一小时之后呢”就卡住了。从那以后我每次做时间序列项目都强制自己先把“多步预测 误差提示”做进去再做界面。这套资源拿到手建议你先把原版跑通再把这一章的多步预测接进去效果会明显不一样。希望帮到你。本文还有配套的精品资源点击获取