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