間序列預(yù)測實(shí)戰(zhàn):從數(shù)據(jù)預(yù)處理到滾動(dòng)預(yù)測全流程)
簡介本資源是一份面向Python數(shù)據(jù)科學(xué)初學(xué)者與中級(jí)開發(fā)者的LSTM時(shí)間序列預(yù)測實(shí)戰(zhàn)教程聚焦金融、電力負(fù)荷及趨勢分析等典型場景系統(tǒng)解決長周期依賴建模難題。壓縮包共12個(gè)文件含5個(gè)核心Python腳本涵蓋數(shù)據(jù)預(yù)處理、模型構(gòu)建、訓(xùn)練與預(yù)測全流程、2個(gè)實(shí)測CSV數(shù)據(jù)集sp500指數(shù)與正弦波模擬時(shí)序、1個(gè)配置文件config.json、1個(gè)說明文檔及基礎(chǔ)環(huán)境與許可文件整體僅108KB輕量易部署。已有962人學(xué)習(xí)下載適合希望快速掌握Keras實(shí)現(xiàn)LSTM建模、理解門控機(jī)制與序列滑窗構(gòu)造、并復(fù)現(xiàn)端到端預(yù)測流程的實(shí)踐者。資源代碼結(jié)構(gòu)清晰模塊解耦明確data_processor.py負(fù)責(zé)特征工程model.py封裝網(wǎng)絡(luò)定義run.py集成訓(xùn)練推理附帶requirements.txt與注意事項(xiàng).md顯著降低環(huán)境配置與調(diào)試門檻。1. 為什么用LSTM做時(shí)間序列預(yù)測不是簡單調(diào)個(gè)sklearn.LinearRegression就完事了你手頭有一組逐小時(shí)的服務(wù)器CPU使用率數(shù)據(jù)想預(yù)測未來24小時(shí)的峰值或者剛拿到某流域過去30年的月均降水量需要為下季度水庫調(diào)度提供依據(jù)。這時(shí)候如果直接套用線性回歸、隨機(jī)森林甚至XGBoost——模型可能在訓(xùn)練集上R2高達(dá)0.95但一到滾動(dòng)預(yù)測rolling forecast階段誤差會(huì)像雪球一樣越滾越大第3步預(yù)測偏差1%第5步就放大到8%第10步完全失真。根本原因在于這些模型把每個(gè)時(shí)間點(diǎn)當(dāng)作獨(dú)立樣本處理徹底丟掉了“前一時(shí)刻的狀態(tài)會(huì)影響下一時(shí)刻輸出”這一時(shí)間序列最核心的因果結(jié)構(gòu)。LSTM長短期記憶網(wǎng)絡(luò)正是為解決這個(gè)問題而生它通過門控機(jī)制遺忘門、輸入門、輸出門顯式建模狀態(tài)傳遞能記住數(shù)天前的關(guān)鍵模式比如上周同一時(shí)段的突增負(fù)載也能主動(dòng)遺忘無關(guān)噪聲比如某次瞬時(shí)傳感器抖動(dòng)。本文不講論文推導(dǎo)只聚焦一個(gè)可立即復(fù)現(xiàn)的Python工程閉環(huán)從原始CSV數(shù)據(jù)加載、滑動(dòng)窗口構(gòu)造、LSTM模型定義與訓(xùn)練到多步滾動(dòng)預(yù)測、結(jié)果反歸一化與可視化驗(yàn)證。所有代碼基于PyTorch 2.0和scikit-learn 1.3無任何第三方非標(biāo)依賴Windows/macOS/Linux三端一致運(yùn)行。2. 構(gòu)建LSTM預(yù)測管道從原始數(shù)據(jù)到可訓(xùn)練張量的四步標(biāo)準(zhǔn)化流程時(shí)間序列預(yù)測的成敗70%取決于數(shù)據(jù)預(yù)處理是否踩中LSTM的“胃口”。LSTM對輸入尺度極度敏感且要求輸入為三維張量batch_size, seq_len, features而原始CSV通常是二維表格rows時(shí)間點(diǎn)cols變量。下面這套流程是工業(yè)場景中反復(fù)驗(yàn)證過的最小可行路徑每一步都對應(yīng)一個(gè)明確的技術(shù)意圖。2.1 原始數(shù)據(jù)清洗與單變量提取以水文徑流數(shù)據(jù)為例假設(shè)你下載的hydro_data.csv包含date,precipitation_mm,temperature_c,runoff_m3s四列但當(dāng)前任務(wù)只需預(yù)測runoff_m3s徑流量。關(guān)鍵動(dòng)作不是直接刪列而是先檢查缺失值分布和異常值import pandas as pd import numpy as np df pd.read_csv(hydro_data.csv, parse_dates[date], index_coldate) print(原始數(shù)據(jù)形狀:, df.shape) print(徑流量缺失值數(shù)量:, df[runoff_m3s].isna().sum()) print(徑流量統(tǒng)計(jì)摘要:\n, df[runoff_m3s].describe()) # 用線性插值填充連續(xù)缺失5個(gè)點(diǎn)否則用前后7天均值填充 df[runoff_m3s] df[runoff_m3s].interpolate(methodlinear, limit5) df[runoff_m3s] df[runoff_m3s].fillna(df[runoff_m3s].rolling(window14, min_periods1).mean()) # 剔除明顯異常值3倍IQR或-3倍IQR Q1 df[runoff_m3s].quantile(0.25) Q3 df[runoff_m3s].quantile(0.75) IQR Q3 - Q1 lower_bound Q1 - 3 * IQR upper_bound Q3 3 * IQR df df[(df[runoff_m3s] lower_bound) (df[runoff_m3s] upper_bound)]提示interpolate(limit5)限制插值長度避免用遠(yuǎn)處數(shù)據(jù)“污染”局部趨勢rolling(window14)用雙周窗口填充比全局均值更能保留季節(jié)性。若數(shù)據(jù)含大量離散突變?nèi)玳l門開關(guān)導(dǎo)致的徑流驟變需改用median替代mean。2.2 滑動(dòng)窗口切片將一維序列轉(zhuǎn)為監(jiān)督學(xué)習(xí)樣本LSTM不能直接吃一整條時(shí)間線必須切成帶標(biāo)簽的“歷史片段→未來目標(biāo)”對。例如用過去7天數(shù)據(jù)預(yù)測第8天徑流量窗口大小seq_len7。核心邏輯是對長度為N的序列生成(N-seq_len)個(gè)樣本每個(gè)樣本含seq_len個(gè)輸入點(diǎn)和1個(gè)輸出點(diǎn)。這里用NumPy向量化實(shí)現(xiàn)比循環(huán)快10倍以上def create_sequences(data, seq_len, pred_len1): data: 一維numpy數(shù)組shape(N,) seq_len: 輸入序列長度如7 pred_len: 預(yù)測步長如1或3 返回: X (samples, seq_len, 1), y (samples, pred_len) X, y [], [] for i in range(len(data) - seq_len - pred_len 1): X.append(data[i:(i seq_len)]) y.append(data[(i seq_len):(i seq_len pred_len)]) return np.array(X).reshape(-1, seq_len, 1), np.array(y) # 提取并切片 series df[runoff_m3s].values.astype(np.float32) X, y create_sequences(series, seq_len7, pred_len1) print(f切片后X形狀: {X.shape}, y形狀: {y.shape}) # 例如 (1092, 7, 1), (1092, 1)注意reshape(-1, seq_len, 1)強(qiáng)制第三維為1這是LSTM輸入input_size1的要求。若要加入溫度、降水等多變量特征此處應(yīng)傳入data[:, [2,3]]選多列X第三維變?yōu)樘卣鲾?shù)。2.3 MinMax歸一化與訓(xùn)練/驗(yàn)證/測試集劃分按時(shí)間順序切割不可隨機(jī)打亂時(shí)間序列嚴(yán)禁用train_test_split(random_state42)必須保證訓(xùn)練集時(shí)間早于驗(yàn)證集驗(yàn)證集早于測試集否則模型會(huì)“偷看”未來信息。歸一化也需嚴(yán)格分離僅用訓(xùn)練集統(tǒng)計(jì)量min/max縮放所有集防止數(shù)據(jù)泄露from sklearn.preprocessing import MinMaxScaler # 僅用訓(xùn)練部分計(jì)算scaler參數(shù)前70%數(shù)據(jù) train_end int(len(X) * 0.7) scaler MinMaxScaler(feature_range(0, 1)) X_train_scaled scaler.fit_transform(X[:train_end].reshape(-1, 1)).reshape(-1, 7, 1) y_train_scaled scaler.transform(y[:train_end].reshape(-1, 1)).reshape(-1, 1) # 驗(yàn)證集和測試集用相同scaler轉(zhuǎn)換 val_start, test_start train_end, int(len(X) * 0.85) X_val_scaled scaler.transform(X[val_start:test_start].reshape(-1, 1)).reshape(-1, 7, 1) y_val_scaled scaler.transform(y[val_start:test_start].reshape(-1, 1)).reshape(-1, 1) X_test_scaled scaler.transform(X[test_start:].reshape(-1, 1)).reshape(-1, 7, 1) y_test_scaled scaler.transform(y[test_start:].reshape(-1, 1)).reshape(-1, 1) print(f訓(xùn)練集樣本數(shù): {len(X_train_scaled)}, 驗(yàn)證集: {len(X_val_scaled)}, 測試集: {len(X_test_scaled)})關(guān)鍵參數(shù)說明feature_range(0,1)確保LSTM激活函數(shù)如tanh工作在線性區(qū)fit_transform只在訓(xùn)練集調(diào)用一次后續(xù)transform復(fù)用同一縮放器。若用Z-score歸一化需替換為StandardScaler并注意mean/std同樣只從訓(xùn)練集計(jì)算。2.4 轉(zhuǎn)換為PyTorch張量并構(gòu)建DataLoaderLSTM在PyTorch中要求輸入為float32張量且DataLoader需設(shè)置shuffleFalse時(shí)間序列順序不可亂import torch from torch.utils.data import TensorDataset, DataLoader # 轉(zhuǎn)換為tensor注意dtype X_train_t torch.tensor(X_train_scaled, dtypetorch.float32) y_train_t torch.tensor(y_train_scaled, dtypetorch.float32) X_val_t torch.tensor(X_val_scaled, dtypetorch.float32) y_val_t torch.tensor(y_val_scaled, dtypetorch.float32) X_test_t torch.tensor(X_test_scaled, dtypetorch.float32) y_test_t torch.tensor(y_test_scaled, dtypetorch.float32) # 構(gòu)建Dataset和DataLoader train_dataset TensorDataset(X_train_t, y_train_t) val_dataset TensorDataset(X_val_t, y_val_t) test_dataset TensorDataset(X_test_t, y_test_t) train_loader DataLoader(train_dataset, batch_size32, shuffleFalse, drop_lastTrue) val_loader DataLoader(val_dataset, batch_size32, shuffleFalse, drop_lastTrue) test_loader DataLoader(test_dataset, batch_size32, shuffleFalse, drop_lastTrue)注意drop_lastTrue丟棄最后一個(gè)不完整batch避免維度不匹配報(bào)錯(cuò)。若需保留全部樣本可在訓(xùn)練循環(huán)中單獨(dú)處理剩余樣本。3. 定義與訓(xùn)練LSTM模型三層結(jié)構(gòu)、門控機(jī)制與早停策略的實(shí)操配置一個(gè)有效的LSTM預(yù)測模型絕非堆疊層數(shù)越多越好。本節(jié)給出經(jīng)過水文、電力、IoT設(shè)備日志等多場景驗(yàn)證的輕量級(jí)結(jié)構(gòu)并詳解每個(gè)超參的物理意義。3.1 LSTM模型類定義明確輸入/輸出維度與門控邏輯import torch.nn as nn class LSTMForecaster(nn.Module): def __init__(self, input_size1, hidden_size50, num_layers2, output_size1, dropout0.2): super(LSTMForecaster, self).__init__() self.hidden_size hidden_size self.num_layers num_layers self.lstm nn.LSTM( input_sizeinput_size, # 每個(gè)時(shí)間步的特征數(shù)單變量1 hidden_sizehidden_size, # 隱層神經(jīng)元數(shù)控制記憶容量 num_layersnum_layers, # LSTM層數(shù)2層已足夠捕獲多數(shù)時(shí)序模式 batch_firstTrue, # 輸入X形狀為(batch, seq, feature)設(shè)True dropoutdropout if num_layers 1 else 0 # 僅在多層間加dropout防過擬合 ) self.fc nn.Linear(hidden_size, output_size) # 全連接層映射到預(yù)測值 self.dropout nn.Dropout(dropout) def forward(self, x): # x: (batch, seq_len, input_size) lstm_out, (h_n, c_n) self.lstm(x) # lstm_out: (batch, seq_len, hidden_size) # 取最后一個(gè)時(shí)間步的輸出seq_len維索引-1 last_output lstm_out[:, -1, :] # (batch, hidden_size) last_output self.dropout(last_output) prediction self.fc(last_output) # (batch, output_size) return prediction # 實(shí)例化模型 model LSTMForecaster(input_size1, hidden_size50, num_layers2, output_size1, dropout0.2) print(model)參數(shù)選擇依據(jù)hidden_size50平衡表達(dá)力與過擬合風(fēng)險(xiǎn)100樣本時(shí)建議32num_layers2因單層LSTM易陷入局部最優(yōu)第二層提升非線性擬合能力dropout0.2在隱藏層間抑制共適應(yīng)但輸入層不加dropout會(huì)破壞時(shí)序連續(xù)性。3.2 訓(xùn)練循環(huán)與損失函數(shù)MAE比MSE更適合預(yù)測任務(wù)時(shí)間序列預(yù)測中MSE損失會(huì)過度懲罰大誤差導(dǎo)致模型偏向平滑預(yù)測而忽略突變點(diǎn)。MAE平均絕對誤差對異常值更魯棒且其梯度恒定訓(xùn)練更穩(wěn)定import torch.optim as optim criterion nn.L1Loss() # MAE損失 optimizer optim.Adam(model.parameters(), lr0.001) scheduler optim.lr_scheduler.ReduceLROnPlateau(optimizer, modemin, factor0.5, patience5, verboseTrue) def train_epoch(model, train_loader, criterion, optimizer, device): model.train() total_loss 0 for X_batch, y_batch in train_loader: X_batch, y_batch X_batch.to(device), y_batch.to(device) optimizer.zero_grad() y_pred model(X_batch) loss criterion(y_pred, y_batch) loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0) # 梯度裁剪防爆炸 optimizer.step() total_loss loss.item() return total_loss / len(train_loader) def validate(model, val_loader, criterion, device): model.eval() total_loss 0 with torch.no_grad(): for X_batch, y_batch in val_loader: X_batch, y_batch X_batch.to(device), y_batch.to(device) y_pred model(X_batch) loss criterion(y_pred, y_batch) total_loss loss.item() return total_loss / len(val_loader)關(guān)鍵技巧clip_grad_norm_(max_norm1.0)防止LSTM梯度爆炸這是訓(xùn)練失敗的最常見原因ReduceLROnPlateau在驗(yàn)證損失5輪不下降時(shí)自動(dòng)減半學(xué)習(xí)率比固定學(xué)習(xí)率收斂更快。3.3 早停Early Stopping與模型保存避免在驗(yàn)證集上過擬合監(jiān)控驗(yàn)證損失當(dāng)連續(xù)10輪未改善時(shí)終止訓(xùn)練并保存最佳模型best_val_loss float(inf) patience_counter 0 best_model_path best_lstm_model.pth for epoch in range(100): # 最大訓(xùn)練輪數(shù) train_loss train_epoch(model, train_loader, criterion, optimizer, device) val_loss validate(model, val_loader, criterion, device) scheduler.step(val_loss) # 根據(jù)驗(yàn)證損失調(diào)整學(xué)習(xí)率 print(fEpoch {epoch1:3d} | Train Loss: {train_loss:.6f} | Val Loss: {val_loss:.6f}) if val_loss best_val_loss: best_val_loss val_loss patience_counter 0 torch.save(model.state_dict(), best_model_path) # 保存最佳權(quán)重 print(f - 保存最佳模型到 {best_model_path}) else: patience_counter 1 if patience_counter 10: print(f - 驗(yàn)證損失連續(xù)10輪未下降觸發(fā)早停) break注意早停輪數(shù)patience10適用于中等規(guī)模數(shù)據(jù)1000樣本若數(shù)據(jù)稀疏500樣本建議設(shè)為5。保存的是state_dict()而非整個(gè)模型便于后續(xù)加載復(fù)用。4. 多步滾動(dòng)預(yù)測與結(jié)果反歸一化從模型輸出到可解釋的業(yè)務(wù)指標(biāo)訓(xùn)練完成的模型只能預(yù)測單步pred_len1。實(shí)際業(yè)務(wù)中常需預(yù)測未來N天如7天徑流預(yù)報(bào)必須采用滾動(dòng)預(yù)測Rolling Forecast策略用已知數(shù)據(jù)上一步預(yù)測結(jié)果作為新輸入迭代生成后續(xù)預(yù)測。此過程極易累積誤差需嚴(yán)格反歸一化并評估。4.1 加載最佳模型并執(zhí)行滾動(dòng)預(yù)測# 加載最佳權(quán)重 model.load_state_dict(torch.load(best_model_path)) model.eval() # 準(zhǔn)備初始輸入取測試集第一個(gè)樣本的7天數(shù)據(jù)即X_test_t[0] initial_input X_test_t[0:1] # shape: (1, 7, 1) predictions [] # 滾動(dòng)預(yù)測7天 current_input initial_input.clone() for _ in range(7): with torch.no_grad(): pred model(current_input) # pred: (1, 1) predictions.append(pred.item()) # 更新輸入丟棄最舊一天加入最新預(yù)測值 new_input torch.cat([current_input[0, 1:, :], pred.unsqueeze(0).unsqueeze(2)], dim1) current_input new_input.unsqueeze(0) # 恢復(fù)batch維 print(7天滾動(dòng)預(yù)測結(jié)果歸一化后:, [f{p:.4f} for p in predictions])邏輯說明current_input[0, 1:, :]取第0個(gè)樣本的第2~7天索引1到6pred.unsqueeze(0).unsqueeze(2)將標(biāo)量預(yù)測擴(kuò)展為(1,1,1)cat沿時(shí)間維拼接得到新(1,7,1)輸入。此操作模擬了真實(shí)業(yè)務(wù)中“每天更新預(yù)測”的流程。4.2 反歸一化與誤差指標(biāo)計(jì)算還原真實(shí)量綱并量化精度歸一化是單向操作反歸一化必須用訓(xùn)練時(shí)的scaler對象且注意scaler.inverse_transform要求輸入為二維# 將預(yù)測結(jié)果和真實(shí)值轉(zhuǎn)為二維數(shù)組進(jìn)行反歸一化 pred_array np.array(predictions).reshape(-1, 1) true_array y_test_t[:7].cpu().numpy() # 取測試集前7個(gè)真實(shí)值 pred_original scaler.inverse_transform(pred_array).flatten() true_original scaler.inverse_transform(true_array).flatten() # 計(jì)算常用誤差指標(biāo) def calculate_metrics(y_true, y_pred): mae np.mean(np.abs(y_true - y_pred)) rmse np.sqrt(np.mean((y_true - y_pred) ** 2)) mape np.mean(np.abs((y_true - y_pred) / (y_true 1e-8))) * 100 # 防除零 return mae, rmse, mape mae, rmse, mape calculate_metrics(true_original, pred_original) print(f\n7天預(yù)測誤差指標(biāo):) print(fMAE: {mae:.3f} m3/s | RMSE: {rmse:.3f} m3/s | MAPE: {mape:.2f}%) # 輸出對比表 results_df pd.DataFrame({ Day: [fDay {i1} for i in range(7)], Predicted (m3/s): np.round(pred_original, 3), Actual (m3/s): np.round(true_original, 3), Error (m3/s): np.round(pred_original - true_original, 3) }) print(\n預(yù)測 vs 真實(shí)值對比:) print(results_df.to_string(indexFalse))注意scaler.inverse_transform必須傳入shape(n_samples, n_features)因此pred_array.reshape(-1,1)必不可少1e-8防分母為零是MAPE計(jì)算的安全實(shí)踐。4.3 可視化預(yù)測效果突出顯示關(guān)鍵誤差區(qū)間用Matplotlib繪制預(yù)測曲線并用紅色高亮標(biāo)注誤差絕對值20%的點(diǎn)業(yè)務(wù)關(guān)注的預(yù)警區(qū)間import matplotlib.pyplot as plt plt.figure(figsize(12, 5)) plt.plot(range(1, 8), true_original, bo-, labelActual Runoff, linewidth2, markersize6) plt.plot(range(1, 8), pred_original, ro--, labelLSTM Prediction, linewidth2, markersize6) # 標(biāo)注高誤差點(diǎn) error_abs np.abs(pred_original - true_original) error_rel error_abs / (true_original 1e-8) high_error_mask error_rel 0.2 for i in np.where(high_error_mask)[0]: plt.annotate(fHigh Error\n{error_rel[i]:.1%}, xy(i1, pred_original[i]), xytext(i1, pred_original[i]0.1*max(true_original)), arrowpropsdict(arrowstyle-, colorred, lw1.5), fontsize10, hacenter, colorred) plt.xlabel(Forecast Day) plt.ylabel(Runoff (m3/s)) plt.title(7-Day Rolling Forecast vs Actual Values) plt.legend() plt.grid(True, alpha0.3) plt.tight_layout() plt.savefig(lstm_forecast_comparison.png, dpi300) plt.show()提示圖中箭頭指向高相對誤差點(diǎn)幫助快速定位模型薄弱環(huán)節(jié)如汛期突變、枯水期低值。若業(yè)務(wù)要求更高精度可針對這些區(qū)間單獨(dú)增強(qiáng)訓(xùn)練數(shù)據(jù)。5. LSTM預(yù)測的三大典型陷阱與規(guī)避方案從水文預(yù)報(bào)到IoT設(shè)備告警的實(shí)戰(zhàn)經(jīng)驗(yàn)即使嚴(yán)格遵循前述流程LSTM預(yù)測仍可能在真實(shí)場景中失效。以下是我在多個(gè)工業(yè)項(xiàng)目中總結(jié)的三個(gè)高頻陷阱每個(gè)都附帶可立即落地的檢測與修復(fù)方法。5.1 陷阱一訓(xùn)練數(shù)據(jù)未覆蓋目標(biāo)場景的周期模式導(dǎo)致預(yù)測系統(tǒng)性偏移現(xiàn)象模型在訓(xùn)練集上MAE0.5但部署后連續(xù)一周預(yù)測值整體偏低15%。根因訓(xùn)練數(shù)據(jù)僅含2020-2022年數(shù)據(jù)而2023年因氣候異常出現(xiàn)持續(xù)高溫少雨徑流基流顯著降低模型未學(xué)習(xí)到該新周期。檢測方法計(jì)算訓(xùn)練集與測試集的滑動(dòng)均值差如30天均值若差異5%則存在分布偏移。修復(fù)方案在數(shù)據(jù)預(yù)處理階段增加seasonal_decompose分解提取殘差序列單獨(dú)建?;虿捎迷诰€學(xué)習(xí)每新增一天真實(shí)值用model.train()微調(diào)最后10個(gè)batchlr1e-5避免災(zāi)難性遺忘。# 快速檢測周期偏移 from statsmodels.tsa.seasonal import seasonal_decompose train_mean np.mean(series[:train_end]) test_mean np.mean(series[train_end:]) print(f訓(xùn)練集均值: {train_mean:.3f}, 測試集均值: {test_mean:.3f}, 偏差: {(test_mean-train_mean)/train_mean*100:.1f}%)5.2 陷阱二多步滾動(dòng)預(yù)測中誤差累積第N步預(yù)測完全失真現(xiàn)象單步預(yù)測MAPE8%但5步滾動(dòng)預(yù)測MAPE飆升至42%。根因LSTM的h_n隱狀態(tài)在滾動(dòng)中未重置錯(cuò)誤記憶被不斷強(qiáng)化。檢測方法對比“多步預(yù)測”與“單步預(yù)測真實(shí)值更新”即用真實(shí)第2天值代替預(yù)測值作為第3天輸入的誤差曲線。若后者誤差平穩(wěn)則確認(rèn)為累積誤差。修復(fù)方案強(qiáng)制在每步預(yù)測后重置LSTM隱狀態(tài)或改用Seq2Seq架構(gòu)編碼器-解碼器# 滾動(dòng)預(yù)測時(shí)重置隱狀態(tài)關(guān)鍵修復(fù) def rolling_predict_fixed(model, initial_input, steps, scaler, device): model.eval() predictions [] current_input initial_input.clone().to(device) # 初始化LSTM隱狀態(tài) h0 torch.zeros(model.num_layers, 1, model.hidden_size).to(device) c0 torch.zeros(model.num_layers, 1, model.hidden_size).to(device) for _ in range(steps): with torch.no_grad(): # 手動(dòng)傳入初始隱狀態(tài) lstm_out, (h0, c0) model.lstm(current_input, (h0, c0)) pred model.fc(lstm_out[:, -1, :]) predictions.append(pred.item()) # 更新輸入同前但隱狀態(tài)h0/c0已由LSTM自動(dòng)更新 new_input torch.cat([current_input[0, 1:, :], pred.unsqueeze(0).unsqueeze(2)], dim1) current_input new_input.unsqueeze(0) return predictions5.3 陷阱三輸入特征未對齊物理意義導(dǎo)致模型學(xué)習(xí)虛假相關(guān)現(xiàn)象加入氣溫特征后驗(yàn)證損失下降但業(yè)務(wù)專家指出“氣溫對徑流影響滯后3天”模型卻學(xué)出即時(shí)響應(yīng)。根因滑動(dòng)窗口未考慮特征滯后lag氣溫?cái)?shù)據(jù)與徑流數(shù)據(jù)時(shí)間戳未對齊。檢測方法繪制交叉相關(guān)圖Cross-Correlation找各特征與目標(biāo)的最大相關(guān)滯后。修復(fù)方案在create_sequences前對滯后特征做位移。例如氣溫滯后3天則temp_shifted temp_series.shift(3)再參與切片# 對滯后特征預(yù)處理以氣溫為例 df[temp_lag3] df[temperature_c].shift(3) # 向下移動(dòng)3行即t時(shí)刻用t-3時(shí)刻氣溫 # 清理因shift產(chǎn)生的NaN df df.dropna(subset[temp_lag3, runoff_m3s]) # 切片時(shí)傳入多列create_sequences(df[[temp_lag3, runoff_m3s]].values, seq_len7)重要提醒所有特征必須與目標(biāo)變量在同一時(shí)間粒度如都是日均值。若溫度是小時(shí)數(shù)據(jù)需先聚合為日均值再做滯后處理。本文還有配套的精品資源點(diǎn)擊獲取