測(cè):從數(shù)據(jù)預(yù)處理到多步預(yù)測(cè)的完整實(shí)踐)
簡(jiǎn)介針對(duì)地鐵站點(diǎn)日??土黝A(yù)測(cè)需求這份基于LSTM的交通客流預(yù)測(cè)壓縮包給出了從數(shù)據(jù)處理到模型訓(xùn)練與可視化的完整方案。項(xiàng)目以2019年某地鐵站平日客流量及天氣因素為輸入按8:2劃分訓(xùn)練集與測(cè)試集借助numpy、pandas完成預(yù)處理再通過sklearn、LSTM神經(jīng)網(wǎng)絡(luò)建模預(yù)測(cè)并利用matplotlib、seaborn輸出結(jié)果適合機(jī)器學(xué)習(xí)初學(xué)者與交通數(shù)據(jù)分析人員學(xué)習(xí)參考。壓縮包共17個(gè)文件含5個(gè)csv原始數(shù)據(jù)、2個(gè)h5訓(xùn)練模型、1個(gè)py預(yù)測(cè)腳本還有doc/ppt/xlsx等說明與展示材料整體3.95MB。目前已有1292人瀏覽學(xué)習(xí)。通過該資源可系統(tǒng)掌握客流量預(yù)測(cè)的完整流程獲得可直接運(yùn)行的代碼、預(yù)訓(xùn)練模型及答辯PPT能夠快速?gòu)?fù)現(xiàn)實(shí)驗(yàn)并延伸應(yīng)用到同類場(chǎng)景。1. 基于 LSTM 的交通客流預(yù)測(cè)資源包一個(gè)能直接跑通的時(shí)間序列項(xiàng)目如果你做過客流預(yù)測(cè)一定見過這種場(chǎng)景早高峰地鐵閘機(jī)的出站量、節(jié)假日前一天的高速收費(fèi)站車流、商場(chǎng)促銷時(shí)的進(jìn)店人數(shù)波動(dòng)大、周期強(qiáng)、突發(fā)情況多用傳統(tǒng) ARIMA 或移動(dòng)平均很難追上突變。這份《基于LSTM交通客流預(yù)測(cè).zip》正是一個(gè)把 LSTM 應(yīng)用到短時(shí)客流預(yù)測(cè)的完整資源核心鏈路是「時(shí)序數(shù)據(jù) → 滑窗構(gòu)造樣本 → LSTM 訓(xùn)練 → 反歸一化評(píng)估」不依賴第三方付費(fèi)數(shù)據(jù)源用公開的客流序列就能復(fù)現(xiàn)。適合三類人剛?cè)腴T時(shí)間序列預(yù)測(cè)的學(xué)生想把 LSTM 落到實(shí)際業(yè)務(wù)但不想從零搭框架的數(shù)據(jù)工程師以及需要一份基線模型做對(duì)比的實(shí)驗(yàn)人員。2. 數(shù)據(jù)預(yù)處理把原始客流序列變成模型能吃的時(shí)間窗LSTM 吃的是「序列」不是單條記錄。原始數(shù)據(jù)通常是按 15 分鐘或 1 小時(shí)間隔記錄的客流數(shù)值比如[2024-01-01 08:00, 352]。這個(gè)結(jié)構(gòu)離模型需要的輸入形狀還差兩步構(gòu)造滑窗、做歸一化。順序不能反先滑窗再歸一化是常規(guī)做法但更穩(wěn)的是先歸一化再滑窗理由后面第五章節(jié)會(huì)說。2.1 拿到數(shù)據(jù)先別急著建模型先做缺失值與異常值客流數(shù)據(jù)最常見的臟數(shù)據(jù)有兩種。第一種是設(shè)備斷傳導(dǎo)致的整段缺失常見于閘機(jī)或攝像頭采集鏈路表現(xiàn)為連續(xù)數(shù)小時(shí)數(shù)值為 0 或直接沒有記錄第二種是瞬時(shí)尖峰比如某天早高峰因?yàn)榱熊囃睃c(diǎn)導(dǎo)致出站量突然翻倍這類點(diǎn)不一定是噪聲但對(duì)訓(xùn)練影響很大。我的處理習(xí)慣是先做探索性可視化把序列畫出來用肉眼確認(rèn)缺失段和異常段的位置再?zèng)Q定填充策略。處理缺失值時(shí)客流序列按周和按天都有明顯周期性所以用前一周同一天同時(shí)段的均值填充比用線性插值更符合業(yè)務(wù)規(guī)律。異常值用滾動(dòng)中位數(shù)檢測(cè)以 24 個(gè)點(diǎn)為一個(gè)窗口如果某個(gè)點(diǎn)的值超過窗口內(nèi)中位數(shù)的 3 倍或低于其 1/3就標(biāo)記為異常然后用前后兩個(gè)正常點(diǎn)的均值替換。注意這里不要用整體均值替換節(jié)假日或促銷日的高峰值一旦被整體均值拉平模型訓(xùn)練出來會(huì)系統(tǒng)性低估峰期客流。import pandas as pd import numpy as np def load_and_clean(df): # df 必須包含兩列: timestamp 和 flow df[timestamp] pd.to_datetime(df[timestamp]) df df.set_index(timestamp).sort_index() # 缺失填充: 前一周同一時(shí)刻的均值 df[flow] df[flow].replace(0, np.nan) # 0 值當(dāng)作缺失處理 for idx in df[df[flow].isna()].index: week_ago idx - pd.Timedelta(days7) df.loc[idx, flow] df.loc[week_ago, flow] # 異常值替換: 滾動(dòng)中位數(shù) 3 倍閾值 median df[flow].rolling(24, min_periods1).median() diff (df[flow] - median).abs() threshold median * 3 df.loc[diff threshold, flow] median.loc[diff threshold] return df這段代碼兩條核心邏輯缺失值用「上周同期」語義填充而不是簡(jiǎn)單的前向填充這是考慮到了客流數(shù)據(jù)的周期屬性異常值檢測(cè)用的是滾動(dòng)中位數(shù)而非滾動(dòng)均值因?yàn)橹形粩?shù)對(duì)尖峰本身不敏感不會(huì)出現(xiàn)「均值被尖峰拉高、結(jié)果尖峰反而看起來正?!沟淖晕颐?。rolling(24)里的 24 對(duì)應(yīng)小時(shí)粒度下的一天如果你數(shù)據(jù)是 15 分鐘粒度就填 96。2.2 滑窗構(gòu)造樣本window 大小和步長(zhǎng)的取舍邏輯滑窗是把連續(xù)序列切成「過去 N 個(gè)時(shí)間點(diǎn)預(yù)測(cè)未來 M 個(gè)時(shí)間點(diǎn)」的樣本對(duì)。窗口大小是這份資源里最值得調(diào)的第一個(gè)參數(shù)。我的經(jīng)驗(yàn)法則窗口至少覆蓋一個(gè)完整的日周期小時(shí)粒度至少 2415 分鐘粒度至少 96如果數(shù)據(jù)包含周末和工作日兩種模式可以考慮把窗口拉到 48 小時(shí)讓模型看到跨天規(guī)律。但窗口不是越大越好LSTM 對(duì)這種長(zhǎng)序列的記憶能力有限超長(zhǎng)窗口反而會(huì)引入大量無關(guān)歷史信息把訓(xùn)練時(shí)間拉長(zhǎng)的同時(shí)還不見得提升精度。步長(zhǎng)step控制樣本的重疊程度。步長(zhǎng)等于 1 時(shí)相鄰兩個(gè)樣本只錯(cuò)開一個(gè)時(shí)間點(diǎn)數(shù)據(jù)量最大但相鄰樣本幾乎一樣訓(xùn)練耗時(shí)高步長(zhǎng)等于預(yù)測(cè)長(zhǎng)度時(shí)樣本完全不重疊數(shù)據(jù)量小但能覆蓋更長(zhǎng)的時(shí)間跨度。我的默認(rèn)配置是窗口 48、步長(zhǎng) 24這樣既保留了日周期特征又不會(huì)讓訓(xùn)練集膨脹到十萬級(jí)。下面是滑窗構(gòu)造的核心代碼。def create_sequences(data, window48, step24, pred_len1): X, y [], [] # 按時(shí)間順序滑動(dòng)不能打亂 for i in range(0, len(data) - window - pred_len 1, step): X.append(data[i : i window]) y.append(data[i window : i window pred_len]) return np.array(X), np.array(y) # 歸一化必須在滑窗之前且 fit 只能用在訓(xùn)練集上 from sklearn.preprocessing import MinMaxScaler scaler MinMaxScaler(feature_range(0, 1)) train_scaled scaler.fit_transform(train_raw.reshape(-1, 1)) val_scaled scaler.transform(val_raw.reshape(-1, 1)) X_train, y_train create_sequences(train_scaled.flatten(), window48, step24) X_val, y_val create_sequences(val_scaled.flatten(), window48, step24)create_sequences返回的X形狀是(樣本數(shù), window, 1)第三個(gè)維度是特征數(shù)單變量預(yù)測(cè)時(shí)就是 1。這里有個(gè)新手最容易忽略的點(diǎn)scaler.fit_transform只對(duì)訓(xùn)練集做驗(yàn)證集和測(cè)試集用transform讓驗(yàn)證集的數(shù)據(jù)分布「模型沒見過」評(píng)估結(jié)果才可信。歸一化到 [0,1] 是 LSTM 這類梯度敏感模型的標(biāo)準(zhǔn)做法客流數(shù)值動(dòng)輒幾千上萬的量級(jí)如果直接喂給模型激活函數(shù)的梯度會(huì)迅速飽和。3. 模型訓(xùn)練搭 LSTM 網(wǎng)絡(luò)與調(diào)參的落地路徑數(shù)據(jù)處理完下一步就是把滑窗樣本喂進(jìn) PyTorch 的nn.LSTM。這章先說網(wǎng)絡(luò)結(jié)構(gòu)怎么定再說訓(xùn)練循環(huán)怎么寫全程按能跑出合理結(jié)果的標(biāo)準(zhǔn)來配置不給花活。3.1 網(wǎng)絡(luò)結(jié)構(gòu)nn.LSTM 四個(gè)關(guān)鍵參數(shù)的選型nn.LSTM里有四個(gè)參數(shù)直接決定模型容量input_size、hidden_size、num_layers、batch_first。input_size等于每個(gè)時(shí)間步的特征維度單變量客流預(yù)測(cè)就是 1如果你加了天氣、節(jié)假日特征這里就變成特征總數(shù)。hidden_size是隱狀態(tài)維度客流預(yù)測(cè)任務(wù) 64 到 128 之間是甜點(diǎn)區(qū)間太小擬合不了周期性波動(dòng)太大容易過擬合且訓(xùn)練時(shí)間成倍上漲。num_layers一般取 1 或 2客流序列的規(guī)律還沒復(fù)雜到需要三層以上堆疊。batch_firstTrue讓輸入形狀變?yōu)?batch, seq_len, input_size)符合直覺代碼不容易繞暈。需要注意nn.LSTM的輸出是一個(gè)元組(output, (h_n, c_n))做單步回歸時(shí)常規(guī)做法是取最后一層在最后一個(gè)時(shí)間步的隱狀態(tài)h_n[-1]再過一個(gè)全連接層把維度壓到預(yù)測(cè)長(zhǎng)度。下面是我在這類任務(wù)上常用的一種結(jié)構(gòu)。import torch import torch.nn as nn class LSTMPredictor(nn.Module): def __init__(self, input_size1, hidden_size64, num_layers2, output_size1, dropout0.2): super().__init__() self.lstm nn.LSTM(input_sizeinput_size, hidden_sizehidden_size, num_layersnum_layers, batch_firstTrue, dropoutdropout) self.fc nn.Linear(hidden_size, output_size) def forward(self, x): # x: (batch, window, input_size) out, (h_n, c_n) self.lstm(x) # h_n[-1]: 最后一層最后一個(gè)時(shí)間步的隱狀態(tài) last_hidden h_n[-1] # (batch, hidden_size) pred self.fc(last_hidden) # (batch, output_size) return prednum_layers2時(shí)如果dropout傳 0.2PyTorch 只會(huì)在兩個(gè) LSTM 層之間丟不會(huì)作用于輸出層這個(gè)dropout是層間隨機(jī)失活不是給輸入數(shù)據(jù)加噪聲理解錯(cuò)容易誤調(diào)。last_hidden h_n[-1]這個(gè)寫法的含義是取最深那一層在所有 batch 樣本上的最后時(shí)刻隱狀態(tài)它保留了整個(gè)輸入序列壓縮后的信息是序列到單點(diǎn)回歸的標(biāo)準(zhǔn)取法。3.2 訓(xùn)練流程損失函數(shù)、優(yōu)化器與早??土黝A(yù)測(cè)本質(zhì)是回歸任務(wù)損失函數(shù)用均方誤差nn.MSELoss()即可它對(duì)大誤差值比如峰期預(yù)測(cè)偏差幾百人的懲罰呈平方級(jí)放大倒逼模型把峰期擬合得更準(zhǔn)。優(yōu)化器選 Adam學(xué)習(xí)率默認(rèn) 0.001這是時(shí)間序列任務(wù)里最不容易翻車的起手配置。訓(xùn)練循環(huán)里比「前向傳播」更需要留意的是數(shù)據(jù)形狀要對(duì)齊X_train的形狀是(樣本數(shù), 窗口, 特征數(shù))但到了模型里 PyTorch 自動(dòng)把第一維當(dāng) batch這正好對(duì)應(yīng)設(shè)計(jì)。早停early stopping建議自己寫一個(gè)最簡(jiǎn)版本盯驗(yàn)證集損失連續(xù) 20 個(gè) epoch 沒有下降就保存歷史最優(yōu)權(quán)重并終止訓(xùn)練。客流數(shù)據(jù)本身噪聲大訓(xùn)練集損失降到很低也不代表泛化驗(yàn)證集才是模型真實(shí)能力的照妖鏡。def train_model(model, X_train, y_train, X_val, y_val, epochs100, lr0.001, patience20): optimizer torch.optim.Adam(model.parameters(), lrlr) criterion nn.MSELoss() best_val_loss float(inf) wait 0 for epoch in range(epochs): model.train() optimizer.zero_grad() pred model(X_train) loss criterion(pred, y_train) loss.backward() optimizer.step() model.eval() with torch.no_grad(): val_pred model(X_val) val_loss criterion(val_pred, y_val).item() # 早停: 驗(yàn)證損失連續(xù) patience 輪不降則終止 if val_loss best_val_loss: best_val_loss val_loss wait 0 torch.save(model.state_dict(), best_lstm.pt) else: wait 1 if wait patience: break model.load_state_dict(torch.load(best_lstm.pt, weights_onlyTrue)) return modeloptimizer.zero_grad()必須在loss.backward()之前漏掉這行梯度就會(huì)跨 batch 累積訓(xùn)練損失曲線會(huì)呈現(xiàn)不規(guī)則的鋸齒。每個(gè) epoch 結(jié)束切到model.eval()再算驗(yàn)證損失是為了讓 dropout 層在驗(yàn)證階段不生效否則驗(yàn)證指標(biāo)每次前向傳播都帶隨機(jī)性沒法做早停判斷。torch.save保存的是網(wǎng)絡(luò)參數(shù)訓(xùn)練完用load_state_dict恢復(fù)到驗(yàn)證集最優(yōu)狀態(tài)避免因最后幾輪過擬合把模型帶偏。4. 評(píng)估驗(yàn)證RMSE、滯后效應(yīng)與預(yù)測(cè)結(jié)果的可信度訓(xùn)練跑通只是第一步模型到底能不能用要看它在測(cè)試集上的表現(xiàn)。這章講三件事指標(biāo)怎么算、預(yù)測(cè)曲線怎么看、以及評(píng)估里最容易被忽略的滯后效應(yīng)。4.1 評(píng)估指標(biāo)RMSE、MAE 與 MAPE 各看什么評(píng)估 LSTM 客流預(yù)測(cè)推薦同時(shí)算三個(gè)指標(biāo)各有側(cè)重點(diǎn)只看一個(gè)容易自欺欺人。RMSE 對(duì)大幅偏差敏感能反映峰期預(yù)測(cè)失誤的嚴(yán)重程度MAE 是平均絕對(duì)誤差單位與人次一致最直觀MAPE 是相對(duì)誤差但由于客流分母可能接近 0深夜低谷時(shí)段會(huì)導(dǎo)致 MAPE 虛高所以建議只算白天時(shí)段的 MAPE。計(jì)算指標(biāo)前必須先把預(yù)測(cè)結(jié)果做反歸一化inverse_transform直接在 [0,1] 尺度上算誤差得到的數(shù)值沒有任何業(yè)務(wù)含義。常見錯(cuò)誤是把驗(yàn)證集的預(yù)測(cè)值和真實(shí)值各自inverse_transform但注意真實(shí)值在歸一化時(shí)是按「整體訓(xùn)練集最大最小值」縮放的反變換時(shí)也要用同一個(gè) scaler不能重新 fit。def evaluate(y_true, y_pred): # y_true, y_pred 傳入時(shí)是原始人次尺度 rmse np.sqrt(np.mean((y_true - y_pred) ** 2)) mae np.mean(np.abs(y_true - y_pred)) mask y_true 0 mape np.mean(np.abs((y_true[mask] - y_pred[mask]) / y_true[mask])) * 100 return {RMSE: round(rmse, 2), MAE: round(mae, 2), MAPE: round(mape, 2)} # 反歸一化示例 pred_org scaler.inverse_transform(pred_scaled.reshape(-1, 1)).flatten() true_org scaler.inverse_transform(y_val_scaled.reshape(-1, 1)).flatten() print(evaluate(true_org, pred_org))mask y_true 0這行是為了剔除深夜零客流的時(shí)段否則這些時(shí)段真實(shí)值可能是 5、10預(yù)測(cè)值即使只差 3 人次MAPE 也會(huì)暴漲到 60% 以上直接把整體指標(biāo)搞失真。RMSE、MAE、MAPE 三個(gè)值建議同時(shí)看如果 MAE 不大但 RMSE 明顯偏高說明存在少數(shù)預(yù)測(cè)極差的時(shí)段這些時(shí)段大概率集中在早晚高峰需要單獨(dú)拿出來分析。4.2 滯后效應(yīng)評(píng)估圖里最容易被忽視的黑匣子訓(xùn)練完成、指標(biāo)也打印了但如果你只是埋頭看數(shù)字很可能漏掉最嚴(yán)重的問題——預(yù)測(cè)曲線滯后。把預(yù)測(cè)值和真實(shí)值畫在同一張圖上如果預(yù)測(cè)曲線整體比真實(shí)曲線「晚了一兩個(gè)時(shí)間點(diǎn)」像貼著真實(shí)曲線平移了一段距離說明模型學(xué)到的是「復(fù)制前一個(gè)時(shí)刻的值」而不是真正預(yù)測(cè)未來。這種現(xiàn)象在客流預(yù)測(cè)里極其常見尤其是單一客流序列輸入時(shí)模型發(fā)現(xiàn)最優(yōu)策略就是把最后的觀測(cè)值原樣輸出因?yàn)榭土鞅旧碛袕?qiáng)自相關(guān)性這么做損失函數(shù)已經(jīng)很低了。判斷方法很簡(jiǎn)單計(jì)算預(yù)測(cè)序列和真實(shí)序列的交叉相關(guān)性看峰值出現(xiàn)在哪個(gè) lag 上更直接的辦法是把預(yù)測(cè)結(jié)果整體向前平移一個(gè)時(shí)間點(diǎn)再算一次 RMSE。如果平移后誤差顯著變小基本可以確認(rèn)模型在偷懶。解決滯后效應(yīng)有兩個(gè)方向一是把窗口拉開到 48 甚至 72讓模型有更多歷史信息去做模態(tài)判斷而不是貼身復(fù)制二是加入多步預(yù)測(cè)損失讓模型同時(shí)輸出未來 1、2、3 步的預(yù)測(cè)值從訓(xùn)練信號(hào)上逼它學(xué)到趨勢(shì)而非復(fù)制。這段屬于我自己的血淚經(jīng)驗(yàn)早先做第一個(gè)客流模型時(shí)指標(biāo)好看到不行畫圖才發(fā)現(xiàn)全滯后了一拍。5. 避坑指南客流序列在 LSTM 里的五個(gè)翻車點(diǎn)這章集中寫我復(fù)現(xiàn)這類資源時(shí)踩過、以及周圍同事踩過的具體坑。每一條都是「現(xiàn)象 → 原因 → 解決」三步走建議在自己機(jī)器上跑的時(shí)候逐條對(duì)照。5.1 數(shù)據(jù)泄漏歸一化 fit 到了全量數(shù)據(jù)現(xiàn)象驗(yàn)證集指標(biāo)極好RMSE 低得離譜但一到上線或者換新數(shù)據(jù)預(yù)測(cè)就崩盤。原因?qū)懘a時(shí)圖省事先對(duì)整個(gè)數(shù)據(jù)集做了MinMaxScaler.fit_transform再接滑窗和劃分訓(xùn)練驗(yàn)證。這樣驗(yàn)證集的最小最大值已經(jīng)被模型在歸一化階段「見過」了數(shù)據(jù)分布的邊界信息泄漏到了訓(xùn)練流程里評(píng)估結(jié)果虛高。解決嚴(yán)格按「先劃分訓(xùn)練/驗(yàn)證/測(cè)試再在訓(xùn)練子集上fit驗(yàn)證集和測(cè)試集只做transform」。我在第 2.2 節(jié)的代碼里就是這個(gè)順序照抄就不會(huì)踩。數(shù)據(jù)泄漏是這類時(shí)序項(xiàng)目里最隱蔽的坑因?yàn)橹笜?biāo)不會(huì)報(bào)錯(cuò)只會(huì)給一個(gè)不真實(shí)的好結(jié)果。5.2 驗(yàn)證集劃分用了隨機(jī)打亂現(xiàn)象訓(xùn)練損失正常下降驗(yàn)證損失也正常但模型在測(cè)試集上一塌糊涂而且沒有滯后問題的曲線也看不出明顯原因。原因train_test_split默認(rèn)shuffleTrue把時(shí)間序列隨機(jī)抽成了訓(xùn)練集和驗(yàn)證集。LSTM 訓(xùn)練時(shí)驗(yàn)證集里混著訓(xùn)練集時(shí)刻前后的樣本相當(dāng)于讓模型提前「看了看」未來數(shù)據(jù)周期規(guī)律被完整泄露。解決時(shí)序項(xiàng)目的劃分強(qiáng)制按時(shí)間序訓(xùn)練集取前 70%~80%驗(yàn)證集取再往后的 10%測(cè)試集取最后 10%。如果擔(dān)心季節(jié)覆蓋不全可以按「滾動(dòng)劃分」做多輪評(píng)估但每輪內(nèi)部依然嚴(yán)格按時(shí)間切分不能用隨機(jī)抽樣的思路。5.3 訓(xùn)練損失降了驗(yàn)證 MAPE 卻極高現(xiàn)象訓(xùn)練集 RMSE 很小驗(yàn)證集 RMSE 也不大但業(yè)務(wù)方問「平均誤差百分之幾」時(shí)MAPE 一算 30% 以上直接沒法看。原因前面提過夜間低谷時(shí)段的真實(shí)客流可能只有 20 人次預(yù)測(cè)值 35 人次絕對(duì)誤差只有 15RMSE 貢獻(xiàn)微乎其微但 MAPE 是 75%。如果按全天 24 小時(shí)包含低谷一起算MAPE 被幾個(gè)深夜點(diǎn)拉爆。解決評(píng)估指標(biāo)按業(yè)務(wù)時(shí)段區(qū)分。做交通客流運(yùn)營(yíng)預(yù)測(cè)重點(diǎn)關(guān)注早高峰 7:00~9:00、晚高峰 17:00~19:00 和全天非低谷時(shí)段分組計(jì)算 MAPE。深夜段用 MAE 衡量即可不要混在一個(gè)數(shù)里。5.4 LSTM 輸入形狀 shape 報(bào)錯(cuò)三維變二維的維度錯(cuò)位現(xiàn)象model(X_train)報(bào)錯(cuò)Expected 3D input, got 2D或者到了nn.Linear時(shí)報(bào) hidden_size 和輸入維度對(duì)不上。原因滑窗構(gòu)造時(shí)X_train是(樣本數(shù), window)的二維數(shù)組而nn.LSTM要求(batch, seq_len, input_size)三維輸入。另一種情況是取隱狀態(tài)時(shí)用了h_n而不是h_n[-1]導(dǎo)致 fc 層的輸入維度變成了(batch, num_layers, hidden_size)的展平值。解決構(gòu)造完序列后做一次X_train X_train.reshape(-1, window, 1)確認(rèn)形狀取最后隱狀態(tài)寫h_n[-1]這是 last layer 的 hidden state維度正好是(batch, hidden_size)和前面的nn.Linear(hidden_size, output_size)能對(duì)上。5.5 多特征拼接時(shí)忘了對(duì)齊時(shí)間戳現(xiàn)象加了天氣溫度、是否是節(jié)假日這些特征后訓(xùn)練直接崩或者 Loss 變成 NaN。原因特征表和客流表的索引沒有對(duì)齊比如客流是 15 分鐘粒度天氣是小時(shí)粒度兩者直接concat后產(chǎn)生了時(shí)間偏移模型讀到的「當(dāng)前時(shí)刻溫度」其實(shí)是 45 分鐘前的。解決先統(tǒng)一時(shí)間粒度客流 15 分鐘粒度時(shí)天氣特征按小時(shí)向前填充forward fill到 15 分鐘粒度節(jié)假日特征要保證按天對(duì)齊不要用未來日期的標(biāo)簽。拼接前打印df.shape和df.isna().sum()確認(rèn)沒有形狀錯(cuò)位。6. 多步預(yù)測(cè)與多特征融合把單步預(yù)報(bào)擴(kuò)展成實(shí)用方案前面整條鏈路做的是「輸入 48 個(gè)小時(shí)預(yù)測(cè)未來 1 個(gè)小時(shí)」但實(shí)際業(yè)務(wù)里更常見的問題是「現(xiàn)在下午 4 點(diǎn)想預(yù)測(cè)今晚 6 點(diǎn)到 8 點(diǎn)的出站客流」。這就涉及多步預(yù)測(cè)有兩條路線遞歸預(yù)測(cè)和直接多輸出。遞歸預(yù)測(cè)的策略是把上一步的預(yù)測(cè)值當(dāng)作下一步的輸入循環(huán)預(yù)測(cè)到目標(biāo)步數(shù)實(shí)現(xiàn)簡(jiǎn)單但誤差會(huì)逐步累積預(yù)測(cè)到第 6 步以后曲線會(huì)趨于平滑因?yàn)槟P桶巡淮_定性平均化了。直接多輸出則是修改模型最后一層把output_size設(shè)為待預(yù)測(cè)步數(shù)一次輸出多步結(jié)果。這兩種方案各有場(chǎng)景做未來 1~3 小時(shí)的客流調(diào)配建議用直接多輸出模型一層輸出就能拿到完整預(yù)測(cè)曲線。注意直接多輸出時(shí)前面滑窗構(gòu)造的pred_len要相應(yīng)調(diào)大訓(xùn)練標(biāo)簽y的形狀變?yōu)?樣本數(shù), pred_len)。多特征融合是把預(yù)測(cè)質(zhì)量往上提一檔的關(guān)鍵。單用客流序列訓(xùn)練模型本質(zhì)上只能學(xué)「過去的客流模式在未來重演」遇到節(jié)假日、暴雨天氣這些外部擾動(dòng)必然失效。常見做法是加入 4 個(gè)特征是否為工作日、是否為節(jié)假日、小時(shí)序號(hào)0~23 的正弦/余弦編碼、天氣等級(jí)0~3 映射。特征拼接的時(shí)機(jī)是在歸一化之后把所有特征與客流序列一起構(gòu)成input_size5的輸入。加入外部特征后滯后效應(yīng)通常會(huì)減輕因?yàn)槟P筒辉僦灰蕾嚉v史客流做貼身復(fù)制而是能感知「今天是工作日且下雨」這種全局狀態(tài)。在那之后我跑客流預(yù)測(cè)養(yǎng)成了一個(gè)習(xí)慣每換一個(gè)數(shù)據(jù)集或改一個(gè)特征都強(qiáng)制先看一眼預(yù)測(cè)對(duì)齊圖再算指標(biāo)。圖和數(shù)字一起通過才敢把結(jié)果拿出去。模型的結(jié)構(gòu)、參數(shù)、數(shù)據(jù)處理全都可以在這一份資源包里直接改希望這份拆解能幫你在自己的客流數(shù)據(jù)上少走幾段彎路。# 直接多輸出 多特征的訓(xùn)練入口示意 # 假設(shè) feature_cols 已與客流序列按時(shí)間對(duì)齊并歸一化 X_multi, y_multi create_sequences(multi_feature_data, window48, step24, pred_len4) model LSTMPredictor(input_sizeX_multi.shape[2], hidden_size128, num_layers2, output_size4) model train_model(model, X_multi, y_multi, X_val_multi, y_val_multi, epochs80, lr0.001, patience15)這份資源包里附帶的數(shù)據(jù)集結(jié)構(gòu)、模型初始參數(shù)和訓(xùn)練腳本都是以此為基準(zhǔn)寫的。把input_size、hidden_size、window三項(xiàng)按你的數(shù)據(jù)量做加減法就能從一個(gè)「能跑的 demo」變成一個(gè)「業(yè)務(wù)上敢用的預(yù)測(cè)模塊」。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取