
簡介基于長短期記憶網絡LSTM的空氣質量數據可視化分析預測項目是面向計算機專業學生的期末大作業完整源碼適用于課程設計、畢業設計與項目實戰訓練。項目經導師認可并通過評審得分99分代碼完整可運行基礎薄弱的學習者也能按注釋理解并上手。壓縮包中共260個文件大小約7.03MB涵蓋Python腳本、HTML頁面、SCSS與CSS樣式文件、CSV空氣監測數據、SQLite數據庫及說明文檔等各類文件按功能分層存放便于定位模型訓練、數據可視化與結果展示模塊。項目完整演示了從數據預處理、LSTM模型構建訓練到預測結果可視化呈現的整套流程可直接用于大作業提交、答辯展示或后續擴展學習。目前已有99人學習下載適合需要高質量參考模板的在校學生。1. 期末大作業選 LSTM 預測空氣質量劃算在哪期末周最磨人的不是復習而是手里握著一份從監測站導出的空氣質量表格卻不知道該拿它做什么才算“像樣的結課作品”。直接做描述性統計撐不住頁面用線性回歸猜 PM2.5答辯時老師一句“非線性呢”就會卡住。空氣質量預測恰好卡在性價比最高的位置數據是公開的逐小時監測記錄任務本質是時間序列預測尾聲再配幾張圖就是一個完整的故事線。LSTM 模型對這類帶周期和延遲效應的序列數據貼合度高源代碼量可控可視化分析又能撐起報告篇幅很多“期末大作業”選它就是看中這三點數據不用爬、模型不是調包完事、圖表能證明工作量。這篇按“數據處理 → 模型 → 評估 → 答辯”展開每一步寫到能直接抄的程度。2. 空氣質量數據預處理從時間表到 LSTM 輸入張量LSTM 吃的是有先后順序的序列不是散著的表格行。期末作業里最常見的翻車點不是模型寫錯而是數據沒有按時間排序、時間列有重復、缺失值直接 dropna 導致序列斷檔。空氣質量監測是按小時采樣的夜間到清晨容易出現設備離線造成的空洞所以“讀進來直接訓練”大概率會在 loss 上表現得莫名其妙。2.1 時間解析與缺失值處理監測數據的第一個坑先確認時間列能不能被 pandas 正確識別。常見做法是parse_dates讀入后立刻 sort再設為索引這一步決定了后面所有滑窗切片的順序是否正確。import pandas as pd df pd.read_csv(air_quality.csv, parse_dates[time]) df df.sort_values(time).set_index(time) print(df.info()) print(df.isnull().sum()) df[pm2_5] df[pm2_5].interpolate(methodlinear) df df.dropna(subset[pm2_5])interpolate(methodlinear)按前后觀測值做線性插補比ffill()能保留更多濃度變化細節適合設備短時掉線。若某一列缺失超過 3%插值意義不大建議直接丟列并在報告里注明。做完這兩步后再檢查兩件事索引是否嚴格遞增、時間步長是否均勻。若出現重復時間戳需要先groupby(level0).mean()聚合成單條記錄否則滑窗時同一個小時會被重復采樣。提示監測數據里的 PM2.5 單位通常是 μg/m3量綱對后續歸一化沒有影響但寫報告時一定要標單位。2.2 訓練/測試切分與歸一化別把未來信息帶進訓練時間序列不能像普通回歸那樣隨機切分。隨機打亂會破壞前后相關性更嚴重的是如果用全量數據做fit_transform測試集的極大極小值已經摻進了歸一化參數等于測試時偷看了未來信息評估出來的 RMSE 虛低答辯時被追問必露餡。from sklearn.preprocessing import MinMaxScaler n_train int(len(df) * 0.7) train_df df.iloc[:n_train] test_df df.iloc[n_train:] sc MinMaxScaler(feature_range(0, 1)) train_scaled sc.fit_transform(train_df[[pm2_5]]) test_scaled sc.transform(test_df[[pm2_5]])fit_transform只用在訓練集上transform只對測試集做同樣映射。如果引入溫度、風速等多個特征就把它們的列一起傳進去LSTM 輸入張量的最后一個維度對應特征數后續模型代碼無需大改。這里還有一個容易被忽略的細節MinMaxScaler對異常值不穩健若某天監測設備爆表記錄到極高濃度歸一化后正常值會被壓縮到很小區間訓練反而變難。若數據長尾明顯可以先用np.log1p對濃度做對數變換再歸一化這個改進可以在報告里單獨寫成一個小實驗。2.3 滑窗序列構造lookback 是期末答辯必問參數LSTM 的輸入不是單條記錄而是一段連續歷史。假設用過去 72 小時預測當前小時那么每一條訓練樣本形狀是(72, 1)72 就是 lookback 窗口。窗口太短學不到日周期太長又把噪聲也裝進記憶72 小時是多數空氣質量預測作業的穩妥起點。import numpy as np def create_sequences(data, lookback): X, y [], [] for i in range(lookback, len(data)): X.append(data[i - lookback:i]) y.append(data[i]) return np.array(X, dtypenp.float32), np.array(y, dtypenp.float32) X_train, y_train create_sequences(train_scaled, lookback72) X_test, y_test create_sequences(test_scaled, lookback72)data[i - lookback:i]取的是連續切片y是對應切片之后的那個時間點數值。循環實現簡單直觀數據量在幾萬條以內完全夠用不需要繞道TimeseriesGenerator。X_train的形狀是(樣本數, 72, 1)y_train是(樣本數,)這個形狀直接對接 Keras 的 LSTM 輸入要求。參數示例值作用與影響lookback72控制記憶長度覆蓋 24 小時周期并留出跨天余量features1單特征時只喂 PM2.5多特征時傳多列訓練占比70%順序切分不打亂測試集留在時間軸末端窗口與預測目標的關系值得多想一層這里做的是“未來 1 小時”單步預測。若作業要求預測未來 24 小時濃度曲線常見做法是用預測輸出遞歸地接回輸入但誤差會逐步累積期末展示時主動說明這個限制比等老師點破更顯專業。3. LSTM 模型構建與訓練從門控機制到可復現的 Keras 代碼空氣質量序列本身是非線性的早晚高峰抬升、雨天下降、前一日高濃度會延續到次日上午。RNN 理論上能處理序列但訓練時梯度在長序列上容易消失。LSTM 加入門控單元讓信息可以選擇性寫入記憶或遺忘這讓它成為時間序列預測作業里最容易出穩定結果的初始選擇。3.1 為什么序列建模選 LSTM記住并遺忘的邊界LSTM 的核心是單元狀態 C_t它像一條傳送帶橫穿整個網絡。遺忘門決定上一時刻的狀態保留多少輸入門決定新信息寫入多少輸出門決定最終輸出什么。若只記住傳送帶概念或只記得“它比 RNN 強”答辯時很容易被細節問住。遺忘門的輸入是什么數據是當前時刻輸入 x_t 與上一時刻隱狀態 h_{t-1} 拼接后的向量經過 sigmoid 輸出 0 到 1 的系數。這個系數與上一時刻單元狀態 C_{t-1} 逐元素相乘完成“遺忘”。對應公式為f_t σ(W_f · [h_{t-1}, x_t] b_f)空氣質量場景里遺忘門學到的東西往往是“今天凌晨 3 點的濃度對下午 3 點的預測沒那么重要”而前一天的相同時刻反而權重更高。這就是 LSTM 處理這種帶日周期數據的直覺邏輯它不把整個歷史等權看待而是學出一套動態權重這一點寫進報告比貼網絡結構圖更有說服力。3.2 兩層 LSTM 模型骨架return_sequences 與 Dropout 的位置Keras 是期末作業里復現成本最低的選擇原因在于它不需要像 PyTorch 那樣手動管理隱狀態初始化LSTM 層的循環邏輯封裝在內部剛接觸深度學習的人不容易在狀態重置上出錯。我更推薦先跑通 Keras若后續想改自定義損失或做多步采樣再換 PyTorch 也不遲。from tensorflow import keras from tensorflow.keras import layers model keras.Sequential([ layers.Input(shape(X_train.shape[1], X_train.shape[2])), layers.LSTM(64, return_sequencesTrue), layers.Dropout(0.2), layers.LSTM(32), layers.Dense(1) ]) model.compile( optimizerkeras.optimizers.Adam(learning_rate1e-3), lossmse, metrics[mae] ) model.summary()第一層 LSTM 必須設return_sequencesTrue否則它只輸出最后一個時間步的狀態形狀變成(None, 64)第二層 LSTM 拿到的就不是序列而是單點維度直接對不上。Input里的X_train.shape[2]是特征數單變量為 1多變量時會自動適配。Dropout 放在兩層之間而不是輸出層之后目的是讓第二層看到的是被隨機置零的序列特征這比在 Dense 后加正則更能抵抗時間序列過擬合。提示model.summary()會顯示每層參數量截圖放進報告可以直觀展示網絡規模是期末作業常見的素材。3.3 訓練參數與回調EarlyStopping 讓 100 輪變成“夠了就停”深度學習作業最容易犯的錯是把epochs固定寫死比如訓練 100 輪后直接取最后一個權重。模型在第 60 輪已經收斂時后面 40 輪只是在過擬合訓練集。正確做法是用回調監控val_loss連續若干輪不降就提前停止并恢復到最佳權重。from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau early_stop EarlyStopping( monitorval_loss, patience15, restore_best_weightsTrue ) reduce_lr ReduceLROnPlateau( monitorval_loss, factor0.5, patience5, min_lr1e-5 ) history model.fit( X_train, y_train, validation_split0.2, epochs100, batch_size64, callbacks[early_stop, reduce_lr], verbose1 )validation_split0.2是在訓練集末尾再切出 20% 作為驗證集不會穿透到測試集。patience15意味著驗證損失連續 15 輪沒有更優就停ReduceLROnPlateau在連續 5 輪平臺期時把學習率減半幫助模型跳出局部極小。訓練完成后注意一件事history里保存的是歸一化空間下的 loss不能直接寫進報告說“loss 是 0.003”要反歸一化后按濃度單位計算誤差。超參數推薦值調參方向第一層 units64數據量小時從 32 起步第二層 units32兩層結構比單層多層更穩dropout0.2過擬合明顯時升到 0.3batch_size64顯存小就降到 32learning_rate1e-3loss 震蕩時降到 5e-4關于“模型越深越好”的誤解這里順便糾正空氣質量數據集通常在幾萬條量級兩層 LSTM 是實踐中的上限。堆到三層以上參數量變大訓練集 loss 好看測試集反而變差期末報告里寫“兩層足夠了”反而體現你對模型容量有判斷力。4. 可視化分析與預測評估把模型結果變成報告的圖表模型跑完只完成一半期末大作業的評分標準里“可視化分析”往往占大頭。可視化不是把訓練曲線貼一遍而是要讓老師能從圖里讀出三個結論模型收斂沒有、預測準不準、失效場景在哪。下面的圖和指標都圍繞這三點展開。4.1 訓練/驗證損失曲線過擬合要從圖上看出來損失曲線是判斷訓練過程是否健康的第一個證據。把 loss 和 val_loss 畫在一起如果兩者同步下降后走平說明訓練充分如果 val_loss 在某輪之后反彈上升而 loss 還在降就是過擬合的典型信號此時應提高 dropout 或提前停止。import matplotlib.pyplot as plt plt.figure(figsize(10, 4)) plt.plot(history.history[loss], labeltrain_mse) plt.plot(history.history[val_loss], labelval_mse) plt.yscale(log) plt.xlabel(epoch) plt.ylabel(MSE loss) plt.title(訓練與驗證損失曲線) plt.legend() plt.tight_layout() plt.savefig(loss_curve.png, dpi200)plt.yscale(log)是常用技巧loss 從 0.01 降到 0.0001 時線性坐標下后半段會被壓成一條直線對數坐標才能看出收斂細節。圖片用savefig保存而不是截圖dpi 設 200 以上放進報告才不會被老師說“看不清”。4.2 預測值與真實值對比評估指標怎么算、怎么寫進報告損失函數是歸一化空間的 MSE不能直接對外匯報。測試集預測結果要反歸一化回原始濃度單位再重新計算指標。注意y_test在create_sequences后是二維數組變一維數組反歸一化前需要 reshape。from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score pred_test model.predict(X_test, verbose0) pred_real sc.inverse_transform(pred_test) y_real sc.inverse_transform(y_test.reshape(-1, 1)) mae mean_absolute_error(y_real, pred_real) rmse mean_squared_error(y_real, pred_real, squaredFalse) r2 r2_score(y_real, pred_real) print(fMAE: {mae:.2f} μg/m3) print(fRMSE: {rmse:.2f} μg/m3) print(fR2: {r2:.3f})指標含義報告里怎么解讀RMSE誤差的均方根對重污染時段的大誤差更敏感取值大于 MAEMAE平均絕對誤差與“平均差多少 μg/m3”直接掛鉤R2決定系數接近 1 說明模型捕捉到了大部分波動趨勢RMSE 大于 MAE 是正常的因為平方運算放大了極端誤差兩者差得越多說明模型在重污染時段的偏移越明顯這正好可以作為 4.3 節的分析入口。R2 的計算在 sklearn 新版中直接返回標量值不推薦手寫公式手寫容易出現負值時解釋不清楚的情況。4.3 誤差分布與重污染時段挖出模型失效的規律對比圖誰都會畫加一張誤差分布圖就能拉開差距。以真實值減預測值為橫軸畫直方圖觀察誤差是否集中在 0 附近。若直方圖明顯左偏說明模型系統性低估右側長尾則說明偶發的高濃度漏報嚴重。residuals y_real[:, 0] - pred_real[:, 0] plt.figure(figsize(8, 4)) plt.hist(residuals, bins30, edgecolorwhite, alpha0.8) plt.xlabel(真實值 - 預測值 (μg/m3)) plt.ylabel(樣本數) plt.title(測試集預測殘差分布) plt.tight_layout() plt.savefig(residual_hist.png, dpi200)更進一步的漏斗圖是把y_real按濃度分桶比如 0-50、50-100、100 以上分別計算每個桶的平均絕對誤差。結果大概率顯示濃度越高的桶誤差越大。這不是模型 bug而是 LSTM 在訓練集里見過的高濃度樣本本身就少且重污染過程的突變性更強。把這張圖放進報告并解釋“模型擅長日常濃度預測重污染事件是邊界場景”比只放一張對比圖顯得更有分析深度。5. 期末答辯現場把源代碼、模型和可視化講成一個閉環答辯演示和寫代碼是兩種能力。代碼能跑只是底線老師更想看到的是“你知道自己在做什么、模型哪部分最脆弱、還能怎么改”。期末大作業的評分錨點往往就在最后十分鐘的追問里。5.1 老師愛問的三個追問的應答方向“為什么用 LSTM 而不是 ARIMA”是出現頻率最高的問題。應答主線是ARIMA 是線性模型對 PM2.5 這種受排放、氣象、地形共同影響的非線性序列擬合能力有限LSTM 通過門控機制學習長期依賴不需要手動指定滯后階數。如果老師追問“你怎么證明 LSTM 比 ARIMA 好”回應方式是“我沒做對比實驗但從文獻和課程討論中做了一個選型判斷”。誠實承認邊界遠比編造對比結果安全。“lookback 為什么設 72”也有固定答法24 小時是日周期48 小時覆蓋前一天同潮汐72 小時留出跨兩天的余量可以補充一句“我還試過 168但訓練樣本變少驗證損失沒有改善”。這句話說明你做過敏感性分析是實打實的加分項。“模型什么時候預測得最差”是壓力最大的追問。直接展示 4.3 的殘差桶狀圖說出“高濃度樣本少模型傾向于向均值回歸所以重污染時段會低估”。老師會接著問“那怎么改”答案不必做出來說出思路即可對高濃度樣本加權損失、把對數變換加入預處理、或改用分位數損失。5.2 演示前值得做的三件小事第一把訓練入口封裝成命令行參數。用argparse暴露--lookback、--epochs、--batch_size三個參數答辯現場老師問“換一個窗口試試”時你只需要改一個數字重新跑而不是改代碼。第二在測試集預測圖上用紅框標出一次重污染事件框出模型低估區間并截兩張局部放大圖這會讓分析環節從“我畫了圖”變成“我發現了問題”。第三把model.load_weights的保存路徑寫清楚答辯前直接加載權重出圖不要現場訓練浪費五分鐘。空氣質量預測類作業的答辯節奏應該是數據怎么清洗、窗口怎么選、網絡怎么搭、誤差在哪、下一步怎么改。這五步講全源代碼本身是否完美反而不那么重要。下次再面對類似的時間序列預測小項目先做周周期觀察再看預測殘差的分布比反復堆訓練輪數更能提前發現問題。本文還有配套的精品資源點擊獲取