
简介一套面向智慧城市场景的空气质量预测与分析系统完整项目资料包包含高分源码、详细文档与配套资源项目获导师指导认可答辩评审分95分代码已测试运行通过适合计算机、人工智能、通信工程、自动化、电子信息、物联网等专业的在校学生、老师或企业员工用于毕业设计、课程设计、作业、项目演示或进阶学习。压缩包共792个文件、约13.59MB以PNG/JPG图片、JS/CSS样式、HTML页面、Python代码和SQLite数据库为主兼顾前端界面、交互逻辑与后端数据存储还包含Markdown文档、配置文件以及数十个GIF动态图便于直观了解运行效果。包内目录结构清晰可直接运行或按需修改便于快速掌握系统开发全流程该项目采用Bootstrap等前端技术搭建界面包含SQLite数据库适合作为课设/毕设的底稿进行二次开发。目前已有54人学习浏览是一份有评分背书且落地性强的参考资料。1. 基于智慧城市的空气质量预测与分析系统从数据到模型的一次完整落地城市空气质量预测这件事绝大多数教程只给你一套LSTM代码就结束但真实项目里数据采集、清洗、特征对齐、模型部署、前端展示是环环相扣的。这个基于智慧城市的空气质量预测与分析系统难能可贵地把整条链路都补齐了不仅有可运行的预测模型还有完整的Web管理界面和详细设计文档。它的核心逻辑并不复杂拿到历史监测数据构造时间特征训练回归模型预测未来几小时的AQI和PM2.5最终通过图表和报警规则辅助决策。适合正在做毕业设计、课程设计或者想完整了解一个“数据到服务”智慧系统如何搭的人。资源本身是一个zip压缩包解压后能看到Bootstrap风格的前端页面、Python后端代码和说明文档。我花了一下午拆解了它的代码结构下面按真实开发顺序讲清楚每一层做了什么。2. 系统架构与数据链路一个智慧系统该有的数据底座2.1 单体应用还是微服务课程设计场景的架构取舍很多同学一上来就想着微服务、消息队列、Docker编排结果课设答辩时被老师问到底层数据流反而答不上来。这个项目采用的是轻量化单体架构后端用Python Flask提供REST接口前端直接使用Bootstrap静态页面数据库用MySQL或SQLite。这种选择不是技术落后而是在“预测与分析”这个核心目标下单体能把代码量控制在可维护范围部署也简单尤其适合单机演示。看过项目源码后我确认它的核心模块包括五块数据采集模块读取历史CSV或爬取监测站数据、数据预处理模块缺失值处理、时间对齐、特征工程模块滞后特征、滑动窗口、预测引擎LSTM或XGBoost、可视化模块ECharts图表和表格。每块之间通过数据字典和统一的时间戳索引解耦这是处理时间序列项目时最值得借鉴的点。如果你需要扩展新传感器数据源只需要在采集模块里增加一个解析函数下游不用动。2.2 数据采集AQI与气象参数怎么进库空气预测不能只靠AQI本身温度、湿度、风速、气压都会影响污染物扩散。这个系统的原始数据来自城市监测站的逐小时报表包含时间戳、PM2.5、PM10、SO2、NO2、CO、O3、AQI、温度、湿度、风向、风速共12个字段。采集过程我建议直接写成幂等任务避免重复跑批时插入脏数据。CREATE TABLE air_quality ( station_id VARCHAR(32) NOT NULL, ts DATETIME NOT NULL, pm25 DECIMAL(6,2), pm10 DECIMAL(6,2), so2 DECIMAL(6,2), no2 DECIMAL(6,2), co DECIMAL(6,2), o3 DECIMAL(6,2), aqi DECIMAL(6,2), temp DECIMAL(5,2), humidity DECIMAL(5,2), wind_dir VARCHAR(8), wind_speed DECIMAL(5,2), PRIMARY KEY (station_id, ts) );这个建表语句把station_id和ts设为联合主键好处是同一监测站同一时刻只能有一条记录重复采集时用INSERT ... ON DUPLICATE KEY UPDATE直接覆盖天然去重。wind_dir用字符串是因为预测模型里会做风向编码数据库里保留人类可读值方便排查。实际开发中我一般会再加一个ingest_time字段做数据血缘追踪但这个项目为了简洁没有加不影响主流程。数据导入建议用Pandas的read_csv加to_sql但要注意时间格式统一。项目里原始CSV的时间是2024-03-01 08:00:00这种标准格式直接pd.to_datetime即可。如果你拿到的数据是2024/3/1 8:00必须指定format%Y/%m/%d %H:%M否则Pandas会推断出年份错误。数据量不大时SQLite足够数据量超过百万行建议切到MySQL并给ts建索引。2.3 数据清洗与对齐时间序列最容易被忽略的陷阱拿到原始数据后第一件事不是建模而是检查时间戳是否连续。监测站偶尔会停机维护导致某个小时缺记录还有的时候传感器异常PM2.5会出现负值或大于1000的离谱值。系统预处理模块的做法是先按时间戳排序然后生成完整的小时序列对缺失值做插值。插值不能用简单均值因为污染物浓度在早晚高峰有明显波动用线性插值比均值更能保持趋势。import pandas as pd def clean_series(df): # 确保时间索引连续缺失的时间点会重新采样出来值为NaN df[ts] pd.to_datetime(df[ts]) df df.set_index(ts).sort_index() full_idx pd.date_range(startdf.index.min(), enddf.index.max(), freqH) df df.reindex(full_idx) # 对数值列做线性插值限制连续缺失不超过6小时才插值 numeric_cols [pm25, pm10, aqi, temp, humidity, wind_speed] df[numeric_cols] df[numeric_cols].interpolate(methodlinear, limit6, limit_areainside) # 超出合理范围的数值直接视为噪声并替换为前后均值 df.loc[df[pm25] 0, pm25] np.nan df[pm25] df[pm25].fillna(df[pm25].rolling(3, min_periods1).mean()) return df.reset_index()这段代码有三个关键参数需要注意。limit6表示连续缺失超过6小时就不再插值因为长时间空白区域线性插值会制造假趋势不如保留空缺让模型忽略。limit_areainside只对序列内部的缺失值插值头部和尾部的缺失不处理避免用未来的数据填充过去的空档。rolling(3, min_periods1)是滚动窗口均值窗口为3小时如果当前值仍是NaN就用邻近值兜底。处理完之后你还应该检查是否有重复时间戳用index.duplicated().sum()快速验证。3. 特征工程与预测模型用梯度提升还是LSTM3.1 特征构造滞后特征、滑动窗口、周期编码空气质量预测本质上是时间序列回归问题。模型看到的不只是当前时刻的污染物浓度更关键的是过去几小时的变化趋势。这个系统设计的核心特征是滞后特征预测t1小时的AQI输入为t、t-1、t-2、t-3小时的AQI、PM2.5、风速和湿度。滞后阶数不是拍脑袋定的而是通过自相关函数ACF图观察找到衰减到0的截断点。城市站点的污染物浓度通常在34小时后相关性显著下降所以取滞后4小时是合理范围。除了滞后特征周期特征也很重要。早晚高峰、昼夜温差、周末与工作日对空气质量影响明显。项目里用正弦和余弦编码小时、星期避免把023的小时数直接当作线性特征喂给模型——0和23相邻但数值上差距很大。滑动窗口特征我用在这里过去6小时PM2.5的均值、最大值、标准差以及AQI的一阶差分。这些统计量能捕捉浓度突变和累积效应。def build_features(df, lag_hours4, window6): df df.sort_values(ts).reset_index(dropTrue) # 滞后特征每个特征过去1~lag_hours小时的值 for hour in range(1, lag_hours 1): df[fpm25_lag{hour}] df[pm25].shift(hour) df[faqi_lag{hour}] df[aqi].shift(hour) df[fwind_speed_lag{hour}] df[wind_speed].shift(hour) # 滑动窗口统计量 df[pm25_win_mean] df[pm25].rolling(window).mean() df[pm25_win_std] df[pm25].rolling(window).std() df[aqi_diff] df[aqi].diff() # 时间周期编码 df[hour_sin] np.sin(2 * np.pi * df[ts].dt.hour / 24) df[hour_cos] np.cos(2 * np.pi * df[ts].dt.hour / 24) df[week_sin] np.sin(2 * np.pi * df[ts].dt.dayofweek / 7) df[week_cos] np.cos(2 * np.pi * df[ts].dt.dayofweek / 7) # 丢弃没有滞后值的前几行 df df.dropna().reset_index(dropTrue) return df滞后特征的shift(hour)会让前几行产生NaN所以构造完必须dropna()。如果你忘记这一步训练集和测试集的长度会对不上而且LightGBM这类模型会报“缺失值”的异常行为虽然它能处理缺失但结果会失真。aqi_diff是一阶差分表示AQI的变化量这对捕捉突变趋势很有用。时间编码部分hour_sin和hour_cos是一对不能只用一个否则0点和23点在正弦值上会重叠。3.2 模型选型XGBoost、LightGBM还是LSTM我拆解这个系统时发现它同时提供了两种模型基于LightGBM的回归和基于LSTM的时序预测用户可以通过配置文件切换。为什么这么设计因为在真实项目里数据量、时间跨度和算力约束直接决定模型选择。如果你的历史数据只有几个月LightGBM加滞后特征通常效果更好训练快不容易过拟合如果你有两年以上的逐小时数据LSTM能学到更长的时序依赖尤其是污染物积累和消散的缓慢过程。这里给出一个选型判断表是我在实际项目中反复验证的准则维度LightGBM / XGBoostLSTM数据量需求数千条即可建议数万条以上特征工程依赖滞后/窗口特征可自动学习时序但仍需构造输入序列训练速度分钟级GPU下也需较长时间可解释性特征重要度可直接输出黑盒需要SHAP辅助长期预测多步预测需递归误差累积可设计seq2seq结构相对稳定部署难度原生支持模型导出需要TensorFlow/PyTorch运行时最终项目默认采用LSTM因为答辩时LSTM“听起来”更有深度演示效果也好。但如果你要做实时预测我建议换成LightGBM因为它的单条推理延迟在微秒级LSTM则需要经过前向传播计算虽然在CPU上也只有几毫秒但高并发下差距会明显。3.3 模型训练与调参一份可复现的代码下面这段代码基于TensorFlow/Keras实现一个简单的LSTM回归网络输入特征维度是lookback个时间步的多个特征输出是未来1小时的AQI。核心在于数据切分时不能随机打乱时间序列必须按时间顺序分割否则就是数据泄露。import numpy as np from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout from sklearn.preprocessing import MinMaxScaler def create_sequences(data, lookback24, horizon1): X, y [], [] for i in range(len(data) - lookback - horizon 1): X.append(data[i:ilookback, :]) y.append(data[ilookbackhorizon-1, 0]) # 预测AQI列 return np.array(X), np.array(y) # 假设df已经做过特征工程aqi是目标列features是输入列 scaler MinMaxScaler() feature_cols [pm25, pm10, temp, humidity, wind_speed, hour_sin, hour_cos, week_sin, week_cos] scaled scaler.fit_transform(df[feature_cols]) lookback 24 X, y create_sequences(scaled, lookback, horizon1) # 按时间顺序切分前80%训练后20%测试 split int(len(X) * 0.8) X_train, X_test X[:split], X[split:] y_train, y_test y[:split], y[split:] model Sequential([ LSTM(64, return_sequencesTrue, input_shape(lookback, X.shape[2])), Dropout(0.2), LSTM(32, return_sequencesFalse), Dropout(0.2), Dense(16, activationrelu), Dense(1) ]) model.compile(optimizeradam, lossmse, metrics[mae]) history model.fit(X_train, y_train, epochs20, batch_size32, validation_data(X_test, y_test), verbose0)lookback24意味着用过去24小时的数据预测未来1小时这个值越大模型能看到的趋势越长但计算量也越大。y.append(...)中取data[... , 0]是因为我们把AQI放在了特征矩阵的第一列如果你调整了列顺序这里要同步修改。训练结束后预测值要执行scaler.inverse_transform才能还原成真实AQI数值否则评估指标都是01区间答辩时说不清。调参方面我一般先固定lookback24然后调整LSTM隐藏单元数32/64/128和Dropout比例。如果在验证集上的MAE在20以上先增加训练轮次到50观察loss曲线是否收敛如果过拟合加大Dropout到0.3。另外batch_size对时间序列收敛影响很大32是通用起点数据量大时可以用64。4. 分析与可视化把预测结果交到决策者手上4.1 预测误差与趋势分析不能只看MAE预测模型训练完不能只给一个MAE就结束。智慧系统的“分析”价值体现在对预测结果的解释上。项目里的分析模块计算了多个维度的误差按小时聚合的误差分布、按星期聚合的偏差以及不同AQI等级下的预测准确率。我建议增加一个“误差热点图”用seaborn画小时和星期的二维MAE热力图可以直观发现早晚高峰期预测偏差大原因是污染物浓度突变时模型反应滞后。import pandas as pd import matplotlib.pyplot as plt import seaborn as sns result_df pd.DataFrame({ actual: y_test, pred: y_pred, hour: test_indexes.hour, weekday: test_indexes.dayofweek }) result_df[abs_error] (result_df[actual] - result_df[pred]).abs() pivot result_df.pivot_table(valuesabs_error, indexweekday, columnshour, aggfuncmean) plt.figure(figsize(12, 6)) sns.heatmap(pivot, cmapYlOrRd, annotFalse) plt.title(Mean Absolute Error by Hour and Weekday) plt.xlabel(Hour) plt.ylabel(Weekday) plt.savefig(error_heatmap.png, dpi150)pivot_table把数据重组为7行24列的矩阵每个单元格是某个星期几、某个小时的绝对误差均值。热力图颜色越深说明该时段模型越不自信。从这张图上如果发现工作日早8点和晚6点误差明暗分明可以考虑在特征中加入“是否高峰时段”的二值变量往往能明显降低峰值误差。4.2 前端可视化基于Bootstrap的实时仪表盘资源包里那些bootstrap.css、style.css、animate.css文件构成了仪表盘的外观层。前端页面通过Ajax定时请求后端/api/predict接口获取最新预测值并用ECharts绘制折线图实时更新。这里的核心不是CSS本身而是前后端数据交换的JSON结构。async function fetchPrediction() { const response await fetch(/api/predict?station1001hours24); const data await response.json(); const chart echarts.init(document.getElementById(aqiChart)); chart.setOption({ xAxis: { type: category, data: data.timestamps }, yAxis: { type: value, name: AQI }, series: [{ name: 实际值, type: line, data: data.actual, smooth: true }, { name: 预测值, type: line, data: data.predict, lineStyle: { type: dashed } }] }); } setInterval(fetchPrediction, 3600000); // 每小时刷新一次这段前端代码的逻辑很直接fetch从后端拿到时间戳、实际值、预测值三个数组然后交给ECharts渲染。setInterval每3600000毫秒即1小时刷新一次因为预测是小时级的。需要注意ECharts的xAxis如果是时间轴建议使用type: time而不是category否则数据点之间的间距会按顺序排列而非按时间间隔排列遇到服务器维护差了几小时时间戳就会出现排列错乱。如果项目里有多个监测站你可以在Ajax请求中带上station参数后端先查该站最近24小时真实值再调用模型生成未来24小时预测值拼装成上述JSON格式。前后端分离的好处是后续你想换Vue或React只需要保留这个接口就行。4.3 异常报警与阈值规则从预测到行动预测本身不产生价值报警和决策才是闭环。系统设计了一套简单的规则引擎当未来3小时预测AQI超过200时系统自动生成红色预警记录。这个阈值完全可配置我建议不要硬编码而是放进一个alerts_config.json里因为不同城市的空气质量标准执行级别不同。{ alert_rules: [ {level: yellow, aqi_min: 101, aqi_max: 150, action: 建议敏感人群减少户外活动}, {level: orange, aqi_min: 151, aqi_max: 200, action: 建议中小学停止户外体育课}, {level: red, aqi_min: 201, aqi_max: 999, action: 启动机动车单双号限行预案} ] }规则引擎判断时取预测序列的最大值作为触发条件。因为预测跨度越长不确定性越大如果只看预测序列的第一个点可能错过后面时段的峰值。我一般在触发报警前还加一道“二次确认”如果当前时刻的实际AQI也超过阈值的80%才真正触发告警否则只记录待观察。这样做能减少因为模型波动造成的误报实际运营中比较有用。5. 资源包排错与二次开发拿到zip后别急着运行5.1 解压与目录结构先看清再动手zip压缩包解压后典型的目录结构会包含web/、models/、data/、docs/、requirements.txt。我见过太多人一解压就双击run.py结果报ModuleNotFoundError。正确顺序是先建虚拟环境再安装依赖然后按文档中说明的顺序启动。如果你在macOS或Linux上注意zip里的文件权限可能丢失解压后要执行chmod x *.sh。unzip 基于智慧城市空气质量预测与分析系统.zip -d air_quality_system cd air_quality_system python -m venv venv source venv/bin/activate # Windows下用 venv\Scripts\activate pip install -r requirements.txt python main.pypip install -r requirements.txt会同时安装Flask、Pandas、scikit-learn、TensorFlow等依赖版本如果不匹配最典型的表现是tensorflow与keras版本冲突。项目文档如果没写死版本号你在新环境里装出来的版本库可能与原开发环境不一致这时模型权重文件.h5可能加载失败。建议以requirements.txt中的版本为准不要轻易用pip install --upgrade。5.2 常见报错排查路径、编码、模型文件缺失这类项目高发错误有三个。第一个是中文路径问题如果项目所在目录包含中文名Python在Windows读取CSV时可能报UnicodeDecodeError处理办法是CSV文件读取时指定encodingutf-8或gbk并在Python文件开头定义常量BASE_DIR。第二个是模型文件找不到LSTM训练后保存为model.h5但项目原包可能只带训练好的权重如果文件位置配置不对启动时会报No such file or directory。我建议在配置文件中使用相对路径并统一从项目根目录定位不要依赖工作目录。第三个是前端接口跨域问题如果你把Flask部署在5000端口前端页面用file://协议直接打开请求会失败。最稳妥的方式是使用Flask的render_template直接渲染页面让前后端同源或者在Flask里配置CORS(app)支持跨域。from flask_cors import CORS from flask import Flask, jsonify, request import joblib app Flask(__name__) CORS(app) # 允许所有来源跨域开发模式下可用 model joblib.load(models/lgbm_aqi.pkl) app.route(/api/predict, methods[GET]) def predict_api(): station request.args.get(station, 1001) # 这里省略特征组装逻辑 result model.predict(features) return jsonify({station: station, predict: result.tolist()})CORS(app)在开发时很方便但生产环境应该限定origins否则任何网页都能请求你的预测接口。joblib.load用于加载LightGBM模型比pickle更安全因为joblib对大数组更高效。如果这个接口要部署到公网建议再加上requests_per_minute限流用Flask-Limiter实现。5.3 把预测结果导出为PDF报告一个交付技巧课程设计答辩时直接展示网页不够有“厚度”我习惯再加一个报告导出功能。用reportlab或weasyprint将当天的预测结果、阈值报警和误差统计合成为PDF。下面这段代码用weasyprint从HTML渲染PDF样式可以直接复用资源包里的CSS非常方便而且比操作PDF库画坐标简单很多。from weasyprint import HTML def generate_report(predict_df, error_mae): html_content html head style body { font-family: SimSun, sans-serif; margin: 40px; } h1 { color: #2c3e50; font-size: 22px; } table { border-collapse: collapse; width: 100%%; } th, td { border: 1px solid #ddd; padding: 8px; text-align: center; } th { background-color: #f5f5f5; } .error-info { color: #e74c3c; font-weight: bold; } /style /head body h1城市空气质量预测报告/h1 p预测周期未来24小时/p p模型MAEspan classerror-info%.2f/span/p table trth时间/thth预测AQI/thth等级/th/tr %s /table /body /html % (error_mae, rows_html) HTML(stringhtml_content).write_pdf(prediction_report.pdf)注意HTML模板里的%%是Python字符串格式化的转义如果你用f-string直接写%即可。这个报告可以把PDF放在docs/目录下答辩时作为系统输出物展示比口头描述更有说服力。我的经验是将模型评估指标MAE、RMSE和报警准确率放入报告首页老师一眼就能看出系统闭环完整。如果你还想更进一步可以在报告中加入未来24小时的AQI曲线缩略图用Matplotlib生成PNG再嵌入HTML但要注意中文字体配置Windows下SimSun可用Linux服务器上需要安装fonts-wqy-zenhei。本文还有配套的精品资源点击获取