優(yōu)化調(diào)度:NSGA-II多目標(biāo)算法建模與Python實(shí)戰(zhàn))
1. 問題背景與核心思路先說清楚這個(gè)項(xiàng)目到底在干什么。水光互補(bǔ)優(yōu)化調(diào)度字面意思就是讓水電站和光伏電站聯(lián)合起來發(fā)電通過協(xié)調(diào)兩者的出力在滿足電網(wǎng)需求的同時(shí)讓綜合效益最大。為什么需要互補(bǔ)因?yàn)楣夥隽刺斐燥埌滋旄叻?、夜里歸零陰雨天更是斷崖式下跌水電站雖然可控性強(qiáng)但受來水約束、庫容限制不可能無限制調(diào)節(jié)。單獨(dú)調(diào)度光伏棄光率可能高得嚇人單獨(dú)調(diào)度水電遇到枯水期又沒法頂上去。把兩者放在一個(gè)系統(tǒng)里統(tǒng)籌優(yōu)化用水庫的調(diào)節(jié)能力去平抑光伏的波動這就是“互補(bǔ)”的核心價(jià)值。這個(gè)項(xiàng)目用的優(yōu)化算法是非支配排序遺傳算法英文縮寫NSGA-II是目前多目標(biāo)優(yōu)化領(lǐng)域最經(jīng)典、最常用的算法之一。所謂多目標(biāo)指的是調(diào)度時(shí)不止看一個(gè)指標(biāo)而是同時(shí)兼顧多個(gè)目標(biāo)比如“發(fā)電量最大”和“棄光率最小”這倆目標(biāo)往往是矛盾的想讓光伏多發(fā)電可能就得讓水電少發(fā)或者多調(diào)峰但水電少發(fā)了總電量就不夠想讓水電多蓄水以備后續(xù)光伏又可能被迫棄掉。經(jīng)典的單目標(biāo)優(yōu)化沒法處理這種矛盾而NSGA-II能輸出一組帕累托最優(yōu)解集讓決策者根據(jù)實(shí)際偏好去挑。適合誰來參考如果你是剛接觸電力系統(tǒng)調(diào)度、或者想把多目標(biāo)算法落地到實(shí)際項(xiàng)目中的學(xué)生和工程師這篇內(nèi)容會很有幫助。我會從數(shù)學(xué)模型怎么搭、目標(biāo)函數(shù)怎么定、約束條件怎么處理到NSGA-II的Python實(shí)現(xiàn)細(xì)節(jié)、結(jié)果怎么分析一步一步講清楚。文章里給的代碼可以直接改數(shù)據(jù)跑通不是那種只貼一段偽代碼的“概念文”。2. 多目標(biāo)優(yōu)化與NSGA-II算法核心原理2.1 為什么不能只用“加權(quán)求和”很多初學(xué)者拿到多目標(biāo)問題第一反應(yīng)是給每個(gè)目標(biāo)乘一個(gè)權(quán)重然后加起來變成單目標(biāo)求解。這樣做很直觀但有一個(gè)致命問題權(quán)重系數(shù)怎么定調(diào)權(quán)重的過程本身就是在讓不同目標(biāo)互相妥協(xié)而且一旦目標(biāo)量綱不一致比如發(fā)電量是兆瓦時(shí)棄光率是百分比權(quán)重系數(shù)根本沒有物理意義。更麻煩的是加權(quán)法只能得到一個(gè)解沒辦法給你一組可供選擇的方案。NSGA-II走的是另一條路它直接在所有可行解里尋找“支配”關(guān)系。假設(shè)我們要求兩個(gè)目標(biāo)都越大越好如果解A的兩個(gè)目標(biāo)值都不比解B差并且至少有一個(gè)比B好那么A支配BB就是被支配解。所有不被任何其他解支配的解就是帕累托前沿。NSGA-II的核心就是通過快速非支配排序把種群分成不同等級的層層數(shù)越低越優(yōu)秀同時(shí)用擁擠距離保證解的多樣性避免最后收斂到一小片區(qū)域。2.2 快速非支配排序和擁擠距離的計(jì)算邏輯快速非支配排序的步驟很簡單對當(dāng)前種群里的每個(gè)個(gè)體計(jì)算它支配哪些個(gè)體、被哪些個(gè)體支配然后找出所有不被支配的個(gè)體作為第一層再把第一層個(gè)體從種群中剔除剩余個(gè)體里繼續(xù)找不被支配的作為第二層以此類推。每一層的個(gè)體數(shù)量可以不一樣但排完序之后層數(shù)越靠前的個(gè)體越優(yōu)先被選進(jìn)下一代。擁擠距離是為了防止“同層個(gè)體抱團(tuán)”。比如第一層里大家目標(biāo)值都很接近只保留其中幾個(gè)就會丟失代表性。擁擠距離就是計(jì)算一個(gè)個(gè)體周圍相鄰個(gè)體之間的距離距離大說明這里比較“空曠”值得保留。具體做法是對同一個(gè)非支配層里的所有個(gè)體按某個(gè)目標(biāo)函數(shù)值排序最兩端的個(gè)體距離設(shè)為無窮大中間個(gè)體的距離就是它前后兩個(gè)個(gè)體在該目標(biāo)上數(shù)值差的絕對值之和再除以該目標(biāo)的最大最小值差。所有目標(biāo)算完后累加就得到這個(gè)個(gè)體的擁擠距離。2.3 NSGA-II的完整進(jìn)化流程N(yùn)SGA-II的流程可以概括為五步。第一步隨機(jī)初始化一個(gè)種群種群規(guī)模通常取100到200每個(gè)個(gè)體就是一組決策變量在調(diào)度問題里就是各時(shí)段的水電出力和光伏出力或發(fā)電計(jì)劃。第二步對當(dāng)前種群做非支配排序同時(shí)計(jì)算每個(gè)個(gè)體的擁擠距離。第三步按照“非支配層序號越小越靠前同層擁擠距離越大越靠前”的規(guī)則用錦標(biāo)賽選擇法選出父代。第四步對選出的父代做模擬二進(jìn)制交叉和多項(xiàng)式變異生成子代種群然后把父代和子代合并成一個(gè)規(guī)模翻倍的大種群。第五步再對合并后的大種群做非支配排序按層優(yōu)先、擁擠距離次之的順序截?cái)喑雠c原種群相同規(guī)模的下一代回到第二步繼續(xù)迭代。直到達(dá)到最大進(jìn)化代數(shù)比如200代整個(gè)流程結(jié)束。最終得到的最后一層非支配解集就是我們要的帕累托前沿。這里需要注意父代和子代合并這一步是NSGA-II的關(guān)鍵它保證了精英保留策略最優(yōu)秀的解不會在進(jìn)化過程中丟失這也是它比普通遺傳算法收斂性好的原因。3. Python實(shí)現(xiàn)數(shù)據(jù)準(zhǔn)備與模型建模3.1 調(diào)度周期和決策變量怎么設(shè)計(jì)我習(xí)慣以一天96個(gè)時(shí)段每15分鐘一個(gè)點(diǎn)做調(diào)度周期也可以按小時(shí)取24個(gè)時(shí)段看你的精度需求。決策變量就是每個(gè)時(shí)段的水電出力P_h[t]和光伏電站實(shí)際出力P_pv[t]光伏實(shí)際出力可以理解為“被接受的光伏功率”。為什么不直接讓光伏等于預(yù)測出力因?yàn)楣夥A(yù)測有誤差而且系統(tǒng)吸收不了那么多實(shí)際出力可以小于預(yù)測值多出來的部分就是棄光。至于水電出力它要滿足水庫的水量平衡約束所以模型里還需要引入水庫蓄水量變量V[t]。水光互補(bǔ)的典型場景是這兩種電源一起接入一個(gè)區(qū)域電網(wǎng)共同滿足負(fù)荷需求。電網(wǎng)調(diào)度中心希望總出力盡量貼合負(fù)荷曲線還得留出備用容量。所以目標(biāo)函數(shù)之一可以是“總發(fā)電量最大化”或“負(fù)荷追蹤誤差最小化”另一個(gè)目標(biāo)可以是“棄光率最小化”。這兩個(gè)目標(biāo)存在天然沖突光伏預(yù)測出力在午間往往超過負(fù)荷需求要消納光伏就得讓水電壓低出力甚至停發(fā)這樣水電發(fā)電量就少了反過來如果水電想多發(fā)電光伏就可能得讓路。我的項(xiàng)目里就用了這兩個(gè)目標(biāo)。3.2 目標(biāo)函數(shù)和約束條件的數(shù)學(xué)表達(dá)先定義參數(shù)T為總時(shí)段數(shù)P_load[t]為時(shí)段t的負(fù)荷需求P_pv_pred[t]為光伏預(yù)測出力P_h_min、P_h_max為水電站最小、最大出力V_min、V_max為水庫蓄水量上下限Q_in[t]為天然來水流量Δt為時(shí)段長度小時(shí)數(shù)15分鐘就是0.25小時(shí)。目標(biāo)函數(shù)第一個(gè)是總發(fā)電量最大化表達(dá)式是max sum(P_h[t] P_pv[t]) * Δt第二個(gè)是棄光率最小化表達(dá)式是min sum(P_pv_pred[t] - P_pv[t]) / sum(P_pv_pred[t])。約束條件包括等式約束是功率平衡P_h[t] P_pv[t] P_load[t] P_loss[t]做簡化處理時(shí)可以忽略網(wǎng)損直接令總出力等于負(fù)荷水量平衡約束是V[t] V[t-1] (Q_in[t] - q_turbine[t] - q_spill[t]) * Δt其中q_turbine[t]是發(fā)電流量和P_h[t]存在水頭-流量-出力轉(zhuǎn)換關(guān)系不等式約束是水電出力上下限、水庫蓄水量上下限、光伏出力在0和預(yù)測值之間以及水電爬坡約束P_h[t] - P_h[t-1]不能超過最大爬坡速率。在處理水頭效應(yīng)時(shí)很多入門文章直接忽略但實(shí)際水電站出力與水庫水位有關(guān)可以簡化為P_h[t] η * q_turbine[t] * H[t]H[t]是水頭一般用蓄水量的線性或非線性函數(shù)近似。如果不想把模型搞得太復(fù)雜可以先用常數(shù)效率系數(shù)后期再慢慢細(xì)化。3.3 環(huán)境準(zhǔn)備和依賴庫選型Python環(huán)境我用的是Python 3.9以上主要依賴三個(gè)庫numpy做數(shù)組運(yùn)算matplotlib繪圖pandas用來讀負(fù)荷和光伏數(shù)據(jù)。做NSGA-II算法時(shí)我不建議直接用現(xiàn)成的遺傳算法庫比如geatpy或pymoo不是說它們不好而是自己手寫一遍NSGA-II能讓你徹底搞懂算法的每一個(gè)細(xì)節(jié)而且方便改成自己的約束條件。當(dāng)然時(shí)間緊張的話用pymoo的NSGA2類封裝一下也行但作為項(xiàng)目實(shí)戰(zhàn)手寫更能體會到算法調(diào)優(yōu)的樂趣。裝環(huán)境最簡單的方式是pip install numpy pandas matplotlib如果要用pymoo就加一個(gè)pymoo。我遇到過很多初學(xué)者在Windows上裝庫失敗多半是Python版本和庫版本不匹配建議用Anaconda創(chuàng)建虛擬環(huán)境避免把系統(tǒng)環(huán)境搞亂。另外運(yùn)行遺傳算法時(shí)建議設(shè)置固定的隨機(jī)種子比如np.random.seed(42)這樣每次跑出來的結(jié)果可復(fù)現(xiàn)調(diào)試問題時(shí)會省很多事。4. NSGA-II調(diào)度算法完整實(shí)現(xiàn)4.1 種群初始化與編碼方式我采用實(shí)數(shù)編碼每個(gè)個(gè)體就是長度為2T的向量前T位是水電出力后T位是光伏出力。初始化時(shí)要保證所有值都在各自的上下限內(nèi)并且滿足最基本的功率平衡。怎么初始化才能滿足等式約束一個(gè)常用技巧是先隨機(jī)生成水電出力序列然后根據(jù)負(fù)荷和光伏預(yù)測算出每個(gè)時(shí)段需要的光伏出力P_pv[t] P_load[t] - P_h[t]如果算出來大于光伏預(yù)測值就把水電出力往上調(diào)直到這個(gè)等式滿足。這樣做能保證初始解基本都是可行解大幅加快收斂速度。初始化代碼片段如下import numpy as np def initialize_population(pop_size, T, P_load, P_pv_pred, P_h_min, P_h_max): pop [] for _ in range(pop_size): P_h np.random.uniform(P_h_min, P_h_max, T) # 粗略處理讓光伏出力恰好補(bǔ)足負(fù)荷與水電出力之差但限制在0~預(yù)測值之間 P_pv P_load - P_h P_pv np.clip(P_pv, 0, P_pv_pred) # 修正水電出力使其加上光伏出力后的總出力盡量接近負(fù)荷簡化 deficit P_load - (P_h P_pv) # 允許一定缺電但不超上限 P_h P_h deficit P_h np.clip(P_h, P_h_min, P_h_max) P_pv P_load - P_h P_pv np.clip(P_pv, 0, P_pv_pred) ind np.concatenate([P_h, P_pv]) pop.append(ind) return np.array(pop)這里需要注意初始化就強(qiáng)制滿足所有約束是不現(xiàn)實(shí)的只要能讓初始解盡量可行即可。后續(xù)在目標(biāo)函數(shù)和約束違反處理里我們會對不可行解加上懲罰。我試過完全隨機(jī)初始化結(jié)果前幾十代幾乎找不到可行解效率很低所以這個(gè)“面向可行域”的初始化技巧值得保留。4.2 目標(biāo)函數(shù)與約束違反度計(jì)算目標(biāo)函數(shù)的計(jì)算很直接。把個(gè)體解碼成P_h和P_pv兩個(gè)數(shù)組然后按公式算總發(fā)電量和棄光率。但一定要檢查約束是否滿足尤其是水量平衡約束和爬坡約束。處理方式我采用外點(diǎn)罰函數(shù)法把每個(gè)約束的違反量累計(jì)成一個(gè)總和然后在目標(biāo)上乘以一個(gè)很大的罰因子加到目標(biāo)值里。注意我們是最大化發(fā)電量所以罰函數(shù)要寫成“減去一個(gè)很大的值”。具體來看水量平衡約束需要用到水庫蓄水量迭代計(jì)算這里我寫了子函數(shù)。假設(shè)水庫初始蓄水量V_init每時(shí)段按水量平衡更新其中發(fā)電流量q_turbine與水電出力P_h成正比簡化關(guān)系為q_turbine P_h / (η * H)。如果不考慮水頭變化直接設(shè)H為常數(shù)那么可以預(yù)先把出力轉(zhuǎn)換為流量的系數(shù)算好。約束違反量分三部分蓄水量越限的平方和、發(fā)電流量越限的平方和、爬坡越限的平方和。函數(shù)實(shí)現(xiàn)如下def evaluate(individual): P_h individual[:T] P_pv individual[T:] # 目標(biāo)1總發(fā)電量兆瓦時(shí) total_energy np.sum(P_h P_pv) * dt # 目標(biāo)2棄光率 curtailment np.sum(P_pv_pred - P_pv) / np.sum(P_pv_pred) if np.sum(P_pv_pred) 0 else 0 # 水量平衡約束 V V_init violation 0.0 for t in range(T): q_turbine P_h[t] / (eta * H_ref) V V (Q_in[t] - q_turbine - q_spill[t]) * dt if V V_min or V V_max: violation (V - V_min)**2 if V V_min else (V - V_max)**2 # 爬坡約束 for t in range(1, T): ramp P_h[t] - P_h[t-1] if abs(ramp) ramp_max: violation (abs(ramp) - ramp_max)**2 penalty 1e6 return np.array([total_energy, curtailment penalty * violation])注意這里我把棄光率作為最小化目標(biāo)但NSGA-II通常默認(rèn)所有目標(biāo)都求最小所以我返回的是[-total_energy, curtailment]也行。為方便理解我直接返回正的總能量和棄光率然后在排序時(shí)按目標(biāo)方向分別處理第一個(gè)目標(biāo)取負(fù)來求最大第二個(gè)目標(biāo)取正來求最小。4.3 快速非支配排序、錦標(biāo)賽選擇、交叉與變異這段是算法的骨架我會完整給出來。快速非支配排序我用了經(jīng)典的兩列表格法復(fù)雜度O(M*N^2)對200個(gè)個(gè)體、2個(gè)目標(biāo)完全夠用。擁擠距離計(jì)算按每個(gè)目標(biāo)排序后累加。錦標(biāo)賽選擇是每次隨機(jī)抽2個(gè)個(gè)體優(yōu)先選非支配層序號小的如果同層就選擁擠距離大的。模擬二進(jìn)制交叉SBX和多項(xiàng)式變異適合實(shí)數(shù)編碼。SBX的核心是生成一個(gè)隨機(jī)數(shù)u按分布指數(shù)ηc計(jì)算展開因子β然后用β去插值兩個(gè)父代。多項(xiàng)式變異的思路類似按分布指數(shù)ηm擾動基因值。這兩個(gè)操作保證了算法能在局部搜索和全局探索之間平衡。def fast_non_dominated_sort(values): # values: (pop_size, n_obj)所有目標(biāo)統(tǒng)一為越小越好 pop_size values.shape[0] domination_count np.zeros(pop_size) dominated_set [[] for _ in range(pop_size)] front [[]] for i in range(pop_size): for j in range(pop_size): if i j: continue # 判斷是否i支配j if dominates(values[i], values[j]): dominated_set[i].append(j) elif dominates(values[j], values[i]): domination_count[i] 1 if domination_count[i] 0: front[0].append(i) # 繼續(xù)分層... return fronts def dominates(a, b): # 所有目標(biāo)越小越好a支配b的條件是a的所有目標(biāo)b且至少一個(gè) return np.all(a b) and np.any(a b) def crowding_distance(front_values): distances np.zeros(len(front_values)) for m in range(front_values.shape[1]): idx np.argsort(front_values[:, m]) distances[idx[0]] np.inf distances[idx[-1]] np.inf if len(idx) 2: fmin front_values[idx[0], m]; fmax front_values[idx[-1], m] norm fmax - fmin if fmax ! fmin else 1e-9 for i in range(1, len(idx)-1): distances[idx[i]] (front_values[idx[i1], m] - front_values[idx[i-1], m]) / norm return distances交叉和變異的具體代碼就不一一展開了但要注意交叉和變異的對象是決策變量而不是目標(biāo)值。交叉時(shí)兩個(gè)父代要同時(shí)交換或插值變異時(shí)要保證基因不越界。我習(xí)慣用numpy的向量化操作對每個(gè)時(shí)段獨(dú)立做交叉。4.4 主循環(huán)進(jìn)化全過程主循環(huán)的步驟就是前面提到的五步。一個(gè)容易踩坑的地方是在環(huán)境選擇時(shí)我們合并父代和子代成2*pop_size的種群然后做非支配排序再按層的順序往里塞。如果塞到某一層時(shí)剩余名額不夠了就用擁擠距離從大到小選這個(gè)層里的個(gè)體直到補(bǔ)滿。這一步需要小心索引對應(yīng)關(guān)系否則容易把子代和父代的順序搞混。下面給一個(gè)簡化版主循環(huán)框架pop initialize_population(...) for gen in range(max_gen): # 計(jì)算目標(biāo)函數(shù)值 vals np.array([evaluate(ind) for ind in pop]) # 注意vals第一列是總能量求最大第二列是棄光率求最小 # 統(tǒng)一為最小化把第一列取負(fù) vals_min np.column_stack([-vals[:,0], vals[:,1]]) fronts fast_non_dominated_sort(vals_min) # 選擇父代 parents selection(pop, fronts, vals_min) # 交叉變異生成子代 offspring crossover_mutation(parents) # 合并 combined_pop np.vstack([pop, offspring]) combined_vals np.array([evaluate(ind) for ind in combined_pop]) combined_vals_min np.column_stack([-combined_vals[:,0], combined_vals[:,1]]) combined_fronts fast_non_dominated_sort(combined_vals_min) # 環(huán)境選擇截?cái)?pop, vals environment_selection(combined_pop, combined_vals_min, combined_fronts, pop_size) if gen % 20 0: print(fGeneration {gen}: front0 size {len(combined_fronts[0])})這里有一個(gè)細(xì)節(jié)環(huán)境選擇后最好把當(dāng)前種群的第一前沿個(gè)體記錄下來作為最終帕累托解集。否則最后一代的種群可能因?yàn)榻財(cái)喽鴣G掉一些最優(yōu)解。5. 結(jié)果分析與可視化5.1 帕累托前沿圖的繪制和解讀跑完200代進(jìn)化后畫出第一前沿所有個(gè)體在兩個(gè)目標(biāo)上的分布橫軸為總發(fā)電量縱軸為棄光率。你會發(fā)現(xiàn)這條曲線通常像一條反L形右上到左下。右上角的點(diǎn)發(fā)電量高但棄光率也高左下角的點(diǎn)棄光率低但發(fā)電量也少。決策者可以根據(jù)當(dāng)天電網(wǎng)的實(shí)際需求來選擇如果系統(tǒng)缺電選右上角如果消納壓力大選左下角。很多時(shí)候水電站調(diào)度員會選“拐點(diǎn)”附近的解那些解在犧牲少量發(fā)電量的情況下能大幅降低棄光率。繪圖代碼很簡單import matplotlib.pyplot as plt plt.scatter(-front0_vals[:,0], front0_vals[:,1], s10) plt.xlabel(Total Energy (MWh)) plt.ylabel(Curtailment Rate) plt.title(Pareto Front) plt.grid(True) plt.show()我建議把每代的帕累托前沿變化動態(tài)顯示出來能看到曲線從隨機(jī)散點(diǎn)慢慢收斂到一條光滑曲線這個(gè)過程對理解算法收斂很有幫助。5.2 調(diào)度結(jié)果曲線水電、光伏和負(fù)荷的匹配選定一個(gè)“折中最優(yōu)解”比如取拐點(diǎn)附近的個(gè)體畫出96個(gè)時(shí)段的水電出力、光伏出力和負(fù)荷曲線。你會看到光伏出力在午間呈“饅頭形”水電出力則相反在午間壓低或停機(jī)在早晚負(fù)荷高峰頂上去。這就是互補(bǔ)調(diào)度的直觀體現(xiàn)。我還習(xí)慣把水庫蓄水量曲線也畫出來檢查蓄水量是否一直在上下限內(nèi)波動如果有越限說明罰函數(shù)權(quán)重還需要提高或者初始解質(zhì)量差了。5.3 對比實(shí)驗(yàn)單目標(biāo)優(yōu)化 vs 多目標(biāo)解集為了證明NSGA-II的優(yōu)勢我會順手跑一個(gè)單目標(biāo)加權(quán)法權(quán)重分別取0.5和0.5把總發(fā)電量和棄光率歸一化后加權(quán)求和。出來的結(jié)果只是帕累托前沿上的一個(gè)點(diǎn)。用這個(gè)點(diǎn)去和NSGA-II解集對比你會發(fā)現(xiàn)加權(quán)法只能得到一個(gè)“運(yùn)氣好”的點(diǎn)而且權(quán)重不同點(diǎn)就亂跑非常不穩(wěn)定。這個(gè)對比實(shí)驗(yàn)我已經(jīng)做了很多次每次都能明顯看到多目標(biāo)算法的價(jià)值。6. 常見問題與調(diào)試心得6.1 初始種群全是不可行解怎么辦最常見的問題是初始化沒考慮天氣來水導(dǎo)致蓄水量約束大面積違反。我的經(jīng)驗(yàn)是把罰函數(shù)的系數(shù)設(shè)成逐步遞增比如初始罰系數(shù)小一點(diǎn)讓算法先探索后期加大罰系數(shù)讓它集中到可行域。另外初始化時(shí)可以用“削峰填谷”策略先讓水電出力等于負(fù)荷減去光伏預(yù)測如果越限就按比例壓縮再把多余的負(fù)荷缺額清零。這個(gè)方法放在真實(shí)數(shù)據(jù)上很有效。6.2 目標(biāo)函數(shù)數(shù)值差異大導(dǎo)致排序失效總發(fā)電量可能是幾萬MWh而棄光率只有0到1兩者量綱差太多直接放到同一支配關(guān)系里量級大的目標(biāo)幾乎主導(dǎo)排序。解決辦法是在計(jì)算支配關(guān)系前做歸一化。我通常對每個(gè)目標(biāo)在當(dāng)前種群內(nèi)做最大最小值歸一化然后再判斷支配。注意歸一化只用于排序不改變實(shí)際目標(biāo)值最終畫圖時(shí)還是要用真實(shí)值。6.3 收斂過快或過慢的調(diào)參建議如果幾十代就停在一個(gè)很窄的區(qū)域大概率是交叉分布指數(shù)ηc太大導(dǎo)致子代和父代過于相似可以調(diào)小到15左右。如果到200代前沿還是很稀疏說明種群多樣性不夠變異概率要提高到0.1以上或者擁擠距離計(jì)算在極端點(diǎn)時(shí)沒有正確賦無窮大。種群規(guī)模也建議和決策變量數(shù)匹配決策變量是192個(gè)96*2種群規(guī)模至少150我一般取200。6.4 Python實(shí)現(xiàn)中容易忽略的列表與NumPy陷阱在實(shí)現(xiàn)過程中很多人會把種群存成list然后每次append子代最后變成嵌套結(jié)構(gòu)導(dǎo)致np.array時(shí)維度出錯(cuò)。我的習(xí)慣是一開始就存成二維數(shù)組所有操作都基于數(shù)組。另一個(gè)坑是在交叉變異時(shí)直接修改了父代數(shù)組因?yàn)閚umpy的切片是淺拷貝需要顯式用.copy()。這個(gè)錯(cuò)誤特別隱蔽我調(diào)試了整整一下午才發(fā)現(xiàn)問題。6.5 數(shù)據(jù)預(yù)處理負(fù)荷和光伏數(shù)據(jù)哪來網(wǎng)上很多公開數(shù)據(jù)集的時(shí)序粒度和單位都不一樣做項(xiàng)目時(shí)要統(tǒng)一單位。負(fù)荷數(shù)據(jù)一般是兆瓦光伏預(yù)測數(shù)據(jù)可能是千瓦不換算直接代入模型會差三個(gè)數(shù)量級。我會先把數(shù)據(jù)重采樣成15分鐘或1小時(shí)的序列然后做線性插值補(bǔ)齊缺失值。光伏數(shù)據(jù)還需要處理夜間為零的情況不能讓光伏出力下限為負(fù)。7. 擴(kuò)展與思考這個(gè)項(xiàng)目目前用的是簡化約束實(shí)際電站還有振動區(qū)、備用容量要求、梯級水庫耦合等把這些加進(jìn)去決策變量和約束會成倍增加。另一個(gè)擴(kuò)展方向是引入場景法處理光伏預(yù)測不確定性用隨機(jī)優(yōu)化代替確定性優(yōu)化。NSGA-II本身也可以改進(jìn)比如用自適應(yīng)交叉變異、引入差分進(jìn)化算子這些都能提高收斂速度。我個(gè)人在實(shí)際操作中的體會是不要上來就追求完美模型先把算法跑通再逐步加約束。我最初只考慮功率平衡和出力上下限跑出來的結(jié)果雖然“能看”但蓄水量忽高忽低完全不符合實(shí)際。后來加上水量平衡約束結(jié)果立刻變得合理。這個(gè)過程遠(yuǎn)比調(diào)參學(xué)到的東西多。最后再分享一個(gè)小技巧調(diào)試多目標(biāo)算法時(shí)只用兩個(gè)目標(biāo)的好處是你能直接用眼睛看帕累托前沿一旦畫出來的曲線呈“鋸齒狀”或“聚集在某一角”就知道排序或者擁擠距離代碼有問題。把這個(gè)基礎(chǔ)版跑順了再往三個(gè)目標(biāo)、四個(gè)目標(biāo)擴(kuò)展會容易得多。水光互補(bǔ)調(diào)度這個(gè)題目說來不難但真正動手寫代碼坑都在細(xì)節(jié)里希望這篇分享能幫你少走一些彎路。