
简介基于LSTM与Django构建的空气质量监测及预测系统是一份完整的高分毕业设计项目面向计算机相关专业学生与Python初学者适用于毕业设计、期末大作业或课程设计场景。系统涵盖空气质量数据采集、LSTM时序预测模型、Django后端逻辑、前端可视化与后台管理模块代码注释详细简单修改配置即可部署运行。压缩包共260个文件、约7.07MB包含15个Python脚本、15个依赖缓存文件、164个SCSS样式及JS/CSS等静态资源还有HTML模板、CSV历史与实时数据集、SQLite数据库以及地图与项目配置文件目录结构清晰便于逐层阅读。从内容预览可见项目提供实时监测、历史趋势、省份分析等多个页面并配套pm2.5数据集覆盖从数据处理到预测展示的完整链路。目前已有384人学习下载适合需要快速搭建空气质量系统或学习LSTMDjango整合的读者可直接作为毕设源码运行也方便二次开发与论文对照。1. 空气质量监测及预测系统到底解决什么问题从 PM2.5 历史数据到未来浓度的完整闭环在毕业设计里选「Python实现基于LSTMDjango的空气质量监测及预测系统」这个题目很多人第一反应是能跑起来给老师演示就行但真正让答辩加分的是把 LSTM 和 Django 两条技术线揉在一起的能力。Django 负责把监测数据的采集、存储、展示串成完整业务闭环LSTM 负责从历史时序数据里学出 PM2.5 的变化规律并预测未来若干小时的浓度。这套系统不是纯理论的神经网络 Demo而是一个能落地、能演示、能扩展成真实监测平台的最小可用项目适合想同时展示后端开发和深度学习能力的同学。2. 系统拆解与选型LSTM 凭什么预测空气质量Django 在系统里管哪一块2.1 监测与预测两条主线的职责划分写代码之前先做需求拆分。常见的做法是把系统拆成两条相对独立的主线监测线和预测线。监测线负责数据采集、入库和展示。数据采集器定时从空气质量监测站点抓取 PM2.5、PM10、SO2、NO2、CO、O3 六项常规污染物的逐小时浓度落地到数据库然后通过网页把历史变化趋势用折线图展示。这条线技术含量不高但它是整个系统的数据基础。没有干净完整的历史数据LSTM 模型再厉害也只会空转预测精度没有任何保障。预测线负责从历史数据里学习规律并输出未来值。它的工作流程是把历史数据按固定时间窗口切成训练样本用 LSTM 训练网络学习污染物的时变规律训练完成后把模型参数保存为文件。系统运行时Django 后端加载这个模型文件接收最近 N 小时的监测数据返回未来 M 小时的预测浓度前端再把预测曲线续在历史曲线后面。两条线的衔接通过数据表完成。监测表存原始数据预测表存模型输出的结果页面上共用同一个时间轴。顺序不能颠倒一定是先跑通监测线再做预测线。见过不少同学先花两个星期调 LSTM 参数最后发现数据采集模块还没写只能手工造小批量样例来演示时间全浪费了。我一般建议按「数据采集接入 → 数据库建模 → LSTM 训练脚本 → Django 接口串联 → 前端图表联调」的顺序推进每一步都有可验证的中间产物。数据采集后先查数据库确认有数LSTM 训练后先画出预测曲线确认趋势跟得上最后才进 Django 做接口。这样做不会到联调阶段才发现链路断了留给答辩的时间也更充裕。2.2 LSTM 门控机制与时间序列预测原理LSTMLong Short-Term Memory是循环神经网络 RNN 的一种变体专门解决普通 RNN 在长序列上的梯度消失问题。普通 RNN 反向传播时梯度要沿着时间步连乘序列一长梯度要么指数爆炸要么指数衰减网络学不到久远时刻的信息。PM2.5 浓度受前一天甚至前几天的气象条件影响这种长期依赖恰恰是普通 RNN 的软肋。LSTM 的核心设计是在每个时间步维护一条细胞状态cell state像传送带一样承载信息通过三个门控机制控制信息的写入与丢弃。遗忘门决定上一时刻的状态中哪些信息要遗忘输入门决定当前时刻的新信息写入多少输出门决定当前时刻输出哪些信息。这种结构让 LSTM 能在几十个时间步的跨度内保留关键特征比普通 RNN 稳定得多。具体到本项目PM2.5 小时浓度是一个典型的单变量或多变量时间序列。单变量方案只把 pm25 序列作为输入多变量方案把 PM10、SO2、NO2、CO、O3 以及可选的气象变量一起作为输入。多变量在理论上能捕捉污染物之间的相关性和气象对扩散条件的影响效果通常更优但特征对齐和数据采集的范围也更大。对有毕业设计需求的同学我的建议是先做多变量的小步尝试至少六项污染物都放进去。训练时经常会发现加入其他污染物后 MAE 比单变量低答辩时你能拿出对比数据讲。相比之下只拿 pm25 一条线做模型结构太简单论证深度不够。热词搜索里「空气质量预测」相关的资料也大多按多变量 LSTM 展开顺着这个方向做参考实现更容易找到。2.3 Django 的 MTV 架构如何承接业务闭环Django 在本项目里承担数据模型定义、接口暴露、页面渲染三层职责。MTV 模式下 Model 和数据表对应Template 负责 HTML 渲染View 负责业务逻辑与数据传递。URL 路由在两者之间做映射没有独立的 Controller。具体到实现AirQuality 模型对应监测数据表Prediction 模型对应预测结果表。Model 定义完后Django admin 后台自动生成增删改查界面调试时值非常方便。View 层写两类代码一类是页面渲染视图把查询结果传给模板另一类是 JSON 接口供前端图表异步拉取数据。URL 层把 /dashboard/、/api/history/、/api/predict/ 分别映射到对应的视图函数。为什么选 Django 而不是 Flask 或 FastAPIDjango 自带 admin、ORM、模板引擎、表单校验能省掉大量重复工作。ORM 对 SQLite 到 MySQL 的切换只需要改配置迁移命令也是现成的。Flask 更轻但所有组件要自己拼装开发周期拉长。FastAPI 性能好且自动生成接口文档可它对模板渲染和 admin 支持偏弱如果前端不是前后端分离架构用起来不如 Django 顺手。以「快速做出完整系统」为首要目标Django 是低成本的选择。3. 数据准备与 LSTM 模型训练从原始数据到能出预测结果的模型文件3.1 数据来源与预处理空气质量数据常用的公开来源有中国环境监测总站的逐小时发布数据以及各类开源数据集。爬虫方案比较常见但需要注意目标页面结构经常变化和反爬机制的存在。更稳妥的做法是下载现成的历史数据集转成 CSV再把 CSV 导入数据库保证训练可复现。拿到原始数据后第一件事不是写模型而是清洗。典型问题有三个时间戳不连续、单小时数据缺失、极端异常值。时间戳不连续直接导致序列断裂需要重采样补齐缺失值我一般先用前向填充forward fill连续缺失超过 3 小时则用前后均值插值异常值用 3σ 原则识别也就是数值偏离均值超过三倍标准差就视为异常替换成相邻时刻的均值。清洗后的数据统一存成 DataFrame索引设为时间戳列包括 pm25、pm10、so2、no2、co、o3如果还有气象站在加 temperature、humidity、wind_speed、pressure 等字段。列顺序在整个项目里要保持一致后期写 Django 接口时要读取哪些字段在训练脚本里就固定下来否则模型接口和训练脚本容易字段对不上。3.2 滑动窗口样本构造与归一化LSTM 训练样本不是独立的一行行数据而是滑动的序列窗口。假设要用过去 24 小时预测未来 1 小时的 PM2.5 浓度那一个样本就是连续 24 行的特征矩阵标签是第 25 行的 PM2.5 值。窗口滑动步长为 1 小时这样 1000 小时的原始数据大约能构造出 975 个样本。时间步长 time_step 是必须调的超参数常见取 24、48 或 72。取 24 意味着空气质量的日变化周期完整包含在窗口里一般比取 12 效果好很多。窗口太长计算量变大模型未必能从更多历史里提炼出有效信息没有明显提升时不要太贪心。归一化使用 MinMaxScaler把所有特征压缩到 0 到 1 之间。这里有一个毕设中特别容易踩的坑MinMaxScaler 必须在训练集上 fit再用同一个 scaler 去 transform 测试集。如果先对全量数据 fit 再划分测试集的信息泄漏进训练集测试指标虚高答辩被问两句就露馅。from sklearn.preprocessing import MinMaxScaler # 假设 df 是清洗后的 DataFrame按时间升序排列 feature_cols [pm25, pm10, so2, no2, co, o3] data df[feature_cols].values # 先划分再归一化测试集不参与 fit train_size int(len(data) * 0.8) train_raw, test_raw data[:train_size], data[train_size:] scaler MinMaxScaler(feature_range(0, 1)) scaler.fit(train_raw) # 只用训练数据估计 min/max train_scaled scaler.transform(train_raw) test_scaled scaler.transform(test_raw)这段代码的关键是先按时间顺序切出 80% 作为训练集在训练集上完成 scaler 的拟合。注意 feature_range 默认是 (0, 1)如果数据分布特别偏也可以考虑 RobustScaler。训练集和测试集分别 transform 之后接下来才能构造窗口样本。构造窗口样本的循环如下。窗口滑动采用复制切片操作内存开销略大但代码清晰、不会出越界错误适合毕设阶段的工程量级import numpy as np def create_sequences(data, time_step24): X, y [], [] for i in range(len(data) - time_step): X.append(data[i:itime_step, :]) y.append(data[itime_step, 0]) # 预测目标为 pm25 列 return np.array(X), np.array(y) X_train, y_train create_sequences(train_scaled, time_step24) X_test, y_test create_sequences(test_scaled, time_step24) # 输出形状(n_samples, time_step, n_features) print(X_train.shape, y_train.shape)X 的形状是 (样本数, 时间步长, 特征数)LSTM 的输入要求就是这种三维张量。把 pm25 放在 feature_cols 第一位y 取第 0 列就是 PM2.5 的标签。实际代码里列顺序如果不同索引也要跟着改别抄完就忘这个前提。3.3 搭建 Keras LSTM 网络并训练模型搭建用 Keras 最直观。两层 LSTM 加一层全连接中间用 Dropout 防过拟合。第一层 LSTM 带 return_sequencesTrue输出完整序列给第二层第二层不带 return_sequences只输出最后一个时间步的隐含状态再接 Dense 输出单值。import tensorflow as tf from tensorflow.keras.models import Model, load_model from tensorflow.keras.layers import LSTM, Dense, Dropout, Input from tensorflow.keras.optimizers import Adam # time_step 和 feature_num 要与构造样本时保持一致 feature_num X_train.shape[2] inp Input(shape(time_step, feature_num)) x LSTM(64, return_sequencesTrue)(inp) x Dropout(0.2)(x) x LSTM(32, return_sequencesFalse)(x) x Dropout(0.2)(x) out Dense(1)(x) model Model(inp, out) model.compile(lossmse, optimizerAdam(learning_rate0.001), metrics[mae]) print(model.summary())第一层 LSTM 用 64 个神经元第二层 32 个是中小规模时序任务的常见起点。Dropout 加在两层 LSTM 输出之后取 0.2能抑制过拟合又不会明显欠拟合。损失函数用均方误差 MSE因为预测目标是连续数值二分类任务才用交叉熵。Adam 优化器初始学习率 0.001一般不需要手动衰减配合后续回调就够。训练时加上早停和模型保存两个回调。EarlyStopping 监控验证集损失patience 设 10连续 10 个 epoch 没有改善就停止。ModelCheckpoint 保存验证损失最小的权重防止训练过程中最好的一版被后期覆盖。from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint early_stop EarlyStopping(monitorval_loss, patience10, verbose1) checkpoint ModelCheckpoint(models/air_lstm.h5, monitorval_loss, save_best_onlyTrue, verbose1) history model.fit( X_train, y_train, batch_size32, epochs100, validation_split0.1, callbacks[early_stop, checkpoint], verbose1 )注意 validation_split0.1 表示从训练集尾部切 10% 作为验证集是尾部而不是随机抽样因为时间序列随机打乱会破坏时序依赖。epochs 不要一开始就设很大先用 50 试跑观察 val_loss 走势再决定增减。训练时间方面几千条样本在两三层 LSTM 上CPU 跑 50 个 epoch 也就几分钟没必要急着上 GPU。3.4 训练过程评估与模型导出训练完成不是看训练集损失有多低关键是验证集损失和测试集的实际预测效果。先用 history 回调画出损失曲线确认训练集和验证集两条曲线没有明显分离。如果 val_loss 在某个 epoch 后持续升高而 train_loss 还在下降就是典型的过拟合信号把 Dropout 调大或把 LSTM 神经元减半再试。测试集上的验证逻辑是拿模型预测输出逆归一化再和真实值对比计算平均绝对误差 MAE 或均方根误差 RMSE。注意逆归一化必须用之前保存的那个 scaler不能重新 fit否则量纲对不上。from sklearn.metrics import mean_absolute_error, mean_squared_error # 在测试集上预测并逆归一化 pred_scaled model.predict(X_test) # scaler 的 inverse_transform 需要完整特征维度 pred_full np.zeros((len(pred_scaled), len(feature_cols))) pred_full[:, 0] pred_scaled[:, 0] pred_inv scaler.inverse_transform(pred_full)[:, 0] # y_test 同样是归一化后的 pm25 值做逆变换获取真实量纲 y_test_full np.zeros((len(y_test), len(feature_cols))) y_test_full[:, 0] y_test y_test_inv scaler.inverse_transform(y_test_full)[:, 0] mae mean_absolute_error(y_test_inv, pred_inv) rmse np.sqrt(mean_squared_error(y_test_inv, pred_inv)) print(fMAE: {mae:.2f} ug/m3, RMSE: {rmse:.2f} ug/m3)MAE 在 10 μg/m³ 以内对小时尺度预测来说已经是不错的表现。把逆归一化的真实值和预测值画在同一个时间轴上人工观察曲线跟随程度这一步在答辩时是必须展示的远比贴一堆训练指标有用。模型保存为 Keras H5 格式后面 Django 接口通过 load_model 加载使用。4. 用 Django 把模型接成系统数据入库、预测接口与前端图表4.1 Django 项目骨架与 App 划分Django 项目创建好之后按业务边界拆 App。常见做法是把 App 拆成三个core 负责页面渲染和基础运维monitor 负责监测数据采集与展示predict 负责 LSTM 模型加载和预测接口。App 拆细一点路由和模型文件更清晰也比全部塞在单 App 里好维护。建立工程骨架的命令如下django-admin startproject air_quality_sys cd air_quality_sys python manage.py startapp monitor python manage.py startapp predict python manage.py startapp core在 settings.py 里注册这三个 App然后配置数据库。毕设阶段直接用默认 SQLite 就能跑通几万行数据毫无压力。如果数据量大或老师要求 MySQL再按 django.db.backends.mysql 改配置需要额外安装 mysqlclient 依赖。4.2 数据模型定义与后台管理monitor App 里的 AirQuality 模型定义如下。时间用 DateTimeField六项污染物用 FloatField再加一个采集源标记字段。预测结果模型单独放在 predict App存预测时间和各污染物预测浓度。from django.db import models class AirQuality(models.Model): station models.CharField(max_length50, defaultdefault) monitor_time models.DateTimeField(db_indexTrue) pm25 models.FloatField(nullTrue, blankTrue) pm10 models.FloatField(nullTrue, blankTrue) so2 models.FloatField(nullTrue, blankTrue) no2 models.FloatField(nullTrue, blankTrue) co models.FloatField(nullTrue, blankTrue) o3 models.FloatField(nullTrue, blankTrue) class Meta: db_table air_quality ordering [monitor_time]db_indexTrue 给 monitor_time 加索引时间范围查询会明显变快。nullTrue 对应数据库允许空值实际清洗时缺失值已处理过这里留空是为了兼容早期导入数据时可能出现的空字段。ordering 指定默认排序前端查历史数据时少写一个 order_by。用 admin 注册模型在浏览器里检查数据内容很方便from django.contrib import admin from .models import AirQuality admin.site.register(AirQuality)注册完执行 python manage.py makemigrations 和 python manage.py migrate 生成数据库表例行操作不需要手动写 SQL。4.3 数据采集与入库监测数据来源如果是爬虫最稳妥的做法是写一个独立 Python 采集脚本定时抓取数据后批量写入数据库。批量写入用 bulk_create 而不是逐条 save。# monitor/crawler.py import requests import pandas as pd from .models import AirQuality def fetch_realtime(): # 假设从接口拿到 JSON这里用简化的 mock 数据结构 url http://your-data-source/api/realtime resp requests.get(url, timeout10) records resp.json()[data] objects [] for r in records: obj AirQuality( monitor_timer[time], pm25r[pm25], pm10r[pm10], so2r[so2], no2r[no2], cor[co], o3r[o3], ) objects.append(obj) AirQuality.objects.bulk_create(objects) # 一次批量写入注意出现重复时间戳会直接产生重复记录所以批量写入前先用 monitor_time 做去重判断或者给 monitor_time 加 unique 约束。批量写入在大数据量时效率提升明显几万条写入只是秒级。定时调度用系统 crontab 就行。在 Linux 服务器上最简单是每小时跑一次采集脚本0 * * * * cd /path/to/project python manage.py runscript monitor.crawler.fetch如果没有 django-extensions可以写成独立脚本通过 os.environ.setdefault 提前导入 Django 环境再调用。4.4 把 LSTM 模型封装成预测接口预测接口是系统最核心的一段逻辑。写一个独立 service 模块负责加载模型和预处理数据视图只负责接收请求、调用 service、返回 JSON。这样模型加载一次后就驻留在进程内存里不会每个请求都重新读 H5 文件。Keras 模型在 Django 开发服务器下重复加载很慢冷启动一下就要几秒必须做全局缓存。# predict/services.py import numpy as np from tensorflow.keras.models import load_model from monitor.models import AirQuality _model None def get_model(): global _model if _model is None: _model load_model(models/air_lstm.h5) return _model def build_recent_input(hours24): # 查最近 hours 条数据逆序排列成时间升序 rows AirQuality.objects.order_by(-monitor_time)[:hours] rows list(reversed(rows)) # 列顺序必须与训练时一致 arr np.array([[r.pm25, r.pm10, r.so2, r.no2, r.co, r.o3] for r in rows]) return arrmodel 加载放在模块级变量用 None 判断做单次加载。同一进程多线程并发调用 Keras 模型预测时存在资源竞争毕设场景最好加 threading.Lock 把预测包起来这个细节放到第五章专门讲。视图函数使用 JsonResponse 返回 JSON# predict/views.py import json from django.http import JsonResponse from django.views.decorators.http import require_POST from .services import predict_next require_POST def api_predict(request): body json.loads(request.body) result predict_next( pm25body.get(pm25), pm10body.get(pm10), so2body.get(so2), no2body.get(no2), cobody.get(co), o3body.get(o3), ) return JsonResponse({success: True, prediction: result})前端通过 POST 请求把最新一小时的六项浓度传进来服务端返回预测值。用 require_POST 限制请求方法避免 GET 请求携带 body 造成的兼容性问题。4.5 前端页面与图表展示页面用 Django 模板渲染框架内容具体数据通过异步请求获取图表使用 ECharts。前端不需要复杂框架一个模板文件加一个 JS 文件就够。历史数据接口按时间范围返回数组预测数据返回数值序列两个序列拼成一个图。ECharts 折线图核心配置// static/js/dashboard.js async function loadDashboard() { const hist await fetch(/api/history/?hours48).then(r r.json()); const pred await fetch(/api/predict/, { method: POST, body: JSON.stringify({ hours: 24 }) }).then(r r.json()); const chart echarts.init(document.getElementById(chart)); chart.setOption({ tooltip: { trigger: axis }, xAxis: { type: category, data: hist.times.concat(pred.times) }, yAxis: { type: value, name: μg/m³ }, series: [ { name: 历史PM2.5, type: line, data: hist.values, smooth: true }, { name: 预测PM2.5, type: line, data: pred.values, smooth: true, lineStyle: { type: dashed } } ] }); }历史部分画实线预测部分画虚线视觉上一眼能区分。时间轴拼接时注意预测的第一个时间点应该是历史最后一个时间点的下一小时否则会在图上重叠。5. 系统联调避坑毕设阶段最容易翻车的 5 个细节5.1 文件编码与中文乱码现象页面控制台打印的时间戳全是乱码爬虫抓回来的站点名称在数据库里显示成问号。原因Windows 环境下 Python 文件默认编码可能与数据源不一致requests 返回文本用了错误编码解码Django 模板文件保存编码不是 UTF-8。解决爬虫代码显式指定 resp.encoding utf-8存储端如果换 MySQL 则设置表为 utf8mb4Django settings 里设置 DEFAULT_CHARSET utf-8模板文件统一用 UTF-8 保存。不要依赖系统默认编码Windows 和 Linux 行为不一致这是最容易踩的第一个坑。5.2 模型接口超时与冷启动现象第一次发起预测请求后端卡了 5 秒以上前端直接超时第二次之后就快了。原因load_model 在进程启动后第一次调用才执行H5 权重加载和计算图重建耗时严重每次新起 Django 开发服务器都会发生。解决把模型加载提前到模块导入阶段完成或者用独立模型服务。开发阶段可以在 App 的 apps.py ready() 方法里加载并用全局变量缓存部署时把模型推理拆成独立进程Django 通过 HTTP 调用它。简单场景下一个线程锁加全局缓存已经够用。提示判断模型是否被重复加载在 service 模块打印一行日志观察每次预测请求是否都有加载日志即可。5.3 数据泄漏归一化时用了测试集的信息现象测试集 MAE 显示只有 3 μg/m³实际演示时效果很差预测曲线严重滞后真实值。原因写代码时先对全量数据 fit_transform再划分训练集和测试集或者窗口切分时把测试集时间窗口的前向信息混进样本。MinMaxScaler 的 min/max 在全量数据上计算测试集的分布信息泄漏到了训练过程。解决先前 80% 划分再在训练集上单独 fit再分别 transform。窗口构造时严格只用过去数据预测未来不能把目标值本身错位到输入窗口里。检验方法是打印训练集 max 和测试集 max如果两者几乎相等就说明对方泄漏了。5.4 前端预测时间轴偏移现象预测曲线整体向右平移了一格看起来预测结果只是历史值的延迟。原因训练标签设置错误。比如第 i 条样本的标签取了 data[itime_step] 本身而不是 data[itime_step1]那预测结果实际上是当前时刻浓度而不是未来时刻。前端拼接历史与预测序列时预测起始点重叠了历史最后一个点也会产生视觉偏移。解决先确认训练代码里标签索引是 itime_step 还是 itime_step1再在前端拼接时让预测第一个点从历史下一小时开始。答辩演示时直接在图上高亮过渡区间一眼能看出是否偏移。5.5 Django 开发服务器下并发推理的隐患现象两个浏览器同时打开页面其中一个请求预测接口报错 500或者模型预测结果出现 NaN。原因Django 开发服务器默认多线程同一个 Keras 模型实例被多个线程同时调用 predictTensorFlow 计算图会话状态存在竞争。模型加载过程的全局变量缓存也没有线程锁保护并发访问下会被多次加载。解决加一个 threading.Lock把模型加载和 predict 调用都包在锁里。更稳妥的方案是让模型常驻独立进程Django 通过 JSON 传输数据预测进程内部按顺序推理从机制上避免竞争。import threading from tensorflow.keras.models import load_model _model_lock threading.Lock() _model None def predict_sequence(inputs): global _model with _model_lock: if _model is None: _model load_model(models/air_lstm.h5) return _model.predict(inputs, verbose0)锁机制能保证并发下模型只被加载一次推理也串行执行。毕设的并发量远低于真实生产系统这个方案在性能和正确性之间是平衡的。6. 从「能跑」到「能答辩」验证方法与三个进阶优化6.1 用滚动回测验证预测稳定性单次测试集评估只能说明模型在某一时间段的拟合情况。更严格的做法是滚动回测从测试集起点开始每次用前 N 个真实值预测下 1 小时把预测值作为下一次输入序列的一部分继续预测连续滚动 48 小时。滚动预测的误差通常大于单步预测因为误差会在迭代中累积能更真实地反映系统在线运行时的表现。答辩时能拿出滚动回测曲线说服力比单次预测强很多。6.2 加入气象特征与多步预测如果想让项目体现深度在六项污染物基础之上加温度、湿度、风速、气压等气象特征预测效果往往有明显改善。多步预测可以采用 seq2seq 结构Encoder 用 LSTM 编码历史窗口Decoder 逐步输出未来 6 小时或 24 小时的预测值。这样模型复杂度提升一个档次也更容易解释 LSTM 在时序建模上的优势。6.3 接口压测与部署验证答辩前一定要做一次接口压测。简单的做法是用 curl 循环请求预测接口for i in $(seq 1 20); do curl -s -X POST http://127.0.0.1:8000/api/predict/ \ -H Content-Type: application/json \ -d {hours: 24} -o /dev/null -w %{time_total}\n done观察单次响应时间是否稳定在可接受范围。如果平均耗时超过 1 秒先检查是不是模型每次都被重复加载再考虑把模型推理移到独立进程。部署环境用 gunicorn 替代 runserver静态文件交给 Nginx 托管数据库迁移到 MySQL 之前先做好备份。整个系统做下来最大的体会是毕设的完成度不取决于模型多花哨而在于每个环节是否都能被验证。我在训练 LSTM 阶段曾经因为归一化泄漏拿到一份漂亮的测试报告实际一画曲线就露馅从此养成了一个习惯任何评价指标都要配上可视化曲线才算数尤其是时间序列这种天然带先后顺序的任务。希望帮到你。本文还有配套的精品资源点击获取