間序列預(yù)測實(shí)戰(zhàn):從數(shù)據(jù)預(yù)處理到多步外推的完整指南)
簡介基于長短期記憶網(wǎng)絡(luò)的時(shí)間序列分析預(yù)測源碼包面向Python開發(fā)者和人工智能入門者提供從數(shù)據(jù)清洗、特征工程、模型構(gòu)建、訓(xùn)練到評估的完整解決方案適用于股票價(jià)格、銷售需求、空氣質(zhì)量等典型序列預(yù)測場景。壓縮包共一百二十六個(gè)文件其中七十五個(gè)py腳本覆蓋數(shù)據(jù)加載、歸一化、滑窗生成、LSTM定義與訓(xùn)練測試二十六個(gè)csv文件提供多組實(shí)驗(yàn)數(shù)據(jù)包含空氣污染指標(biāo)十五個(gè)txt說明標(biāo)注參數(shù)與環(huán)境配置另有h5格式訓(xùn)練好的模型權(quán)重和md項(xiàng)目文檔整包約五點(diǎn)四二兆字節(jié)輕量易上手。已有五千零九十六人學(xué)習(xí)下載社區(qū)驗(yàn)證度較高。通過該項(xiàng)目可深入理解LSTM輸入門、遺忘門、輸出門的作用掌握時(shí)序數(shù)據(jù)歸一化、滑動(dòng)窗口構(gòu)建、均方誤差與平均絕對誤差評估等方法并能基于附帶數(shù)據(jù)集復(fù)現(xiàn)結(jié)果快速遷移至自己的預(yù)測任務(wù)。1. 先認(rèn)清LSTM 預(yù)測腳本為什么“跑得通”不等于“測得準(zhǔn)”拿到這套基于 LSTM 的時(shí)間序列分析預(yù)測代碼包第一件事不是找哪個(gè)文件是訓(xùn)練入口而是先想清楚一個(gè)問題你要的預(yù)測到底是“把歷史曲線擬合得像”還是“未來一段時(shí)間的走勢真正可參考”。這兩個(gè)目標(biāo)在代碼里對應(yīng)完全不同的數(shù)據(jù)處理方式和評估口徑。很多跑通了這個(gè)源碼的人對著訓(xùn)練集曲線滿意地點(diǎn)頭然后一換到未來數(shù)據(jù)就翻車——問題幾乎都不在 LSTM 模型本身而是滑窗怎么切、數(shù)據(jù)怎么歸一化、測試集怎么劃分。這套源碼的核心是把“近 N 個(gè)點(diǎn)預(yù)測未來 M 個(gè)點(diǎn)”的完整流程串起來數(shù)據(jù)預(yù)處理、構(gòu)造樣本、訓(xùn)練 LSTM 神經(jīng)網(wǎng)絡(luò)、輸出預(yù)測值再反歸一化。適合做銷量、氣溫、設(shè)備指標(biāo)、流量這類帶時(shí)間戳的回歸預(yù)測也適合剛?cè)腴T深度學(xué)習(xí)、想把 LSTM 落地而不是停留在教程 demo 上的從業(yè)者。2. 數(shù)據(jù)準(zhǔn)備把原始序列切成 LSTM 能吃的樣本兩個(gè)參數(shù)決定上限2.1 先別訓(xùn)練單變量還是多變量、缺失值怎么補(bǔ)常見的 LSTM 時(shí)間序列項(xiàng)目里數(shù)據(jù)格式形形色色。極簡的是一列數(shù)值比如每天的電價(jià)、每個(gè)小時(shí)的在線人數(shù)復(fù)雜一點(diǎn)的是多列除了目標(biāo)值還有輔助特征。拿到數(shù)據(jù)后第一個(gè)動(dòng)作不是寫模型而是確認(rèn)兩件事目標(biāo)列是哪一列、時(shí)間順序是否已經(jīng)被打亂。CSV 讀進(jìn)來如果是亂序的必須先按時(shí)間戳排序這一步漏掉后面所有滑窗樣本的時(shí)間含義就全錯(cuò)了。缺失值處理也要區(qū)分場景。普通表格可以用均值填充時(shí)序數(shù)據(jù)我會(huì)優(yōu)先用前后合法值插值因?yàn)樾蛄欣锏闹凳沁B續(xù)變化的直接取均值會(huì)把局部波動(dòng)抹平。異常值的處理更要慎重一個(gè)脈沖尖峰在普通回歸里只是損失一個(gè)點(diǎn)但在滑窗構(gòu)造樣本的時(shí)候這個(gè)壞點(diǎn)會(huì)被包含進(jìn)多個(gè)窗口等于把一個(gè)錯(cuò)誤復(fù)制了十幾遍。檢查序列里有沒有超過正常范圍幾個(gè)數(shù)量級的跳變先用可視化掃一遍比急著調(diào)模型參數(shù)重要得多。import pandas as pd import numpy as np df pd.read_csv(data.csv, parse_dates[timestamp]) df df.sort_values(timestamp).reset_index(dropTrue) # 缺失值用線性插值補(bǔ)充避免均值填充抹掉趨勢細(xì)節(jié) df[value] df[value].interpolate(methodlinear) # 看一眼基本統(tǒng)計(jì)量重點(diǎn)檢查有沒有遠(yuǎn)超均值量級的異常尖峰 print(df[value].describe())這段代碼里interpolate(methodlinear)是時(shí)序缺失值處理的最常用手段比fillna(methodffill)更平滑。檢查統(tǒng)計(jì)量時(shí)重點(diǎn)看 max 和 mean 之間的差距如果 max 是 mean 的幾十倍就要懷疑是否存在壞點(diǎn)而不是直接拿去做訓(xùn)練。2.2 訓(xùn)練集和測試集必須按時(shí)間切這是時(shí)序跟普通監(jiān)督學(xué)習(xí)的邊界普通機(jī)器學(xué)習(xí)里隨機(jī)劃分訓(xùn)練集和測試集沒問題因?yàn)闃颖惊?dú)立。時(shí)間序列不行因?yàn)橄噜彉颖局g存在天然相關(guān)性尤其在滑窗重疊構(gòu)造樣本的場景下如果隨機(jī)劃分訓(xùn)練集里很容易出現(xiàn)“測試樣本的上一段窗口”模型等于提前看到了測試段的輸入部分評估結(jié)果當(dāng)然虛高。這種泄漏不會(huì)報(bào)錯(cuò)但會(huì)給你一個(gè)根本不真實(shí)的測試指標(biāo)。正確做法是嚴(yán)格按時(shí)間切開比如前 80% 做訓(xùn)練后 20% 做測試驗(yàn)證集再從訓(xùn)練段尾部切一部分出來不能打亂。切完后做歸一化時(shí)順序也很有講究。MinMaxScaler只能 fit 訓(xùn)練段的數(shù)據(jù)然后用這個(gè)已經(jīng)學(xué)習(xí)好參數(shù)的標(biāo)準(zhǔn)去 transform 測試段。如果對整個(gè)序列統(tǒng)一 fit測試段的最大值和最小值會(huì)提前混入縮放邏輯同樣屬于數(shù)據(jù)泄漏。train_size int(len(df) * 0.8) valid_size int(train_size * 0.8) train_df df.iloc[:valid_size] valid_df df.iloc[valid_size:train_size] test_df df.iloc[train_size:] from sklearn.preprocessing import MinMaxScaler scaler MinMaxScaler(feature_range(0, 1)) train_values scaler.fit_transform(train_df[[value]]) valid_values scaler.transform(valid_df[[value]]) test_values scaler.transform(test_df[[value]])這里fit_transform只出現(xiàn)在訓(xùn)練段上驗(yàn)證段和測試段只用transform。驗(yàn)證段用來在訓(xùn)練過程中挑超參數(shù)測試段只做最終評估嚴(yán)格保持“三次隔離”。我習(xí)慣再用df.iloc[:]的時(shí)間切法而不是隨機(jī)抽樣強(qiáng)調(diào)的是順序敏感。2.3 滑窗構(gòu)造樣本seq_len 和 step 兩個(gè)參數(shù)決定你能預(yù)測什么LSTM 不能直接吃一長串原始序列它的輸入是固定長度的窗口。比如用過去 24 個(gè)小時(shí)預(yù)測下 1 個(gè)小時(shí)那就把序列切成每 24 個(gè)點(diǎn)一組第 25 個(gè)點(diǎn)作為標(biāo)簽。這個(gè)滑動(dòng)窗口機(jī)制里有兩個(gè)參數(shù)往往比網(wǎng)絡(luò)結(jié)構(gòu)本身還影響最終效果窗口長度seq_len和滑動(dòng)步長step。seq_len決定模型能“回頭看”多遠(yuǎn)必須結(jié)合數(shù)據(jù)本身的周期來選。如果數(shù)據(jù)是日粒度通常用 7 的倍數(shù)因?yàn)橐采w一周的周期如果數(shù)據(jù)是小時(shí)粒度才優(yōu)先考慮 24。很多人照搬別人項(xiàng)目里的seq_len24結(jié)果換到日粒度數(shù)據(jù)上效果一塌糊涂因?yàn)?24 天對日數(shù)據(jù)來說既不是周周期也不是月周期。step1會(huì)讓相鄰樣本大量重疊訓(xùn)練集膨脹但有效信息增量不大step設(shè)大一點(diǎn)比如 3 或 5樣本量會(huì)明顯下降訓(xùn)練速度更快代價(jià)是能學(xué)習(xí)的模式密度變低。我一般先用小步長跑通再用大步長調(diào)優(yōu)。def make_sequences(values, seq_len24, step1): X, y [], [] for i in range(0, len(values) - seq_len, step): X.append(values[i:iseq_len]) y.append(values[iseq_len]) return np.array(X), np.array(y) seq_len 24 train_X, train_y make_sequences(train_values, seq_lenseq_len) test_X, test_y make_sequences(test_values, seq_lenseq_len) # LSTM 期望的輸入形狀: (樣本數(shù), 時(shí)間步數(shù), 特征維度) train_X train_X.reshape(-1, seq_len, 1) test_X test_X.reshape(-1, seq_len, 1)最后reshape(-1, seq_len, 1)這一步非常關(guān)鍵1代表單變量特征數(shù)量如果是多變量預(yù)測這個(gè)維度等于特征列數(shù)。注意測試集構(gòu)造樣本時(shí)只需要從原始測試值中取窗口即可標(biāo)簽就是下一個(gè)點(diǎn)但實(shí)際預(yù)測場景里最后一個(gè)窗口之后再無標(biāo)簽?zāi)蔷褪钦嬲耐馔祁A(yù)測——后面第 4 章專門展開。3. 模型搭建與訓(xùn)練把 LSTM 調(diào)到“記得住又不死記”的實(shí)用參數(shù)3.1 LSTM 解決什么問題門控結(jié)構(gòu)在時(shí)序回歸里的作用為什么不直接用普通全連接網(wǎng)絡(luò)做時(shí)間序列回歸時(shí)序數(shù)據(jù)的特點(diǎn)是當(dāng)前值常常依賴于很多個(gè)時(shí)間步之前的信息。普通網(wǎng)絡(luò)把所有輸入特征平等對待既沒有先后概念也沒有“記憶”機(jī)制。LSTM 在循環(huán)結(jié)構(gòu)基礎(chǔ)上加入了三個(gè)門遺忘門決定過去的信息保留多少輸入門決定當(dāng)前時(shí)間步的新信息寫入多少輸出門決定當(dāng)前時(shí)間步輸出什么。這套機(jī)制理論上能捕捉“昨天這個(gè)時(shí)候的值對今天有影響”和“一周前同一天的模式也有影響”這類跨步長依賴。對你寫代碼的人來說理解到這一層已經(jīng)足夠。真正要調(diào)的是讓這種“記憶”既不要太長造成過擬合也不要太短記不住周期。這一節(jié)不展開數(shù)學(xué)推導(dǎo)重點(diǎn)放在怎么搭一個(gè)能跑起來、能調(diào)參的模型結(jié)構(gòu)。3.2 用 PyTorch 搭一個(gè)可直接運(yùn)行的 LSTM 回歸網(wǎng)絡(luò)常見做法是用 PyTorch 實(shí)現(xiàn)一個(gè) LSTM 層接收滑窗序列輸出最后一個(gè)時(shí)間步的隱藏狀態(tài)再接一個(gè)全連接層映射到預(yù)測值。這個(gè)結(jié)構(gòu)簡潔、穩(wěn)定適合絕大多數(shù)單變量或多變量預(yù)測任務(wù)。下面這個(gè)網(wǎng)絡(luò)結(jié)構(gòu)是每個(gè)時(shí)間序列預(yù)測任務(wù)的基本起點(diǎn)。import torch import torch.nn as nn class LSTMPredictor(nn.Module): def __init__(self, input_size1, hidden_size32, num_layers2, pred_len1): super().__init__() self.lstm nn.LSTM( input_sizeinput_size, hidden_sizehidden_size, num_layersnum_layers, batch_firstTrue ) self.regressor nn.Linear(hidden_size, pred_len) def forward(self, x): out, _ self.lstm(x) # 輸出每個(gè)時(shí)間步的隱狀態(tài) last out[:, -1, :] # 只取最后一個(gè)時(shí)間步的隱狀態(tài) return self.regressor(last)batch_firstTrue讓輸入張量形狀符合 (batch, seq_len, features)省去轉(zhuǎn)置的麻煩pred_len是你要預(yù)測的未來步數(shù)默認(rèn) 1。如果做多步預(yù)測可以直接把pred_len設(shè)為目標(biāo)步數(shù)全連接層輸出對應(yīng)數(shù)量的值這與后面要講的滾動(dòng)預(yù)測走的是兩條路各有適用場景。訓(xùn)練環(huán)節(jié)的代碼同樣有講究。損失函數(shù)用均方誤差即可優(yōu)化器選 Adam學(xué)習(xí)率從 0.001 起步。最關(guān)鍵的是訓(xùn)練時(shí)數(shù)據(jù)處理別踩亂序的坑。from torch.utils.data import TensorDataset, DataLoader train_dataset TensorDataset( torch.tensor(train_X, dtypetorch.float32), torch.tensor(train_y, dtypetorch.float32) ) train_loader DataLoader(train_dataset, batch_size64, shuffleFalse) val_loader DataLoader( TensorDataset( torch.tensor(valid_X, dtypetorch.float32), torch.tensor(valid_y, dtypetorch.float32) ), batch_size64, shuffleFalse ) model LSTMPredictor(input_size1, hidden_size32, num_layers2, pred_len1) optimizer torch.optim.Adam(model.parameters(), lr1e-3) loss_fn nn.MSELoss() for epoch in range(80): model.train() epoch_loss 0.0 for batch_x, batch_y in train_loader: optimizer.zero_grad() pred model(batch_x).squeeze(-1) loss loss_fn(pred, batch_y) loss.backward() optimizer.step() epoch_loss loss.item() print(fepoch {epoch:2d}, train loss {epoch_loss / len(train_loader):.6f})這里shuffleFalse是刻意設(shè)置的?;皹?gòu)造的相鄰樣本高度重疊打亂順序雖然不影響單個(gè)樣本內(nèi)部結(jié)構(gòu)但 batch 內(nèi)數(shù)據(jù)的分布會(huì)被攪亂訓(xùn)練收斂不穩(wěn)定。驗(yàn)證集 loss 不參與梯度更新只在每個(gè) epoch 末尾觀察用。訓(xùn)練到后期如果 train loss 持續(xù)下降而驗(yàn)證 loss 開始抬升那就是過擬合信號應(yīng)該提前停掉而不是繼續(xù)跑滿 80 個(gè) epoch。3.3 訓(xùn)練時(shí)看什么loss 曲線、驗(yàn)證窗口和隨機(jī)種子訓(xùn)練過程中的觀察遠(yuǎn)比調(diào)參本身重要。第一個(gè)觀測點(diǎn)是 loss 下降曲線如果前幾個(gè) epoch loss 紋絲不動(dòng)多半是學(xué)習(xí)率太大導(dǎo)致震蕩或者歸一化沒做好數(shù)值不穩(wěn)定如果 loss 快速降到極小值、之后驗(yàn)證集 loss 反彈那就是過擬合。第二個(gè)觀測點(diǎn)是定義一個(gè)與訓(xùn)練集不重疊的驗(yàn)證窗口用于在訓(xùn)練過程中評估真實(shí)泛化能力這個(gè)窗口不能參與任何調(diào)參決策。第三個(gè)容易被忽略的是隨機(jī)種子LSTM 初始化權(quán)重是隨機(jī)的崩潰概率很低但如果你同一個(gè)腳本跑兩次結(jié)果差異很大說明沒有固定隨機(jī)種子排查問題時(shí)會(huì)非常痛苦。def set_seed(seed42): np.random.seed(seed) torch.manual_seed(seed) set_seed(42)固定種子之后再調(diào)參每次對比才有意義。否則你改了hidden_size從 32 換成 64效果變好還是變壞無法分辨是參數(shù)起作用還是運(yùn)氣起作用。這是很多調(diào)參花大量時(shí)間卻原地踏步的重要原因。4. 預(yù)測執(zhí)行從預(yù)測下一點(diǎn)到外推未來 30 步4.1 單步預(yù)測的最小調(diào)用注意張量形狀和反歸一化模型訓(xùn)練完真正去預(yù)測的時(shí)候最容易出問題的是形狀和量綱。訓(xùn)練時(shí)輸入是 (batch, seq_len, features)推理時(shí)只有一條輸入所以要構(gòu)造 (1, seq_len, features)。預(yù)測出來的值也別忘了它是在 0~1 區(qū)間經(jīng)過縮放的必須用前面同一個(gè) scaler 反歸一化還原成真實(shí)量綱。model.eval() # 用測試集最后 seq_len 個(gè)點(diǎn)作為輸入窗口 current_seq test_values[-seq_len:].reshape(1, seq_len, 1) with torch.no_grad(): pred_scaled model(torch.tensor(current_seq, dtypetorch.float32)).item() pred_value scaler.inverse_transform(np.array([[pred_scaled]])) print(f預(yù)測值: {pred_value[0][0]:.2f})這段代碼的關(guān)鍵在最后一步scaler.inverse_transform期望輸入形狀是 (n_samples, n_features)所以這里必須包一層np.array([[pred_scaled]])否則會(huì)報(bào)維度錯(cuò)或者得到錯(cuò)誤結(jié)果。單步預(yù)測本身邏輯簡單但它是一切多步預(yù)測的基石。4.2 多步外推兩種思路滾動(dòng)預(yù)測和直接多輸出如果目標(biāo)是預(yù)測未來 7 天、30 天就不能只做一步。兩種常見做法滾動(dòng)預(yù)測和直接多輸出。滾動(dòng)預(yù)測的思路是用當(dāng)前窗口預(yù)測出下一個(gè)值把這個(gè)預(yù)測值拼進(jìn)窗口尾部再扔掉最前面一個(gè)值保持窗口長度不變繼續(xù)預(yù)測下一步。實(shí)現(xiàn)簡單完全復(fù)用單步模型但誤差會(huì)累積——第二步的輸入里已經(jīng)包含了第一步的預(yù)測誤差隨著步數(shù)增加預(yù)測序列會(huì)逐漸漂移。直接多輸出的思路是把模型最后一層全連接的pred_len設(shè)為目標(biāo)步數(shù)比如 7訓(xùn)練時(shí)直接把標(biāo)簽數(shù)組當(dāng)成 7 個(gè)值來監(jiān)督。推理時(shí)一次輸出未來 7 個(gè)點(diǎn)誤差不累積但需要重新準(zhǔn)備多步標(biāo)簽數(shù)據(jù)訓(xùn)練成本稍高。def rolling_forecast(model, init_seq, steps30): model.eval() window init_seq.copy().reshape(-1) preds [] with torch.no_grad(): for _ in range(steps): x torch.tensor( window.reshape(1, seq_len, 1), dtypetorch.float32 ) y_scaled model(x).item() preds.append(y_scaled) # 滾動(dòng)窗口丟最老的點(diǎn)拼入新預(yù)測值 window np.append(window[1:], y_scaled) return scaler.inverse_transform(np.array(preds).reshape(-1, 1))滾動(dòng)預(yù)測里window[1:]丟一個(gè)點(diǎn)、np.append補(bǔ)一個(gè)新點(diǎn)這個(gè)操作本身簡單但要注意窗口里的值必須是歸一化狀態(tài)不能混入原始量綱否則模型輸入分布突變預(yù)測直接發(fā)散。我一般把滾動(dòng)預(yù)測用在短期外推不超過 7 步更長的預(yù)測建議用直接多輸出模型或者兩者結(jié)合用直接多輸出出 7 個(gè)點(diǎn)再用這 7 個(gè)點(diǎn)作為新的窗口做滾動(dòng)延伸。4.3 評估必須用 rolling 指標(biāo)只看第一步的結(jié)果會(huì)騙人訓(xùn)練過程中評估用的是單步預(yù)測 loss但真正做多步預(yù)測時(shí)評估口徑要跟著變。很多人報(bào)告“預(yù)測效果挺好”細(xì)問才知道只統(tǒng)計(jì)了第一步的誤差——第一步的輸入全部來自真實(shí)歷史值難度最低第二步起輸入開始摻入模型自身輸出誤差逐漸積累。所以評估多步預(yù)測時(shí)要把 h1、h7、h30 的誤差分開計(jì)算。實(shí)際操作上用測試集從第seq_len個(gè)點(diǎn)開始逐點(diǎn)滾動(dòng)預(yù)測每一步都記錄當(dāng)前步的預(yù)測值與真實(shí)值之差最終按步數(shù)分桶計(jì)算平均絕對誤差或均方根誤差。不同步數(shù)下的誤差曲線能直觀展示模型到底能外推多遠(yuǎn)。如果 h1 誤差很小h7 誤差翻倍h30 誤差已經(jīng)接近基線那說明這個(gè)模型只適合短期預(yù)測強(qiáng)行做長期預(yù)測沒有意義。5. 避坑清單LSTM 時(shí)間序列預(yù)測翻車的 5 個(gè)經(jīng)典原因5.1 數(shù)據(jù)集隨機(jī)劃分測試集信息泄漏進(jìn)訓(xùn)練現(xiàn)象訓(xùn)練曲線擬合得很好測試集上的指標(biāo)也很漂亮但一旦投入真實(shí)未來數(shù)據(jù)進(jìn)行預(yù)測曲線立刻偏離真實(shí)走勢偏差比設(shè)想的要大得多。原因滑窗構(gòu)造的樣本之間高度重疊相鄰樣本共享大部分輸入。如果用train_test_split(random_state42)這類隨機(jī)劃分方式測試集里的某一個(gè)樣本很可能它的整個(gè)輸入窗口也出現(xiàn)在訓(xùn)練集里。模型等于直接背過答案測試指標(biāo)虛高。解決嚴(yán)格按時(shí)間順序切分先按df.iloc[:train_size]取出訓(xùn)練段再取測試段不做任何打亂。這也是前面第 2 章里強(qiáng)調(diào)的時(shí)間切分方式屬于整個(gè)流程里最基礎(chǔ)也最要命的一道防線。5.2 MinMaxScaler 先 fit 全量數(shù)據(jù)再做歸一化現(xiàn)象預(yù)測曲線整體看起來和真實(shí)曲線像一對平移的平行線邊界處數(shù)值始終差一截。原因MinMaxScaler.fit()在全部數(shù)據(jù)上執(zhí)行等于讓模型在訓(xùn)練階段就看到了測試集的最大值和最小值。模型輸出被壓縮在一個(gè)偏向測試分布的空間里反歸一化后自然產(chǎn)生系統(tǒng)性偏移。解決只對訓(xùn)練段數(shù)據(jù)調(diào)用fit_transform驗(yàn)證段和測試段統(tǒng)一用這個(gè)已經(jīng)定型的transform。推理階段拿到的新數(shù)據(jù)也要用同一個(gè) scaler 做transform預(yù)測完成后再用同一個(gè) scalerinverse_transform還原。5.3 多步預(yù)測只報(bào)告第一步誤差現(xiàn)象匯報(bào)里寫著“預(yù)測誤差 2%”但業(yè)務(wù)方實(shí)際使用后說你的預(yù)測根本沒法看尤其是第 7 天之后的預(yù)測偏差大到失去參考價(jià)值。原因第一步預(yù)測的輸入完全來自真實(shí)歷史觀察值模型沒有機(jī)會(huì)自我污染第二步開始輸入窗口里有模型上一輪輸出的預(yù)測值誤差開始累積。如果只統(tǒng)計(jì) h1 的誤差測的根本不是模型在真實(shí)使用場景下的表現(xiàn)。解決多步預(yù)測必須按步數(shù)分層評估。記錄預(yù)測序列每一個(gè)時(shí)間步與真實(shí)值的誤差按 h1、h5、h10、h30 分別計(jì)算 MAE把這條誤差增長曲線暴露出來再?zèng)Q定模型能承諾多少步以內(nèi)的預(yù)測。5.4 缺失值用均值填充、異常尖峰不清理現(xiàn)象訓(xùn)練過程中 loss 下降正常但預(yù)測出的曲線比真實(shí)數(shù)據(jù)平滑得多很多正常波動(dòng)都被吞掉了。原因均值填充會(huì)把序列里的局部波動(dòng)抹平模型學(xué)到的規(guī)律變得過于平滑異常尖峰沒清除的話滑窗會(huì)把同一個(gè)壞點(diǎn)復(fù)制進(jìn)幾十個(gè)訓(xùn)練樣本導(dǎo)致模型專門學(xué)習(xí)這個(gè)錯(cuò)誤的模式。解決列缺失用interpolate(methodlinear)保留趨勢對于明顯超出正常波動(dòng)范圍的脈沖點(diǎn)用前后窗口的中位數(shù)替換或者直接剔除再插值??傊茸屝蛄斜旧砀蓛粼僬?dòng)?xùn)練效果。5.5 換數(shù)據(jù)集后直接照搬 seq_len24現(xiàn)象上一個(gè)項(xiàng)目里seq_len24效果好換到新的業(yè)務(wù)數(shù)據(jù)后怎么調(diào)都上不去模型似乎永遠(yuǎn)慢半拍。原因24 這個(gè)值通常是為小時(shí)粒度數(shù)據(jù)準(zhǔn)備的因?yàn)橐惶煊?24 小時(shí)。如果換到日粒度數(shù)據(jù)24 天既不是一個(gè)完整的周周期也不是月周期換到分鐘粒度數(shù)據(jù)24 分鐘又太短。解決拿到新數(shù)據(jù)集先做周期探測。畫出數(shù)據(jù)的自相關(guān)圖或直接按不同seq_len7、14、24、28、48分別訓(xùn)練幾次看驗(yàn)證集 loss 誰最低?;伴L度應(yīng)該和數(shù)據(jù)本身的周期對齊而不是沿用別人項(xiàng)目里的習(xí)慣值。6. 讓它能真正投入多種子評估與滾動(dòng)重訓(xùn)的落地做法前面幾章已經(jīng)覆蓋了從數(shù)據(jù)準(zhǔn)備到多步預(yù)測的完整流程但真要把這套代碼用在業(yè)務(wù)決策里還差兩個(gè)非常實(shí)用的做法。第一個(gè)做法是多隨機(jī)種子評估。LSTM 初始權(quán)重隨機(jī)單次訓(xùn)練的結(jié)果帶有運(yùn)氣成分。我會(huì)用 5 個(gè)不同種子分別訓(xùn)練保存每個(gè)種子的預(yù)測序列把所有預(yù)測放在同一張圖上。預(yù)測線比較粗、互相分散說明模型對數(shù)據(jù)模式的把握還很不穩(wěn)定如果幾條線幾乎重合說明模型確實(shí)學(xué)到了穩(wěn)定的規(guī)律。更進(jìn)一步的可以從多次預(yù)測中取 10% 和 90% 分位數(shù)畫一條預(yù)測區(qū)間業(yè)務(wù)匯報(bào)時(shí)給區(qū)間比給單點(diǎn)有用得多。preds [] for seed in [42, 123, 999, 7, 2024]: set_seed(seed) model LSTMPredictor(...) # 重新訓(xùn)練并預(yù)測 preds.append(rolling_forecast(model, init_seq, steps30)) preds np.array(preds) # shape: (5, 30) lower np.percentile(preds, 10, axis0) upper np.percentile(preds, 90, axis0)第二個(gè)做法是滾動(dòng)重訓(xùn)。業(yè)務(wù)數(shù)據(jù)不斷更新模型不可能訓(xùn)一次用一年。我一般以一周為周期把新觀測追加到訓(xùn)練集尾部用上一次訓(xùn)練得到的模型參數(shù)作為初始權(quán)重繼續(xù)訓(xùn)練 10~20 個(gè) epoch。這種方式叫 warm start比每周從頭訓(xùn)練快得多也更容易適應(yīng)緩慢的數(shù)據(jù)漂移。重訓(xùn)后用最新一段真實(shí)數(shù)據(jù)做一輪 h7 的驗(yàn)證誤差如果明顯惡化說明數(shù)據(jù)分布發(fā)生了結(jié)構(gòu)性變化需要重新審視特征和窗口設(shè)置。最后分享一個(gè)我自己的習(xí)慣任何新數(shù)據(jù)集跑 LSTM 之前先跑一個(gè)最簡單的基線——直接用上一個(gè)值當(dāng)預(yù)測值persistence model。如果 LSTM 連這個(gè)傻瓜基線的誤差都壓不下來 10% 以上那不是模型能力問題而是前面的數(shù)據(jù)處理或參數(shù)選擇出了問題。這個(gè)習(xí)慣幫我擋掉了大量盲目調(diào)參的時(shí)間也讓我更容易分辨模型是真的學(xué)到了規(guī)律還是僅僅在復(fù)讀歷史。希望這些內(nèi)容能幫你在 LSTM 時(shí)間序列預(yù)測這條路上少踩幾個(gè)坑把代碼包真正變成可上線、可解釋的預(yù)測工具。本文還有配套的精品資源點(diǎn)擊獲取