測實戰(zhàn):原理詳解、PyTorch實現(xiàn)與調(diào)參指南)
簡介一份面向時間序列預(yù)測開發(fā)者的完整實現(xiàn)基于時間卷積網(wǎng)絡(luò)TCN對外匯中間價進行多輸入信號預(yù)測展示該模型在時序任務(wù)中相對于循環(huán)神經(jīng)網(wǎng)絡(luò)的精度優(yōu)勢。壓縮包內(nèi)共5個文件包含兩個Jupyter Notebook分別完成數(shù)據(jù)預(yù)處理與模型訓(xùn)練演示、一個模型腳本、兩個說明文檔整體僅1.04MB結(jié)構(gòu)緊湊便于快速上手。目前已有2393人參與學(xué)習(xí)。內(nèi)容涵蓋數(shù)據(jù)清洗、TCN網(wǎng)絡(luò)構(gòu)建、訓(xùn)練過程與早停策略并對比了不同階段的預(yù)測效果能幫助讀者掌握多變量時間序列預(yù)測的完整流程。該方案可遷移至其他存在因果關(guān)系的時序場景但需要注意區(qū)分因果關(guān)系與相關(guān)性并準(zhǔn)備足夠樣本以降低過擬合風(fēng)險同時資源提供的實驗顯示模型對后期走勢預(yù)測更好對理解時序建模的局限性與適用條件很有價值readme文檔也說明了運行環(huán)境與操作步驟便于復(fù)現(xiàn)和二次開發(fā)。1. 時間序列預(yù)測為什么要盯上TCN從LSTM的痛點說起TCNTemporal Convolutional Network時間卷積神經(jīng)網(wǎng)絡(luò)是近些年時間序列預(yù)測領(lǐng)域里最能打的方向之一。做量化交易策略代碼、銷量預(yù)測、設(shè)備故障預(yù)警的同行應(yīng)該都見過這個標(biāo)題下的模型它用卷積替代循環(huán)結(jié)構(gòu)專門處理按時間排序的數(shù)據(jù)。我最早是從LSTM轉(zhuǎn)到TCN的原因很直接——LSTM訓(xùn)練慢、梯度不穩(wěn)定、調(diào)參像玄學(xué)而TCN用堆疊的因果卷積和膨脹卷積把“序列記憶”變成可控的感受野訓(xùn)練速度更快、長序列表現(xiàn)也更穩(wěn)。這篇文章寫給剛配好Python環(huán)境、想入門時間序列預(yù)測但還沒選好模型的讀者也寫給已經(jīng)跑過LSTM想換結(jié)構(gòu)的熟手。下面直接給出可運行的PyTorch實現(xiàn)把參數(shù)怎么調(diào)、坑在哪一次講透。2. TCN模型結(jié)構(gòu)拆解因果卷積、膨脹卷積與殘差塊如何撐起時間記憶2.1 因果卷積為什么TCN的卷積不會“偷看”未來普通卷積在卷積核覆蓋一個窗口時窗口中心兩側(cè)的信息都會被加權(quán)。如果拿這種卷積處理時間序列t時刻的輸出會用到t1甚至t2時刻的值這在預(yù)測場景里等于作弊因為真實預(yù)測時未來根本還沒發(fā)生。TCN的解決辦法是因果卷積對于長度為L的輸入輸出位置t的值時卷積核只能訪問t、t-1、t-2這些歷史位置不能碰到未來。實現(xiàn)上常見做法是在每個TCNBlock的forward里對輸入做左側(cè)填充再調(diào)用Conv1d。注意這里不能用Conv1d自帶的padding參數(shù)直接完成因為Conv1d的padding是左右等量填充會把未來時刻的信息也塞進卷積窗口。TCN論文和主流開源實現(xiàn)用的都是自定義左側(cè)pad這也是初學(xué)者最容易忽略的細(xì)節(jié)。import torch import torch.nn as nn import torch.nn.functional as F class TCNBlock(nn.Module): def __init__(self, in_channels, out_channels, kernel_size, dilation): super().__init__() self.kernel_size kernel_size self.dilation dilation self.padding (kernel_size - 1) * dilation self.conv1 nn.Conv1d(in_channels, out_channels, kernel_size, dilationdilation, biasFalse) self.bn1 nn.BatchNorm1d(out_channels) self.conv2 nn.Conv1d(out_channels, out_channels, kernel_size, dilationdilation, biasFalse) self.bn2 nn.BatchNorm1d(out_channels) self.relu nn.ReLU() # 輸入輸出通道不一致時用1x1卷積把殘差投影到相同通道數(shù) self.residual (nn.Conv1d(in_channels, out_channels, 1, biasFalse) if in_channels ! out_channels else nn.Identity()) def forward(self, x): residual self.residual(x) # F.pad 的第二個參數(shù) (left, right)這里只填左邊、右邊不填 out F.pad(x, (self.padding, 0)) out self.relu(self.bn1(self.conv1(out))) out F.pad(out, (self.padding, 0)) out self.bn2(self.conv2(out)) return self.relu(out residual)這個塊是TCN結(jié)構(gòu)的基本單元。第一個F.pad的(self.padding, 0)表示左側(cè)增加padding個時間步、右側(cè)增加0個。Conv1d經(jīng)過kernel_size窗口滑動后輸出長度恰好等于輸入長度且每個輸出位置只依賴當(dāng)前及之前的數(shù)據(jù)。padding的計算公式是(kernel_size - 1) * dilation理解這條公式對后續(xù)算感受野很關(guān)鍵。第二個關(guān)鍵點是殘差連接。兩層卷積堆疊后梯度路徑很長殘差把輸入直接加到輸出上讓梯度有一條“高速通道”可以回流。當(dāng)in_channels不等于out_channels時不能直接相加必須用1x1卷積投影到相同維度。大多數(shù)TCN開源實現(xiàn)都沿用了這個寫法改動空間不大真正影響效果的是每一層的膨脹系數(shù)和通道數(shù)。2.2 膨脹卷積與感受野TCN的“時間記憶”能覆蓋多遠(yuǎn)因果卷積只能保證不泄露未來但一層卷積的感受野只有kernel_size那么長處理稍長的序列根本不夠。膨脹卷積Dilated Convolution在這個問題上是核心手段它不改變卷積核參數(shù)量而是讓卷積核在輸入上每隔dilation個點采樣一次。dilation1就是普通卷積dilation2時卷積核會跨過1個點再采樣感受野立刻擴大。TCN模型結(jié)構(gòu)會把多個TCNBlock串聯(lián)起來dilation按2的指數(shù)增長即第i層的dilation為2^i。這樣深層網(wǎng)絡(luò)的感受野呈指數(shù)級擴大。感受野的計算公式如下receptive_field 1 sum((kernel_size - 1) * dilation_i for each layer i)假設(shè)kernel_size3共4層dilation分別為1、2、4、8則感受野為1 2*(1248) 31。這意味著輸出位置的每個值最多可以“看到”31個歷史時間步。輸入序列長度只有小于這個數(shù)模型的有效記憶就會打折扣這也是后面5.3節(jié)滯后預(yù)測的主要誘因。class TemporalConvNet(nn.Module): def __init__(self, num_inputs1, num_channels(32, 64, 64), kernel_size3, dropout0.2): super().__init__() self.blocks nn.ModuleList() # 每一層的dilation按2的冪次遞增 for i, out_channels in enumerate(num_channels): in_channels num_inputs if i 0 else num_channels[i-1] dilation 2 ** i self.blocks.append( TCNBlock(in_channels, out_channels, kernel_size, dilation) ) self.dropout nn.Dropout(dropout) def forward(self, x): # x: (batch_size, input_channels, seq_len) for block in self.blocks: x block(x) return self.dropout(x)這段代碼把TCNBlock串成完整網(wǎng)絡(luò)。num_channels是一個元組每個元素代表一層的輸出通道數(shù)元組長度就是層數(shù)。dilation從1開始每層翻倍所以4層網(wǎng)絡(luò)對應(yīng)dilation1,2,4,8。我一般建議先用3層起步確認(rèn)效果后再加深到5層以上注意每一層頂部的感受野要大于輸入序列長度的一半。2.3 TCN與LSTM的取舍什么時候換掉你的LSTM在量化交易、負(fù)荷預(yù)測這類實際任務(wù)里TCN和LSTM的對比一直是選型核心。我自己的使用體感是LSTM擅長捕捉極長距離的依賴但訓(xùn)練串行、難以并行門控機制在長序列上容易梯度衰減TCN的優(yōu)勢在于訓(xùn)練時可并行、感受野可控、推理速度更快在多數(shù)中等長度序列上精度不低于LSTM。對比項LSTMTCN訓(xùn)練并行度低按時間步串行高卷積天然并行長距離依賴?yán)碚撋蠠o上限受感受野限制需手動設(shè)計參數(shù)數(shù)量中等但隱狀態(tài)計算開銷大取決于通道數(shù)和層數(shù)調(diào)參難度隱層維度、層數(shù)、dropout層數(shù)、通道數(shù)、kernel_size、dilation適合場景超長序列、在線增量學(xué)習(xí)GPU批量訓(xùn)練、中長度序列預(yù)測這里必須說一句公道話TCN不是萬能替代者。如果序列長度超過幾千步且依賴跨很遠(yuǎn)的歷史信息TCN要堆很多層才能覆蓋此時LSTM或Transformer可能更省參數(shù)。反過來絕大多數(shù)用72小時歷史預(yù)測未來24小時、用30天均線預(yù)測次日走勢這類場景TCN的感受野設(shè)計起來非常直觀這也是它能在工業(yè)界快速普及的原因。3. 用Python從零搭建TCN數(shù)據(jù)處理、模型定義與完整訓(xùn)練代碼3.1 數(shù)據(jù)準(zhǔn)備滑動窗口與歸一化動手前先把環(huán)境確認(rèn)一下Python 3.8以上numpy、pandas、sklearn、torch都裝好。如果還沒裝numpy和sklearn直接pip install numpy scikit-learn torch即可不需要額外編譯。為了先驗證流程我習(xí)慣用帶噪聲的正弦波作為合成數(shù)據(jù)它和真實時序一樣有趨勢、有起伏跑通后再把CSV路徑換成自己的數(shù)據(jù)。import numpy as np import pandas as pd from sklearn.preprocessing import MinMaxScaler # 合成數(shù)據(jù)正弦波 噪聲模擬具有周期性的時間序列 np.random.seed(42) t np.arange(0, 1000, 0.5) data np.sin(t / 10) np.random.normal(0, 0.1, len(t)) df pd.DataFrame({value: data}) df.to_csv(synth_data.csv, indexFalse) def create_sequences(data, input_steps48, pred_steps1): X, y [], [] for i in range(len(data) - input_steps - pred_steps 1): X.append(data[i:i input_steps]) y.append(data[i input_steps:i input_steps pred_steps]) return np.array(X), np.array(y) # 先fit訓(xùn)練集的scaler再transform整個數(shù)據(jù)集避免信息泄露 scaler MinMaxScaler(feature_range(0, 1)) scaled scaler.fit_transform(df[[value]].values) X, y create_sequences(scaled) print(X.shape, y.shape) # 輸出(1903, 48, 1) (1903, 1)這里數(shù)據(jù)集的shape很關(guān)鍵X是(batch_size, time_steps, features)y是(batch_size, pred_steps)。把這段代碼里的CSV路徑換成你自己的數(shù)據(jù)即可平滑過渡到真實場景。注意create_sequences在數(shù)據(jù)量少時要小心內(nèi)存占用幾十萬行的數(shù)據(jù)建議用TensorDataset或自定義DataSet分批生成不要一次性全塞進數(shù)組。數(shù)據(jù)切分要遵守時間順序不能用train_test_split默認(rèn)的shuffleTrue。常見做法是前80%做訓(xùn)練、后20%做驗證因為時間序列里未來不可用于訓(xùn)練。train_ratio 0.8 train_size int(len(X) * train_ratio) X_train, X_val X[:train_size], X[train_size:] y_train, y_val y[:train_size], y[train_size:] # 轉(zhuǎn)成Tensor并構(gòu)造成DataLoader import torch from torch.utils.data import TensorDataset, DataLoader X_train_t torch.FloatTensor(X_train).permute(0, 2, 1) # (batch, features, seq_len) y_train_t torch.FloatTensor(y_train) X_val_t torch.FloatTensor(X_val).permute(0, 2, 1) y_val_t torch.FloatTensor(y_val) train_dataset TensorDataset(X_train_t, y_train_t) val_dataset TensorDataset(X_val_t, y_val_t) train_loader DataLoader(train_dataset, batch_size64, shuffleTrue) val_loader DataLoader(val_dataset, batch_size64, shuffleFalse)這里permute把X從(batch, seq_len, features)轉(zhuǎn)成(batch, features, seq_len)因為Conv1d默認(rèn)在最后一維做卷積序列長度應(yīng)該放在最后。shuffleTrue只打亂批次順序不打亂每個樣本內(nèi)部的時序這不會造成數(shù)據(jù)泄露。3.2 模型定義TCN主干加預(yù)測頭TCN主干輸出的shape是(batch, channels, seq_len)要變成預(yù)測值還需要一個全連接輸出層。這里有一個容易忽略的細(xì)節(jié)到底取最后一個時間步的特征做預(yù)測還是取全部時間步做全局平均我測試下來取最后一個時間步通常更貼合“用歷史預(yù)測未來”的語義因為t時刻的輸出特征是模型看過t時刻及之前數(shù)據(jù)后得到的編碼。class TCNForecaster(nn.Module): def __init__(self, num_inputs1, num_channels(32, 64, 64), kernel_size3, dropout0.2, pred_steps1): super().__init__() self.tcn TemporalConvNet(num_inputs, num_channels, kernel_size, dropout) self.fc nn.Linear(num_channels[-1], pred_steps) def forward(self, x): # x: (batch, features, seq_len) out self.tcn(x) # 取最后一個時間步的特征 out out[:, :, -1] return self.fc(out) model TCNForecaster(num_inputs1, num_channels(32, 64, 64), kernel_size3, pred_steps1) print(model) # 輸出結(jié)構(gòu)TCN主干3層 Linear參數(shù)選擇上num_channels的通道數(shù)從32起步逐層保持或翻倍。通道太多會過擬合太少則特征表達能力不夠。pred_steps是你想預(yù)測的未來步數(shù)先設(shè)1跑通后續(xù)第6章會專門講多步預(yù)測改造。3.3 訓(xùn)練循環(huán)手寫雙循環(huán)避免黑匣子我不太喜歡把訓(xùn)練封裝成黑匣子手寫雙循環(huán)能直觀看到每一輪的loss變化出了問題也容易定位。下面的訓(xùn)練函數(shù)包含訓(xùn)練和驗證兩個階段并返回訓(xùn)練過程中的loss記錄。import torch.nn as nn def train_model(model, train_loader, val_loader, epochs30, lr1e-3): optimizer torch.optim.Adam(model.parameters(), lrlr) criterion nn.MSELoss() best_val_loss float(inf) for epoch in range(epochs): model.train() train_loss 0.0 for X_batch, y_batch in train_loader: optimizer.zero_grad() pred model(X_batch) loss criterion(pred, y_batch) loss.backward() optimizer.step() train_loss loss.item() * X_batch.size(0) model.eval() val_loss 0.0 with torch.no_grad(): for X_batch, y_batch in val_loader: pred model(X_batch) val_loss criterion(pred, y_batch).item() * X_batch.size(0) train_loss / len(train_loader.dataset) val_loss / len(val_loader.dataset) if (epoch 1) % 5 0: print(fEpoch {epoch1:3d} | train_loss {train_loss:.6f} | val_loss {val_loss:.6f}) # 簡單保存最優(yōu)模型 if val_loss best_val_loss: best_val_loss val_loss torch.save(model.state_dict(), best_tcn.pt) train_model(model, train_loader, val_loader, epochs30, lr1e-3)注意loss計算這里用的是每個batch的平均loss乘以batch_size再除以總樣本數(shù)也就是按樣本數(shù)加權(quán)平均避免batch size不一致時loss失真。實際跑30輪訓(xùn)練loss會一路下降驗證loss在最優(yōu)值附近波動。保存模型時我習(xí)慣同時保存best_val_loss對應(yīng)的state_dict而不是最后一輪的權(quán)重這樣能規(guī)避驗證集上后期過擬合的問題。4. 訓(xùn)練TCN的三個必調(diào)參數(shù)與收斂判斷4.1 感受野先算再調(diào)別讓模型“記性”不夠TCN最核心的調(diào)參是確保感受野大于輸入序列長度的一半。很多初學(xué)者直接套用LSTM的窗口長度輸入給64步歷史但TCN只設(shè)計了3層、kernel_size3感受野只有12*(124)15模型根本看不到64步之前的信息。結(jié)果預(yù)測值在突變處明顯遲鈍看起來就像“跟著真實值走”。調(diào)整的方法是先算感受野再設(shè)計網(wǎng)絡(luò)。上面的例子中要覆蓋64步歷史3層TCN感受野只有15那就把kernel_size調(diào)到5或把num_channels層數(shù)增加到5層。5層、kernel_size3的感受野為12*(124816)63剛好覆蓋64步輸入。我給一個經(jīng)驗值輸入序列長度設(shè)為感受野的1.5到2倍留出冗余讓每一層都有足夠的上下文。def calc_receptive_field(kernel_size, num_layers): 計算TCN網(wǎng)絡(luò)的感受野 rf 1 for i in range(num_layers): rf (kernel_size - 1) * (2 ** i) return rf print(calc_receptive_field(3, 4)) # 31 print(calc_receptive_field(5, 4)) # 61 print(calc_receptive_field(3, 6)) # 127這段代碼可以直接復(fù)制到你的腳本里做設(shè)計校驗。先定輸入序列長度再反推層數(shù)和kernel_size能省掉大量試錯時間。4.2 學(xué)習(xí)率、kernel_size與dropout的配合學(xué)習(xí)率是最容易出問題的超參數(shù)。Adam默認(rèn)的1e-3在大多數(shù)中小規(guī)模數(shù)據(jù)上夠用但如果loss在訓(xùn)練初期就發(fā)散先檢查數(shù)據(jù)是否做了歸一化然后降到1e-4。kernel_size決定單層感受野增量常用值為3或5。7以上會讓參數(shù)量快速上升在數(shù)據(jù)量不大時幾乎必然過擬合。dropout的用法和LSTM類似但要更保守。TCN的每一層都加了dropout如果設(shè)0.5信號經(jīng)過多層后會被嚴(yán)重削弱典型表現(xiàn)是訓(xùn)練loss和驗證loss都降不下去。我一般把dropout控制在0.1到0.2之間數(shù)據(jù)量小用0.1數(shù)據(jù)量大或明顯過擬合時再升到0.3。超參數(shù)推薦范圍調(diào)參策略kernel_size3 ~ 5感受野不夠時優(yōu)先加大num_channels32 → 64 → 128 遞增每層翻倍避免首層過寬層數(shù)3 ~ 6先少后多配合感受野計算dropout0.1 ~ 0.3訓(xùn)練loss不降時減小驗證loss漲時增大learning_rate1e-3 ~ 1e-4Adam默認(rèn)1e-3發(fā)散了就降batch_size32 ~ 128數(shù)據(jù)波動大用較小batch4.3 怎么判斷收斂訓(xùn)練loss低不等于模型好很多新手看到訓(xùn)練loss降到0.001就高呼叫好但時間序列預(yù)測要看的核心指標(biāo)是驗證集上的loss趨勢。正常收斂過程是訓(xùn)練loss和驗證loss同步下降驗證loss在某個epoch后進入平臺期如果訓(xùn)練loss繼續(xù)降而驗證loss掉頭上升說明開始過擬合。我習(xí)慣在每個epoch打印兩個數(shù)字驗證loss相對于上一輪的差值、驗證loss與訓(xùn)練loss的比值。當(dāng)驗證loss連續(xù)5輪不再下降就觸發(fā)早停比值大于1.5時說明過擬合已經(jīng)比較明顯需要調(diào)小dropout或減少層數(shù)。early_stop_patience 5 best_val float(inf) patience 0 for epoch in range(epochs): # 訓(xùn)練并計算 train_loss, val_loss if val_loss best_val: best_val val_loss patience 0 torch.save(model.state_dict(), best_tcn.pt) else: patience 1 if patience early_stop_patience: print(fEarly stop at epoch {epoch1}) break這套早停邏輯簡單但管用。要注意驗證loss的波動在時間序列任務(wù)里很常見選best_val時要看3輪平均而不是單輪最低值否則容易被噪聲帶偏。5. TCN時間序列預(yù)測的五個常見坑現(xiàn)象、原因與排查5.1 歸一化泄漏驗證集表現(xiàn)好上線就翻車現(xiàn)象訓(xùn)練集和驗證集loss都很低但把模型部署到新數(shù)據(jù)上時預(yù)測誤差明顯變大。原因最常見的是在整段數(shù)據(jù)上做了MinMaxScaler的fit_transformscaler偷看了驗證集和測試集的統(tǒng)計量等于把未來信息泄露給了訓(xùn)練過程。時間序列里這不是玄學(xué)而是實打?qū)嵉臄?shù)據(jù)泄露。解決在訓(xùn)練集上先fit再用訓(xùn)練集的scaler去transform驗證集和測試集。本文3.1節(jié)的代碼已經(jīng)按這個順序?qū)懥藢嶋H項目里要特別注意。5.2 數(shù)據(jù)切分用了隨機shuffle導(dǎo)致時間錯亂現(xiàn)象驗證loss遠(yuǎn)低于訓(xùn)練loss或者模型表現(xiàn)時好時壞。原因直接調(diào)用train_test_split(X, y, test_size0.2, shuffleTrue)會讓模型同時看到過去和未來的樣本驗證集里混入訓(xùn)練窗口附近的樣本。時間序列一旦打亂順序模型學(xué)到的根本不是時序規(guī)律。解決按時間順序切分前80%訓(xùn)練后20%驗證。如果要手動打亂訓(xùn)練集也要確保打亂的只是批次順序不破壞每個樣本內(nèi)部的時序。5.3 預(yù)測結(jié)果比真實值滯后一拍模型在做“復(fù)制粘貼”現(xiàn)象預(yù)測曲線和真實曲線形態(tài)一致但整體向右平移了一個或多個時間步。原因感受野不足或dropout過大導(dǎo)致模型沒學(xué)到趨勢只學(xué)到“上一時刻的值近似當(dāng)前值”。在平穩(wěn)序列上這很容易騙過loss指標(biāo)因為預(yù)測值緊貼真實值看圖才發(fā)現(xiàn)根本沒有預(yù)測能力。解決先按4.1節(jié)的公式計算感受野確保大于輸入序列長度的一半然后把pred_steps設(shè)成2或3做自檢——如果多步預(yù)測時誤差迅速變大說明模型確實在“抄近期值”。5.4 BatchNorm在分布漂移的數(shù)據(jù)上反而幫倒忙現(xiàn)象模型在訓(xùn)練集上收斂正常驗證集上loss劇烈波動甚至出現(xiàn)NaN。原因BatchNorm統(tǒng)計的是當(dāng)前batch的均值和方差時間序列數(shù)據(jù)存在非平穩(wěn)性不同batch分布差異大時BatchNorm的統(tǒng)計量會抖動。這在金融序列、傳感器數(shù)據(jù)上尤其明顯。解決把TCNBlock里的BatchNorm1d換成GroupNorm或LayerNorm。對于回歸任務(wù)也可以直接把BN層去掉只保留卷積加ReLU效果往往更穩(wěn)。5.5 dropout設(shè)得過大信號被當(dāng)噪聲丟掉了現(xiàn)象訓(xùn)練loss和驗證loss都居高不下訓(xùn)練很多輪也不下降。原因dropout在每一層都會隨機屏蔽部分神經(jīng)元設(shè)成0.5時深層特征幾乎被丟光。TCN的特征圖比LSTM的隱狀態(tài)更依賴連續(xù)激活模式dropout過大直接毀掉特征提取。解決dropout從0.1開始驗證loss不降再每次加0.05。如果模型還是過擬合優(yōu)先減小num_channels而不是繼續(xù)加dropout。6. 讓TCN輸出多步預(yù)測三個進階策略與驗收技巧單步預(yù)測跑通后下一步就是讓模型輸出未來多個時間步。最常見的做法有三種遞歸預(yù)測、直接多步、多模型組合。遞歸預(yù)測就是把預(yù)測值拼到輸入末尾再喂給模型實現(xiàn)簡單但誤差會隨時間步累積直接多步是在全連接輸出層把pred_steps設(shè)為目標(biāo)步數(shù)一次輸出全部未來值誤差不累積但長步預(yù)測精度會下降。我一般在業(yè)務(wù)上優(yōu)先用直接多步因為訓(xùn)練穩(wěn)定、部署簡單而且TCN的感受野天然適合一次給出完整未來窗口。改成直接多步時訓(xùn)練集標(biāo)簽y的形狀要從(batch, 1)變成(batch, pred_steps)。數(shù)據(jù)準(zhǔn)備階段的create_sequences里pred_steps已經(jīng)是可變參數(shù)只需要在構(gòu)造DataLoader時把標(biāo)簽的維度對齊。驗證時不要只用MSE我習(xí)慣把預(yù)測曲線和真實曲線畫在同一張圖上用肉眼看滯后和形態(tài)。這里有一個實用的小技巧把測試集的預(yù)測結(jié)果反歸一化回原始單位再計算平均絕對百分比誤差這樣才能直觀看出模型在業(yè)務(wù)上到底準(zhǔn)不準(zhǔn)。另一個進階技巧是加入時間特征作為額外通道。比如把“小時”“星期幾”“是否節(jié)假日”歸一化后拼到輸入通道上TCN的Conv1d支持多通道輸入只需把num_inputs從1改成特征總數(shù)。這個操作對銷量預(yù)測和電力負(fù)荷預(yù)測的提升往往比調(diào)模型結(jié)構(gòu)更明顯。這套流程我已經(jīng)在電力負(fù)荷和金融序列上跑過多次踩得最深的一次就是感受野沒算夠模型預(yù)測滯后到無法使用。后來我給自己立了個規(guī)矩任何時間序列項目先算感受野、先畫預(yù)測曲線再談?wù){(diào)參。希望幫到你。本文還有配套的精品資源點擊獲取