化BP神經(jīng)網(wǎng)絡(luò):時(shí)間序列預(yù)測(cè)超參數(shù)調(diào)優(yōu)實(shí)戰(zhàn))
最近在時(shí)間序列預(yù)測(cè)的項(xiàng)目里折騰了個(gè)貝葉斯優(yōu)化的BP神經(jīng)網(wǎng)絡(luò)模型記錄點(diǎn)實(shí)戰(zhàn)經(jīng)驗(yàn)。直接上代碼干貨邊寫邊聊踩過的坑。先說背景。項(xiàng)目是某跨平臺(tái)系統(tǒng)的負(fù)荷預(yù)測(cè)模塊輸入是歷史負(fù)荷、溫度、濕度、日期類型等特征輸出是未來24小時(shí)的逐時(shí)預(yù)測(cè)值。前期用普通BP神經(jīng)網(wǎng)絡(luò)跑了一版效果能用但一到節(jié)假日或者天氣劇烈波動(dòng)的時(shí)候預(yù)測(cè)偏差就會(huì)飚上去。后來換了個(gè)思路在BP的基礎(chǔ)上引入貝葉斯優(yōu)化來做超參數(shù)搜索替換掉之前手調(diào)參數(shù)的方式實(shí)測(cè)下來整體精度提升明顯驗(yàn)證集上的平均絕對(duì)百分比誤差大約下降了18%左右。這篇文章就把整個(gè)折騰過程記錄下來從核心思路到代碼實(shí)現(xiàn)再到排查過的坑適合正在做時(shí)間序列預(yù)測(cè)、想優(yōu)化神經(jīng)網(wǎng)絡(luò)超參數(shù)但又不想上太復(fù)雜框架的開發(fā)者參考。1. 內(nèi)容整體設(shè)計(jì)與思路拆解1.1 為什么偏偏是貝葉斯優(yōu)化配BP神經(jīng)網(wǎng)絡(luò)先說BP神經(jīng)網(wǎng)絡(luò)。它在時(shí)間序列預(yù)測(cè)里算是老熟人結(jié)構(gòu)簡(jiǎn)單、易于部署、對(duì)數(shù)據(jù)量要求不那么苛刻尤其適合中小規(guī)模樣本的場(chǎng)景。但BP網(wǎng)絡(luò)有個(gè)讓人頭疼的問題——對(duì)超參數(shù)極其敏感。隱層神經(jīng)元數(shù)量、學(xué)習(xí)率、批次大小、激活函數(shù)、正則化系數(shù)這些東西稍微變一變預(yù)測(cè)結(jié)果就可能天差地別。以前的做法是什么網(wǎng)格搜索加經(jīng)驗(yàn)試錯(cuò)。比如學(xué)習(xí)率從0.1、0.01、0.001挨個(gè)試隱層神經(jīng)元從32、64、128這么輪著測(cè)。聽起來很合理但實(shí)際操作下來會(huì)發(fā)現(xiàn)問題網(wǎng)格搜索在參數(shù)維度升高以后搜索次數(shù)是指數(shù)級(jí)膨脹的。假設(shè)我們有5個(gè)超參數(shù)每個(gè)參數(shù)取10個(gè)候選值那就是10的5次方10萬次訓(xùn)練實(shí)驗(yàn)。哪怕每次訓(xùn)練只要30秒也是不間斷跑上一個(gè)月的時(shí)間。而隨機(jī)搜索雖然能減少一些次數(shù)但它完全是盲人摸象試到哪個(gè)算哪個(gè)缺少方向性。貝葉斯優(yōu)化的核心價(jià)值在于它會(huì)利用已有的評(píng)估結(jié)果來建?!俺瑓?shù)→目標(biāo)函數(shù)”的概率模型然后根據(jù)這個(gè)模型去計(jì)算下一步最值得嘗試的參數(shù)組合。簡(jiǎn)單說它不是盲目亂試而是像人一樣“總結(jié)經(jīng)驗(yàn)、尋找方向”。配合BP網(wǎng)絡(luò)之后可以直接在驗(yàn)證集上定義目標(biāo)函數(shù)讓優(yōu)化算法自動(dòng)找到一組泛化性能最好的超參數(shù)。從實(shí)際效果來看這套組合特別適合那種“單次訓(xùn)練成本中等、需要反復(fù)試驗(yàn)調(diào)參”的場(chǎng)景。如果你的項(xiàng)目里已經(jīng)有了一套可運(yùn)行的BP預(yù)測(cè)腳本又不想為了調(diào)參去引入復(fù)雜的深度學(xué)習(xí)框架或者AutoML平臺(tái)貝葉斯優(yōu)化是一個(gè)性價(jià)比很高的升級(jí)方案。1.2 方案選型深入理解參數(shù)空間與目標(biāo)函數(shù)的定義方式貝葉斯優(yōu)化聽上去高深但它的數(shù)學(xué)含義其實(shí)用一句話就能概括清楚在參數(shù)空間中尋找使目標(biāo)函數(shù)值最優(yōu)的點(diǎn)每次迭代都利用之前的信息更新對(duì)目標(biāo)函數(shù)的估計(jì)再做下一次采樣。實(shí)際操作中需要定義兩件事參數(shù)空間和目標(biāo)函數(shù)。參數(shù)空間指的是我們要優(yōu)化的超參數(shù)各自在什么范圍內(nèi)取值。這一步看起來簡(jiǎn)單但坑不少。比如學(xué)習(xí)率如果設(shè)成[1e-4, 1]優(yōu)化器很容易在早期嘗試較大學(xué)習(xí)率導(dǎo)致訓(xùn)練發(fā)散浪費(fèi)大量時(shí)間。而如果把范圍收窄到[1e-4, 1e-2]搜索空間更合理收斂速度和解的質(zhì)量都會(huì)明顯改善。所以參數(shù)范圍的設(shè)定不僅僅是一個(gè)“給個(gè)邊界”的操作更要結(jié)合模型和數(shù)據(jù)特點(diǎn)做預(yù)判。目標(biāo)函數(shù)通常定義為“在訓(xùn)練集上訓(xùn)練模型在驗(yàn)證集上計(jì)算誤差”。誤差指標(biāo)一般選MAPE或者RMSE。時(shí)間序列預(yù)測(cè)場(chǎng)景里我們更關(guān)注相對(duì)偏差的大小所以MAPE更常用。但要注意MAPE有個(gè)毛病——當(dāng)真實(shí)值接近0時(shí)誤差會(huì)變得異常大。如果目標(biāo)變量的值經(jīng)常出現(xiàn)接近0的情況建議改用SMAPE或者帶分母保護(hù)的自定義指標(biāo)。我當(dāng)時(shí)的目標(biāo)函數(shù)設(shè)計(jì)是這樣的每次迭代取出參數(shù)組合用訓(xùn)練集訓(xùn)練BP網(wǎng)絡(luò)訓(xùn)練完畢后在驗(yàn)證集上做多步預(yù)測(cè)返回SMAPE作為目標(biāo)函數(shù)值。這個(gè)過程本身比較耗時(shí)但貝葉斯優(yōu)化可以接受這種成本因?yàn)樗⒉灰髱浊先f次的評(píng)估通常幾十次到上百次迭代就能找到不錯(cuò)的參數(shù)區(qū)域。1.3 相比傳統(tǒng)調(diào)參方式這套方案的核心優(yōu)勢(shì)傳統(tǒng)網(wǎng)格搜索最怕的是參數(shù)維度爆炸。貝葉斯優(yōu)化在我的測(cè)試中通常只需要網(wǎng)格搜索大約15%到20%的計(jì)算量就能達(dá)到相近甚至更好的結(jié)果。這并不是說貝葉斯優(yōu)化一定比網(wǎng)格搜索更“聰明”而是它把每一次評(píng)估的信息都用起來了不像網(wǎng)格搜索那樣孤立地看待每個(gè)實(shí)驗(yàn)。隨機(jī)搜索雖然比網(wǎng)格搜索更高效但它缺少“利用已有信息”的過程。貝葉斯優(yōu)化通過高斯過程代理模型Gaussian Process刻畫目標(biāo)函數(shù)的后驗(yàn)分布在選擇下一個(gè)采樣點(diǎn)時(shí)能夠自動(dòng)平衡“開發(fā)”在已知表現(xiàn)好的區(qū)域繼續(xù)深入搜索和“探索”去尚未嘗試的區(qū)域看看有沒有更好的可能。這種機(jī)制保證了它在有限預(yù)算下能找到更優(yōu)的參數(shù)組合。而且貝葉斯優(yōu)化的工程實(shí)現(xiàn)并不復(fù)雜。直接使用開源的優(yōu)化庫比如 scikit-optimize 或 Optuna都可以很快上手。我最終選擇了 scikit-optimize因?yàn)樗慕涌诤?jiǎn)單和NumPy、pandas等數(shù)據(jù)處理庫配合流暢不需要額外引入復(fù)雜的依賴。2. 核心細(xì)節(jié)解析與實(shí)操要點(diǎn)2.1 數(shù)據(jù)預(yù)處理中的時(shí)節(jié)性特征編碼與平穩(wěn)化處理時(shí)間序列預(yù)測(cè)任務(wù)里數(shù)據(jù)預(yù)處理的重要性不亞于模型本身。我在這部分花了不少時(shí)間踩過的坑比模型調(diào)參還多。先說要預(yù)測(cè)的目標(biāo)變量本身。負(fù)荷序列通常帶有明顯的日周期性、周周期性還有節(jié)假日效應(yīng)。如果不做任何處理直接把原始值丟給神經(jīng)網(wǎng)絡(luò)模型很容易被劇烈的波動(dòng)帶偏。我用的方案是先做差分處理。差分的目的在于把非平穩(wěn)序列轉(zhuǎn)化為相對(duì)平穩(wěn)的序列讓神經(jīng)網(wǎng)絡(luò)更容易學(xué)習(xí)其中的模式。比如當(dāng)前小時(shí)與前一小時(shí)負(fù)荷的差值如果這個(gè)差值是近似平穩(wěn)的那訓(xùn)練難度就會(huì)明顯下降。預(yù)測(cè)完成后再把差分值做逆變換還原成真實(shí)負(fù)荷值。但差分會(huì)帶來一個(gè)問題——誤差會(huì)累積。多步預(yù)測(cè)時(shí)前一步的預(yù)測(cè)誤差會(huì)傳遞到后面的預(yù)測(cè)結(jié)果里。所以差分階數(shù)不宜過大一階差分通常就夠了。特征方面的處理有幾個(gè)關(guān)鍵動(dòng)作時(shí)間特征提取小時(shí)、星期幾、是否工作日、是否節(jié)假日等。這些離散特征要合理編碼。比如小時(shí)可以同時(shí)保留數(shù)值特征0到23和周期特征sin和cos變換避免模型把0點(diǎn)和23點(diǎn)誤解為相差很遠(yuǎn)。溫度濕度特征這類外部變量需要做歸一化否則神經(jīng)網(wǎng)絡(luò)訓(xùn)練時(shí)數(shù)值范圍差異過大會(huì)導(dǎo)致收斂變慢。我用的是MinMaxScaler把所有特征都縮放到0到1之間。滯后特征取前1小時(shí)、前24小時(shí)、前48小時(shí)的負(fù)荷值作為特征。滯后特征對(duì)預(yù)測(cè)精度的提升非常明顯尤其是24小時(shí)前和48小時(shí)前的負(fù)荷可以有效刻畫日周期和周周期的影響。數(shù)據(jù)劃分也要講究。我在訓(xùn)練集、驗(yàn)證集、測(cè)試集劃分時(shí)強(qiáng)制按時(shí)間順序切分絕不隨機(jī)打亂。時(shí)間序列數(shù)據(jù)一旦隨機(jī)打亂模型就偷看到了未來的信息驗(yàn)證結(jié)果會(huì)虛假好看真正上線后表現(xiàn)一塌糊涂。2.2 目標(biāo)函數(shù)和交叉驗(yàn)證設(shè)計(jì)的注意事項(xiàng)貝葉斯優(yōu)化的目標(biāo)函數(shù)是整個(gè)調(diào)參過程的核心。如果目標(biāo)函數(shù)設(shè)計(jì)得有問題那么再先進(jìn)的優(yōu)化算法也沒有用。我第一版的目標(biāo)函數(shù)很簡(jiǎn)單訓(xùn)練BP網(wǎng)絡(luò)返回驗(yàn)證集SMAPE。但很快就發(fā)現(xiàn)這個(gè)做法有一個(gè)隱患——驗(yàn)證集波動(dòng)太大。由于時(shí)間序列數(shù)據(jù)內(nèi)部存在強(qiáng)相關(guān)性驗(yàn)證集不同時(shí)間段的誤差會(huì)有明顯差異單次評(píng)估的結(jié)果噪音很高。貝葉斯優(yōu)化會(huì)把這部分噪音當(dāng)成真實(shí)的性能差異導(dǎo)致選擇到“碰巧在某個(gè)時(shí)間段表現(xiàn)好”的參數(shù)而不是真正泛化能力強(qiáng)的參數(shù)。解決辦法是采用滑動(dòng)窗口式的時(shí)間序列交叉驗(yàn)證。具體做法是把訓(xùn)練數(shù)據(jù)按時(shí)間分成多段每次用其中一段做驗(yàn)證其他段做訓(xùn)練最后取各段驗(yàn)證誤差的平均值。這樣做能顯著降低目標(biāo)函數(shù)評(píng)估的方差。但滑動(dòng)窗口交叉驗(yàn)證有個(gè)代價(jià)——訓(xùn)練次數(shù)成倍增加。比如分5折一次參數(shù)評(píng)估就要訓(xùn)練5次模型。我一開始覺得太耗時(shí)間后來把BP網(wǎng)絡(luò)的最大迭代次數(shù)限定到500輪啟用提前停止early stopping每次訓(xùn)練大概15到20秒5折就是100秒左右。貝葉斯優(yōu)化迭代100次的話總時(shí)長(zhǎng)大約3小時(shí)還能接受。還有一點(diǎn)要提醒的是目標(biāo)函數(shù)的返回值方向。貝葉斯優(yōu)化的庫通常默認(rèn)優(yōu)化目標(biāo)越小越好所以我們要把誤差指標(biāo)直接返回不要畫蛇添足地取負(fù)值。2.3 參數(shù)空間的具體設(shè)置與實(shí)際調(diào)優(yōu)效果對(duì)比參數(shù)空間是貝葉斯優(yōu)化要搜索的候選集合。我在項(xiàng)目里用了6個(gè)超參數(shù)具體如下參數(shù)名稱取值范圍說明隱層神經(jīng)元數(shù)量16~128兩層隱層每層數(shù)量取相同值學(xué)習(xí)率1e-4 ~ 1e-2對(duì)數(shù)均勻分布采樣批大小16~128控制每次參數(shù)更新使用的樣本數(shù)Dropout比率0~0.5防止過擬合L2正則化系數(shù)1e-6 ~ 1e-2對(duì)數(shù)均勻分布采樣訓(xùn)練輪數(shù)上限200~800配合提前停止使用這里特別說明兩個(gè)細(xì)節(jié)。第一學(xué)習(xí)率和L2正則化系數(shù)都采用對(duì)數(shù)尺度因?yàn)檫@類參數(shù)通??缭蕉鄠€(gè)數(shù)量級(jí)線性均勻采樣會(huì)讓小值區(qū)域被嚴(yán)重壓縮大值區(qū)域占比過大搜索效果變差。第二Dropout取值范圍上限設(shè)為0.5因?yàn)槌^0.5以后BP這種基礎(chǔ)網(wǎng)絡(luò)往往會(huì)出現(xiàn)欠擬合收益不大。我做了幾組對(duì)比實(shí)驗(yàn)基線BP手調(diào)參數(shù)隱層64學(xué)習(xí)率0.005批大小64不加Dropout。驗(yàn)證集SMAPE約5.8%。隨機(jī)搜索50次驗(yàn)證集SMAPE約5.2%。貝葉斯優(yōu)化100次驗(yàn)證集SMAPE約4.9%。從結(jié)果看貝葉斯優(yōu)化相比手調(diào)參數(shù)提升約0.9個(gè)百分點(diǎn)對(duì)比隨機(jī)搜索也有可觀的增益。更重要的收獲是貝葉斯優(yōu)化找到的最優(yōu)參數(shù)組合非常“反直覺”——它選擇了中等偏小的隱層48Dropout設(shè)為0.21L2正則系數(shù)選擇了偏小的1e-5學(xué)習(xí)率則是0.003。這些值如果靠人工去試大概率不會(huì)剛好落在這些位置上。3. 實(shí)操過程與核心環(huán)節(jié)實(shí)現(xiàn)3.1 從零搭建BP神經(jīng)網(wǎng)絡(luò)預(yù)測(cè)模型的完整步驟我用的開發(fā)環(huán)境是Python 3.9深度學(xué)習(xí)框架選擇PyTorch。PyTorch的靈活性在這種自定義訓(xùn)練邏輯比較多的場(chǎng)景里很占優(yōu)勢(shì)。第一步是準(zhǔn)備數(shù)據(jù)。把原始數(shù)據(jù)處理成特征矩陣X和目標(biāo)向量y所有特征歸一化到[0,1]樣本按時(shí)間順序排列。第二步定義BP網(wǎng)絡(luò)結(jié)構(gòu)包括兩個(gè)隱層和輸出層激活函數(shù)默認(rèn)用ReLU加BatchNorm來穩(wěn)定訓(xùn)練。第三步定義訓(xùn)練邏輯包括損失函數(shù)、優(yōu)化器和提前停止邏輯。第四步把訓(xùn)練邏輯封裝成目標(biāo)函數(shù)接入貝葉斯優(yōu)化模塊。先看BP網(wǎng)絡(luò)本身的定義import torch import torch.nn as nn class BPNET(nn.Module): def __init__(self, input_dim, hidden_dim, output_dim, dropout): super(BPNET, self).__init__() self.model nn.Sequential( nn.Linear(input_dim, hidden_dim), nn.ReLU(), nn.Dropout(dropout), nn.Linear(hidden_dim, hidden_dim // 2), nn.ReLU(), nn.Dropout(dropout), nn.Linear(hidden_dim // 2, output_dim) ) def forward(self, x): return self.model(x)輸入維度取決于特征數(shù)量輸出維度為待預(yù)測(cè)的時(shí)長(zhǎng)步數(shù)。我這里是單步滾動(dòng)多步預(yù)測(cè)所以輸出維度設(shè)成1預(yù)測(cè)完一步以后把它當(dāng)作歷史值繼續(xù)預(yù)測(cè)下一步。訓(xùn)練部分的關(guān)鍵代碼如下注意提前停止的邏輯保證了訓(xùn)練不會(huì)一跑到底節(jié)省了大量無效迭代def train_model(model, train_loader, val_loader, lr, weight_decay, max_epochs): optimizer torch.optim.Adam(model.parameters(), lrlr, weight_decayweight_decay) scheduler torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, modemin, patience10) criterion torch.nn.MSELoss() best_val_loss float(inf) patience_counter 0 best_state None for epoch in range(max_epochs): model.train() for x_batch, y_batch in train_loader: optimizer.zero_grad() pred model(x_batch) loss criterion(pred, y_batch) loss.backward() optimizer.step() 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() val_loss / len(val_loader) if val_loss best_val_loss: best_val_loss val_loss patience_counter 0 best_state {k: v.clone() for k, v in model.state_dict().items()} else: patience_counter 1 scheduler.step(val_loss) if patience_counter 15: break model.load_state_dict(best_state) return model這里要特別提一下ReduceLROnPlateau調(diào)度器。它能在驗(yàn)證損失長(zhǎng)時(shí)間不下降時(shí)自動(dòng)減小學(xué)習(xí)率讓模型在接近局部最優(yōu)的區(qū)域做更精細(xì)的“微調(diào)”。原本我擔(dān)心這種調(diào)度器會(huì)引入額外超參數(shù)增加調(diào)參難度實(shí)際用下來發(fā)現(xiàn)它的閾值和耐心值設(shè)置得稍微寬松一點(diǎn)效果就很穩(wěn)定。3.2 貝葉斯優(yōu)化模塊的完整代碼實(shí)現(xiàn)貝葉斯優(yōu)化我用的是scikit-optimize庫里的BayesSearchCV風(fēng)格接口但為了更好地控制訓(xùn)練邏輯我改成了自己寫循環(huán)調(diào)用。from skopt import Optimizer from skopt.space import Real, Integer param_space [ Integer(16, 128, namehidden_dim), Real(1e-4, 1e-2, priorlog-uniform, namelr), Integer(16, 128, namebatch_size), Real(0.0, 0.5, namedropout), Real(1e-6, 1e-2, priorlog-uniform, nameweight_decay), Integer(200, 800, namemax_epochs) ] optimizer Optimizer(param_space, base_estimatorGP, n_initial_points10) for i in range(100): params optimizer.ask() hidden_dim params[0] lr params[1] batch_size params[2] dropout params[3] weight_decay params[4] max_epochs params[5] smape evaluate_params(hidden_dim, lr, batch_size, dropout, weight_decay, max_epochs) optimizer.tell(params, smape) print(f[{i1}/100] SMAPE: {smape:.4f}, params: {params})這里有兩個(gè)關(guān)鍵點(diǎn)要重點(diǎn)解釋。第一個(gè)是n_initial_points。這個(gè)參數(shù)控制優(yōu)化的“冷啟動(dòng)”階段即先用隨機(jī)采樣的方式生成若干個(gè)初始點(diǎn)等積累了一定數(shù)量的評(píng)估結(jié)果以后再開始用高斯過程模型指導(dǎo)搜索方向。初始點(diǎn)數(shù)量太少高斯過程模型對(duì)目標(biāo)函數(shù)的估計(jì)會(huì)很粗糙太多又會(huì)浪費(fèi)不必要的計(jì)算。我試過5、10、20三個(gè)值10是兼顧效果和效率的平衡點(diǎn)。第二個(gè)是Optimizer.ask和Optimizer.tell的配合。ask是獲取下一步要嘗試的參數(shù)組合tell是告訴優(yōu)化器這次評(píng)估的結(jié)果。這種交替調(diào)用模式非常靈活你可以在兩次ask之間插入任何自定義邏輯比如加載數(shù)據(jù)、訓(xùn)練模型、計(jì)算指標(biāo)完全不受庫的限制。evaluate_params是核心環(huán)節(jié)它把數(shù)據(jù)加載、模型訓(xùn)練、驗(yàn)證評(píng)估全部串起來。實(shí)際實(shí)現(xiàn)時(shí)可以用交叉驗(yàn)證來降低評(píng)估方差def evaluate_params(hidden_dim, lr, batch_size, dropout, weight_decay, max_epochs): smapes [] for train_idx, val_idx in cv_splits: train_data, val_data load_data(train_idx, val_idx) model BPNET(input_dim, hidden_dim, 1, dropout) model train_model(model, train_data, val_data, lr, weight_decay, max_epochs) preds model.predict(val_data) smapes.append(calculate_smape(val_data[actual], preds)) return np.mean(smapes)3.3 實(shí)際訓(xùn)練過程中的監(jiān)控與資源管理訓(xùn)練過程中我比較看重三個(gè)指標(biāo)損失曲線、驗(yàn)證集誤差、訓(xùn)練時(shí)長(zhǎng)。因?yàn)槊恳徊絽?shù)評(píng)估都要完整訓(xùn)練模型如果某個(gè)參數(shù)組合導(dǎo)致訓(xùn)練不收斂或者資源消耗異常高需要及時(shí)發(fā)現(xiàn)并跳過。我加了一個(gè)簡(jiǎn)單粗暴的保護(hù)機(jī)制單次訓(xùn)練超過10分鐘就強(qiáng)制中斷標(biāo)記該參數(shù)組合為高耗時(shí)的“壞點(diǎn)”。貝葉斯優(yōu)化允許我們?cè)谀繕?biāo)函數(shù)里返回一個(gè)很大的懲罰值這樣優(yōu)化器就自動(dòng)避開這些參數(shù)區(qū)域。實(shí)際跑下來絕大部分參數(shù)組合的單次訓(xùn)練時(shí)長(zhǎng)集中在1到3分鐘。出現(xiàn)超過10分鐘的基本都是學(xué)習(xí)率設(shè)得偏大接近0.01而且沒有觸發(fā)提前停止的情況說明這種保護(hù)機(jī)制確實(shí)有必要。監(jiān)控方面沒有用什么高大上的工具就是直接打印日志外加把每次評(píng)估結(jié)果追加寫入CSV文件。訓(xùn)練完成以后分析CSV里的記錄可以很直觀地看到參數(shù)探索軌跡哪些區(qū)域被反復(fù)搜索哪些區(qū)域被放棄對(duì)理解貝葉斯優(yōu)化的決策過程很有幫助。代碼實(shí)現(xiàn)中還有一些細(xì)節(jié)值得注意每次訓(xùn)練之前要把PyTorch的隨機(jī)種子固定住否則相同參數(shù)組合在不同次評(píng)估中會(huì)有微小差異干擾優(yōu)化器的判斷。數(shù)據(jù)加載使用DataLoader時(shí)要注意設(shè)置num_workers如果機(jī)器是Windows系統(tǒng)這個(gè)值設(shè)為0最穩(wěn)妥。整個(gè)優(yōu)化過程是不支持?jǐn)帱c(diǎn)續(xù)跑的。中途斷了只能從頭開始。如果要避免這種風(fēng)險(xiǎn)可以把優(yōu)化器狀態(tài)定期保存下來下次啟動(dòng)時(shí)恢復(fù)。4. 常見問題與排查技巧實(shí)錄4.1 訓(xùn)練過程中損失值異常增大的原因與解決貝葉斯優(yōu)化搜索過程中我遇到過好幾次損失值不降反升的情況。排查下來主要有這幾種原因第一種是學(xué)習(xí)率過大導(dǎo)致發(fā)散。比如貝葉斯優(yōu)化在探索階段可能把學(xué)習(xí)率調(diào)到0.008以上這種情況下BP網(wǎng)絡(luò)很容易在訓(xùn)練初期就出現(xiàn)NaN。NaN損失一出現(xiàn)整個(gè)訓(xùn)練就廢了必須跳過這組參數(shù)。我的解決辦法是在訓(xùn)練循環(huán)里加了一個(gè)損失值檢查一旦發(fā)現(xiàn)loss不是有限數(shù)值立即終止訓(xùn)練并返回一個(gè)很大的懲罰值。第二種是輸入數(shù)據(jù)中存在異常值。時(shí)間序列數(shù)據(jù)里偶爾會(huì)出現(xiàn)極端的毛刺數(shù)據(jù)比如傳感器故障或者人為誤操作導(dǎo)致的值。如果不做處理模型會(huì)被這些異常值帶偏。我后來加了分位數(shù)截?cái)嗵幚戆殉鯷1%, 99%]分位數(shù)的值強(qiáng)制縮到邊界值上效果很好。第三種是特征尺度不匹配。如果某些特征的數(shù)值范圍特別大比如溫度高達(dá)40而另一些特征只有0到1神經(jīng)網(wǎng)絡(luò)各層權(quán)重的更新幅度會(huì)非常不均勻。訓(xùn)練前期損失下降緩慢后期還可能難以穩(wěn)定收斂。歸一化處理能解決大部分問題同時(shí)要記得把歸一化參數(shù)在訓(xùn)練集上擬合測(cè)試集上只做轉(zhuǎn)換不能把測(cè)試集的統(tǒng)計(jì)信息混進(jìn)來。4.2 貝葉斯優(yōu)化結(jié)果不穩(wěn)定或收斂異常的排查思路貝葉斯優(yōu)化本身有時(shí)候也會(huì)表現(xiàn)得很“怪”。我遇到過的現(xiàn)象有這些首先是早期就收斂到一個(gè)看起來不太好的點(diǎn)。這種情況通常因?yàn)槌跏茧S機(jī)采樣點(diǎn)太少高斯過程模型還沒學(xué)到目標(biāo)函數(shù)的真實(shí)形狀就把采樣集中到了某個(gè)局部區(qū)域。解決辦法是增大n_initial_points比如從10調(diào)整到20并增加每次采樣的隨機(jī)性。其次是優(yōu)化過程的波動(dòng)特別大。相鄰兩次迭代的目標(biāo)函數(shù)值變化非常劇烈這說明單次評(píng)估的噪音水平很高。除了使用時(shí)間序列交叉驗(yàn)證之外還可以考慮對(duì)每個(gè)參數(shù)組合做兩次重復(fù)實(shí)驗(yàn)取平均值可以把噪音進(jìn)一步壓低。還有一種情況是參數(shù)組合之間差異不大導(dǎo)致優(yōu)化器認(rèn)為很多地方都差不多出現(xiàn)“平臺(tái)期”。這種情況下可以考慮改變參數(shù)空間的分布比如把學(xué)習(xí)率的范圍往上調(diào)或者往下調(diào)打破平衡。最后提醒一個(gè)容易被忽略的細(xì)節(jié)目標(biāo)函數(shù)在參數(shù)空間邊界附近往往比較敏感。比如Dropout取值接近0時(shí)結(jié)果可能跟0.01時(shí)差不多但優(yōu)化器不會(huì)知道邊界外是什么狀況。如果最終選出的最優(yōu)參數(shù)恰好卡在邊界上比如批大小取到16或128這種極值我建議手動(dòng)把這個(gè)參數(shù)向邊界外擴(kuò)展一點(diǎn)再做一次實(shí)驗(yàn)確認(rèn)是否還有進(jìn)一步優(yōu)化的空間。4.3 預(yù)測(cè)效果不好時(shí)的常見原因分析模型調(diào)參完成之后如果預(yù)測(cè)效果還是不理想通常問題不在模型本身而在于數(shù)據(jù)或評(píng)估方式。我遇到過一個(gè)典型情況模型在訓(xùn)練集上表現(xiàn)很好驗(yàn)證集上也可以但一到未來真正預(yù)測(cè)時(shí)誤差大幅增加。后來發(fā)現(xiàn)原因在于我的時(shí)間序列數(shù)據(jù)里存在概念漂移即數(shù)據(jù)分布隨時(shí)間發(fā)生了變化。比如某段時(shí)間的負(fù)荷模式因?yàn)橥獠凯h(huán)境變化而整體偏移模型學(xué)到的規(guī)律不再適用。這種問題的解決思路是周期性地重新訓(xùn)練模型或者在訓(xùn)練數(shù)據(jù)里引入時(shí)間衰減權(quán)重讓近期樣本對(duì)模型的影響更大。另一個(gè)常見問題是多步預(yù)測(cè)的誤差累積。我剛開始做未來24小時(shí)預(yù)測(cè)時(shí)是用遞歸方式逐步預(yù)測(cè)的——用第t步的預(yù)測(cè)值作為第t1步的輸入。由于每步都有誤差逐漸累積后第24小時(shí)的預(yù)測(cè)精度慘不忍睹。后來改用直接多步預(yù)測(cè)方式即模型直接輸出未來24小時(shí)的所有預(yù)測(cè)值雖然單步精度略降但長(zhǎng)時(shí)段的整體誤差明顯改善。還有一種情況是評(píng)估指標(biāo)選得不合適。時(shí)間序列預(yù)測(cè)里不同的評(píng)估指標(biāo)對(duì)模型好壞的衡量維度完全不同。RMSE對(duì)大誤差更敏感所以它會(huì)偏向那些“少犯錯(cuò)但不犯大錯(cuò)”的模型。MAPE則對(duì)相對(duì)誤差更敏感。如果業(yè)務(wù)上更關(guān)心大偏差的容忍度RMSE更合適如果更關(guān)心整體偏差水平MAPE更直觀。我當(dāng)時(shí)兩者都算了因?yàn)樵陔娏ω?fù)荷預(yù)測(cè)場(chǎng)景里峰值時(shí)段的相對(duì)誤差往往比低谷時(shí)段更敏感所以要綜合考慮。4.4 常見問題速查表整理了一個(gè)自己在項(xiàng)目中使用的速查表方便快速排查問題現(xiàn)象可能原因解決方案訓(xùn)練損失出現(xiàn)NaN學(xué)習(xí)率過大、特征包含NaN降低學(xué)習(xí)率上限、清洗數(shù)據(jù)、損失函數(shù)加數(shù)值檢查驗(yàn)證誤差遠(yuǎn)大于訓(xùn)練誤差過擬合增大Dropout、增大L2正則系數(shù)、減少訓(xùn)練輪數(shù)貝葉斯優(yōu)化收斂太快初始采樣點(diǎn)太少增大n_initial_points、增加采樣隨機(jī)性優(yōu)化結(jié)果波動(dòng)大單次評(píng)估噪音高用交叉驗(yàn)證、重復(fù)實(shí)驗(yàn)取平均預(yù)測(cè)結(jié)果整體滯后差分處理引入延遲嘗試增加滯后特征、改用非遞歸預(yù)測(cè)方式長(zhǎng)時(shí)間預(yù)測(cè)誤差累積嚴(yán)重遞歸多步預(yù)測(cè)改成直接多步預(yù)測(cè)、引入外部特征輔助假日預(yù)測(cè)精度差訓(xùn)練集中假日樣本少增加時(shí)間特征、對(duì)假日樣本過采樣、引入相似日信息5. 經(jīng)驗(yàn)總結(jié)與后續(xù)改進(jìn)方向這段折騰下來我的核心體會(huì)是貝葉斯優(yōu)化的價(jià)值不在于它比人工調(diào)參“更聰明”而在于它能持續(xù)地、系統(tǒng)性地搜索參數(shù)空間把人工試錯(cuò)過程中的盲目性降到最低。尤其是當(dāng)參數(shù)空間維度超過3以后人工調(diào)參的效率和可靠性都大幅下降這時(shí)候貝葉斯優(yōu)化的優(yōu)勢(shì)就體現(xiàn)出來了。另外要提的一點(diǎn)是調(diào)參不是萬能的。模型結(jié)構(gòu)本身是否合理、數(shù)據(jù)特征是否充分、評(píng)估方式是否靠譜這些問題的優(yōu)先級(jí)都在調(diào)參之前。我在項(xiàng)目初期花了不少時(shí)間折騰超參數(shù)后來發(fā)現(xiàn)把滯后特征加全、把節(jié)假日信息編碼好提升比單純調(diào)參大得多。所以我的建議是先保證數(shù)據(jù)和特征的質(zhì)量再考慮用貝葉斯優(yōu)化去壓榨模型的最后幾個(gè)百分點(diǎn)。在實(shí)操層面我會(huì)建議剛開始接觸這套方案的人不必一上來就追求復(fù)雜的交叉驗(yàn)證和并行化訓(xùn)練??梢韵扰芤粋€(gè)簡(jiǎn)化版本單次訓(xùn)練、驗(yàn)證集評(píng)估、貝葉斯優(yōu)化迭代50次。跑通了以后再逐步加入滑動(dòng)窗口交叉驗(yàn)證、重復(fù)實(shí)驗(yàn)取均值、并行評(píng)估等進(jìn)階手段這樣更容易定位問題。后續(xù)有幾個(gè)可以繼續(xù)改進(jìn)的方向一是把貝葉斯優(yōu)化和超參數(shù)熱力圖分析結(jié)合每次優(yōu)化結(jié)束后畫出參數(shù)與目標(biāo)函數(shù)的二維投影能更直觀地理解哪些參數(shù)影響更大二是嘗試在優(yōu)化過程中動(dòng)態(tài)調(diào)整參數(shù)空間比如把已經(jīng)掌握的高價(jià)值區(qū)域細(xì)化把明顯無效的區(qū)域裁剪掉三是把整個(gè)流程封裝成一套可復(fù)用的流水線輸入歷史數(shù)據(jù)就直接輸出優(yōu)化后的模型和預(yù)測(cè)結(jié)果。時(shí)間序列預(yù)測(cè)本身是個(gè)水很深的領(lǐng)域每個(gè)環(huán)節(jié)都有不少講究。貝葉斯優(yōu)化配BP神經(jīng)網(wǎng)絡(luò)只是其中一個(gè)可行方案它的優(yōu)勢(shì)是結(jié)構(gòu)清晰、實(shí)現(xiàn)門檻低、效果提升穩(wěn)定。如果你的項(xiàng)目也卡在調(diào)參效率上不妨試試這套思路。踩過的坑記錄下來希望后來的朋友能少走一些彎路。