調(diào)優(yōu):粒子群與網(wǎng)格搜索的工程取舍)
簡(jiǎn)介這套 PSO-SVR 代碼與實(shí)驗(yàn)材料面向機(jī)器學(xué)習(xí)學(xué)習(xí)者、算法工程師及需要做回歸建模的研究人員圍繞粒子群算法優(yōu)化支持向量機(jī)超參數(shù)懲罰因子與核參數(shù)并同傳統(tǒng) SVR 對(duì)比展開(kāi)。壓縮包共 64 個(gè)文件、約 941KB包含 Java、Python、C 與 MATLAB 實(shí)現(xiàn)及 Makefile、README、可執(zhí)行文件和 heart_scale、data1.mat 等數(shù)據(jù)便于跨語(yǔ)言復(fù)現(xiàn)實(shí)驗(yàn)、查看 libsvm 調(diào)用方式與參數(shù)尋優(yōu)流程。已有 220 人學(xué)習(xí)下載。讀者可據(jù)此理解 PSO 的全局搜索如何替代網(wǎng)格搜索、如何設(shè)置種群與迭代參數(shù)并通過(guò)回歸擬合結(jié)果比較模型精度與泛化表現(xiàn)同時(shí)借鑒目錄組織和排錯(cuò)思路用于課程實(shí)驗(yàn)或小型預(yù)測(cè)項(xiàng)目。1. PSO-SVR 和裸 SVR 的差距八成在超參數(shù)兩成在算法本身先說(shuō)結(jié)論在我手上那幾組小樣本回歸任務(wù)里PSO-SVR 相對(duì)默認(rèn)參數(shù)支持向量機(jī)回歸的提升八成來(lái)自懲罰系數(shù) C 和核參數(shù) gamma 選對(duì)了跟粒子群算法本身關(guān)系不大。換句話說(shuō)你換成網(wǎng)格搜索、隨機(jī)搜索甚至手動(dòng)試只要落在同一片參數(shù)區(qū)域精度差不多。粒子群算法真正的價(jià)值在于參數(shù)維度上去以后——比如同時(shí)調(diào) C、gamma、epsilon網(wǎng)格搜索的組合數(shù)會(huì)指數(shù)級(jí)炸開(kāi)而二十來(lái)個(gè)粒子、幾十次迭代就能摸到最優(yōu)區(qū)域附近。這篇就按這個(gè)判斷往下講支持向量機(jī)回歸的超參數(shù)到底怎么影響擬合、PSO 的適應(yīng)度函數(shù)怎么寫(xiě)才不騙自己、以及在什么數(shù)據(jù)上這套組合值得跑、什么數(shù)據(jù)上是白白浪費(fèi)算力。2. 網(wǎng)格搜索和粒子群的分岔口先算清搜索空間的賬2.1 網(wǎng)格搜索在三維以上為什么撐不住網(wǎng)格搜索的好處很實(shí)在確定、可復(fù)現(xiàn)、好并行、結(jié)論不含隨機(jī)性。你在論文或者實(shí)驗(yàn)報(bào)告里寫(xiě)「網(wǎng)格搜索得到 C12.9、gamma0.031」別人復(fù)現(xiàn)一遍就是同一組數(shù)。這個(gè)優(yōu)點(diǎn)在小參數(shù)空間里無(wú)可替代。壞處是分辨率和范圍天然打架。假設(shè)只調(diào) C 和 gamma為了把量級(jí)跨度覆蓋住每個(gè)參數(shù)取 8 個(gè)值就是 64 組組合配上 5 折交叉驗(yàn)證總擬合次數(shù) 320 次。一次 RBF-SVR 在幾千樣本上擬合一秒上下三百多秒還能忍。把 epsilon 也納入優(yōu)化8×8×8512 組5 折就是 2560 次擬合二十分鐘起步。如果為了降低隨機(jī)性再做 3 次重復(fù)直接到小時(shí)級(jí)。更難受的是你想精細(xì)一點(diǎn)。范圍從 1e-1 到 1e4 拉滿五個(gè)數(shù)量級(jí)8 個(gè)點(diǎn)意味著相鄰兩點(diǎn)差了三倍多最優(yōu)點(diǎn)附近基本靠運(yùn)氣要精細(xì)就得加密到 20 個(gè)點(diǎn)組合數(shù)直接翻到 8000。這就是網(wǎng)格搜索在三維以上撐不住的真正原因——不是它不準(zhǔn)是你付不起那個(gè)算力。粗算一下三種參數(shù)個(gè)數(shù)下的擬合次數(shù)下面的表按 5 折交叉驗(yàn)證、每維 8 個(gè)取值算優(yōu)化參數(shù)組合數(shù)5 折擬合次數(shù)單次 1 秒時(shí)的耗時(shí)量級(jí)C、gamma64320分鐘級(jí)C、gamma、epsilon5122560二十分鐘級(jí)再加核權(quán)重或 tol409620480數(shù)小時(shí)粒子群在這個(gè)賬本上是另一種算法種群 24、迭代 60上限 1440 次擬合而且?guī)г缤?shí)際跑下來(lái)經(jīng)常只有 600 到 800 次。參數(shù)從 2 維漲到 3 維擬合次數(shù)幾乎不變——這是它相對(duì)網(wǎng)格搜索的核心優(yōu)勢(shì)別的都是次要的。2.2 粒子在搜什么把 C、gamma、epsilon 換到對(duì)數(shù)空間很多人第一次寫(xiě) PSO-SVR 就直接把粒子的位置當(dāng)真實(shí)超參數(shù)用初始化區(qū)間寫(xiě)成 C ∈ [0.1, 10000]、gamma ∈ [0.0001, 10]然后發(fā)現(xiàn)跑了二十代還在邊界上晃。原因很樸素在 [0.1, 10000] 上均勻撒粒子落進(jìn) [0.1, 1] 這個(gè)區(qū)間的概率不到萬(wàn)分之一而不少任務(wù)的合理 C 恰恰就在 1 到 100 之間。粒子全擠在大數(shù)那一頭搜索等于沒(méi)做。常見(jiàn)的做法是換到對(duì)數(shù)空間讓粒子位置的每一位代表「10 的幾次方」。C 在 [1e-1, 1e4] 上采樣就變成位置在 [-1, 4] 上均勻采樣gamma 在 [1e-4, 1e1] 上采樣變成 [-4, 1]。這樣每個(gè)數(shù)量級(jí)拿到粒子的機(jī)會(huì)是一樣的收斂曲線也好看得多。epsilon 要多說(shuō)一句。它是不敏感帶的寬度直接跟目標(biāo)變量 y 的量綱掛鉤y 是幾千塊的金額epsilon 取 0.01 等于沒(méi)設(shè)y 標(biāo)準(zhǔn)化到均值 0 方差 1epsilon 取 0.01 又可能過(guò)寬。所以要么先把 y 標(biāo)準(zhǔn)化再固定搜索范圍要么按 y 的標(biāo)準(zhǔn)差給 epsilon 定范圍。我一般傾向前者省事。推薦的搜索邊界如下位置列就是粒子實(shí)際使用的坐標(biāo)參數(shù)物理含義原始范圍對(duì)數(shù)空間位置調(diào)大之后的表現(xiàn)C懲罰系數(shù)對(duì)誤差的容忍度1e-1 ~ 1e4-1 ~ 4越貼訓(xùn)練數(shù)據(jù)越大越過(guò)擬合gammaRBF 核寬度倒數(shù)1e-4 ~ 1e1-4 ~ 1單樣本影響范圍變小越大越容易過(guò)擬合epsilon不敏感帶寬1e-3 ~ 1e0-3 ~ 0越大擬合越平極端時(shí)把趨勢(shì)壓沒(méi)這張表不是抄來(lái)的固定答案是我踩過(guò)幾輪之后收斂下來(lái)的默認(rèn)值。你的數(shù)據(jù)如果特征維度特別高幾百維以上gamma 的上界可以往下壓一壓因?yàn)楦呔S下 RBF 核本來(lái)就容易過(guò)度局部化。2.3 適應(yīng)度函數(shù)用交叉驗(yàn)證的 RMSE別用訓(xùn)練集 RMSE這是整個(gè) PSO-SVR 里最容易騙自己的一步。最省事的寫(xiě)法是拿訓(xùn)練集 RMSE 當(dāng)適應(yīng)度代碼三行就寫(xiě)完但它有個(gè)致命問(wèn)題RBF-SVR 的 C 越大、gamma 越大訓(xùn)練集誤差單調(diào)往下降。粒子群作為一個(gè)最小化算法會(huì)非常忠實(shí)地把所有粒子推到邊界上最后你得到一個(gè)訓(xùn)練集幾乎零誤差、測(cè)試集一塌糊涂的模型收斂曲線還特別漂亮。常見(jiàn)的思路是 K 折交叉驗(yàn)證把訓(xùn)練集切 K 份每次用 K-1 份擬合、剩 1 份算 RMSE取平均。5 折是折中10 折更穩(wěn)但貴一倍樣本量小于 200 的時(shí)候 5 折已經(jīng)夠用。切分要 shuffle種子要固定否則每次算出來(lái)的適應(yīng)度都帶隨機(jī)抖動(dòng)粒子會(huì)跟著抖。還有一層更隱蔽的標(biāo)準(zhǔn)化必須在交叉驗(yàn)證內(nèi)部做。如果在切分之前就對(duì)全量數(shù)據(jù) fit 了 StandardScaler驗(yàn)證折的均值和方差里已經(jīng)混進(jìn)了訓(xùn)練折的信息CV 分?jǐn)?shù)會(huì)偏樂(lè)觀PSO 就會(huì)往一個(gè)「看起來(lái)很好」的區(qū)域收斂。用 sklearn 的 pipeline 把 scaler 和 SVR 包在一起cross_val_score內(nèi)部會(huì)按折重新 fit這一層就自動(dòng)隔開(kāi)了。判斷適應(yīng)度有沒(méi)有問(wèn)題看收斂曲線就行。正常的曲線是前五到十代快速下降后面緩慢逼近最后幾乎水平。如果第一代就貼到接近 0先別高興回頭查兩件事是不是把測(cè)試集混進(jìn)適應(yīng)度了是不是 scaler 在 CV 外面 fit 了。3. 把 PSO-SVR 跑起來(lái)一份能直接改的代碼骨架3.1 數(shù)據(jù)入口與標(biāo)準(zhǔn)化的位置先解決數(shù)據(jù)?;貧w任務(wù)里 SVR 對(duì)特征尺度極其敏感同一個(gè) gamma 在標(biāo)準(zhǔn)化前后完全是兩個(gè)模型所以標(biāo)準(zhǔn)化不是可選項(xiàng)。但標(biāo)準(zhǔn)化的位置有講究它必須待在交叉驗(yàn)證內(nèi)部這一點(diǎn)前面說(shuō)過(guò)代碼上用 pipeline 實(shí)現(xiàn)最省心。import numpy as np from sklearn.datasets import load_diabetes from sklearn.model_selection import train_test_split # 442 條、10 維特征的公開(kāi)回歸數(shù)據(jù)用來(lái)演示流程足夠 X, y load_diabetes(return_X_yTrue) # 先切出 20% 做最終驗(yàn)收這份數(shù)據(jù)在整個(gè)調(diào)參過(guò)程中一次都不碰 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42 ) print(X_train.shape, X_test.shape) # (353, 10) (89, 10)這段的意義不在于加載數(shù)據(jù)而在于那個(gè)random_state42和「一次都不碰」的約定。測(cè)試集在調(diào)參階段被看上一眼你后面所有對(duì)比數(shù)字的可信度都要打折扣因?yàn)榱W尤簳?huì)通過(guò)你的手間接擬合到測(cè)試集上。血淚經(jīng)驗(yàn)是先把測(cè)試集切出來(lái)、存成文件、調(diào)參腳本里根本不 import 它。要不要順手把 y 也標(biāo)準(zhǔn)化如果 y 的量級(jí)跨得大比如從幾十到幾萬(wàn)標(biāo)準(zhǔn)化能讓 epsilon 的搜索范圍固定下來(lái)省掉一輪范圍調(diào)整。代價(jià)是預(yù)測(cè)結(jié)果要反變換回來(lái)多兩行代碼。3.2 粒子編碼、邊界與速度限幅編碼用上一節(jié)的對(duì)數(shù)空間解碼函數(shù)單獨(dú)抽出來(lái)方便在實(shí)驗(yàn)記錄里還原真實(shí)超參數(shù)。邊界用數(shù)組存好處是后面裁剪時(shí)一行np.clip搞定不用給每一維寫(xiě) if。from sklearn.svm import SVR from sklearn.pipeline import make_pipeline from sklearn.preprocessing import StandardScaler from sklearn.model_selection import KFold, cross_val_score # 粒子位置對(duì)數(shù)空間- 真實(shí)超參數(shù) def decode(pos): C 10.0 ** pos[0] # 位置 -1~4 還原成 0.1~10000 gamma 10.0 ** pos[1] # 位置 -4~1 還原成 1e-4~10 epsilon 10.0 ** pos[2] # 位置 -3~0 還原成 0.001~1 return C, gamma, epsilon # 適應(yīng)度只在訓(xùn)練集內(nèi)部做 K 折測(cè)試集不參與 def fitness(pos, X, y, n_splits5, seed0): C, gamma, eps decode(pos) pipe make_pipeline( StandardScaler(), # 標(biāo)準(zhǔn)化放在 CV 內(nèi)部 SVR(kernelrbf, CC, gammagamma, epsiloneps) ) kf KFold(n_splitsn_splits, shuffleTrue, random_stateseed) scores cross_val_score(pipe, X, y, cvkf, scoringneg_root_mean_squared_error) return -scores.mean() # 取正越小越好fitness每次調(diào)用會(huì)擬合 5 次模型這就是 PSO 的成本單位。想讓整體快一倍把n_splits降到 3代價(jià)是適應(yīng)度噪聲變大、粒子軌跡變抖一般不建議在樣本量夠的時(shí)候省這一步。速度要限幅這一條很多人漏掉。粒子速度如果不設(shè)上限某一位可能一步跨過(guò)好幾個(gè)數(shù)量級(jí)位置被np.clip拉回邊界之后速度還很大下一代會(huì)繼續(xù)往邊界撞,粒子就廢了。經(jīng)驗(yàn)值是把速度上限設(shè)成搜索區(qū)間寬度的 15% 到 20%。參數(shù)建議值作用與調(diào)整方向種群規(guī)模20 ~ 30小于 15 容易早熟大于 40 收益遞減迭代次數(shù)50 ~ 100配合早停用多數(shù)任務(wù) 30 代內(nèi)就平了慣性權(quán)重 w0.9 線性降到 0.4前期廣撒網(wǎng)后期精修學(xué)習(xí)因子 c1、c22.0、2.0經(jīng)驗(yàn)值c1 大偏個(gè)體探索c2 大偏群體收斂速度上限區(qū)間寬度的 0.15 ~ 0.2防止粒子一步飛出有效區(qū)域早停 patience8 ~ 12 代gbest 連續(xù)這么多代沒(méi)改善就停3.3 主循環(huán)慣性權(quán)重遞減與早停主循環(huán)本身不到三十行但幾個(gè)細(xì)節(jié)決定了它能不能收斂到有用的地方慣性權(quán)重隨代數(shù)線性遞減、速度先限幅再更新位置、gbest 變化小于閾值時(shí)計(jì)一次停滯。def pso_svr(X, y, n_particles24, iters60, seed42, patience10): rng np.random.default_rng(seed) bounds np.array([[-1.0, 4.0], # C 的 log10 范圍 [-4.0, 1.0], # gamma 的 log10 范圍 [-3.0, 0.0]]) # epsilon 的 log10 范圍 lo, hi bounds[:, 0], bounds[:, 1] dim len(lo) # 初始位置在區(qū)間內(nèi)均勻撒點(diǎn)初始速度取小區(qū)間隨機(jī) pos rng.uniform(lo, hi, size(n_particles, dim)) vel rng.uniform(-0.1, 0.1, size(n_particles, dim)) * (hi - lo) pbest pos.copy() pbest_val np.array([fitness(p, X, y) for p in pos]) gi np.argmin(pbest_val) gbest pbest[gi].copy() gbest_val pbest_val[gi] c1 c2 2.0 vmax 0.18 * (hi - lo) # 速度上限按區(qū)間寬度比例給 stall, history 0, [gbest_val] for t in range(iters): w 0.9 - (0.9 - 0.4) * t / iters # 慣性權(quán)重線性遞減 r1 rng.random((n_particles, dim)) r2 rng.random((n_particles, dim)) vel w * vel c1 * r1 * (pbest - pos) c2 * r2 * (gbest - pos) vel np.clip(vel, -vmax, vmax) # 先限速 pos np.clip(pos vel, lo, hi) # 再裁位置 vals np.array([fitness(p, X, y) for p in pos]) better vals pbest_val # 個(gè)體歷史最優(yōu)更新 pbest[better] pos[better] pbest_val[better] vals[better] gi np.argmin(pbest_val) # 群體最優(yōu)更新 if pbest_val[gi] gbest_val - 1e-6: gbest, gbest_val pbest[gi].copy(), pbest_val[gi] stall 0 else: stall 1 history.append(gbest_val) if stall patience: # 連續(xù)多代無(wú)改善就收手 print(fearly stop at iter {t}, gbest{gbest_val:.4f}) break return decode(gbest), gbest_val, history調(diào)用的時(shí)候返回的第一個(gè)值是真實(shí)超參數(shù)元組第二是 CV RMSE第三是收斂歷史——第三個(gè)數(shù)別看輕畫(huà)出來(lái)能一眼看出是早熟還是正常收斂。如果曲線在第五代就平了說(shuō)明種群多樣性不夠或者邊界給窄了如果是鋸齒狀上下跳多半是 CV 折數(shù)太少或沒(méi) shuffle。3.4 和默認(rèn) SVR、網(wǎng)格 SVR 放在同一張表里比跑完 PSO 不算完得有個(gè)參照系。我一般會(huì)同時(shí)跑三條線默認(rèn)參數(shù)的 SVRC1、epsilon0.1、gammascale、小網(wǎng)格搜索的 SVR、PSO 調(diào)優(yōu)的 SVR。三條線用同一個(gè)random_state切數(shù)據(jù)、同一套評(píng)估函數(shù)否則數(shù)字沒(méi)法比。from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score import time def evaluate(model, X_train, y_train, X_test, y_test): t0 time.perf_counter() model.fit(X_train, y_train) cost time.perf_counter() - t0 pred model.predict(X_test) return { rmse: mean_squared_error(y_test, pred) ** 0.5, mae : mean_absolute_error(y_test, pred), r2 : r2_score(y_test, pred), fit_time: cost, } # PSO 返回的真實(shí)超參數(shù)直接塞進(jìn)模型標(biāo)準(zhǔn)化仍用 pipeline 包住 (C, gamma, eps), cv_score, hist pso_svr(X_train, y_train) pso_model make_pipeline( StandardScaler(), SVR(kernelrbf, CC, gammagamma, epsiloneps) ) print(evaluate(pso_model, X_train, y_train, X_test, y_test))注意evaluate里的fit_time只算了最后一次擬合不含調(diào)參開(kāi)銷。真要比較總成本得把pso_svr里所有 fitness 調(diào)用的耗時(shí)累加起來(lái)那才是你實(shí)際付出的電費(fèi)。做選型匯報(bào)的時(shí)候這兩個(gè)數(shù)要分開(kāi)寫(xiě)不然容易被問(wèn)住。4. 四個(gè)維度擺一起比PSO-SVR 與 SVR 各自該上場(chǎng)的場(chǎng)景4.1 精度、成本、穩(wěn)定性、可維護(hù)性只比精度是不完整的我在實(shí)際項(xiàng)目里被問(wèn)得最多的反而是「這套東西上線之后誰(shuí)維護(hù)」。四個(gè)維度拆開(kāi)看精度上限。PSO-SVR 通常能比默認(rèn)參數(shù) SVR 好一截但和精調(diào)過(guò)的網(wǎng)格 SVR 基本打平差距在噪聲范圍內(nèi)。原因是兩者搜的是同一片參數(shù)空間PSO 的優(yōu)勢(shì)在效率不在表達(dá)能力。指望換算法帶來(lái)質(zhì)的飛躍大概率會(huì)失望。調(diào)參成本。這是量化差距最明顯的一項(xiàng)。前面算過(guò)三維參數(shù)下網(wǎng)格搜索 2560 次擬合PSO 上限 1440 次、實(shí)際常落在 600 到 800 次。批處理任務(wù)里這就是十幾分鐘和兩分鐘的區(qū)別。穩(wěn)定性。PSO 有隨機(jī)性同一個(gè)數(shù)據(jù)集跑三次交叉驗(yàn)證分?jǐn)?shù)能差出百分之幾網(wǎng)格搜索給定同樣的網(wǎng)格結(jié)果是確定的。這一條在需要出報(bào)告的場(chǎng)合很要命解決辦法是固定種子跑三次取均值加減標(biāo)準(zhǔn)差把波動(dòng)亮出來(lái)而不是藏起來(lái)。可維護(hù)性。PSO 調(diào)完之后落到生產(chǎn)上的一定是一組固定超參數(shù)粒子群本身不會(huì)跟著上線。所以真正的維護(hù)成本在于「數(shù)據(jù)分布漂移之后要不要重調(diào)」。如果數(shù)據(jù)每個(gè)月都在變每次重訓(xùn)都跑一遍 PSO成本會(huì)累積這時(shí)候更常見(jiàn)的做法是離線調(diào)一次、在線只做擬合隔季度再校準(zhǔn)。示意性的對(duì)比結(jié)果如下數(shù)字來(lái)自一次演示跑量級(jí)參考換數(shù)據(jù)必須重測(cè)方案關(guān)鍵參數(shù)來(lái)源測(cè)試集 RMSE測(cè)試集 R2擬合次數(shù)調(diào)參耗時(shí)默認(rèn) SVR庫(kù)默認(rèn)值基準(zhǔn)基準(zhǔn)1忽略網(wǎng)格 SVR8×8×8 網(wǎng)格 5 折優(yōu)于默認(rèn)略高2560十幾分鐘PSO-SVR24 粒子 × 60 代 早停與網(wǎng)格相近相近約 700兩三分鐘4.2 什么數(shù)據(jù)上 PSO 值得什么數(shù)據(jù)上是白費(fèi)電判斷標(biāo)準(zhǔn)不是「數(shù)據(jù)難不難」而是「參數(shù)維度除以可承受的擬合次數(shù)」。三個(gè)場(chǎng)景我分得很清楚樣本量小、特征維度低幾百條以內(nèi)、十幾維。這種數(shù)據(jù)上默認(rèn) SVR 加上手動(dòng)試幾組 C 和 gamma 就夠了網(wǎng)格搜索 6×6 也就 180 次擬合幾秒鐘的事。上 PSO 屬于殺雞用牛刀還額外引入了隨機(jī)性。樣本量中等、特征維度較高、參數(shù)三個(gè)以上。這是 PSO-SVR 最舒服的區(qū)間。特征上百維之后 gamma 的敏感性陡增粗網(wǎng)格經(jīng)常跳過(guò)最優(yōu)點(diǎn)而 PSO 在對(duì)數(shù)空間里能細(xì)粒度地摸過(guò)去。同時(shí)樣本量還沒(méi)大到讓單次擬合變慢整個(gè)搜索在可接受時(shí)間內(nèi)跑完。需要頻繁重訓(xùn)的在線場(chǎng)景。這種場(chǎng)景下 PSO 是負(fù)擔(dān)不是助力。每次數(shù)據(jù)更新都重新跑幾百次 CV 擬合延遲頂不住。合理做法是離線把參數(shù)區(qū)間定下來(lái)線上用固定參數(shù)增量擬合隔一段時(shí)間做一次全量校準(zhǔn)。還有一類要警惕目標(biāo)變量噪聲極大、信噪比很低的數(shù)據(jù)。PSO 會(huì)非常努力地去擬合噪聲最后選出的參數(shù)往往是 C 大、epsilon 小驗(yàn)證集上看著還行新樣本上崩得厲害。這種數(shù)據(jù)上 epsilon 的下界要收緊別讓它跑到 1e-3 那種幾乎不容忍誤差的位置。4.3 決策什么情況下選哪一個(gè)把上面的判斷壓成一張表照著填就行你的處境選擇理由參數(shù)只有 C 和 gamma樣本幾千以內(nèi)網(wǎng)格搜索確定性強(qiáng)幾分鐘出結(jié)果參數(shù)三個(gè)及以上或 gamma 敏感性高PSO-SVR維度漲了成本幾乎不變需要寫(xiě)報(bào)告、結(jié)果必須完全可復(fù)現(xiàn)網(wǎng)格搜索 固定種子PSO 的隨機(jī)性要額外處理頻繁重訓(xùn)、延遲敏感離線調(diào)參 線上固定PSO 只在離線階段出現(xiàn)信噪比低、異常值多先清洗數(shù)據(jù)再談算法換優(yōu)化器救不了臟數(shù)據(jù)一句話結(jié)論P(yáng)SO-SVR 解決的是「參數(shù)多到網(wǎng)格搜不動(dòng)」這個(gè)具體問(wèn)題不是「讓模型變準(zhǔn)」的通用方案。把它當(dāng)調(diào)參加速器看期望就對(duì)了。5. 避坑記錄五個(gè)讓我重跑實(shí)驗(yàn)的翻車現(xiàn)場(chǎng)5.1 收斂曲線漂亮但測(cè)試集更差適應(yīng)度泄漏現(xiàn)象PSO 的 gbest 曲線一路平滑下降到第三十代幾乎貼地看著特別舒服。拿測(cè)試集一驗(yàn)RMSE 比默認(rèn)參數(shù)的 SVR 還高。原因適應(yīng)度函數(shù)把測(cè)試集或者全量數(shù)據(jù)算進(jìn)去了。最常見(jiàn)的寫(xiě)法是fitness里直接對(duì)X, y做 CV而X, y是切分之前的全量數(shù)據(jù)——驗(yàn)證折里含著測(cè)試樣本等價(jià)于拿測(cè)試集調(diào)參。另一種更隱蔽的是 scaler 在 CV 外面 fit 過(guò)信息從驗(yàn)證折漏回訓(xùn)練折。解決適應(yīng)度函數(shù)只接收X_train, y_train測(cè)試集在調(diào)參腳本里根本不出現(xiàn)。scaler 一律用 pipeline 包在 CV 內(nèi)部。改完之后曲線會(huì)難看一些但測(cè)試集數(shù)字才是真的。5.2 十幾代之后粒子全擠成一團(tuán)早熟收斂現(xiàn)象種群位置的方差迅速縮小二十代不到所有粒子幾乎重合gbest 還在邊界附近明顯沒(méi)搜到位。原因初始化區(qū)間太窄粒子一開(kāi)始就沒(méi)有多樣性或者速度上限給小了粒子動(dòng)不起來(lái)也可能是慣性權(quán)重降得太快前期沒(méi)來(lái)得及探索。解決初始化用整個(gè)搜索區(qū)間均勻撒點(diǎn)別用[0, 1]這種歸一化后就忘了反變換的寫(xiě)法。速度上限提到區(qū)間寬度的 0.18 左右。慣性權(quán)重從 0.9 線性降到 0.4別一上來(lái)就固定成 0.5。還不行就加一手多樣性保護(hù)每隔十代把適應(yīng)度最差的兩個(gè)粒子重置到隨機(jī)位置。5.3 驗(yàn)證集 R2 不錯(cuò)但預(yù)測(cè)明顯偏高偏低標(biāo)準(zhǔn)化漏做或 epsilon 過(guò)寬現(xiàn)象交叉驗(yàn)證分?jǐn)?shù)挺好看實(shí)際拿新樣本預(yù)測(cè)整體系統(tǒng)性偏移像被壓平了一樣。原因兩個(gè)可能。一是特征沒(méi)標(biāo)準(zhǔn)化量綱大的那幾維主導(dǎo)了 RBF 核的距離計(jì)算模型實(shí)際上只學(xué)到了少數(shù)特征。二是 epsilon 調(diào)得太大不敏感帶把真實(shí)趨勢(shì)也一起容忍掉了。解決標(biāo)準(zhǔn)化必做而且要在 pipeline 里做。epsilon 的搜索上界壓到 1e0 以內(nèi)如果 y 沒(méi)標(biāo)準(zhǔn)化就按 y 的標(biāo)準(zhǔn)差給范圍別用固定的 0.001 到 1。改完之后先看殘差圖殘差如果均勻分布在零附近就對(duì)了。5.4 換臺(tái)機(jī)器結(jié)果就變隨機(jī)種子沒(méi)管住現(xiàn)象同一個(gè)腳本同事那邊跑出來(lái)一組參數(shù)你這邊跑出來(lái)另一組精度還差一截。誰(shuí)也說(shuō)不清誰(shuí)對(duì)。原因粒子初始化用了全局隨機(jī)、KFold 沒(méi)傳random_state、train_test_split沒(méi)固定種子這三處任何一處漏了結(jié)果都不可復(fù)現(xiàn)。另外 numpy 的新舊隨機(jī) API 混用也會(huì)帶來(lái)差異。解決種子從入口傳到底np.random.default_rng(seed)生成一個(gè) rng 對(duì)象往下傳不要到處調(diào)np.random.rand。KFold 的random_state顯式寫(xiě)出來(lái)。跑三次取均值加減標(biāo)準(zhǔn)差把波動(dòng)公開(kāi)寫(xiě)進(jìn)報(bào)告比藏起來(lái)強(qiáng)。5.5 PSO 比網(wǎng)格搜索還慢沒(méi)開(kāi)早停、邊界給太寬現(xiàn)象本來(lái)指望 PSO 省時(shí)間結(jié)果跑了四十分鐘還沒(méi)結(jié)束比網(wǎng)格搜索還慢。原因迭代次數(shù)設(shè)了 200種群 505 折一次不落全跑完。搜索邊界又給得極寬粒子大半時(shí)間在無(wú)效區(qū)域游蕩。早停的 patience 設(shè)成 30等于沒(méi)設(shè)。解決迭代次數(shù)壓到 60、種群 24、patience 8 到 12 代。先用粗網(wǎng)格快速定位大致量級(jí)再把 PSO 的邊界收窄到那個(gè)量級(jí)的 ±1.5 個(gè)數(shù)量級(jí)。這一步能把搜索空間砍掉一大半效果立竿見(jiàn)影。6. 復(fù)現(xiàn)閉環(huán)在同一份數(shù)據(jù)上把三種方案驗(yàn)一遍調(diào)參調(diào)到最后必須有一個(gè)閉環(huán)把結(jié)論釘死否則過(guò)兩周你自己都不記得那些數(shù)字是怎么來(lái)的。我的做法是寫(xiě)一個(gè)獨(dú)立的驗(yàn)收腳本輸入只有固定種子的訓(xùn)練集和測(cè)試集輸出一張三行表每次都從干凈狀態(tài)跑。from sklearn.model_selection import GridSearchCV import numpy as np # 三條線共用同一個(gè)切分和同一套評(píng)估函數(shù) base make_pipeline(StandardScaler(), SVR(kernelrbf)) grid GridSearchCV( make_pipeline(StandardScaler(), SVR(kernelrbf)), param_grid{svr__C: np.logspace(-1, 4, 8), svr__gamma: np.logspace(-4, 1, 8), svr__epsilon: np.logspace(-3, 0, 8)}, cv5, scoringneg_root_mean_squared_error, n_jobs-1 ) rows [] rows.append((默認(rèn) SVR, evaluate(base, X_train, y_train, X_test, y_test))) grid.fit(X_train, y_train) rows.append((網(wǎng)格 SVR, evaluate(grid.best_estimator_, X_train, y_train, X_test, y_test))) rows.append((PSO-SVR, evaluate(pso_model, X_train, y_train, X_test, y_test))) for name, m in rows: print(f{name:10s} RMSE{m[rmse]:.2f} MAE{m[mae]:.2f} R2{m[r2]:.4f})這個(gè)腳本有兩個(gè)細(xì)節(jié)值得強(qiáng)調(diào)。n_jobs-1讓網(wǎng)格搜索并行不然 2560 次擬合串行跑會(huì)非常難受并行之后網(wǎng)格搜索的墻鐘時(shí)間大幅縮短PSO 的成本優(yōu)勢(shì)就沒(méi)那么突出了——這一點(diǎn)在選型時(shí)容易被忽略實(shí)際項(xiàng)目里機(jī)器核數(shù)多的話網(wǎng)格搜索往往是更劃算的選擇。另外 PSO 那條線要用前面調(diào)好的固定參數(shù)直接構(gòu)造模型不要在驗(yàn)收腳本里重跑一次優(yōu)化否則每次驗(yàn)收的結(jié)果都不一樣失去了對(duì)比意義。跑完之后看三件事。第一PSO-SVR 和網(wǎng)格 SVR 的測(cè)試集 RMSE 差多少差在 3% 以內(nèi)基本可以認(rèn)為等價(jià)選誰(shuí)看維護(hù)成本。第二默認(rèn) SVR 差多少差得遠(yuǎn)說(shuō)明這份數(shù)據(jù)確實(shí)吃調(diào)參差得近說(shuō)明這份數(shù)據(jù)的信號(hào)本來(lái)就弱花在調(diào)參上的時(shí)間不如拿去做特征工程。第三PSO 三次運(yùn)行的 R2 標(biāo)準(zhǔn)差有多大超過(guò) 0.02 就得回頭查種子和折數(shù)。從那以后我每次做超參數(shù)調(diào)優(yōu)都強(qiáng)制走一遍「先切測(cè)試集、再寫(xiě)適應(yīng)度、最后同臺(tái)驗(yàn)收」這個(gè)順序中間任何一步想抄近路后面都要用雙倍時(shí)間補(bǔ)回來(lái)。這套流程本身沒(méi)什么玄學(xué)值錢的地方全在那些不顯眼的約束里——測(cè)試集不許碰、標(biāo)準(zhǔn)化關(guān)進(jìn) pipeline、種子從入口傳到底。粒子群只是這套流程里的一個(gè)可選零件換成隨機(jī)搜索、貝葉斯優(yōu)化骨架一模一樣。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取