化算法的p-Hub選址問題Matlab實(shí)現(xiàn))
簡介基于粒子群優(yōu)化算法的物流樞紐選址p-HubMatlab實(shí)現(xiàn)面向物流工程、管理科學(xué)與工程等方向的本科與碩士教研場景適合需要掌握智能優(yōu)化算法與選址建模的讀者。壓縮包共9個文件包含6個.m源碼、1個.mat數(shù)據(jù)集及2張運(yùn)行結(jié)果圖源碼覆蓋隨機(jī)解生成、變異策略、成本計(jì)算、粒子群主程序及結(jié)果解析等完整流程可直接在Matlab 2019a中運(yùn)行并復(fù)現(xiàn)選址結(jié)果。包體僅36KB輕量易部署便于對照代碼理解p-Hub中位與分配協(xié)同優(yōu)化的核心思路。已有257人學(xué)習(xí)資源附有運(yùn)行效果圖與測試數(shù)據(jù)phlap_20.mat可輔助驗(yàn)證算法性能并開展參數(shù)調(diào)整實(shí)驗(yàn)。對正在做物流網(wǎng)絡(luò)規(guī)劃課程設(shè)計(jì)或畢業(yè)設(shè)計(jì)的同學(xué)這份精簡代碼能快速提供從問題建模到PSO求解的完整參考節(jié)省編碼與調(diào)試時間。1. p-Hub選址問題為什么值得用粒子群優(yōu)化算法求解物流網(wǎng)絡(luò)、航空貨運(yùn)和快遞分撥里p-Hub選址問題很容易碰到全網(wǎng)有幾十個節(jié)點(diǎn)要選出p個樞紐其余節(jié)點(diǎn)通過樞紐中轉(zhuǎn)目標(biāo)是總運(yùn)輸成本最低。樞紐選錯了干線不走捷徑全網(wǎng)成本立刻抬升而且這種設(shè)施一旦建成就很難調(diào)整。p-Hub是典型的NP-hard組合優(yōu)化問題節(jié)點(diǎn)一多窮舉所有樞紐組合根本不現(xiàn)實(shí)精確求解器也只能扛到二三十個節(jié)點(diǎn)。粒子群優(yōu)化算法在這個場景里一直是被反復(fù)驗(yàn)證的解法實(shí)現(xiàn)成本低、不需要求導(dǎo)、對離散選址問題只要做一層連續(xù)編碼就能跑起來加上Matlab寫起來順手很多仿真和預(yù)研項(xiàng)目都直接用這套方案。這篇文章沿一條主線往下講先建立p-Hub的數(shù)學(xué)模型和粒子編碼方式再給出完整的Matlab核心迭代代碼然后解決PSO參數(shù)調(diào)整和早熟問題最后擴(kuò)展到帶容量約束的版本和驗(yàn)證方法。適合要用Matlab快速跑通p-Hub場景的工程師也適合正在做畢業(yè)設(shè)計(jì)、需要一套可復(fù)現(xiàn)代碼的研究者。2. 從數(shù)學(xué)模型到編碼p-Hub選址問題的定義與粒子表示方法2.1 p-Hub選址問題的數(shù)學(xué)模型與目標(biāo)函數(shù)先定義標(biāo)準(zhǔn)p-Hub問題有n個節(jié)點(diǎn)節(jié)點(diǎn)i到j(luò)之間有一個流量需求W(i,j)以及單位運(yùn)輸成本D(i,j)。需要從n個節(jié)點(diǎn)里選出p個作為樞紐。非樞紐節(jié)點(diǎn)之間的流量必須經(jīng)過樞紐中轉(zhuǎn)具體路徑是起點(diǎn)i先送到樞紐k再由樞紐k送到樞紐m最后由樞紐m送到終點(diǎn)j。樞紐之間單位運(yùn)輸成本享受折扣系數(shù)α0到1之間用來體現(xiàn)干線規(guī)模效應(yīng)。目標(biāo)是最小化全網(wǎng)總運(yùn)輸成本寫成標(biāo)準(zhǔn)形式min sum_i sum_j W(i,j) * ( sum_k X(i,k)*D(i,k) alpha * sum_k sum_m X(i,k)*X(j,m)*D(k,m) sum_m X(j,m)*D(j,m) )X(i,k)是0-1變量表示節(jié)點(diǎn)i是否接入樞紐k。約束包括每個節(jié)點(diǎn)恰好接入一個樞紐被選作樞紐的節(jié)點(diǎn)個數(shù)是p。當(dāng)節(jié)點(diǎn)i本身是樞紐時D(i,i)0接入成本自動消失所以公式不需要額外判斷。這個模型的可行解數(shù)量是排列組合量級的還想同時決定“哪些節(jié)點(diǎn)當(dāng)樞紐”和“各自接入哪個樞紐”搜索空間非常大。粒子群優(yōu)化算法正好擅長在連續(xù)空間中做群體搜索關(guān)鍵是怎么把離散選址表達(dá)成粒子的實(shí)數(shù)位置。2.2 粒子編碼連續(xù)實(shí)數(shù)向量如何表達(dá)離散選址決策粒子群優(yōu)化算法直接處理連續(xù)值向量不能直接輸出“哪幾個點(diǎn)是樞紐”。常見做法是給每個節(jié)點(diǎn)一個實(shí)數(shù)“優(yōu)先級”或“權(quán)重”粒子位置x是n維向量每一維對應(yīng)一個節(jié)點(diǎn)。解出x之后按數(shù)值從大到小排序取前p個索引當(dāng)作樞紐集合其余節(jié)點(diǎn)再按距離最近或成本最低接入某個樞紐。這種編碼有兩個優(yōu)點(diǎn)。第一粒子的位置變化是連續(xù)的PSO的速度-位置更新公式可以原封不動使用。第二最終決策只依賴于節(jié)點(diǎn)間的相對排名不依賴絕對數(shù)值大小因此初始化時取值范圍可以放得比較寬比如0到10之間的均勻分布。需要注意一點(diǎn)這種編碼天然保證了每個粒子都對應(yīng)一個可行樞紐集合不會出現(xiàn)“解的p個樞紐重復(fù)”的問題因?yàn)榕判蛳聵?biāo)不會重復(fù)。2.3 適應(yīng)度函數(shù)用Matlab計(jì)算全網(wǎng)絡(luò)運(yùn)輸成本適應(yīng)度函數(shù)是整個PSO唯一評估解質(zhì)量的入口寫錯一個下標(biāo)后面全白做。我的習(xí)慣是把“從粒子x解碼出樞紐集合”和“根據(jù)樞紐集合計(jì)算成本”拆成兩個函數(shù)這樣后面做枚舉驗(yàn)證、局部搜索都能復(fù)用同一個計(jì)算核心。function cost evaluateFitness(x, W, D, p, alpha) % x: 1 x n 向量每個節(jié)點(diǎn)一個實(shí)數(shù)值表示被選為樞紐的傾向 % W: n x n 流量矩陣W(i,j) 是從 i 到 j 的運(yùn)輸量 % D: n x n 距離或成本矩陣 % p: 樞紐數(shù)量 % alpha: 樞紐間折扣系數(shù) [~, idx] sort(x, descend); hubs sort(idx(1:p)); cost evalHubs(hubs, W, D, alpha); end function cost evalHubs(hubs, W, D, alpha) n size(D, 1); cost 0; for i 1:n for j 1:n if i j || W(i, j) 0 continue; end tmp D(i, hubs) alpha * D(hubs, hubs) D(hubs, j); cost cost W(i, j) * min(tmp(:)); end end endevalHubs是整個計(jì)算的核心。D(i,hubs)是1×p的行向量alpha*D(hubs,hubs)是p×p矩陣D(hubs,j)是p×1列向量。三者相加時Matlab會自動做隱式擴(kuò)展得到一個p×p矩陣矩陣的每個元素恰好對應(yīng)“i經(jīng)樞紐k到樞紐m再到j(luò)”的完整路徑成本。min(tmp(:))取其中的最小值就是od對(i,j)在當(dāng)前樞紐集合下的最優(yōu)繞行路徑。當(dāng)i或j本身是樞紐時D(i,i)0或D(j,j)0所以不需要額外判斷分支。evaluateFitness先按粒子位置排序取樞紐再調(diào)用evalHubs。后面要枚舉所有組合、寫局部搜索時直接調(diào)evalHubs即可不需要把排序邏輯重復(fù)寫一遍。參數(shù)方面W和D必須保證節(jié)點(diǎn)編號順序一致alpha一般設(shè)0.6到0.9太小會導(dǎo)致所有流量都擠在同一條樞紐干線太大又體現(xiàn)不出樞紐中轉(zhuǎn)的優(yōu)勢。3. 用Matlab從零實(shí)現(xiàn)粒子群優(yōu)化算法求解p-Hub選址核心迭代代碼3.1 數(shù)據(jù)準(zhǔn)備與距離/流量矩陣構(gòu)造開始寫PSO主循環(huán)之前先把D和W準(zhǔn)備到位。常見做法有兩種一是從CSV讀入現(xiàn)成矩陣二是用隨機(jī)數(shù)據(jù)驗(yàn)證算法正確性。下面的例子演示隨機(jī)生成一個20節(jié)點(diǎn)的測試集坐標(biāo)隨機(jī)后按歐氏距離計(jì)算D流量W用隨機(jī)整數(shù)填充。n 20; coords rand(n, 2) * 100; D sqrt((coords(:,1) - coords(:,1)).^2 ... (coords(:,2) - coords(:,2)).^2); W randi([1, 50], n, n); W(1:n1:end) 0;D是對稱矩陣W故意不要求對稱可以模擬現(xiàn)實(shí)中往返流量不一致的情況。W(1:n1:end)0這行用了Matlab的線性索引把對角線元素全部清零消除節(jié)點(diǎn)到自身的流量。如果手頭有真實(shí)OD矩陣用readmatrix(flows.csv)直接讀入即可只要保證D和W的維度一致、節(jié)點(diǎn)順序一致就行。3.2 PSO主循環(huán)速度更新、位置更新與最優(yōu)解記錄有了適應(yīng)度函數(shù)主循環(huán)的框架非常標(biāo)準(zhǔn)。這里采用線性遞減慣性權(quán)重前期w大粒子探索范圍廣后期w小集中在局部精細(xì)搜索。p-Hub適應(yīng)度曲面存在大量平臺和跳變這種策略比固定權(quán)重更容易跳出局部最優(yōu)。% PSO 參數(shù) p 3; alpha 0.75; swarmSize 30; maxIter 200; wMax 0.9; wMin 0.4; c1 1.5; c2 1.5; vMax 2; % 初始化 pos rand(swarmSize, n) * 10; vel zeros(swarmSize, n); pBest pos; pBestCost inf(1, swarmSize); gBestCost inf; for k 1:swarmSize c evaluateFitness(pos(k, :), W, D, p, alpha); pBestCost(k) c; if c gBestCost gBestCost c; gBest pos(k, :); end end % 迭代 history zeros(1, maxIter); for iter 1:maxIter w wMax - (wMax - wMin) * iter / maxIter; for k 1:swarmSize r1 rand(1, n); r2 rand(1, n); vel(k, :) w * vel(k, :) ... c1 * r1 .* (pBest(k, :) - pos(k, :)) ... c2 * r2 .* (gBest - pos(k, :)); vel(k, :) max(min(vel(k, :), vMax), -vMax); pos(k, :) pos(k, :) vel(k, :); c evaluateFitness(pos(k, :), W, D, p, alpha); if c pBestCost(k) pBest(k, :) pos(k, :); pBestCost(k) c; end if c gBestCost gBest pos(k, :); gBestCost c; end end history(iter) gBestCost; end [~, idx] sort(gBest, descend); bestHubs sort(idx(1:p)); fprintf(最優(yōu)樞紐: %s\n, mat2str(bestHubs)); fprintf(最優(yōu)成本: %.2f\n, gBestCost);速度更新公式拆開看有三項(xiàng)w*vel是慣性項(xiàng)讓粒子延續(xù)之前的運(yùn)動趨勢c1*r1.*(pBest-pos)是自我認(rèn)知項(xiàng)把粒子拉向自己歷史上的最優(yōu)位置c2*r2.*(gBest-pos)是社會認(rèn)知項(xiàng)讓粒子向全局最優(yōu)靠攏。r1和r2每次隨機(jī)生成保證搜索有探索性。速度截?cái)嘤胢ax(min(...),...)一步完成防止粒子速度超出合理范圍。vMax取2時粒子每次最多移動2個單位而初始位置范圍是0到10相當(dāng)于每一步最多跨過20%的搜索空間這個比例在p-Hub問題里收斂效果較好。注意gBest和pBest保存的是連續(xù)位置向量不是樞紐編號。每次評估時通過排序解碼這樣的好處是位置向量保持連續(xù)下一輪速度更新可以繼續(xù)使用。history數(shù)組在每輪迭代末尾記錄全局最優(yōu)成本用于后續(xù)繪制收斂曲線。3.3 檢查收斂曲線與第一版結(jié)果第一版跑通后先別急著調(diào)參畫收斂曲線判斷要不要改figure; plot(history, LineWidth, 1.5); xlabel(迭代次數(shù)); ylabel(全局最優(yōu)成本); grid on;比較典型的曲線是前30代快速下降中后期變成平緩的階梯。如果曲線一直在鋸齒狀波動且成本不下降優(yōu)先檢查vMax是否太大導(dǎo)致粒子在最優(yōu)位置附近來回震蕩如果曲線早早變平說明w衰減太快或c2分量過大粒子過早被拉進(jìn)局部最優(yōu)。第一版結(jié)果可以和后面的枚舉法做交叉驗(yàn)證確認(rèn)成本計(jì)算邏輯沒有原則性錯誤。4. 粒子群優(yōu)化算法參數(shù)怎么設(shè)w、c1、c2、vMax與種群規(guī)模的取舍4.1 慣性權(quán)重與速度上限先解決“收斂慢”問題p-Hub的目標(biāo)函數(shù)結(jié)構(gòu)特殊樞紐集合只要換一個節(jié)點(diǎn)成本就可能跳變一大截。在這種曲面上PSO最常見的兩個毛病就是收斂慢和早熟。收斂慢時先動慣性權(quán)重。標(biāo)準(zhǔn)做法是線性遞減wMax0.9到wMin0.4這個區(qū)間是工程中反復(fù)驗(yàn)證過的默認(rèn)值。如果跑到150代成本還在明顯下降說明搜索步長不夠把wMin降到0.3或者把maxIter拉到300。早熟則反向處理提高wMin到0.5保持后期一定的探索能力。vMax的影響經(jīng)常被忽略。pos初始化成0到10之間的隨機(jī)數(shù)如果vMax也設(shè)成10粒子一次速度更新就能從搜索空間一端飛到另一端排序結(jié)果劇烈抖動收斂曲線自然不好看。常見做法是vMax取位置范圍的15%到25%這里設(shè)2合適。p-Hub有個特殊性位置絕對值并不重要節(jié)點(diǎn)間的相對排名才決定樞紐集合所以vMax過大會直接導(dǎo)致排名次序頻繁跳變這會比普通連續(xù)優(yōu)化問題更敏感。4.2 參數(shù)對照表不同組合下p-Hub求解結(jié)果的差異下面用一個n20、p3、alpha0.75的測試集固定隨機(jī)種子每組參數(shù)跑20次后看平均結(jié)果。數(shù)值是示意性的重點(diǎn)看相對趨勢。參數(shù)組合w設(shè)置c1/c2平均最終成本平均收斂代數(shù)線性遞減0.9到0.41.5/1.55832132固定 w0.70.7恒定1.5/1.56015210固定 w0.50.5恒定2.0/1.56387256線性遞減的組合收斂代數(shù)最短平均最終成本也最低。固定小w的組雖然最終也會收斂但后期步長不夠不容易跳出局部最優(yōu)固定大w的組探索能力強(qiáng)卻缺少后期精細(xì)搜索最終成本偏高。c1和c2的比值影響也很大c1太大會讓每個粒子只顧自己的歷史位置群體收斂很差c2太大會讓粒子過早朝某個局部最優(yōu)集中。除非有明確實(shí)驗(yàn)依據(jù)否則從c1c21.5開始調(diào)是比較省事的路徑。4.3 早熟收斂的識別與處理早熟的特征是收斂曲線很早就不動了但你知道成本明顯高于枚舉驗(yàn)證值。一個簡單判斷方法是同一組參數(shù)跑20次如果最優(yōu)成本的標(biāo)準(zhǔn)差很小成本卻高于已知驗(yàn)證值基本就是早熟。處理可以從三個方向入手。第一隨機(jī)重置部分粒子。如果全局最優(yōu)連續(xù)20代沒有任何變化就把20%的粒子位置和速度重新初始化但pBest保留這樣新粒子一旦找到更優(yōu)解會立即更新個體最優(yōu)和全局最優(yōu)。if iter 20 history(iter) history(iter - 20) resetIdx randperm(swarmSize, round(swarmSize * 0.2)); pos(resetIdx, :) rand(length(resetIdx), n) * 10; vel(resetIdx, :) zeros(length(resetIdx), n); end這段代碼放在每輪迭代的末尾。history(iter)history(iter-20)判斷的是全局最優(yōu)成本連續(xù)20代未變化而不是兩個粒子成本恰好相等。重置后不修改pBest和gBest避免丟失已獲得的搜索成果。第二對全局最優(yōu)做局部搜索具體方法在第5章展開。第三如果項(xiàng)目允許用多輪重啟的方式跑20次取最優(yōu)不要指望單次運(yùn)行一定能找到全局最優(yōu)。也可以調(diào)一次matlab優(yōu)化工具箱里的particleswarm作為對照baseline但p-Hub的離散解碼邏輯必須自己寫工具箱只負(fù)責(zé)連續(xù)搜索部分。5. 擴(kuò)展成帶容量約束的p-Hub選址懲罰函數(shù)與局部搜索5.1 容量約束樞紐轉(zhuǎn)運(yùn)量上限的建模實(shí)際項(xiàng)目里樞紐不是無限容量的。分撥中心每日處理量、機(jī)場跑道起降架次都有上限這個約束在基礎(chǔ)p-Hub模型里沒有。加入容量約束后每個樞紐k經(jīng)過的總流量不能超過容量Cap(k)。流量包括三部分從其他節(jié)點(diǎn)接入的流量、從本樞紐轉(zhuǎn)出的流量、以及樞紐間中轉(zhuǎn)流量。完整計(jì)算吞吐量比較復(fù)雜工程上通常只統(tǒng)計(jì)接入流量作為近似因?yàn)榇蠖鄶?shù)場景下接入流量已經(jīng)能反映樞紐負(fù)荷。5.2 在Matlab中實(shí)現(xiàn)懲罰函數(shù)實(shí)現(xiàn)容量約束最省事的方式是懲罰函數(shù)法不需要改動PSO主體只需要寫一個帶約束的適應(yīng)度函數(shù)。function cost evaluateFitnessWithCapacity(x, W, D, p, alpha, cap, lambda) % cap: 1 x p 向量每個樞紐的容量上限 % lambda: 懲罰系數(shù) n size(D, 1); [~, idx] sort(x, descend); hubs sort(idx(1:p)); hubFlow zeros(1, p); for i 1:n if ismember(i, hubs) continue; end [~, k] min(D(i, hubs)); hubFlow(k) hubFlow(k) sum(W(i, :)); end overflow max(0, hubFlow - cap); penalty lambda * sum(overflow); cost evalHubs(hubs, W, D, alpha) penalty; endhubFlow的計(jì)算只統(tǒng)計(jì)了起點(diǎn)接入流量。實(shí)際項(xiàng)目中如果要更精確還需要把離開流量W(:,i)和中轉(zhuǎn)流量也累加到對應(yīng)樞紐上邏輯相同只是多幾層fro循環(huán)。懲罰系數(shù)lambda的取值很關(guān)鍵太小約束形同虛設(shè)太大粒子全部被壓在可行域邊界附近搜索效率低。經(jīng)驗(yàn)做法是先跑一次不帶約束的解算出最大超限量再把lambda設(shè)為原始成本的1到2倍除以這個超限量。5.3 對全局最優(yōu)做局部搜索處理組合爆炸的常用技巧不帶約束的標(biāo)準(zhǔn)PSO在p-Hub上已經(jīng)有不錯的解質(zhì)量加容量約束后可行域被壓縮粒子找到可行解更難。這時對全局最優(yōu)做局部搜索是投入產(chǎn)出比很高的手段。做法是從gBest解碼出樞紐集合hubs遍歷所有“替換一個樞紐”的組合把p個樞紐中的某一個替換成非樞紐節(jié)點(diǎn)如果成本下降就更新。function improved localSearch(hubs, W, D, alpha) n size(D, 1); nonHubs setdiff(1:n, hubs); improved hubs; improvedCost evalHubs(hubs, W, D, alpha); for i 1:length(hubs) for j 1:length(nonHubs) cand hubs; cand(i) nonHubs(j); c evalHubs(cand, W, D, alpha); if c improvedCost improvedCost c; improved cand; end end end end這個雙重循環(huán)的復(fù)雜度是p*(n-p)次evalHubs調(diào)用n20時開銷可以忽略n100時也只在特定輪次執(zhí)行。調(diào)用時可以每20代做一次找到更優(yōu)解后用簡單方式回寫編碼把gBest向量除新樞紐外的分量都設(shè)為0新樞紐分量設(shè)為100。這樣后續(xù)粒子仍然能參考這個更優(yōu)的gBest排序解碼出來的結(jié)果就是局部搜索后的樞紐集合。6. 驗(yàn)證Matlab實(shí)現(xiàn)正確性的3個技巧枚舉對比、隨機(jī)種子與穩(wěn)健性測試6.1 用枚舉法驗(yàn)證小規(guī)模問題實(shí)現(xiàn)完成后先別急著在大數(shù)據(jù)集上跑先用小規(guī)模枚舉驗(yàn)證正確性。取n10、p3所有樞紐組合只有C(10,3)120種窮舉完全可行。comb nchoosek(1:n, p); bestEnum inf; for i 1:size(comb, 1) c evalHubs(comb(i, :), W, D, alpha); if c bestEnum bestEnum c; bestEnumHubs comb(i, :); end end把枚舉結(jié)果和PSO最終輸出對比如果成本一致說明評估函數(shù)和編碼邏輯基本正確如果不一致先檢查evalHubs里D(i,hubs)的行列方向和alpha*D(hubs,hubs)是否寫反。這個驗(yàn)證步驟只需要幾秒鐘但能避免后面所有實(shí)驗(yàn)建立在錯誤代碼上。6.2 固定隨機(jī)種子與多輪重啟PSO是隨機(jī)算法單次運(yùn)行結(jié)果沒有統(tǒng)計(jì)意義。調(diào)試期間先用rng(2026)固定隨機(jī)種子保證每次跑出來一樣方便定位代碼改動對結(jié)果的影響。鎖定邏輯正確后再在相同參數(shù)下跑20到30輪記錄每輪的最優(yōu)成本計(jì)算min、median和標(biāo)準(zhǔn)差。多輪重啟的價值不是“挑一次運(yùn)氣好的結(jié)果”而是看結(jié)果的分布是否集中。如果標(biāo)準(zhǔn)差很大說明參數(shù)設(shè)置或模型結(jié)構(gòu)本身對初值敏感需要結(jié)合第4章的早熟處理手段來改進(jìn)。6.3 對流量矩陣做穩(wěn)健性測試數(shù)據(jù)噪聲在真實(shí)業(yè)務(wù)中不可避免。把流量矩陣W乘上一個小的擾動因子例如W .* (1 0.05 * rand(n, n))然后重新求解觀察最優(yōu)樞紐集合是否劇烈變化。如果p個樞紐動不動換成完全不同的一組說明模型對這個數(shù)據(jù)集的區(qū)分度不夠或者存在多條成本接近的替代方案。遇到這種情況回到數(shù)據(jù)源檢查流量矩陣?yán)锸欠裼挟惓4蟮腛D對把它單獨(dú)拿出來做敏感性分析比直接改算法參數(shù)更有效。本文還有配套的精品資源點(diǎn)擊獲取