測期末源碼拆解:從數(shù)據(jù)處理到答辯調(diào)參)
簡介面向高校期末大作業(yè)與課程設(shè)計場景的時間序列預(yù)測項目源碼基于LSTM模型實現(xiàn)同時提供RNN和Transformer對比模型。代碼結(jié)構(gòu)清晰曾獲高分驗證適合深度學(xué)習(xí)初學(xué)者快速理解不同循環(huán)神經(jīng)網(wǎng)絡(luò)在序列建模中的差異。壓縮包共31個文件包含Python腳本、CSV時序數(shù)據(jù)集、說明文檔、運行截圖與項目配置整體大小28.48MB。其中CSV數(shù)據(jù)涵蓋ETTh1、ETTm1、pollution等常見負(fù)荷與空氣質(zhì)量序列模型代碼覆蓋數(shù)據(jù)預(yù)處理、訓(xùn)練、評估和預(yù)測全流程README附有環(huán)境配置與運行說明圖片直觀展示訓(xùn)練曲線和預(yù)測效果解壓后即可按說明部署運行。目前已有1026人學(xué)習(xí)下載無論用于課程設(shè)計、期末答辯還是代碼復(fù)用都能提供完整可跑的參考實現(xiàn)。1. 時間序列預(yù)測LSTM 模型是玄學(xué)還是送分題一份高分期末源碼的完整拆解時間序列預(yù)測LSTM 模型這門課在很多人眼里是玄學(xué)同樣的數(shù)據(jù)有人調(diào)三天還在原地打轉(zhuǎn)有人一把過還拿了高分。差別往往不在模型多高級而在數(shù)據(jù)和流程的細(xì)節(jié)對不對。這份源碼是已經(jīng)拿高分通過的期末大作業(yè)一個可直接打開的 PyCharm 工程內(nèi)置 ETTh1、ETTm1、pollution 三套公開數(shù)據(jù)集RNN、RNN_gpu、Transformer 三個模型文件齊整README 連運行截圖都配好了解壓裝好依賴就能復(fù)現(xiàn)。它解決的不是讀論文而是交作業(yè)期末要做時間序列課設(shè)的學(xué)生或者想快速跑通 LSTM 完整流程的入門開發(fā)者都能直接抓起來用。下面按數(shù)據(jù)、模型、跑通、踩坑的順序把這套工程的關(guān)鍵細(xì)節(jié)一次講透。2. 數(shù)據(jù)先行ETTh1、ETTm1、pollution 三套數(shù)據(jù)集怎么選、怎么做滑窗2.1 三套數(shù)據(jù)的出身、字段與適用選題ETTh1.csv 和 ETTm1.csv 來自同一個公開的電力變壓器溫度數(shù)據(jù)集前者每小時一條h 后綴表示 hourly后者每 15 分鐘一條m 后綴表示 minutely。特征以電力負(fù)荷、油溫、風(fēng)速這類物理量為主適合基于 LSTM 的變壓器油溫預(yù)測或電力負(fù)荷短期預(yù)測這類選題。pollution.csv 是北京 PM2.5 多變量數(shù)據(jù)集字段包括 pm2.5、DEWP露點、TEMP溫度、PRES氣壓、cbwd風(fēng)向、Iws風(fēng)速、Is積雪時間、Ir降雨量做空氣質(zhì)量預(yù)測最合適。數(shù)據(jù)集采樣粒度典型選題變量特征ETTh1.csv1 小時電力負(fù)荷 / 油溫預(yù)測多變量周期以天、周為尺度ETTm1.csv15 分鐘分鐘級短期預(yù)測多變量周期特征更密集pollution.csv1 小時含完整日期時間PM2.5 濃度預(yù)測8 個特征含分類風(fēng)向選數(shù)據(jù)只有一個原則需要弄清作業(yè)要求單變量預(yù)測就把目標(biāo)列單獨提出來當(dāng) y其余列要么當(dāng)外部特征、要么直接舍棄要求多變量預(yù)測就整表進特征。這份工程把三套數(shù)據(jù)都配好了切換只需要改代碼里的 csv 路徑。2.2 歸一化與劃分順序數(shù)據(jù)泄露是期末課設(shè)的第一個坑很多人在這一步翻車讀 csv 之后先對整個 DataFrame 做 MinMaxScaler再切訓(xùn)練集和測試集。這個順序會讓測試集的 min/max 信息提前暴露給訓(xùn)練過程訓(xùn)練時相當(dāng)于偷看到了未來序列的分布測試 RMSE 虛低等答辯現(xiàn)場換一段新數(shù)據(jù)預(yù)測立刻露餡。正確做法是先按時間切分再分別歸一化scaler 只在訓(xùn)練集上 fit。import pandas as pd from sklearn.preprocessing import MinMaxScaler df pd.read_csv(data/pollution.csv, index_col0, parse_datesTrue) # 前 80% 時間做訓(xùn)練后 20% 做測試時間序列不能隨機 shuffle split int(len(df) * 0.8) train_df, test_df df.iloc[:split], df.iloc[split:] scaler MinMaxScaler(feature_range(0, 1)) scaled_train scaler.fit_transform(train_df) # 只在訓(xùn)練集上 fit scaled_test scaler.transform(test_df) # 測試集只 transformfit_transform 和 transform 的分工是這套流程的核心fit 學(xué)的是訓(xùn)練集的 min 和 maxtransform 只是套用測試集一旦參與 fit就等同于提前看到了未來數(shù)據(jù)的取值范圍。歸一化完成后再做滑窗喂給 LSTM 的每個窗口都在同一尺度下梯度更新才穩(wěn)定。2.3 滑動窗口lookback 是整天作業(yè)里最值得調(diào)的參數(shù)LSTM 不是把整條序列直接丟進網(wǎng)絡(luò)而是構(gòu)造用過去 lookback 個時間步預(yù)測下一個時間步的樣本。我一般會把滑窗函數(shù)單獨抽出來方便后面換 lookback 做對照實驗import numpy as np def make_sequences(data, lookback24): X, y [], [] for i in range(len(data) - lookback): X.append(data[i:i lookback]) # 過去 24 個時刻 y.append(data[i lookback]) # 未來 1 個時刻 return np.array(X), np.array(y) lookback 24 X_train, y_train make_sequences(scaled_train, lookback) X_test, y_test make_sequences(scaled_test, lookback) print(X_train.shape, y_train.shape)輸出形狀是 (樣本數(shù), lookback, 特征數(shù)) 和 (樣本數(shù), 特征數(shù))。lookback 的選擇必須跟著采樣粒度走ETTh1 是小時級、預(yù)測下一天24 起步想看周周期拉 168ETTm1 是 15 分鐘級、一天有 96 步取 48 或 96 更合理pollution 小時級24 小時是默認(rèn)起點。lookback 太大訓(xùn)練變慢且容易過擬合太小學(xué)不到周期這筆賬用驗證集跑兩組對照再定。pollution.csv 原始數(shù)據(jù)存在缺失值常見做法是先沿時間方向 ffill 前向填充、再丟掉頭部空行最后歸一化df df.ffill().dropna()3. 模型實現(xiàn)RNN.py、RNN_gpu.py、Transformer.py 三份代碼分別承擔(dān)什么角色3.1 基礎(chǔ) RNN 為什么打不過 LSTM先弄懂門控在干什么這份工程里有 RNN.py 和 RNN_gpu.py 兩個文件。基礎(chǔ) RNN 的短板在于梯度沿時間步逐層相乘序列一長梯度要么小到消失、要么大到爆炸結(jié)果模型對窗口早期信息幾乎沒記憶。LSTM 用遺忘門、輸入門、輸出門配合 sigmoid 和 tanh決定哪些信息扔掉、哪些寫進長期記憶梯度在細(xì)胞狀態(tài)路徑上有相對平緩的通道長序列也能穩(wěn)定訓(xùn)練。課設(shè)把 LSTM 當(dāng)主角不是因為它概念新而是同樣的數(shù)據(jù)和訓(xùn)練輪數(shù)下驗證 loss 通常比基礎(chǔ) RNN 低一截。拿到源碼后建議按角色分工理解RNN.py 跑 baselineRNN_gpu.py 是 LSTM 或 GRU 的 GPU 加速版本裝好 GPU 版 TensorFlow 后直接跑Transformer.py 是答辯加分項。先跑通 LSTM 版本再回頭補 baseline 和 Transformer 的對比報告才有內(nèi)容可寫。3.2 搭一個能出成果的 LSTMunits、return_sequences、dropout 怎么設(shè)工程里的 LSTM 是雙層的典型課設(shè)結(jié)構(gòu)第一層輸出完整時間步給第二層第二層只留最后一個時間步接全連接from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout n_features X_train.shape[2] model Sequential([ LSTM(64, return_sequencesTrue, input_shape(lookback, n_features), dropout0.2), LSTM(32, dropout0.2), Dense(n_features) ]) model.compile(optimizeradam, lossmse, metrics[mae]) model.summary()參數(shù)含義逐個說清楚第一個 LSTM 的 units64 是隱藏神經(jīng)元數(shù)return_sequencesTrue 表示輸出每個時間步的隱藏狀態(tài)給下一層第二層 return_sequences 不設(shè)默認(rèn) False只輸出最后一步的向量再接 Dense。input_shape(lookback, n_features)即滑窗長度和特征數(shù)。dropout 放在 LSTM 內(nèi)部抑制過擬合課設(shè)從 0.2 起步穩(wěn)妥。優(yōu)化器 adam、損失 mse 是回歸預(yù)測的默認(rèn)組合mae 作為輔助評估指標(biāo)。單變量預(yù)測時 n_features1多變量時是 8 或更多Dense 輸出維度跟著改。模型保存的約定是 model 目錄放權(quán)重文件state 目錄放訓(xùn)練狀態(tài)或歸一化 scaler 信息model.save(model/lstm_pollution.h5)3.3 Transformer.py 的定位對照實驗怎么做才不顯得堆材料Transformer 對時間序列的優(yōu)勢是并行處理整個窗口用自注意力直接建模任意兩個時間步的關(guān)系。這類課設(shè)工程里的 Transformer.py 通常是簡化版編碼器結(jié)構(gòu)位置編碼、多頭注意力、前饋網(wǎng)絡(luò)、層歸一化。答辯時用白板畫清楚輸入先加位置編碼再過多頭注意力層注意力權(quán)重表示第 3 小時的油溫變化會強烈影響第 20 小時的預(yù)測——這是 LSTM 很難直觀解釋的一點。from tensorflow.keras.layers import Layer, MultiHeadAttention, LayerNormalization, Dense class TransformerBlock(Layer): def __init__(self, d_model64, num_heads4, ff_dim128): super().__init__() self.att MultiHeadAttention(num_headsnum_heads, key_dimd_model) self.ffn Sequential([ Dense(ff_dim, activationrelu), Dense(d_model), ]) self.layernorm1 LayerNormalization(epsilon1e-6) self.layernorm2 LayerNormalization(epsilon1e-6) def call(self, inputs): attn_output self.att(inputs, inputs) # 自注意力 out1 self.layernorm1(inputs attn_output) # 殘差 層歸一化 out2 self.layernorm2(out1 self.ffn(out1)) return out2注意 Transformer 對數(shù)據(jù)量的要求比 LSTM 高這份工程在 pollution.csv 上能出合理結(jié)果是因為序列長度和樣本量正好夠用。換更小的數(shù)據(jù)集時先降 d_model 和 num_heads否則訓(xùn)練 loss 壓不下去。做對照組時固定同一批數(shù)據(jù)、同一個 lookback、同一個 scaler只換模型結(jié)構(gòu)答辯老師才挑不出毛病。4. 完整復(fù)現(xiàn)路徑從配環(huán)境、跑通三份代碼到畫出預(yù)測曲線4.1 環(huán)境準(zhǔn)備Python 版本與依賴清單工程里帶著 .idea 目錄用 PyCharm 直接打開項目根目錄即可運行配置都在包里不用手動建環(huán)境再逐個配參數(shù)。Python 建議用 3.8 到 3.10TensorFlow 2.x 在這幾個版本下最穩(wěn)定。依賴就四件套pip install numpy pandas matplotlib scikit-learn tensorflow只跑 LSTM 的話CPU 版 TensorFlow 完全夠用要跑 RNN_gpu.py 的 GPU 路徑需要確認(rèn)機器的 CUDA 和 cuDNN 版本跟 TensorFlow 匹配。遇到 Python 版本太新裝不上 TensorFlow 的情況最省事的解法是建一個 3.10 以下的虛擬環(huán)境再在這個環(huán)境里重裝依賴。4.2 按 README 順序跑通三套模型README.md 里每一步截圖都放好了照著順序執(zhí)行就行python RNN.py # baseline python RNN_gpu.py # GPU/CPU 均可跑內(nèi)部自動判斷設(shè)備 python Transformer.py # 對照模型每個腳本獨立運行輸出統(tǒng)一寫到 model 和 state 目錄。第一次跑建議只跑 RNN_gpu.py確認(rèn)數(shù)據(jù)路徑和 TensorFlow 版本沒問題后再補跑另外兩個。腳本里寫的是相對路徑 data/ETTh1.csv解壓后如果把數(shù)據(jù)文件移走了會直接報文件不存在先檢查當(dāng)前工作目錄。4.3 訓(xùn)練參數(shù)速查表與調(diào)參順序參數(shù)常見值說明lookback24 / 48 / 96 / 168滑窗長度小時級數(shù)據(jù) 24 起步units32 / 64 / 128第一層 LSTM 神經(jīng)元數(shù)dropout0.1 - 0.3防過擬合課設(shè) 0.2 起步batch_size16 / 32 / 64越大批次訓(xùn)練越快但可能收斂到較差解epochs50 - 150配合早停使用lossmse / mae回歸任務(wù)默認(rèn) mseoptimizeradam學(xué)習(xí)率 0.001 起步調(diào)參順序我習(xí)慣先固定 lookback配上 epochs 和早停再動 units最后才碰 dropout。一上來同時改三個參數(shù)最后訓(xùn)完根本說不清效果變化是誰帶來的。4.4 訓(xùn)練完看什么loss 曲線和真實值預(yù)測值曲線README 截圖里最核心的圖是真實值和預(yù)測值的曲線對比。自己驗證時用這段代碼出圖import matplotlib.pyplot as plt inv_pred scaler.inverse_transform(pred.reshape(-1, n_features)) inv_true scaler.inverse_transform(y_test.reshape(-1, n_features)) plt.figure(figsize(12, 4)) plt.plot(inv_true[:200], labeltrue) plt.plot(inv_pred[:200], labelpred) plt.legend() plt.title(LSTM Prediction vs True) plt.show()pred 是模型輸出的歸一化值畫圖前必須用訓(xùn)練時的 scaler 反歸一化回去否則縱軸范圍只有 0-1跟真實值完全對不上。反歸一化之前要把 pred reshape 成 (樣本數(shù), 特征數(shù)) 再調(diào) inverse_transform。兩條曲線趨勢一致、局部波動有一點相位差是正常的看到預(yù)測曲線幾乎是平移的直線說明訓(xùn)練環(huán)節(jié)出了問題大概率是數(shù)據(jù)泄露或滑窗構(gòu)造錯了下一章逐個排查。5. 避坑實錄這份源碼復(fù)現(xiàn)路上最常見的 5 個翻車點5.1 預(yù)測曲線是一條筆直的躺平線現(xiàn)象訓(xùn)練 loss 還在降驗證 loss 也正常畫出來的預(yù)測曲線卻像把上一天的值平移了一下變化極小。原因絕大多數(shù)情況是歸一化和劃分順序做反了scaler 在切分前對整個數(shù)據(jù)集 fit造成數(shù)據(jù)泄露也可能是 lookback 太短模型退化成只學(xué)會復(fù)制最近一個點還有可能是 Dense 輸出維度對應(yīng)不上特征數(shù)模型擬合到均值上去。解決回到第 2 章的流程確認(rèn) scaler 只在訓(xùn)練集上 fit把 lookback 從 24 往上調(diào)試 48、96逼模型看更長歷史檢查數(shù)據(jù)有沒有排序錯亂的行時間序列所有操作都不能 shuffle。5.2 inverse_transform 報 ValueError維度對不上現(xiàn)象報錯類似 Found input variables with inconsistent numbers of samples或者 reshape 時長度正好差一個 lookback。原因訓(xùn)練集和測試集構(gòu)造滑窗后樣本數(shù)不一致測試樣本數(shù)等于原始測試行數(shù)減 lookback畫圖和計算指標(biāo)時拿錯長度。解決所有涉及 y_test 和 pred 的地方統(tǒng)一用同一個切片長度predict 返回的結(jié)果先 reshape 成 (n_samples, n_features)再傳給 scaler.inverse_transform。5.3 模型構(gòu)建直接報 incompatibleexpected ndim3, found ndim2現(xiàn)象構(gòu)建 Sequential 時TensorFlow 報 Input 0 of layer lstm is incompatible: expected ndim3, found ndim2。原因LSTM 要求輸入三維格式是 (樣本數(shù), lookback, 特征數(shù))把二維數(shù)組直接丟進去了說明滑窗沒做或數(shù)據(jù)被 reshape 壓平了。解決打印 X_train.shape 確認(rèn)是三維滑窗函數(shù)沒寫對回第 2 章重新構(gòu)造多變量預(yù)測時把 n_features 和 Dense 輸出維度對齊。5.4 驗證 loss 先降后升訓(xùn)練 loss 還在掉現(xiàn)象前幾十個 epoch 兩條 loss 一起降往后驗證 loss 開始反彈訓(xùn)練 loss 卻繼續(xù)走低。原因典型過擬合。LSTM 參數(shù)量不小pollution 這類數(shù)據(jù)量也就幾千行訓(xùn)練太久模型把訓(xùn)練集噪聲當(dāng)成了規(guī)律。解決把 dropout 加到 0.2 以上加早停是更省事的辦法patience 設(shè) 10-15監(jiān)控 val_loss連續(xù)沒改善就回滾到最佳權(quán)重from tensorflow.keras.callbacks import EarlyStopping early_stop EarlyStopping(monitorval_loss, patience10, restore_best_weightsTrue) model.fit(X_train, y_train, validation_data(X_test, y_test), epochs100, batch_size32, callbacks[early_stop])5.5 加載保存的模型后預(yù)測全 NaN現(xiàn)象訓(xùn)練完保存了 model/lstm_pollution.h5換個腳本重新加載預(yù)測輸出大面積 NaN。原因加載和訓(xùn)練用了不同版本的 TensorFlow或者保存的是自定義 Transformer 層、加載腳本沒注冊對應(yīng)的層定義另一個常見原因是歸一化前的數(shù)據(jù)本身帶 NaN訓(xùn)練時表面正常保存時權(quán)重已經(jīng)被污染。解決加載和訓(xùn)練必須同一環(huán)境自定義層對象要在加載腳本里先 import 進來回到第 2 章df.ffill().dropna() 先把數(shù)據(jù)清干凈再歸一化。這條習(xí)慣能躲掉九成 NaN 鬧鬼。6. 把作業(yè)從能跑改到能答辯多步預(yù)測、回歸指標(biāo)和 baseline 對照6.1 單步改多步滾動預(yù)測和它的誤差累積單步預(yù)測是用過去 24 小時預(yù)測下 1 小時答辯時老師通常會追問能不能預(yù)測未來 24 小時。最小改動是滾動預(yù)測把上一步的預(yù)測結(jié)果當(dāng)成新窗口輸入循環(huán) 24 次。def forecast(model, last_window, steps24): preds [] window last_window.copy() for _ in range(steps): p model.predict(window[np.newaxis, :, :], verbose0)[0] preds.append(p) window np.concatenate([window[1:], p[np.newaxis, :]], axis0) return np.array(preds)window 形狀是 (lookback, n_features)每預(yù)測一步把窗口往前推一格末尾補上剛預(yù)測的值。多步預(yù)測的坑是誤差累積第 24 步的誤差遠(yuǎn)大于第 1 步答辯時主動承認(rèn)這一點反而顯得你理解序列預(yù)測的本質(zhì)。6.2 回歸三件套RMSE、MAE、R2 出一張指標(biāo)表光有曲線不夠看答辯還需要指標(biāo)表。計算后按 RMSE、MAE、R2 排三行直接進報告from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score rmse mean_squared_error(inv_true, inv_pred, squaredFalse) mae mean_absolute_error(inv_true, inv_pred) r2 r2_score(inv_true, inv_pred) print(fRMSE{rmse:.3f}, MAE{mae:.3f}, R2{r2:.3f})新版 sklearn 里 squaredFalse 是老版 mean_squared_error 的替代寫法老接口會報警告但不影響數(shù)值。R2 接近 1 說明擬合好負(fù)值說明模型比直接預(yù)測均值還差指標(biāo)表里順手把 baseline 也算上。6.3 樸素 baseline讓 LSTM 的優(yōu)勢有對照答辯被問憑什么說 LSTM 有效時最實在的回答是給一個基線用過去 24 小時的均值當(dāng)預(yù)測結(jié)果算同一套 RMSE、MAE 對比。通常 LSTM 能把 RMSE 壓到基線的一半左右這個數(shù)字比講任何原理都有說服力。建議做實驗時固定同一個 scaler 和后處理流程算出 LSTM、RNN baseline、Transformer 三行指標(biāo)形成一張干干凈凈的對比表課程報告直接把表貼進去。這套工程解壓后對照 README 用省掉的配置時間能把三個模型都跑一遍。從那以后我每次跑序列預(yù)測課設(shè)都強制自己先打印數(shù)據(jù)行列數(shù)、確認(rèn)歸一化只在訓(xùn)練集上 fit、再檢查滑窗 shape三個檢查項過完才談?wù){(diào)參這套流程幫我避開了絕大多數(shù)翻車現(xiàn)場希望幫到你。本文還有配套的精品資源點擊獲取