優(yōu)化Matlab實(shí)現(xiàn):Pareto前沿與算法參數(shù)詳解)
簡介NSGA-II算法的Matlab實(shí)現(xiàn)面向多目標(biāo)進(jìn)化計算初學(xué)者與科研人員提供一套模塊化、易運(yùn)行的代碼骨架。實(shí)現(xiàn)中包含非支配排序、擁擠距離賦值、精英策略保存以及遺傳操作等關(guān)鍵環(huán)節(jié)主程序與各功能函數(shù)分離邏輯清晰讀者可單獨(dú)調(diào)試每個模塊也可以替換測試函數(shù)研究不同問題下的算法表現(xiàn)。測試函數(shù)覆蓋二維ZDT1至ZDT6和三維DTLZ1至DTLZ6共十二個典型算例每個算例均附帶測試數(shù)據(jù)文本與fig仿真圖便于對照分析Pareto最優(yōu)解的分布與收斂過程從二維到三維問題難度遞進(jìn)適合循序漸進(jìn)驗(yàn)證算法擴(kuò)展能力。壓縮包共27個文件其中10個m代碼文件負(fù)責(zé)核心計算10個txt文件提供標(biāo)準(zhǔn)輸入數(shù)據(jù)7個fig文件存儲實(shí)驗(yàn)結(jié)果圖像整體僅2.41MB輕量且便于攜帶。這項(xiàng)工作已被3208人瀏覽學(xué)習(xí)適合用于NSGA-II原理驗(yàn)證、畢業(yè)設(shè)計參考和新型多目標(biāo)算法的對比測試代碼風(fēng)格簡明注釋清晰便于二次開發(fā)。1. NSGA-II的Matlab實(shí)現(xiàn)多目標(biāo)優(yōu)化里被驗(yàn)證最多次的框架之一NSGA-II多目標(biāo)優(yōu)化框架核心三板斧是非支配排序、擁擠距離與精英策略這套組合在工程和學(xué)術(shù)場景里被反復(fù)驗(yàn)證了十幾年。如果你手里有一個問題存在多個互相沖突的目標(biāo)——比如成本要低、交付要快、可靠性還要高傳統(tǒng)加權(quán)求和只能給一個解而NSGA-II一次給出一整條Pareto前沿讓你在結(jié)果里挑最優(yōu)折中。這篇筆記面向兩類人剛接觸多目標(biāo)優(yōu)化的新手需要一份能跑通全流程的Matlab參考實(shí)現(xiàn)以及已跑過代碼但被參數(shù)和邊界坑折騰過的熟手想快速定位問題出在哪個算子。我拆過的項(xiàng)目里凡是涉及路徑、調(diào)度、參數(shù)整定、資源配置的場景NSGA-II基本都能當(dāng)主力算法用跑通了再換成別的算法也不遲。2. 核心模塊拆解非支配排序、擁擠距離與精英策略的協(xié)作邏輯2.1 非支配排序到底在排什么多目標(biāo)優(yōu)化和單目標(biāo)最大的區(qū)別是沒有唯一的“最好”只有“不被別人支配”。個體 p 支配 q意味著 p 在所有目標(biāo)上都不比 q 差且至少在一個目標(biāo)上嚴(yán)格優(yōu)于 q。比如最小化成本的同時最小化延誤時間方案 A 成本 100、延誤 2 小時方案 B 成本 120、延誤 3 小時那 A 直接支配 BB 可以扔了。但 A 成本 100、延誤 3 小時B 成本 120、延誤 2 小時兩者各贏一項(xiàng)互不支配都該保留。非支配排序就是把種群按“誰支配誰”分成一層層的前沿。第一層是所有不被任何個體支配的解第二層是去掉第一層后剩下的不被任何剩余個體支配的解以此類推。層數(shù)越靠前解的質(zhì)量越高。實(shí)現(xiàn)時先兩兩比較 N 個個體給每個個體記錄兩個信息n_p 表示有多少個體支配它S_p 表示它支配了哪些個體。function [fronts, rank] non_dominated_sort(f) N size(f, 1); M size(f, 2); n_p zeros(N, 1); % 被支配計數(shù) S_p cell(N, 1); % 支配集合 fronts cell(N, 1); % 存放各前沿個體索引 rank zeros(N, 1); for i 1:N for j i1:N if all(f(i,:) f(j,:)) any(f(i,:) f(j,:)) S_p{i} [S_p{i}, j]; n_p(j) n_p(j) 1; elseif all(f(j,:) f(i,:)) any(f(j,:) f(i,:)) S_p{j} [S_p{j}, i]; n_p(i) n_p(i) 1; end end end front_idx 1; for i 1:N if n_p(i) 0 fronts{front_idx} [fronts{front_idx}, i]; rank(i) front_idx; end end while ~isempty(fronts{front_idx}) next_front []; for i fronts{front_idx} for j S_p{i} n_p(j) n_p(j) - 1; if n_p(j) 0 next_front [next_front, j]; rank(j) front_idx 1; end end end front_idx front_idx 1; fronts{front_idx} next_front; end fronts( front_idx:end ) []; end邏輯上整個排序分三步先兩兩比較得到 n_p 和 S_p再找出 n_p 為 0 的第一層個體然后迭代式地從第一層出發(fā)逐個訪問它的支配集合被支配計數(shù)減到 0 就劃入下一層。關(guān)鍵點(diǎn)在于每一層都是“當(dāng)前剩余個體中的非支配集”而不是全局的。復(fù)雜度方面最壞情況是 O(MN2)M 是目標(biāo)個數(shù)N 是種群大小。三個目標(biāo)以內(nèi)跑起來沒什么壓力五目標(biāo)以上每代排序的開銷就明顯漲上去了這也是為什么高維問題要換 NSGA-III 而不是硬扛 NSGA-II。2.2 擁擠距離保證Pareto前沿分布均勻的唯一手段非支配排序只解決了“分層”沒解決“同一層里留誰”。同一層的個體質(zhì)量等價但如果隨意選前沿會擠在一塊覆蓋不到兩端。擁擠距離做的事很簡單對同一非支配層的個體按每個目標(biāo)函數(shù)分別排序給每個個體算它兩邊鄰居的距離邊界個體的距離直接設(shè)成無窮大。function dist crowding_distance(front, f) n length(front); M size(f, 2); dist zeros(1, n); for m 1:M f_m f(front, m); [f_sorted, idx] sort(f_m); dist(idx(1)) inf; dist(idx(end)) inf; f_range f_sorted(end) - f_sorted(1); for k 2:n-1 if f_range 0 dist(idx(k)) dist(idx(k)) ... (f_sorted(k1) - f_sorted(k-1)) / f_range; end end end end這里有兩個容易忽略的設(shè)計細(xì)節(jié)一是邊界個體距離設(shè)無窮大這保證了前沿的兩個端點(diǎn)一定會被保留下來否則每次迭代兩端都會被內(nèi)部個體擠掉前沿越縮越窄二是每個目標(biāo)單獨(dú)歸一化后再累加距離f_range 除以目標(biāo)實(shí)際取值范圍避免量綱差異——比如成本是萬元級、時間是分鐘級如果不歸一化成本目標(biāo)的間距會完全淹沒時間目標(biāo)的信息。我一般會在排序后把每層的最優(yōu)個體單獨(dú)打印出來看一眼如果邊界個體頻繁消失第一反應(yīng)就是檢查擁擠距離的 inf 賦值是否生效。2.3 精英策略父代子代合并后怎么選下一代單目標(biāo)遺傳算法里精英策略通常是“保留歷史最優(yōu)解”。但在多目標(biāo)里只保留一個最優(yōu)解沒有意義你得保留一整條有代表性的前沿。NSGA-II 的做法是每代生成子代后把父代 N 個個體和子代 N 個個體合并成 2N 的候選池再對這個池子做一次非支配排序和擁擠距離計算然后從第一層開始依次裝填下一代同一層內(nèi)按擁擠距離從大到小取直到填滿 N 個。這樣做的價值在于父代里哪怕有個解在前沿上位置很偏、暫時沒有在遺傳操作中被選中只要它仍然非支配合并后依然有機(jī)會被保留。多目標(biāo)優(yōu)化里丟失一個折中解比單目標(biāo)丟失一個最優(yōu)解代價更大。因?yàn)闆]有“全局最優(yōu)”兜底丟失的解可能代表某個實(shí)際決策偏好比如成本低但時間長的方案在客戶只關(guān)心成本時就是金礦。精英策略也直接影響收斂速度和穩(wěn)定性的平衡。合并后重排序會讓種群淘汰更保守代價是每代排序的計算量翻倍。工程上為了性能有的人會改成“父代直接保留前k%子代占剩余槽位”這種簡化在目標(biāo)數(shù)少時可行但我調(diào)試過的幾個項(xiàng)目里一旦出現(xiàn)前沿分布崩壞大多是因?yàn)檫@種偷懶省掉了合并重排。3. Matlab代碼實(shí)戰(zhàn)從主循環(huán)到選擇、交叉與變異算子3.1 工程目錄與文件結(jié)構(gòu)拿到 NSGA-II 的 Matlab 代碼包先別急著跑把文件結(jié)構(gòu)理一遍更穩(wěn)妥。一個能長期復(fù)用的實(shí)現(xiàn)至少應(yīng)該拆成六個文件每個文件只干一件事后面替換目標(biāo)函數(shù)、調(diào)整算子都方便nsga2_matlab/ ├─ main.m % 主循環(huán)與全部參數(shù)設(shè)置 ├─ evaluate_objective.m % 目標(biāo)函數(shù)替換成實(shí)際問題即可 ├─ non_dominated_sort.m % 非支配排序 ├─ crowding_distance.m % 擁擠距離計算 ├─ tournament_select.m % 錦標(biāo)賽選擇 └─ genetic_operator.m % SBX交叉 多項(xiàng)式變異我不太建議把所有邏輯寫進(jìn)一個 main.m 里跑通就完事頭兩版能跑第三版開始改參數(shù)、換目標(biāo)函數(shù)時你會發(fā)現(xiàn)自己陷進(jìn)一個黑匣子改一處判斷就要通讀全文件。3.2 參數(shù)初始化每一個值都有它的出處先貼一組用于中等規(guī)模問題的默認(rèn)參數(shù)十變量級、兩個目標(biāo)都能用% main.m 參數(shù)區(qū) N 50; % 種群大小10變量問題建議 40~100 gen 200; % 最大迭代代數(shù)先跑到200觀察收斂曲線 pc 0.9; % 交叉概率實(shí)數(shù)編碼常用0.9別低于0.8 n_var 10; % 決策變量個數(shù) pm 1 / n_var; % 變異概率常用 1/變量數(shù) eta_c 20; % SBX擴(kuò)展指數(shù)控制子代離父代的距離 eta_m 20; % 多項(xiàng)式變異分布指數(shù) lb zeros(1, n_var); % 決策變量下界 ub ones(1, n_var); % 決策變量上界N 和 gen 是最容易拍腦袋設(shè)的兩個數(shù)。N 太小前面說過多樣性撐不起來N 太大每代排序和評估的耗時線性上漲很多真實(shí)工程里評估目標(biāo)函數(shù)要跑仿真一次幾秒N 設(shè) 200 就意味著一代最壞要跑 400 次仿真。pm1/n_var 來自遺傳算法常用的經(jīng)驗(yàn)規(guī)則變量數(shù)越多基因越容易被打亂變異概率應(yīng)隨維度升高而降低。eta_c 和 eta_m 這兩個指數(shù)我后面單獨(dú)講它們直接決定子代偏離父代的幅度。3.3 主循環(huán)選擇、交叉、變異、合并周而復(fù)始% 初始化均勻隨機(jī)生成第一代種群 pop rand(N, n_var) .* (ub - lb) lb; f zeros(N, 2); for i 1:N f(i, :) evaluate_objective(pop(i, :)); end [fronts, rank] non_dominated_sort(f); crowd zeros(N, 1); for k 1:length(fronts) crowd(fronts{k}) crowding_distance(fronts{k}, f); end for t 1:gen % 錦標(biāo)賽選擇選出父代配對池 [parent_idx, ~] tournament_select(rank, crowd, N); % 遺傳算子生成子代 [child_pop, child_f] genetic_operator(... pop, parent_idx, f, lb, ub, pc, eta_c, eta_m); % 精英合并父代 子代 2N pop_all [pop; child_pop]; f_all [f; child_f]; % 對合并種群重排 [fronts, rank] non_dominated_sort(f_all); crowd zeros(2*N, 1); for k 1:length(fronts) crowd(fronts{k}) crowding_distance(fronts{k}, f_all); end % 填裝下一代從第一層開始取滿 N 個 next_pop []; k 1; while length(next_pop) length(fronts{k}) N next_pop [next_pop, fronts{k}]; k k 1; end % 最后一層按擁擠距離降序補(bǔ)足 if length(next_pop) N last fronts{k}; last_crowd crowd(last); [~, idx] sort(last_crowd, descend); next_pop [next_pop, last(idx(1:N-length(next_pop)))]; end pop pop_all(next_pop, :); f f_all(next_pop, :); end注意幾個工程細(xì)節(jié)。首先是 tournament_select 的輸入用的是 rank 和 crowd而不是原始目標(biāo)函數(shù)值。非支配層相當(dāng)于“排序優(yōu)先級”擁擠距離相當(dāng)于“同一優(yōu)先級內(nèi)的多樣度”這兩者組合出的虛擬適應(yīng)度比任何加權(quán)評分都更客觀。其次是子代種群數(shù)量必須等于父代種群數(shù)量 N這樣才能保證合并后是 2N否則填裝下代時“最后一層補(bǔ)足”的分支邏輯會翻車。最后是比賽選擇每次從種群中隨機(jī)抽兩個個體選 rank 更靠前或同層擁擠距離更大的那個進(jìn)配對池tournament size 保持 2 別加大加大會讓最優(yōu)個體被頻繁選中種群多樣性下滑。3.4 SBX與多項(xiàng)式變異這兩個算子決定子代長什么樣SBX模擬二進(jìn)制交叉雖然名字帶“二進(jìn)制”實(shí)際處理的是實(shí)數(shù)編碼。它的核心是生成一個隨機(jī)數(shù) u再映射成擴(kuò)展系數(shù) λλ 決定子代相對父代的位置。公式里的 η_c 越大λ 越接近 1子代就越貼近父代均值搜索越保守。function [c1, c2] sbx(p1, p2, lb, ub, eta_c) u rand; if u 0.5 lambda (2 * u)^(1 / (eta_c 1)); else lambda (1 / (2 * (1 - u)))^(1 / (eta_c 1)); end c1 0.5 * ((1 lambda) * p1 (1 - lambda) * p2); c2 0.5 * ((1 - lambda) * p1 (1 lambda) * p2); % 越界裁剪直接收緊到邊界 c1 min(max(c1, lb), ub); c2 min(max(c2, lb), ub); end多項(xiàng)式變異類似通過隨機(jī)數(shù) u 計算擾動幅度 delta然后對單個解的分量做擾動function c poly_mutate(p, lb, ub, eta_m) u rand; if u 0.5 delta (2 * u)^(1 / (eta_m 1)) - 1; else delta 1 - (2 * (1 - u))^(1 / (eta_m 1)); end c p delta .* (ub - lb); c min(max(c, lb), ub); end兩個算子的細(xì)節(jié)是同一個坑分母上的 eta_c1 和 eta_m1 一旦漏掉 1指數(shù)會大得夸張子代直接飛越整個搜索空間結(jié)果全是邊界值。我見過不止一次有人在手寫版本里把 (2u)^(1/(eta_c1)) 誤寫成 (2u)^eta_c導(dǎo)致前幾代種群就糊到邊界上。越界處理用 min/max 裁剪是工程上最省心且穩(wěn)定的做法不要在這里做復(fù)雜的反射或重新采樣裁剪后靠變異去恢復(fù)多樣性即可。4. 參數(shù)怎么調(diào)才不翻車目標(biāo)函數(shù)封裝與六組參數(shù)經(jīng)驗(yàn)值4.1 先把目標(biāo)函數(shù)封裝成標(biāo)準(zhǔn)接口拿到代碼包后第一件事是換目標(biāo)函數(shù)。NSGA-II 的目標(biāo)函數(shù)接口約定很簡單輸入一行決策變量 x輸出一行目標(biāo)函數(shù)值 f每個目標(biāo)對應(yīng)一列。所有目標(biāo)默認(rèn)按最小化處理如果你的實(shí)際指標(biāo)是“利潤最大化”直接取負(fù)號別在排序代碼里額外做反轉(zhuǎn)那會引入不必要的混亂。function f evaluate_objective(x) % 輸入x 為 1 x n_var 的行向量 % 輸出f 為 1 x M 的行向量M 為目標(biāo)個數(shù) % 示例成本函數(shù)與時間函數(shù) cost sum(x.^2) 2; time (x(1) - 1)^2 sum(x(2:end).^2) 1; f [cost, time]; end注意 constraints 的處理。標(biāo)準(zhǔn) NSGA-II 的排序邏輯不管約束它只看目標(biāo)函數(shù)值。帶約束的問題最常見的做法是罰函數(shù)法把違反約束的程度乘以一個足夠大的懲罰系數(shù)加進(jìn)對應(yīng)目標(biāo)值里。工程上我習(xí)慣在 evaluate_objective 內(nèi)部做約束判斷違反約束時直接把該目標(biāo)函數(shù)值置為一個非常大的數(shù)讓它在非支配排序中被自然淘汰。但罰系數(shù)不要設(shè)成 NaN 或 InfNaN 會污染整個排序鏈Inf 在邊界判斷里也可能引發(fā)意外。4.2 參數(shù)表與調(diào)整方向參數(shù)建議起點(diǎn)調(diào)大/調(diào)小的實(shí)際影響什么情況動它種群大小 N50N 增大前沿更密但每代評估耗時線性上漲前沿點(diǎn)稀疏、分布斷檔時加大到 80~100迭代代數(shù) gen200太小收斂不完整前沿離真解遠(yuǎn)觀察前沿每 50 代還在明顯移動加到 500交叉概率 pc0.9小于 0.8 時新解產(chǎn)生慢種群容易被舊的局部解占據(jù)默認(rèn)保持 0.9一般不用動變異概率 pm1/n_var過大導(dǎo)致解像隨機(jī)噪聲過小導(dǎo)致早期陷入局部前沿保持 1/n_var變量維度特別低時可放寬SBX指數(shù) eta_c20越大子代越貼近父代探索范圍變小前沿覆蓋范圍差降到 10~15 提升探索變異指數(shù) eta_m20越大變異擾動越小局部細(xì)化更好收斂末期前沿粗糙提高到 50 做精細(xì)搜索eta_c 和 eta_m 是最值得花時間調(diào)的兩個參數(shù)但我建議不要一開始就調(diào)它們。先固定成默認(rèn)值把 N 和 gen 調(diào)到位再說。多目標(biāo)優(yōu)化里的參數(shù)耦合效應(yīng)很強(qiáng)N 不夠大時你把 eta_c 調(diào)得再低也沒用種群就那么點(diǎn)個體探索范圍再大也覆蓋不出完整前沿。反過來 N 太大而 eta_c 過小每一代都像重新初始化收斂速度會拖到讓人懷疑人生。4.3 決策變量邊界與量綱歸一化決策變量邊界處理是最容易被忽略的工程障礙。假設(shè)你的問題里有三個變量第一個取值范圍 [0, 1]第二個 [500, 5000]第三個 [0.01, 0.1]。SBX 和多項(xiàng)式變異的擾動幅度是按變量絕對尺度算的。eta_c20 產(chǎn)生的 λ 變化范圍在 0.5 到 2 之間對第一個變量可能擾動 0.5對第二個變量擾動 2500對第三個變量擾動半個數(shù)量級。結(jié)果就是大尺度變量主導(dǎo)整個尋優(yōu)過程小尺度變量幾乎原地不動。我一般會建議在算法內(nèi)部統(tǒng)一搜索 [0, 1] 區(qū)間評估時才映射到真實(shí)范圍% 搜索空間統(tǒng)一在 [0,1] x_norm pop(i, :); % 評估前映射到真實(shí)邊界 x_real lb x_norm .* (ub - lb); f(i, :) evaluate_objective(x_real);這段映射邏輯放在主循環(huán)的評估位置而交叉變異仍然對 x_norm 操作。這樣所有決策變量在遺傳算子眼里都是同一量綱SBX 的擴(kuò)展系數(shù)對所有變量施加相同比例的相對擾動。4.4 從單目標(biāo)思維里跳出來為什么加權(quán)求和不行很多第一次接觸 NSGA-II 的人會問我為什么不用權(quán)重把多個目標(biāo)加起來一次跑出一個解在目標(biāo)之間量綱一致且偏好明確的前提下加權(quán)求和確實(shí)省事但真實(shí)工程里目標(biāo)往往不可公度——成本是錢時間是小時可靠性是概率。權(quán)重怎么定本身就是另一個難題而且加權(quán)求和的致命弱點(diǎn)是當(dāng)Pareto前沿是凹的加權(quán)法無論如何組合權(quán)重都找不到前沿中間段的解這是運(yùn)籌學(xué)里被反復(fù)驗(yàn)證過的結(jié)論。NSGA-II 的價值是給了你一整條前沿之后你再根據(jù)業(yè)務(wù)偏好做決策。后面第 6 章我會講如何從 Paret解集中選出最終的那個解。理解了這個轉(zhuǎn)變你就不會在調(diào)試時問出“為什么最優(yōu)解不唯一”這種問題了。5. 常見問題排查四條血淚踩坑記錄5.1 坑一迭代沒幾代種群全擠成一團(tuán)現(xiàn)象Pareto 前沿上的點(diǎn)明顯聚集在中部兩端稀疏甚至沒有點(diǎn)種群多樣性下降得特別快。原因最常見的是 SBX 交叉的指數(shù)公式實(shí)現(xiàn)錯誤。把 (2u)^(1/(eta_c1)) 寫成了 (2u)^(eta_c1)λ 要么極大要么極小子代直接跳到父代兩邊很遠(yuǎn)的區(qū)域前幾代看著探索得開但下一代再交叉時這些邊界解被排序又淘汰種群迅速萎縮。另一個高頻原因是錦標(biāo)賽選擇的 tournament size 被設(shè)成大于 2比如取 4 甚至直接取全局最優(yōu)導(dǎo)致高 rank 個體被反復(fù)選中子代全是它們的后代。解決先用最簡單的雙變量測試函數(shù)跑一遍每次迭代把種群分布畫出來。交叉算子正確的情況下前 50 代前沿會從散點(diǎn)逐步收攏如果前 10 代就收成一團(tuán)基本可以斷定是 λ 公式寫錯。重寫時按我第 3 章的 sbx 函數(shù)對照一遍尤其注意 u 的隨機(jī)性——每個子代分量要重新生成 u不能一次性用同一個 u 給所有維度。5.2 坑二Pareto前沿兩端永遠(yuǎn)塌陷現(xiàn)象最后求出的前沿只有中間一段兩端缺失看起來像被人從兩邊各砍了一刀。原因擁擠距離計算里忘給邊界個體賦 inf。邊界個體的擁擠距離如果不設(shè)無窮大它們的距離值可能比內(nèi)部個體還小在精英合并填裝下一代時邊界點(diǎn)容易被同層的內(nèi)部點(diǎn)擠掉一代一代累積兩端越來越空。解決crowding_distance 函數(shù)里對每目標(biāo)排序后的第一個和最后一個索引直接賦值 inf。還有另一個細(xì)節(jié)如果某個目標(biāo)的 f_range 接近 0說明種群在這個目標(biāo)上幾乎沒有差異此時該目標(biāo)的擁擠距離貢獻(xiàn)為 0跳過即可不要硬除零。我一般會加一個閾值判斷if f_range 1e-12 dist(idx(k)) dist(idx(k)) ... (f_sorted(k1) - f_sorted(k-1)) / f_range; end5.3 坑三目標(biāo)函數(shù)返回 NaN排序結(jié)果像隨機(jī)數(shù)現(xiàn)象偶爾迭代到某幾代Pareto 前沿上出現(xiàn)完全孤立于整體的點(diǎn)或者所有個體突然都排到第一層怎么看都像是排序邏輯出錯。原因目標(biāo)函數(shù) evaluate_objective 內(nèi)部出現(xiàn)了除零、log(0)、sqrt(負(fù)數(shù)) 等情況返回 NaN 或 Inf。非支配排序里任何包含 NaN 的比較結(jié)果都是 false導(dǎo)致個體的 n_p 計數(shù)統(tǒng)計不準(zhǔn)確整個分層邏輯失真。最典型的觸發(fā)場景是決策變量越界——某個變量經(jīng)過 SBX 或變異后跑出定義域目標(biāo)函數(shù)里的中間運(yùn)算直接崩潰。解決三個層面同時設(shè)防。第一交叉變異后立刻做邊界裁剪代碼里已經(jīng)有的 min/max 必須保留第二evaluate_objective 內(nèi)部對所有風(fēng)險運(yùn)算做防御出現(xiàn)非法值就返回一個超大懲罰數(shù)比如 1e10第三主要排錯時在評估函數(shù)入口加一句斷言if any(isnan(x)) error(決策變量包含 NaN); end這三層全上90% 以上的 NaN 翻車都能攔下來。剩下的 10% 是在目標(biāo)函數(shù)內(nèi)部生成 NaN那只能靠打印 f 矩陣來定位了。5.4 坑四跑了 500 代前沿還是離真實(shí)前沿很遠(yuǎn)現(xiàn)象算法沒有崩收斂曲線也在下降但求出的前沿始終沒有貼近理論P(yáng)areto前沿差著一截距離。原因最常見的是種群初始化范圍太小。如果初始種群本來就覆蓋不到前沿附近比如真實(shí)最優(yōu)解在 [0.8, 0.9] 區(qū)間而初始化時變量只隨機(jī)生成在 [0.1, 0.3] 范圍那 500 代很難靠交叉和變異爬過去。另一個原因是交叉概率 pc 被手動降到了 0.5 以下新解產(chǎn)生速度太慢整個種群陷在初始區(qū)域里慢慢磨。解決把初始化改成在完整決策變量邊界內(nèi)均勻隨機(jī)采樣這是最基本的后悔藥。其次檢查 pc實(shí)數(shù)編碼問題保持 0.9 附近不要因?yàn)榕聛G失優(yōu)秀父代而調(diào)低——精英合并機(jī)制已經(jīng)在保護(hù)父代了交叉率低只會拖慢探索速度。另外我有個習(xí)慣每迭代 100 代把種群中 2%~5% 的個體重新隨機(jī)初始化讓一小撮新鮮血液反復(fù)擾動種群這招對擺脫局部前沿效果很明顯代價是前沿可能會短暫抖動但整體收斂路徑會穩(wěn)健得多。還存在一個隱蔽情況gen500 對于多峰問題可能真的不夠。判斷方法是看每 50 代前沿變化的幅度如果最近 100 代前沿幾乎不移動再加大代數(shù)也沒有意義反而是上面幾個原因之一。6. 驗(yàn)證與進(jìn)階用ZDT1測試算例收尾再看最終決策怎么選6.1 用 ZDT1 做回歸測試拿到任何 NSGA-II 代碼先別急著上你的實(shí)際問題用 ZDT1 測試函數(shù)跑一遍確認(rèn)框架本身沒毛病。ZDT1 的雙目標(biāo)定義很經(jīng)典第一目標(biāo)取第一個變量本身第二目標(biāo)由其余變量構(gòu)造理論P(yáng)areto前沿是 f2 1 - sqrt(f1)。這樣你一眼就能看出算法有沒有逼近真實(shí)前沿。function f evaluate_objective(x) n length(x); g 1 9 * sum(x(2:n)) / (n - 1); f(1) x(1); f(2) g * (1 - sqrt(x(1) / g)); end跑完后把結(jié)果和真實(shí)前沿疊畫plot(f(:,1), f(:,2), o, MarkerSize, 4); hold on; f1_true 0:0.01:1; f2_true 1 - sqrt(f1_true); plot(f1_true, f2_true, -, LineWidth, 1.5); hold off; legend(NSGA-II結(jié)果, 真實(shí)前沿);兩個目標(biāo)、30 變量以內(nèi)ZDT1 跑 200 代結(jié)果應(yīng)該和真實(shí)前沿貼合得很好。如果這一步就翻車問題先出在框架別急著怪你的業(yè)務(wù)模型。6.2 從前沿到最終決策TOPSIS 與偏好選擇算法交給你幾十個非支配解實(shí)際工程還要拍板選一個。我習(xí)慣的做法是先看各目標(biāo)量綱然后對每個解算到理想點(diǎn)的歸一化距離或者直接用 TOPSIS確定每個目標(biāo)的正理想解和負(fù)理想解給目標(biāo)按業(yè)務(wù)重要性配權(quán)重算每個解的相對貼近度取最大者。這一步本質(zhì)是把第 4 章舍棄的“權(quán)重”重新?lián)旎貋怼獏^(qū)別是現(xiàn)在權(quán)重只用于最終決策不參與尋優(yōu)過程不會影響 Pareto 前沿的完整性。從那以后我每次拿到一個多目標(biāo)項(xiàng)目都會強(qiáng)制自己走一遍先用 ZDT1 驗(yàn)證框架再換真實(shí)目標(biāo)函數(shù)跑完不急著選解先看前沿形狀再做 TOPSIS 決策。這套流程幫我擋掉了不少翻車現(xiàn)場希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取