:用Python搭建成績預測模型)
簡介面向希望掌握LSTM多變量預測的Python開發(fā)者這份資源以成績預測等場景為主線系統(tǒng)覆蓋時間序列轉(zhuǎn)監(jiān)督數(shù)據(jù)、數(shù)據(jù)預處理、模型定義與訓練、評估優(yōu)化等關鍵環(huán)節(jié)同時按單變量、多變量和多步預測拆分為多個模塊并配置shampoo-sales等數(shù)據(jù)集便于上手實踐。壓縮包共33個文件含19個csv數(shù)據(jù)文件和14個py腳本整體3.88MB腳本從預處理、縮放、穩(wěn)定化到模型開發(fā)與預測完整串聯(lián)適合初學者循序漸進閱讀。已有3419人瀏覽學習資源代碼結(jié)構(gòu)清晰、模塊劃分明確可直接復用于風電、股票、天氣等序列預測任務也可作為入門LSTM的實操參考資料。1. LSTM 多變量預測是什么拿一片成績單能算出下學期的線「LSTM 多變量預測」翻譯成人話你手里有一張按周排開的成績明細每行不是孤零零一個分數(shù)而是一組互相影響的指標——出勤率、作業(yè)提交率、周測得分、自習時長。要做的是拿過去 4 到 6 周這些多列數(shù)據(jù)預測下一次考試能考多少。很多人一上來就把歷次分數(shù)排成一列丟給 LSTM結(jié)果模型輸出一條接近水平的線真正能跑通的是把它建模成多變量時間序列讓網(wǎng)絡自學“出勤下滑之后兩周成績才開始掉”這類延遲關聯(lián)。這篇適合手里有歷次測驗明細、想用 Python 跑通一版預測腳本的從業(yè)者按數(shù)據(jù)處理、模型搭建、訓練驗證和踩坑排查的順序展開照抄能復現(xiàn)。2. 為什么成績預測要選 LSTM從梯度消失到多變量輸入輸出設計2.1 RNN 記不住長序列的根源梯度消失與 LSTM 的閘門普通 RNN 在每個時間步把上一刻的隱藏狀態(tài)和當前輸入一起算得到新的隱藏狀態(tài)。序列一長反向傳播時梯度沿時間軸連乘權(quán)重矩陣的特征值只要小于 1梯度就會指數(shù)級衰減十幾步之前的信息對后續(xù)更新幾乎沒貢獻。成績預測里“第 3 周開始刷題量加大、第 5 周進入疲態(tài)”這類信號經(jīng)常要隔 6 到 10 周才在大考上兌現(xiàn)普通 RNN 根本傳不到那么遠。LSTM 的解決辦法是引入細胞狀態(tài)和三個門控遺忘門決定上一時刻的細胞狀態(tài)保留多少輸入門決定當前新信息寫進多少輸出門決定細胞狀態(tài)對外放出多少。梯度在細胞狀態(tài)這條“傳送帶”上可以近似無損地流過關鍵信息能傳幾十步。這也是為什么時間序列預測里LSTM 比普通 RNN 更常用。LSTM 還有個更節(jié)儉的親戚 GRU把三個門簡化成重置門和更新門參數(shù)更少后面如果遇到小樣本過擬合把兩層 LSTM 換成 GRU 是性價比最高的第一招。2.2 多變量預測的成績建模特征表、時間步與預測目標多變量預測指的是輸入不是單列分數(shù)而是多個隨時間變化的特征列預測目標可以是其中的某一列也可以是一個單獨的標簽。成績預測最常見的建模方式每個學生每周生成一條記錄取過去 n_steps 周的所有特征作為輸入 x預測下一個時間點的考試分數(shù)作為 y。我一般先這樣定義特征表以教務系統(tǒng)能導出的字段為例特征列含義取值范圍attendance本周出勤率0–100homework作業(yè)提交率0–100quiz周測平均分0–100study_hours本周自習總時長小時rank上周大考班級排名正整數(shù)預測目標 target 是下一次考試分數(shù)。注意 rank 這類“數(shù)值越小越好”的特征歸一化后語義不會顛倒但解讀反歸一化結(jié)果時要心里有數(shù)。特征也不是越多越好成績預測尤其忌諱把排名和成績同時塞進去——兩者高度共線模型會把注意力全放在 rank 上一旦排名波動預測就跟著抖。既然要走序列建模先解釋一個繞不開的問題為什么不用隨機森林或 XGBoost因為它們默認把每行樣本當獨立個體不利用順序信息。同樣是“上一周出勤低”發(fā)生在連續(xù)三周下滑和偶爾一周波動樹模型看到的特征向量完全一樣LSTM 通過時間步結(jié)構(gòu)能區(qū)分這兩種模式成績預測的關鍵恰恰在趨勢和延遲效應。當然這不代表 LSTM 一定更好第 6 章會給驗證方法。預測目標的頻率也要先想清楚。如果直接預測一學期只有三四次的大考樣本量會小到?jīng)]法訓練常見做法是先用周測成績當近端目標把模型訓穩(wěn)再在學期末的大考上做一次遠端校驗。另一種做法是每 4 周一個窗口預測第 5 周周測最后看期末大考的殘差。這就是“先搞清楚 y 的頻率”的重要性它決定了你的樣本量級。2.3 動手前的 Python 環(huán)境numpy、pandas、tensorflow 怎么配跑 LSTM 之前先確認環(huán)境這一步翻車的人最多。我一般建議 python 3.8 以上配合 tensorflow 2.x、numpy、pandas、scikit-learn。Windows 上最容易出問題的是 python 環(huán)境變量配置命令行敲 python 沒反應十有八九是安裝時沒勾選“Add Python to PATH”或者 PATH 里同時存在多個 python 入口。# 先升級 pip避免裝包時出現(xiàn)依賴沖突 python -m pip install --upgrade pip # 安裝本次要用到的核心庫numpy 和 pandas 會一起裝好 python -m pip install numpy pandas scikit-learn tensorflow裝完用一個最小腳本驗證環(huán)境別直接跑模型import numpy as np import pandas as pd import tensorflow as tf print(np.__version__) print(pd.__version__) print(tf.__version__)如果 import tensorflow 報 DLL 加載失敗常見原因是用 32 位 python或者 python 版本太新而 tf 還沒跟上這種情況建議換 64 位 python 3.8–3.11或者直接用 tf 的 CPU 版本。成績預測這種小數(shù)據(jù)量任務 CPU 完全夠用不必為 GPU 再折騰 CUDA。VSCode 里跑的話右下角確認解釋器選的是同一個 python別讓 Anaconda 的 base 環(huán)境混進來。環(huán)境一次配好后面代碼報錯才找得到根因。3. 把多變量成績數(shù)據(jù)切成 LSTM 能吃的形狀窗口化、歸一化與切分3.1 按學生滑動窗口n_steps 取值與切窗口代碼LSTM 的輸入要求是三維數(shù)組樣本數(shù) × 時間步 × 特征數(shù)。成績表通常是長表每行是一個學生某一周的一條記錄。第一步是把每個學生的記錄按時間排序再各自切窗口。手工用 for 循環(huán)切沒問題數(shù)據(jù)量大就用 pandas groupby。關鍵決策是 n_steps 取多少。預測周測成績我一般取 4 到 6 周預測學期末大考至少取 8 周以上讓模型能看到一個完整的“狀態(tài)變化周期”。n_steps 太大有兩個副作用樣本量成倍縮小訓練變慢n_steps 太小則學不到趨勢。常見做法是先畫出幾個學生的成績曲線人工看看成績從下滑到回升大概幾周再定窗口。import numpy as np import pandas as pd def make_sequences(df, n_steps6, feature_colsNone, target_colscore): 按學生切滑動窗口返回 (X, y)。 X 形狀: (樣本數(shù), n_steps, len(feature_cols)) y 形狀: (樣本數(shù),) X, y [], [] for sid, group in df.groupby(student_id): group group.sort_values(week) # 每個學生必須按周升序 vals group[feature_cols].values target group[target_col].values for i in range(len(vals) - n_steps): X.append(vals[i:i n_steps, :]) y.append(target[i n_steps]) return np.array(X), np.array(y)這里有兩個新手常踩的坑。一個是 groupby 之后忘記 sort_values導致同一學生的不同周樣本亂序模型等于在看隨機時間順序訓練 loss 能降但驗證一塌糊涂。另一個是把不同學生拼成一條長序列直接滑窗窗口會跨過學生邊界把學生 A 的尾巴和學生 B 的開頭當成連續(xù)事件。上面代碼按 student_id 分組就是為了避開這兩個問題。3.2 切分訓練/驗證/測試集按時間切還是按學生切時間序列不能隨機打亂再切分。成績有明顯學期趨勢期初、期中、期末分布不同隨機切會讓訓練集混進“未來”樣本模型等于提前看到考題。最直接的做法是按時間順序切前 70% 周次訓練中間 15% 驗證最后 15% 測試。n_total X.shape[0] train_end int(n_total * 0.7) val_end int(n_total * 0.85) X_train, X_val, X_test X[:train_end], X[train_end:val_end], X[val_end:] y_train, y_val, y_test y[:train_end], y[train_end:val_end], y[val_end:]但這里有個容易被忽略的問題按時間切分時訓練集里某個學生的最后幾周和測試集里同一學生的前幾周是連續(xù)的模型在訓練時見過該學生的狀態(tài)測試時其實是“繼續(xù)預測同一個人的下一個點”這和真實場景里要預測一個新學生不一樣。如果目標是跨學生泛化我一般改成按學生 id 切分train_sids df[student_id].unique()[:70] train_mask df[student_id].isin(train_sids)按周切分測的是“這個學生未來怎么樣”按學生切分測的是“沒見過的人能不能預測”兩者面對的問題不同腳本里要二選一并寫清楚注釋。3.3 歸一化順序scaler 必須在訓練集上 fit成績和自習時長的量綱差很多直接喂給 LSTM 會讓數(shù)值大的特征主導梯度。常見做法是用 MinMaxScaler 把所有特征壓到 0–1。順序有講究先在訓練集上 fit再 transform 訓練集、驗證集和測試集測試集絕不能參與 fit。from sklearn.preprocessing import MinMaxScaler n_samples, n_steps, n_features X_train.shape scaler_X MinMaxScaler() X_train_2d X_train.reshape(-1, n_features) X_train_scaled scaler_X.fit_transform(X_train_2d).reshape(n_samples, n_steps, n_features) X_val_scaled scaler_X.transform(X_val.reshape(-1, n_features)).reshape(X_val.shape[0], n_steps, n_features) X_test_scaled scaler_X.transform(X_test.reshape(-1, n_features)).reshape(X_test.shape[0], n_steps, n_features) scaler_y MinMaxScaler() y_train_scaled scaler_y.fit_transform(y_train.reshape(-1, 1)).ravel() y_val_scaled scaler_y.transform(y_val.reshape(-1, 1)).ravel() y_test_scaled scaler_y.transform(y_test.reshape(-1, 1)).ravel()上面的 reshape 是在三維和二維之間切換經(jīng)常有人在這里把形狀搞亂報錯先記下 X_train 的原始三維形狀transform 完再 reshape 回去。fit/transform 順序混亂是成績預測里最典型的玄學問題如果在整份數(shù)據(jù)上 fit scaler等于讓模型在訓練時接觸了測試集的數(shù)據(jù)分布線下指標好看換一批真實數(shù)據(jù)立刻現(xiàn)原形。測試集不僅不能進 fit連 min/max 都不該看見。4. 用 Python 搭建 LSTM 成績預測模型結(jié)構(gòu)、訓練與畫圖4.1 兩層 LSTM Dropout 的模型結(jié)構(gòu)怎么定數(shù)據(jù)形狀確定后模型結(jié)構(gòu)我一般從兩層 LSTM 起步。第一層用 return_sequencesTrue讓每個時間步都輸出隱藏狀態(tài)給第二層第二層 return_sequencesFalse只輸出最后一步的編碼接一個帶 relu 的中間層再壓到 1 個輸出節(jié)點。成績預測輸出是連續(xù)分數(shù)所以最后一層不加激活函數(shù)等價于線性輸出用 MSE 作為損失。from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout from tensorflow.keras.optimizers import Adam n_steps X_train_scaled.shape[1] n_features X_train_scaled.shape[2] model Sequential() model.add(LSTM(64, return_sequencesTrue, input_shape(n_steps, n_features))) model.add(Dropout(0.2)) model.add(LSTM(32, return_sequencesFalse)) model.add(Dropout(0.2)) model.add(Dense(16, activationrelu)) model.add(Dense(1))LSTM 單元數(shù)在這個樣本量級不需要堆太大。6432 是穩(wěn)妥起點第一層 64 負責捕捉每個特征各自的時間模式第二層 32 負責把多個時間步的隱狀態(tài)融合成整體表達。Dropout 放在 LSTM 層之間而不是 LSTM 內(nèi)部配合后面的早?;灸軌鹤∵^擬合。如果訓練集很小把結(jié)構(gòu)縮到 3216 更穩(wěn)參數(shù)少了不容易硬背訓練樣本。4.2 編譯與訓練參數(shù)adam、mse、early stopping編譯時優(yōu)化器用 Adam學習率默認 0.001 對這種任務通常夠。損失用 mse同時監(jiān)控 mae因為 MSE 對離群成績比如某次大考整體偏難導致全班低分過于敏感MAE 更能反映平均偏差。EarlyStopping 是 LSTM 訓練里最值得設的參數(shù)成績預測數(shù)據(jù)量少訓練到過擬合是常態(tài)它的價值是停在驗證集最好的位置并回滾權(quán)重。from tensorflow.keras.callbacks import EarlyStopping model.compile(optimizerAdam(learning_rate0.001), lossmse, metrics[mae]) early_stop EarlyStopping(monitorval_loss, patience15, restore_best_weightsTrue) history model.fit( X_train_scaled, y_train_scaled, validation_data(X_val_scaled, y_val_scaled), epochs100, batch_size16, callbacks[early_stop], verbose1 )這里幾個參數(shù)按經(jīng)驗給參考參數(shù)建議值說明LSTM 第一層 units64時間模式提取LSTM 第二層 units32多特征融合Dropout0.2防止過擬合batch_size16小樣本下梯度更穩(wěn)learning_rate0.001Adam 默認值patience15早停耐心越小越保守batch_size 對成績預測這種小樣本很敏感設 128 會讓每個 batch 里學生多樣性不夠梯度方向不穩(wěn)設 4 則統(tǒng)計噪聲太大收斂慢。epochs 給到 100 甚至 200 都行反正早停兜底實際通常在 30 到 50 個 epoch 內(nèi)就觸發(fā)。訓練完看 historyval_loss 先降后升說明早停生效如果從頭到尾都在升先查數(shù)據(jù)順序和歸一化再調(diào)結(jié)構(gòu)。4.3 預測結(jié)果還原與曲線繪制橫坐標太密集怎么處理模型輸出的 y 是 0–1 之間的歸一化值要還原成真實分數(shù)才能拿去解讀。預測與反歸一化如下y_pred_scaled model.predict(X_test_scaled) y_pred scaler_y.inverse_transform(y_pred_scaled).ravel() y_test_real scaler_y.inverse_transform(y_test_scaled.reshape(-1, 1)).ravel()inverse_transform 時經(jīng)常有人傳了形狀不對的數(shù)組把 y 先 reshape(-1, 1) 再傳即可這也是最常見的 python 類型轉(zhuǎn)換坑。畫出真實與預測曲線時樣本一多 x 軸刻度就會擠成一團我把刻度旋轉(zhuǎn)一下再顯示import matplotlib.pyplot as plt plt.figure(figsize(12, 5)) plt.plot(y_test_real, label真實成績, linewidth2) plt.plot(y_pred, label預測成績, linewidth2) plt.title(LSTM 多變量預測成績測試集對比) plt.legend() plt.xticks(rotation45) # 橫坐標太密集時旋轉(zhuǎn)避免疊成一團 plt.grid(alpha0.3) plt.show()畫圖只做直觀確認數(shù)值指標用 MAE 和 RMSE 才有說服力。成績 0–100 分制下MAE 在 5 分以內(nèi)算是能落地8 分以上基本等于白預測回去檢查特征和窗口。5. LSTM 預測成績的五條踩坑記錄現(xiàn)象、原因、解決5.1 loss 不降或直接變 NaN現(xiàn)象訓練一開始 loss 就是 nan或者幾百步都不降。原因多數(shù)是數(shù)據(jù)里帶 NaN 或無窮大MinMaxScaler 處理不了其次是學習率太大梯度爆炸。成績表里某個學生缺考、周測缺勤填充方式不對就會留下空值。解決清洗時先 df.isna().sum() 查空值用前向填充或班級均值填充再把學習率從 0.001 降到 0.0001 試。若仍然 NaN檢查是否有一個特征的量綱異常大比如排名填了 999scaler 之前先做去極值。5.2 訓練集表現(xiàn)好、測試集一塌糊涂現(xiàn)象訓練 loss 很低驗證 loss 也不差測試 MAE 突然飆高。原因最常見的是數(shù)據(jù)泄漏——歸一化在整份數(shù)據(jù)上 fit或者切分時把未來數(shù)據(jù)混進了訓練集其次是按時間切分但學生重疊模型只是在“續(xù)寫”見過的學生。解決嚴格先切分再 fit scaler測試數(shù)據(jù)連 min/max 都不許碰如果要預測新學生按 student_id 切分并驗證跨學生效果。這個坑最隱蔽也是很多人覺得 LSTM 是黑匣子的主要原因。5.3 預測曲線整體滯后一拍現(xiàn)象預測曲線和真實曲線形狀幾乎一樣但整體向右平移了一個點。原因模型學到的其實是“把上一周的分數(shù)抄下來”而不是“預測下一周的變化”。當目標序列高度自相關、輸入特征變化不充分時LSTM 的最小化損失策略就是輸出接近上一步的值。解決加入更有預測力的特征比如作業(yè)完成率的變化量、復習時長增量把目標從原始分數(shù)改成差分讓模型預測變化而不是絕對值或者把預測目標改為未來兩周的均值降低單次噪聲。5.4 報錯 expected ndim3, found ndim2現(xiàn)象第一次跑 fit 時直接報維度錯誤說期望三維輸入但拿到了兩維。原因LSTM 輸入要求三維而 X_train 在切分后可能是二維忘了把時間步維度保留。常見于用 for 循環(huán)組裝特征時把時間維壓掉了。解決檢查 X_train.shape期望是 (樣本數(shù), n_steps, n_features)。如果只有兩維說明 n_steps1 或者窗口切片寫錯回到 make_sequences 重新切并在模型前面加一句 assert len(X_train_scaled.shape) 3 防呆。5.5 小樣本下每生只有十幾次記錄怎么做都過擬合現(xiàn)象訓練 loss 降到接近 0Dropout 加了也沒用。原因LSTM 參數(shù)動輒幾千上萬而一個學生可能只有 12 周記錄去掉窗口后只剩幾個樣本模型直接把訓練樣本背下來。解決一是把多個班、多學期數(shù)據(jù)合并擴大樣本二是縮小模型到單層 LSTM 16 個單元三是用 GRU 替代 LSTM它參數(shù)更少在短序列上往往更抗過擬合。先跑通再談精度小數(shù)據(jù)硬上大模型是成績預測最常見的翻車現(xiàn)場。6. 讓成績預測真正上考場滾動驗證與樸素基線對照6.1 滾動多步驗證別只測一步單步預測容易給人“模型很準”的錯覺每次都給真實歷史窗口模型只猜下一個點誤差自然小。我現(xiàn)在的習慣是訓練好后做滾動驗證——給定前 n_steps 周數(shù)據(jù)預測下一周再把預測值當真值拼進窗口預測再往后一周直到把整個測試期推完。這樣暴露出來的累積誤差才是真實使用場景里的誤差。def rolling_predict(model, X_start, n_predict, scaler_X, scaler_y): window X_start[0].copy() # 第一個窗口 preds [] for _ in range(n_predict): next_pred model.predict(window[np.newaxis, :, :], verbose0) pred scaler_y.inverse_transform(next_pred)[0, 0] preds.append(pred) new_step np.concatenate([window[1:], next_pred], axis0) window new_step return preds滾動預測里每步都會把誤差帶進去累積幾步之后曲線往往就飄了。如果你的模型滾動三步誤差已經(jīng)不可接受說明它只適合做短周期預警不適合做期末長周期預測。6.2 和“用上次成績當預測”的基線比一比判斷 LSTM 值不值得用最硬的標準是和一個樸素基線對照直接用該學生上一次考試分數(shù)作為本次預測算 MAE。LSTM 的 MAE 如果壓不過這個基線說明多變量特征沒有提供增量信息再調(diào)參也是白費。我踩過這個坑之后每次建模第一步都是先跑基線再做 LSTM。成績預測方向最終能不能投入教學管理看的是滾動誤差和基線差值而不是訓練集那條漂亮的下降曲線。先跑通最小腳本再用滾動驗證和基線對照去質(zhì)問結(jié)果只有當多變量輸入真的帶來增益時LSTM 才算在成績預測里站住了腳。希望幫到你。本文還有配套的精品資源點擊獲取