化的PatchTST綜合能源負荷多變量預(yù)測實現(xiàn))
做綜合能源系統(tǒng)的負荷預(yù)測最讓人頭疼的往往不是單條曲線怎么擬合而是幾條不同能源品種的曲線放在一起時它們之間的耦合關(guān)系、量綱差異、突變規(guī)律怎么處理。我手頭這個項目就是這么來的一個園區(qū)級的綜合能源系統(tǒng)需要同時預(yù)測電負荷、熱負荷和冷負荷外部還要疊加上溫度、濕度、光照、風速這些氣象因素以及工作日/節(jié)假日標記。如果用傳統(tǒng)的LSTM一路堆到底效果很容易到瓶頸換標準Transformer又會遇到長序列計算量大、局部模式提取弱的問題。后來我把模型切換成PatchTST調(diào)參再用貝葉斯優(yōu)化去搜整體思路就是標題里寫的基于貝葉斯優(yōu)化的PatchTST綜合能源負荷多變量時間序列預(yù)測Python代碼完整實現(xiàn)。這篇內(nèi)容適合兩類人看一類是做電力、能源、園區(qū)微網(wǎng)相關(guān)的算法工程師另一類是正在研究時間序列預(yù)測、想把PatchTST這類新模型落地到自己數(shù)據(jù)集上的同學。我會把模型機制、調(diào)參思路、數(shù)據(jù)構(gòu)造以及實際訓練中踩過的坑都串起來講代碼部分也會給出可以直接套用的骨架。1. 項目拆解為什么把寶押在PatchTST和貝葉斯優(yōu)化上先說說整個項目的技術(shù)路線是怎么定下來的。綜合能源負荷預(yù)測本質(zhì)上是多變量時間序列預(yù)測問題輸入是一段歷史窗口的多維張量輸出是未來一段時間的多條負荷曲線。和普通單序列預(yù)測比難點主要在三個地方。第一個難點是通道數(shù)多且量綱差異大。電負荷可能是幾千千瓦熱負荷可能是吉焦每小時冷負荷又不一樣這幾個變量如果直接拼成一個矩陣丟給模型訓練時梯度很容易被量綱大的通道主導。所以需要做精細的歸一化并且模型本身要具備一定的通道獨立性。第二個難點是局部模式很重要。綜合能源負荷曲線不像股票那樣充滿隨機游走它有明顯日內(nèi)周期性、氣溫敏感性和節(jié)假日突變性。這些模式往往體現(xiàn)在幾十個時間步的局部片段里比如早上電負荷爬坡這一段往往持續(xù)三五個采樣點就有明顯趨勢特征。標準Transformer用的是逐點注意力每個時間步單獨做embedding對局部片段的建模效率不高而PatchTST的核心就是把時間步先切成patch把一個patch當成一個token這樣自注意力直接作用在語義更完整的patch上局部趨勢一下就清楚了很多。第三個難點是調(diào)參空間大人工試錯成本太高。PatchTST涉及的參數(shù)很多輸入窗口長度、patch長度、stride步長、Transformer層數(shù)、頭數(shù)、d_model、dropout、學習率再加上優(yōu)化器和調(diào)度策略如果靠網(wǎng)格搜索或者手動試一組跑下來半個多小時根本試不完。貝葉斯優(yōu)化正好解決這個問題它能根據(jù)歷史試驗結(jié)果選擇下一組最有潛力的參數(shù)用更少的嘗試次數(shù)找到更好的配置。所以整個項目的架構(gòu)就很清晰了數(shù)據(jù)側(cè)做好多變量窗口構(gòu)造和歸一化模型側(cè)用PatchTST提取patch級語義特征優(yōu)化側(cè)用貝葉斯替代模型去搜超參數(shù)最終目標是讓驗證集上的多步預(yù)測誤差盡量小。這個組合在實際跑下來之后效果確實比原來的LSTM基線好了不少尤其是熱負荷這種帶明顯延遲慣性的曲線預(yù)測精度提升很直觀。2. PatchTST核心機制從內(nèi)到外講透每個設(shè)計選擇想用好一個模型不能只調(diào)庫得把它的設(shè)計邏輯搞清楚。PatchTST的關(guān)鍵創(chuàng)新可以概括為三個詞Patch、Channel Independence、Transformer Encoder。下面逐個拆開講。2.1 時序分割與Patch化處理標準Transformer處理時間序列時通常是把每個時間步映射成一個token。假設(shè)輸入序列長度是96那就有96個token每個token只代表一個采樣點的值自注意力在這96個點之間做交互。這里的問題很明顯單點信息密度低而且注意力矩陣是96x96計算和內(nèi)存開銷都不小。PatchTST的做法是把時間維切成小段每個小段叫一個patch把patch整體映射成一個token。假設(shè)patch長度是16stride是8序列長度96就能切出約11個patch注意力矩陣從96x96縮到11x11計算量少了將近一個數(shù)量級同時每個token包含16步的局部上下文語義信息豐富得多。這個設(shè)計背后的邏輯很像看圖時的滑窗識別人眼看一幅圖不會逐像素掃描而是先看一個個局部區(qū)域再把這些區(qū)域組合起來理解全局。PatchTST的patch化也是這個道理它讓模型天然聚焦在局部形態(tài)上而局部形態(tài)恰恰是負荷曲線的核心特征。實際實現(xiàn)時patch化有兩種常見做法。一種是用一維卷積配合合適的卷積核大小和步長來實現(xiàn)另一種是先unfold再線性映射。效果上兩者差別不大但用unfold加Linear的方式更直觀方便之后調(diào)整patch長度。有一點要特別注意patch數(shù)量不能太少否則序列全局信息會丟得太多。如果序列長度是96patch長度和stride都設(shè)16patch數(shù)量能到6個左右再往下減信息損失就會明顯了。2.2 通道獨立與多變量協(xié)作通道獨立這個設(shè)計我剛開始覺得有點反直覺。多變量預(yù)測難道不應(yīng)該讓不同變量之間充分交互嗎怎么還搞獨立PatchTST的Channel Independence策略指的是每個變量的patch序列在embedding和編碼階段各自獨立處理共用同一套映射和Transformer參數(shù)最后再通過預(yù)測頭輸出各自的結(jié)果。這樣做的好處是不同通道的量綱差異不會在embedding層就被混在一起模型可以把更多容量花在通道內(nèi)部的時序模式上。但獨立并不等于完全不合作。在多變量場景里我們可以做兩件事來引入通道間交互。第一在輸入側(cè)把外生變量氣象、日歷等作為額外通道一起喂進去讓模型在通道維度上間接學到它們的聯(lián)合分布第二在輸出側(cè)用多任務(wù)預(yù)測頭把電、熱、冷三個負荷同時輸出共享損失和梯度更新通過共用底層特征實現(xiàn)隱性的跨通道協(xié)同。我在項目里實際采用的是“通道獨立編碼多任務(wù)輸出”的組合。實測下來這種方式比完全把多通道concat成一個矩陣讓模型自由混合效果更穩(wěn)尤其是冷負荷和熱負荷這種相關(guān)性弱的通道不會被強通道的信息淹沒。2.3 Encoder與預(yù)測頭PatchTST的編碼器用的就是標準的Transformer Encoder由多頭自注意力、前饋網(wǎng)絡(luò)、殘差連接和層歸一化組成。注意這里的自注意力作用在patch序列上也就是說注意力對象是若干個patch之間的關(guān)系而不是時間點之間的關(guān)系。這樣做的好處是注意力可以更高效地捕捉跨時間的依賴比如今天下午3點的負荷模式與昨天下午3點的負荷模式之間的相似性。預(yù)測頭部分PatchTST用的是直接多步預(yù)測一次性輸出未來所有時間點的值而不是像有些方法那樣逐步遞歸生成。直接多步預(yù)測的好處非常明顯避免了遞歸多步預(yù)測里的誤差累積訓練時也用不著teacher forcing。實現(xiàn)上Encoder輸出的特征向量會被展平通過一個線性層直接映射到 pred_len 長度的輸出。對于多變量版本預(yù)測頭可以設(shè)計成輸出形狀為 (batch, num_channels, pred_len) 的張量。每一路用獨立的線性映射但也共享底層的Encoder特征。這樣電、熱、冷三條曲線既各自有獨立的輸出投影又能共享同一套時序特征提取網(wǎng)絡(luò)效果比較均衡。3. 貝葉斯優(yōu)化如何介入深度學習調(diào)參模型選好之后下一個問題是參數(shù)怎么定。說實話PatchTST這個模型對超參數(shù)還算敏感尤其是patch長度和stride直接決定token序列的信息粒度手動試錯非常痛苦。我一開始用的是傳統(tǒng)網(wǎng)格搜索一組參數(shù)跑一次完整訓練結(jié)果發(fā)現(xiàn)完全行不通。后來切到貝葉斯優(yōu)化整個流程才順利起來。3.1 為什么不用網(wǎng)格搜索網(wǎng)格搜索的本質(zhì)是把參數(shù)空間切成均勻網(wǎng)格然后逐個組合嘗試。它最大的問題是維度災(zāi)難參數(shù)一旦多起來組合數(shù)爆炸式增長。假設(shè)我們只搜6個參數(shù)每個參數(shù)取5個候選值組合數(shù)就是5的6次方一萬五千多種組合一次訓練要幾分鐘全跑完需要幾個月根本不現(xiàn)實。隨機搜索稍微好一些它可以指定嘗試次數(shù)但因為是隨機采樣不會利用已經(jīng)跑過的結(jié)果前面的經(jīng)驗對后面的搜索沒有幫助。貝葉斯優(yōu)化的思路完全不同。它把超參數(shù)搜索當成一個黑盒優(yōu)化問題目標函數(shù)是一次完整的訓練流程輸入是一組超參數(shù)輸出是驗證集誤差。貝葉斯優(yōu)化會在每次試驗后更新一個概率代理模型來描述“哪些參數(shù)組合更可能讓誤差小”下一次試驗就選擇代理模型認為最有潛力的參數(shù)組合去跑。這就像找餐廳網(wǎng)格搜索是把整條街每家店都吃一遍隨機搜索是閉著眼挑幾家貝葉斯優(yōu)化則是吃完幾家后總結(jié)出“這條街上的高分店都集中在某個區(qū)域”然后重點去那片區(qū)域繼續(xù)找。3.2 搜索空間設(shè)計與采集函數(shù)機制貝葉斯優(yōu)化里有兩個關(guān)鍵組件一個是參數(shù)搜索空間一個是采集函數(shù)。搜索空間決定了我們允許模型在哪些范圍里找參數(shù)。我設(shè)計PatchTST的搜索空間時大概分成三類。第一類是模型結(jié)構(gòu)參數(shù)包括d_model、n_heads、e_layers、d_ff。這些參數(shù)決定模型容量。注意n_heads必須能整除d_model否則多注意力頭沒法均分維度。我一般把d_model設(shè)在64到128之間n_heads從4和8里面選e_layers控制在2到4d_ff按d_model的2到4倍去搜。第二類是patch化參數(shù)包括patch_len和stride。這是PatchTST特有的影響最大。patch_len太小局部語義不足太大patch數(shù)量太少序列信息壓縮過狠。我通常把patch_len放在8到24之間stride放在4到12之間。第三類是訓練參數(shù)包括學習率、dropout、batch_size、weight_decay。學習率我習慣用對數(shù)均勻分布采樣范圍1e-4到1e-2dropout在0.05到0.3之間batch_size在32到128之間weight_decay在1e-6到1e-3之間。采集函數(shù)的作用是從代理模型推薦下一組參數(shù)。常用的有期望改進EI和置信上界UCB。EI會權(quán)衡“這個區(qū)域可能達到多好”和“這個區(qū)域的不確定性有多大”既不會只盯著已知最優(yōu)區(qū)域?qū)е戮植渴諗恳膊粫耆教巵y試。我用的優(yōu)化庫是Optuna它默認的TPE采樣器使用的就是類似的思想把歷史試驗分成表現(xiàn)好和表現(xiàn)差的兩組通過概率密度比值來指導下一步采樣。3.3 基于Optuna的實現(xiàn)思路Optuna是目前最順手的貝葉斯優(yōu)化框架和PyTorch集成很方便。整個流程分三步定義objective函數(shù)、創(chuàng)建study、運行優(yōu)化。objective函數(shù)內(nèi)部就是一次完整的模型訓練和評估流程最終返回驗證集上的MAE或者RMSE作為需要最小化的目標值。import optuna from optuna.pruners import MedianPruner from optuna.samplers import TPESampler def objective(trial): params { d_model: trial.suggest_categorical(d_model, [64, 96, 128]), n_heads: trial.suggest_categorical(n_heads, [4, 8]), e_layers: trial.suggest_int(e_layers, 2, 4), patch_len: trial.suggest_int(patch_len, 8, 24, step4), stride: trial.suggest_int(stride, 4, 12, step4), dropout: trial.suggest_float(dropout, 0.05, 0.3), lr: trial.suggest_float(lr, 1e-4, 1e-2, logTrue), batch_size: trial.suggest_categorical(batch_size, [32, 64, 128]), weight_decay: trial.suggest_float(weight_decay, 1e-6, 1e-3, logTrue), } # 數(shù)據(jù)加載、模型構(gòu)建、訓練循環(huán)在這里完成 # val_loss train_and_evaluate(params) # 這個函數(shù)內(nèi)部包含完整的訓練和驗證邏輯 return val_loss sampler TPESampler(seed42, n_startup_trials10) pruner MedianPruner(n_startup_trials5, n_warmup_steps10) study optuna.create_study(directionminimize, samplersampler, prunerpruner) study.optimize(objective, n_trials60, timeout7200)這里有兩個細節(jié)值得說。第一n_startup_trials是指在真正用TPE決策之前先做多少次完全隨機采樣目的是給代理模型提供初始數(shù)據(jù)。設(shè)太小比如設(shè)成3代理模型容易在剛開始就鎖死到錯誤的區(qū)域。我實際用下來設(shè)10到15比較穩(wěn)。第二pruner的MedianPruner指的是如果某個trial在訓練中途的中間結(jié)果比歷史同期中位數(shù)差就直接終止它把計算資源讓給更有潛力的試驗。這類剪枝機制在調(diào)參任務(wù)里非常省時間尤其是在搜索空間很大的時候。4. 數(shù)據(jù)處理綜合能源負荷預(yù)測的建?;A(chǔ)模型和調(diào)參框架都定了數(shù)據(jù)這邊反而是最容易出問題但最容易被忽視的部分。綜合能源系統(tǒng)的原始數(shù)據(jù)通常來自SCADA系統(tǒng)、氣象站和日歷表數(shù)據(jù)結(jié)構(gòu)五花八門采樣間隔可能不齊異常值和缺失值也很多。處理不好后面模型再先進也是白搭。4.1 多變量特征集合與來源我梳理這個項目的數(shù)據(jù)時把特征分成了三類。第一類是預(yù)測目標也就是電負荷、熱負荷、冷負荷這三條主曲線采樣間隔是15分鐘一個點一天96個點。第二類是外部氣象特征包括干球溫度、相對濕度、水平面總輻射、風速等這些特征對空調(diào)負荷和供熱負荷影響很大尤其是溫度和輻射與冷負荷的相關(guān)系數(shù)相當高。第三類是日歷特征比如是否工作日、是否節(jié)假日、一天中的第幾個采樣點。日歷特征看起來簡單但對模型幫助很大。綜合能源負荷在工作日和周末的形態(tài)差異非常明顯如果模型不確定“今天是星期幾”它很難區(qū)分同一時刻兩種不同的合理模式。還有一類容易被忽略的特征是滯后變量。綜合能源系統(tǒng)里的熱負荷有很強的熱慣性今天的負荷不僅取決于今天的氣象還和過去幾個小時的熱量積累有關(guān)。所以我把目標變量的前24個點也作為額外特征窗口輸入讓模型有機會捕捉這種慣性。不過要注意加了滯后變量后輸入通道數(shù)會明顯增加特征之間的多重共線性也會變強需要靠模型的正則化去吸收。4.2 歸一化方案的取舍歸一化這個環(huán)節(jié)我一開始吃過虧。直接用StandardScaler對整個特征矩陣做標準化表面上看沒錯但會把不同負荷通道的量綱差異壓縮到同一范圍反而丟掉了不同能源品種之間的相對規(guī)模信息。后來我改成每個通道單獨做歸一化也就是對電負荷、熱負荷、冷負荷、溫度、濕度這些列分別fit一個scaler。這樣做的另一個重要原因是預(yù)測結(jié)果需要反變換回原始量綱才能計算誤差指標。如果多個通道混合縮放反變換的時候矩陣操作容易出錯分開處理后會清晰很多。預(yù)測目標通道建議使用MinMaxScaler讓它嚴格落在0到1之間這樣和模型輸出層的激活范圍匹配更好訓練初期收斂更穩(wěn)定。外部氣象特征用StandardScaler就行不需要強制限定到某個區(qū)間。反變換的公式很簡單但要注意在測試集上必須用訓練集fit好的scaler不能在測試集上重新fit否則會造成信息泄漏評估結(jié)果虛高。4.3 滑動窗口與多步預(yù)測構(gòu)造滑動窗口是時間序列預(yù)測里最基礎(chǔ)的操作但細節(jié)里藏著不少坑。假設(shè)我們用前96個點預(yù)測后24個點那訓練樣本就是在一個完整的時間序列上以一定step滑動截取。比如step設(shè)24每天可以截出約4組樣本0到95預(yù)測96到11924到119預(yù)測120到143依此類推。這里有一個很關(guān)鍵的選擇輸入窗口和預(yù)測窗口之間是否要有g(shù)ap。有些場景里我們可以在輸入末尾和預(yù)測開始之間留一段間隔比如輸入96個點跳過6個點再預(yù)測未來24個點。這個gap的存在讓模型不能只依賴最近幾個點的慣性外推必須真正學到模式泛化能力更好些。但如果gap太大模型會丟掉近期的有效信息誤差反而不降。我在實測里用gap等于1到2個采樣點作為折中效果比完全無縫預(yù)測略好。窗口構(gòu)造完成后數(shù)據(jù)要整理成模型需要的形狀。PatchTST的多變量輸入通常是 (batch, seq_len, num_channels) 這樣的三維張量。注意通道維放在最后和圖像任務(wù)的 (batch, channels, height, width) 不一樣剛開始容易搞混。如果使用PyTorch建議在DataSet里直接返回這個形狀的tensor避免在訓練循環(huán)里頻繁做permute。5. Python代碼實現(xiàn)與核心邏輯解讀這一節(jié)我把項目代碼的主干邏輯拆出來講。不貼全部源碼但關(guān)鍵部分都會給到并且解釋每一段在做什么、為什么要這么做。5.1 數(shù)據(jù)加載與窗口生成數(shù)據(jù)加載部分我習慣自己寫DataSet類而不是直接用現(xiàn)成的TSDataset庫因為綜合能源數(shù)據(jù)通常要融合好幾個csv自定義更靈活。class EnergyDataset(torch.utils.data.Dataset): def __init__(self, df, target_cols, feature_cols, input_len96, pred_len24, step24): self.df df.reset_index(dropTrue) self.target_cols target_cols self.feature_cols feature_cols self.input_len input_len self.pred_len pred_len self.step step self.inputs, self.targets self._build_samples() def _build_samples(self): data self.df[self.target_cols self.feature_cols].values.astype(float32) total len(data) max_start total - self.input_len - self.pred_len samples_in [] samples_out [] for start in range(0, max_start, self.step): end_in start self.input_len end_out end_in self.pred_len x data[start:end_in] y data[end_in:end_out, :len(self.target_cols)] samples_in.append(x) samples_out.append(y) return ( torch.from_numpy(np.stack(samples_in)), torch.from_numpy(np.stack(samples_out)) ) def __len__(self): return len(self.inputs) def __getitem__(self, idx): return self.inputs[idx], self.targets[idx]這里有幾個設(shè)計點。第一切窗口時只保留完整覆蓋輸入和預(yù)測長度的樣本末尾不足的部分直接丟掉避免padding帶來的邊界噪聲。第二step控制窗口滑動步長數(shù)據(jù)量不夠的時候step小一點可以增加樣本數(shù)但要注意相鄰樣本高度重疊會導致訓練集和驗證集之間的數(shù)據(jù)泄漏風險我的做法是把時間靠前的80%作為訓練集時間靠后的20%作為驗證集不做隨機劃分。時間序列數(shù)據(jù)不能用隨機劃分這一點非常重要隨機劃分會讓驗證集提前看到訓練集未來的信息。5.2 PatchTST模型實現(xiàn)模型主體我基于官方思路重新實現(xiàn)便于修改和加自己的預(yù)測頭。核心分兩塊patch嵌入和Transformer編碼器。import torch import torch.nn as nn class PatchEmbedding(nn.Module): def __init__(self, patch_len, stride, d_model, dropout): super().__init__() self.patch_len patch_len self.stride stride self.padding stride if patch_len stride else 0 self.linear nn.Linear(patch_len, d_model) self.pos_embed nn.Parameter(torch.randn(1, 100, d_model) * 0.02) self.dropout nn.Dropout(dropout) def forward(self, x): # x: (batch, seq_len, num_channels) batch, seq_len, num_channels x.shape x x.permute(0, 2, 1) # (batch, num_channels, seq_len) x nn.functional.unfold( x.unsqueeze(-1), kernel_size(self.patch_len, 1), dilation1, padding(self.padding, 0), stride(self.stride, 1) ) # 整理形狀 x x.permute(0, 2, 1).reshape(batch * num_channels, -1, self.patch_len) x self.linear(x) # (batch*num_channels, num_patches, d_model) x x self.pos_embed[:, :x.size(1), :] x self.dropout(x) x x.reshape(batch, num_channels, -1, self.linear.out_features) return x這段代碼里最容易出問題的是unfold的padding設(shè)置。設(shè)置padding為stride有一個作用保證序列末尾也能形成一個patch讓尾部的信息不會直接被丟棄。雖然末位patch是部分填充的但線性層在訓練過程中可以學到忽略無效位置這比直接截斷要好。Transformer編碼器部分直接用PyTorch的TransformerEncoderLayer堆疊即可。注意這里要讓num_channels個通道的token進入同一個Transformer因此把通道維展開到序列維。也就是說注意力可以跨通道和跨patch同時發(fā)生這也是為什么模型在多變量場景下依然能捕捉到通道間交互。預(yù)測頭部分我需要把Transformer輸出的特征重新整理成每個通道獨立的多步輸出。因為輸出是直接多步預(yù)測所以這里就是一個Linear層把特征映射到pred_len維度。我在實際實現(xiàn)時每個通道的輸出投影是獨立的Linear層使用nn.ModuleList包裹避免所有通道共享同一套投影權(quán)重。5.3 貝葉斯優(yōu)化主流程集成貝葉斯優(yōu)化的objective函數(shù)內(nèi)部最關(guān)鍵的是要讓每次試驗在相同條件下公平競爭。我在試驗里固定了隨機種子包括PyTorch、NumPy和Python內(nèi)置random否則有的試驗跑得好純粹是運氣好貝葉斯優(yōu)化會被假信號帶偏。另一個要注意的是驗證集的選擇。我建議固定一個時間窗口作為驗證集比如最近20%的時間段。不要在每次試驗里重新劃分驗證集否則不同試驗之間的比較基準不一致。如果數(shù)據(jù)量夠大也可以抽一個固定的時間長段做驗證但同樣不能隨機抽取。objective函數(shù)的執(zhí)行流程通常是用傳入的超參構(gòu)建模型初始化數(shù)據(jù)加載器進入訓練循環(huán)每個epoch結(jié)束后在固定驗證集上計算MAE或RMSE記錄歷史最優(yōu)模型返回驗證集最優(yōu)誤差。整個過程相當于一次小型訓練任務(wù)。為了降低單次試驗耗時可以限制epoch數(shù)到50左右配合早停策略。早停的patience建議設(shè)5到10個epoch太小容易在曲線還沒收斂時就提前停止太大又浪費時間。5.4 訓練與評估閉環(huán)訓練循環(huán)本身沒有太多特別之處但有幾個細節(jié)會影響最終效果。首先是優(yōu)化器的選擇我用的是AdamW而不是普通Adam。AdamW把權(quán)重衰減從梯度更新里去耦對Transformer這類大模型的正則化效果更可控不容易出現(xiàn)普通Adam里weight_decay和自適應(yīng)學習率相互干擾的問題。學習率調(diào)度方面我推薦先用one cycle或余弦退火。PatchTST訓練早期學習率太大會導致loss爆炸太小會導致收斂太慢one cycle策略在前幾個epoch快速提升學習率探索然后平滑下降收斂實測比固定學習率效果穩(wěn)定。損失函數(shù)用的是MSE因為MSE對大誤差的懲罰更強烈會讓模型更關(guān)注尖峰負荷的擬合。如果后續(xù)覺得預(yù)測結(jié)果太平均、尖峰不夠尖可以換成MAE或者Huber損失做對比甚至組合起來用。評估指標方面除了RMSE我同時會算MAE、MAPE和R2因為RMSE對異常值敏感單看它容易被個別點的極端誤差誤導。MAPE對接近零的負荷值會失真所以計算時需要對真實值設(shè)置一個下限閾值避免除以接近0的數(shù)。6. 訓練過程中踩過的坑與排查心得這個項目前前后后跑了很久踩的坑不少專門整理一節(jié)出來按問題類型分類寫清楚。6.1 loss不降或突然變成NaNloss不降是最常見的問題。我的排查順序是先看數(shù)據(jù)里有沒有NaN或inf這一步可以通過一行代碼檢查然后看歸一化是否完成如果原始值在幾百上千的量綱直接送進模型Transformer的注意力softmax很容易溢出再看學習率PatchTST對學習率比LSTM更敏感如果初始學習率超過1e-2訓練大概率直接飛掉建議先固定到1e-3跑一遍基線再讓貝葉斯優(yōu)化去搜索。loss突然變成NaN大概率有兩個原因。一個是梯度爆炸解決辦法是給模型加梯度裁剪max_norm設(shè)1.0比較穩(wěn)妥。另一個原因是學習率調(diào)度策略里出現(xiàn)了過大的尖峰比如one cycle策略的峰值學習率設(shè)太高可以在實現(xiàn)時限制峰值不超過最大學習率的10倍。還有一個容易被忽略的原因輸入數(shù)據(jù)里某條通道的標準差為0。比如某個傳感器故障連續(xù)幾天的值恒等于一個常數(shù)標準化之后變成NaN模型訓練自然就崩了。6.2 Patch尺寸與序列長度約束Patch化和序列長度之間的約束關(guān)系我在調(diào)整參數(shù)時經(jīng)常被繞進去。給定序列長度Lpatch長度Pstride Spatch數(shù)量N可以估算為 (L - P) / S 1如果加上末尾填充大約是 (L - P) / S 2。這個N不能太小否則輸入到Transformer的token數(shù)量太少注意力機制的優(yōu)勢發(fā)揮不出來。經(jīng)驗上N至少應(yīng)該在5到8個以上。如果序列長度是96我一般建議patch_len從8到16之間選stride從4到8之間選。patch數(shù)量大約在11到23個之間這個量級對Transformer來說很舒適。如果序列長度更長比如168或336可以把patch_len適當調(diào)大到12到24。還有一個需要注意的地方是patch_len必須能被輸入序列長度大致整除否則末尾padding過多有效信息比例下降。如果發(fā)現(xiàn)未填充patch數(shù)量太少優(yōu)先調(diào)大stride而不是調(diào)大patch_len這樣能在保持patch粒度的情況下盡量維持語義完整度。6.3 貝葉斯優(yōu)化的收斂與過擬合問題貝葉斯優(yōu)化跑著跑著會出現(xiàn)兩種讓人困惑的現(xiàn)象。第一種是搜索后期目標值不再下降反而在某個區(qū)間震蕩。這通常是搜索空間里某些參數(shù)組合方差太大導致的比如batch_size從32跳到128模型表現(xiàn)波動明顯代理模型很難找到穩(wěn)定的優(yōu)化方向。解決辦法是把參數(shù)限制到更窄的合理范圍或者把離散參數(shù)的候選值減少到2到3個。第二種現(xiàn)象是貝葉斯優(yōu)化找到的參數(shù)在訓練集上效果很好但驗證集效果變差典型的過擬合。這里要區(qū)分是模型過擬合還是調(diào)參過擬合。模型過擬合表現(xiàn)為訓練loss遠低于驗證loss需要加大dropout或weight_decay或者減小d_model。調(diào)參過擬合則是試驗次數(shù)過多代理模型在驗證集上反復(fù)測試間接把驗證集信息泄露給了搜索過程。解決辦法是盡量保證驗證集只有一個固定窗口不要頻繁更換同時trials數(shù)控制在50到80次以內(nèi)超過之后增益會明顯遞減。我還發(fā)現(xiàn)一個隱藏的坑早停的patience在貝葉斯優(yōu)化里會影響公平性。某個trial訓練特別久模型有機會充分收斂另一個trial因為patience設(shè)置不同提前結(jié)束了。這時候比較兩者的最終loss就不公平。我的做法是每次試驗固定使用相同的早停配置并且統(tǒng)一epoch上限。7. 跑通之后的效率觀察與擴展思路模型跑通之后我對整體性能和擴展方向做了整理這里一并說說。7.1 整體效果與時間開銷最終固定下來的配置大致是d_model128n_heads8e_layers3patch_len16stride8dropout0.1學習率約2e-3batch_size64。在這個配置下電負荷、熱負荷、冷負荷三通道24步預(yù)測的RMSE對比原始LSTM基線大約下降了15%到20%。其中熱負荷提升最明顯因為熱慣性帶來的長程依賴恰好是PatchTST注意力機制的強項。冷負荷提升相對小因為冷負荷受瞬時輻射影響較大而氣象預(yù)報本身的誤差會限制預(yù)測上限。訓練時間方面序列長度96、patch數(shù)量13左右、三通道展開后總共39個token一個epoch在中等規(guī)模數(shù)據(jù)集上只需要幾秒50個epoch加早停大概在3到5分鐘完成一次試驗。60次貝葉斯優(yōu)化試驗算上剪枝節(jié)省的時間總耗時在2到3個小時左右。這個成本對應(yīng)用來說可以接受比起手動調(diào)參一遍遍試要高效得多。7.2 還可以怎么往下走如果想把這套方案繼續(xù)深化有幾個方向我個人覺得很有價值。第一個是把模型輸出從確定性預(yù)測改成概率預(yù)測具體做法是在預(yù)測頭加一個分布層輸出均值和方差損失函數(shù)換成負對數(shù)似然。這樣得到的預(yù)測不僅有數(shù)值還有置信區(qū)間對調(diào)度決策來說參考意義更大。第二個方向是引入更細的外生變量比如電價、設(shè)備啟停狀態(tài)、天氣預(yù)報的不確定性。綜合能源系統(tǒng)調(diào)度往往和現(xiàn)貨市場電價強相關(guān)把電價作為輸入通道加入模型可以進一步挖掘負荷對價格信號的響應(yīng)。第三是做多任務(wù)學習的變體不只預(yù)測負荷還同時預(yù)測光伏出力和儲能狀態(tài)。因為這些變量的變化規(guī)律和負荷有耦合聯(lián)合建??赡軒眍~外的泛化提升。最后還想提一個工程上的小技巧模型訓練完保存 checkpoint 時記得把scaler也一起保存。部署上線時新數(shù)據(jù)的歸一化必須用訓練時保存的scaler否則線上輸入分布和訓練分布不一致預(yù)測效果會莫名其妙地變差。我在項目里因為這個問題上線后翻過一次車排查了半天才發(fā)現(xiàn)是新寫的預(yù)處理腳本沒有復(fù)用原有scaler造成的。踩過那次坑之后我把scaler納入模型artifacts統(tǒng)一管理之后的迭代就再沒出過類似的問題。