nsga2多目标优化之核心知识点(快速非支配排序、拥挤距离、精英选择策略)详解(python实现)
文章目錄
- 一、多目標優化算法簡介
- 1.基本知識
- 二、NSGA2算法
- 1.基本原理
- 2.快速非支配排序
- 2.1快速非支配排序 python實現
- 3.擁擠距離
- 3.1 擁擠距離python 實現
- 4.精英選擇策略
- 4.1 精英選擇策略python 實現
- 總結
一、多目標優化算法簡介
?????什么是多目標優化算法,見鏈接:
十分鐘了解完多目標優化算法
1.基本知識
支配:假設小明9歲,50斤,小紅8歲,45斤,小明無論是歲數還是體重都比小紅大,所以小明支配小紅。
互不支配:假設小明7歲,50斤,小紅8歲,45斤,小明歲數比小紅小,但體重比小紅大,所以小明和小紅互不支配。
帕累托集:在這個集合中,任意兩個解互不支配。 如果有2個目標函數,帕累托集應該分布成一條曲線;如果有3個目標函數,帕累托集應該分布成一個超平面。 常規的2個目標函數,解法是目標加權得到是一個點,一個解;帕累托集,目標函數之間沒有加權關系,所以得到是一條曲線。
非支配排序:將一組解分成n個集合:rank1,rank2…rankn,每個集合中所有的解都互不支配,但ranki中的任意解支配rankj中的任意解(i<j).
二、NSGA2算法
1.基本原理
?????多目標遺傳算法是用來分析和解決多目標優化問題的一種進化算法,其核心就是協調各個目標函數之間的關系,找出使得各個目標函數都盡可能達到比較大的(或者比較小的)函數值的最優解集。在眾多目標優化的遺傳算法中,NSGA2算法是影響最大和應用范圍最廣的一種多目標遺傳算法。在其出現后,由于它簡單有效以及比較明顯的優越性,使得該算法已經成為多目標優化問題中的基本算法之一。
?????介紹NSGA2,首先來介紹以下NSGA算法。
?????NSGA通過基于非支配排序的方法保留了種群中的優良個體,并且利用適應度共享函數保持了群體的多樣性,取得了非常良好的效果。但實際工程領域中發現NSGA算法存在明顯不足,這主要體現在如下3個方面:
- (1)非支配排序的高計算復雜性。非支配排序算法一般要進行mN^3次搜索(m是目標函數的數目,體重和年齡可以被視為兩個目標函數,N是一組解的大小),搜索的次數隨著目標函數數量和種群大小的增加而增多。
- (2)缺少精英策略。研究結果表明,引用精英策略可以加快遺傳算法的執行,并且還助于防止優秀的個體丟失。
- (3)需要指定共享參數share,在NSGA算法中保持種群和解的多樣性方法都是依賴于共享的概念,共享的主要問題之一就是需要人為指定一個共享參數share。
?????為了克服非支配排序遺傳算法(NSGA)的上述不足,印度科學家Deb于2002年在NSGA算法的基礎上進行了改進,提出了帶精英策略的非支配排序遺傳算法(Elitist Non-Dominated Sorting Genetic Algorithm,NSGA-II),NSGA-II 算法針對NSGA的缺陷通過以下三個方面進行了改進[16]:
- 提出了快速非支配的排序算法,降低了計算非支配序的復雜度,使得優化算法的復雜度由原來的 降為 ( 為目標函數的個數, 為種群的大小)。
- 引入了精英策略,擴大了采樣空間。將父代種群與其產生的子代種群組合在一起,共同通過競爭來產生下一代種群,這有利于是父代中的優良個體得以保持,保證那些優良的個體在進化過程中不被丟棄,從而提高優化結果的準確度。并且通過對種群所有個體分層存放,使得最佳個體不會丟失,能夠迅速提高種群水平。
- 引入擁擠度和擁擠度比較算子,這不但克服了NSGA算法中需要人為指定共享參數 的缺陷,而且將擁擠度作為種群中個體之間的比較準則,使得準Pareto域中的種群個體能均勻擴展到整個Pareto域,從而保證了種群的多樣性。
2.快速非支配排序
?????解的支配關系與Pareto最優解
?????快速非支配排序步驟:
快速非支配排序就是將解集分解為不同次序的Pareto前沿的過程。
它可以描述為:
- 1.為每個解p分配兩個關鍵量:支配p的解個數n_p以及被p支配的解集S_p;
- 2.設置i=1,將n_p=0的個體歸入F_i;
- 3.對于F_i中的個體,遍歷每個解p的S_p,將其中每個解的n_p減1;
- 4.i+=1,將n_p=0的解歸入F_i;
- 5.重復3、4,直到解集中所有個體都被歸入某一個F_i。
2.1快速非支配排序 python實現
#!/usr/bin/env python3 # -*- coding: utf-8 -*- # @Author: yudengwu(余登武) # @Date : 2021/10/26 #@email:1344732766@qq.com #============導入相關包======== import numpy as np import matplotlib.pyplot as plt import matplotlib as mpl import matplotlib; matplotlib.use('TkAgg') mpl.rcParams['font.sans-serif'] = ['SimHei'] # 指定默認字體 mpl.rcParams['axes.unicode_minus'] = False # 解決保存圖像是負號'-'顯示為方塊的問題#==========定義兩個目標函數============= # 定義函數1 def function1(x):value = -(x+2) ** 2+2*xreturn value # 定義函數2 def function2(x):value = -(x - 2) ** 2return value#=========定義群體,并繪制初始解的分布圖===== pop_size = 10 max_gen = 100 # 迭代次數 #Initialization min_x=-10 max_x=10 np.random.seed(10)#固定隨機數種子,使每次生成的初始解集一樣 solution=np.random.uniform(min_x,max_x,pop_size) #生成的初始解集#函數1 對應的初始目標函數值 values1=map(function1,solution) #python中的map用法,可以對遍歷一個列表如solution,然后將列表元素傳入到函數function1。得到解。得到的格式為對象 values1=[i for i in values1] #因為上一步得到是對象格式,需要轉換成列表格式#函數2 對應的初始目標函數值 values2=map(function2,solution) #python中的map用法,可以對遍歷一個列表如solution,然后將列表元素傳入到函數function2。得到解。得到的格式為對象 values2=[i for i in values2] #因為上一步得到是對象格式,需要轉換成列表格式plt.scatter(values1,values2, s=20, marker='o') for i in range(pop_size):plt.annotate(i, xy=(values1[i], values2[i]), xytext=(values1[i] - 0.05, values2[i] - 0.05),fontsize=18) plt.xlabel('function1') plt.ylabel('function2') plt.title('解的分布示意圖') plt.show()#=================快速非支配排序============== '''1.n[p]=0 s[p]=[] 2.對所有個體進行非支配判斷,若p支配q,則將q加入到S[p]中,并將q的層級提升一級。若q支配p,n[p]+1. 3.找出種群中np=0的個體,即最優解,并找到最優解的支配解集合。存放到front[0]中 4 i==0 5.判斷front是否為空,若不為空,將front中所有的個體sp中對應的被支配個體數減去1,(存放np==0的解序號進front[i+1]);i=i+1,跳到2;若為空,則表明得到了所有非支配集合,程序結束 '''values=[values1,values2] #解集【目標函數1解集,目標函數2解集...】 def fast_non_dominated_sort(values):"""優化問題一般是求最小值:param values: 解集【目標函數1解集,目標函數2解集...】:return:返回解的各層分布集合序號。類似[[1], [9], [0, 8], [7, 6], [3, 5], [2, 4]] 其中[1]表示Pareto 最優解對應的序號"""values11=values[0]#函數1解集S = [[] for i in range(0, len(values11))]#存放 每個個體支配解的集合。front = [[]] #存放群體的級別集合,一個級別對應一個[]n = [0 for i in range(0, len(values11))]#每個個體被支配解的個數 。即針對每個解,存放有多少好于這個解的個數rank = [np.inf for i in range(0, len(values11))]#存放每個個體的級別for p in range(0, len(values11)):#遍歷每一個個體# ====得到各個個體 的被支配解個數 和支配解集合====S[p] = [] #該個體支配解的集合 。即存放差于該解的解n[p] = 0 #該個體被支配的解的個數初始化為0 即找到有多少好于該解的 解的個數for q in range(0, len(values11)):#遍歷每一個個體less = 0 #的目標函數值小于p個體的目標函數值數目equal = 0 #的目標函數值等于p個體的目標函數值數目greater = 0 #的目標函數值大于p個體的目標函數值數目for k in range(len(values)): # 遍歷每一個目標函數if values[k][p] > values[k][q]: # 目標函數k時,q個體值 小于p個體less = less + 1 # q比p 好if values[k][p] == values[k][q]: # 目標函數k時,p個體值 等于于q個體equal = equal + 1if values[k][p] < values[k][q]: # 目標函數k時,q個體值 大于p個體greater = greater + 1 # q比p 差if (less + equal == len(values)) and (equal != len(values)):n[p] = n[p] + 1 # q比p, 比p好的個體個數加1elif (greater + equal == len(values)) and (equal != len(values)):S[p].append(q) # q比p差,存放比p差的個體解序號#=====找出Pareto 最優解,即n[p]===0 的 個體p序號。=====if n[p]==0:rank[p] = 0 #序號為p的個體,等級為0即最優if p not in front[0]:# 如果p不在第0層中# 將其追加到第0層中front[0].append(p) #存放Pareto 最優解序號# =======劃分各層解========"""#示例,假設解的分布情況如下,由上面程序得到 front[0] 存放的是序號1個體序號 被支配個數 支配解序號 front1 0 2,3,4,5 02, 1, 3,4,53, 1, 4,54, 3, 55 4, 0#首先 遍歷序號1的支配解,將對應支配解[2,3,4,5] ,的被支配個數-1(1-1,1-1,3-1,4-1)得到表個體序號 被支配個數 支配解序號 front1 0 2,3,4,5 02, 0, 3,4,53, 0, 4,54, 2, 55 2, 0#再令 被支配個數==0 的序號 對應的front 等級+1得到新表..."""i = 0while (front[i] != []): # 如果分層集合為不為空Q = []for p in front[i]: # 遍歷當前分層集合的各個個體pfor q in S[p]: # 遍歷p 個體 的每個支配解qn[q] = n[q] - 1 # 則將fk中所有給對應的個體np-1if (n[q] == 0):# 如果nq==0rank[q] = i + 1if q not in Q:Q.append(q) # 存放front=i+1 的個體序號i = i + 1 # front 等級+1front.append(Q)del front[len(front) - 1] # 刪除循環退出 時 i+1產生的[]return front #返回各層 的解序號集合 # 類似[[1], [9], [0, 8], [7, 6], [3, 5], [2, 4]]front=fast_non_dominated_sort(values)#=================打印結果======================= #遍歷各層 for i in range(len(front)):print('第%d層,解的序號為%s'%(i,front[i]))jie=[]for j in front[i]:#遍歷第i層各個解jie.append(solution[j])print('第%d層,解為%s'%(i,jie)) 結果如圖,發現成功找到最優解3.擁擠距離
??????擁擠距離的定義
??????在NSGA-II中,為了衡量在同一個前沿中各個解質量的優劣,作者為每個解分配了一個擁擠距離,其背后的思想是讓求得的Pareto最優解在objective space中盡量分散,也就有更大可能讓解在Pareto最優前沿上均勻分布。
??????擁擠距離主要是維持種群中個體的多樣性。具體而言,一般來說 是指種群按照支配關系進行非支配排后單個Rank層中個體的密集程度。常用于支配關系的多目標算法中,例如NSGA-1I.
主要步驟如下:
- 取單個前沿中個體按照一個目標上的值從小到大排序
- 將最大目標值作為max,最小目標值保留作為min。并且這兩個極值點的擁擠距離都被設置為inf即無窮大。因此注意,一個層中可能有多個具有inf的點,即如果層中有多個點在至少一個目標上相等,并且最大或最小,那么這些點的擁擠距離都是無窮大! !因為目標上呈現垂直的關系也是屬于非支配的關系! !如果出現這種情況,說明你算法的多樣性很爛! ~或者在某些算法早期可能出現這種情況
- 在這個目標上計算每個個體最相鄰個體之間的距離,即i-1和i+1的目標值的差。 并使用max和min對次值進行歸一化。
- 遍歷目標,將目標上已經歸一化的擁擠距離相加。
- 進入下一層front前沿
- 擁擠距離越大越好,最后按照擁擠距離重新排序各層,進而排序種群。
3.1 擁擠距離python 實現
#!/usr/bin/env python3 # -*- coding: utf-8 -*- # @Author: yudengwu(余登武) # @Date : 2021/10/27 #@email:1344732766@qq.com #============導入相關包======== import numpy as np import matplotlib.pyplot as plt import matplotlib as mpl import matplotlib; matplotlib.use('TkAgg') mpl.rcParams['font.sans-serif'] = ['SimHei'] # 指定默認字體 mpl.rcParams['axes.unicode_minus'] = False # 解決保存圖像是負號'-'顯示為方塊的問題#==========定義兩個目標函數============= # 定義函數1 def function1(x):y = (x - 1) ** 2return y # 定義函數2 def function2(x):y = np.cos(x)return y#=========定義群體,并繪制初始解的分布圖===== pop_size = 50 max_gen = 100 # 迭代次數 #Initialization min_x=-10 max_x=10 np.random.seed(10)#固定隨機數種子,使每次生成的初始解集一樣 solution=np.random.uniform(min_x,max_x,pop_size) #生成的初始解集#函數1 對應的初始目標函數值 values1=map(function1,solution) #python中的map用法,可以對遍歷一個列表如solution,然后將列表元素傳入到函數function1。得到解。得到的格式為對象 values1=[i for i in values1] #因為上一步得到是對象格式,需要轉換成列表格式#函數2 對應的初始目標函數值 values2=map(function2,solution) #python中的map用法,可以對遍歷一個列表如solution,然后將列表元素傳入到函數function2。得到解。得到的格式為對象 values2=[i for i in values2] #因為上一步得到是對象格式,需要轉換成列表格式plt.scatter(values1,values2, s=20, marker='o') for i in range(pop_size):plt.annotate(i, xy=(values1[i], values2[i]), xytext=(values1[i] - 0.05, values2[i] - 0.05),fontsize=18) plt.xlabel('function1') plt.ylabel('function2') plt.title('解的分布示意圖') plt.show()#=================快速非支配排序============== values=[values1,values2] #解集【目標函數1解集,目標函數2解集...】 def fast_non_dominated_sort(values):"""優化問題一般是求最小值:param values: 解集【目標函數1解集,目標函數2解集...】:return:返回解的各層分布集合序號。類似[[1], [9], [0, 8], [7, 6], [3, 5], [2, 4]] 其中[1]表示Pareto 最優解對應的序號"""values11=values[0]#函數1解集S = [[] for i in range(0, len(values11))]#存放 每個個體支配解的集合。front = [[]] #存放群體的級別集合,一個級別對應一個[]n = [0 for i in range(0, len(values11))]#每個個體被支配解的個數 。即針對每個解,存放有多少好于這個解的個數rank = [np.inf for i in range(0, len(values11))]#存放每個個體的級別for p in range(0, len(values11)):#遍歷每一個個體# ====得到各個個體 的被支配解個數 和支配解集合====S[p] = [] #該個體支配解的集合 。即存放差于該解的解n[p] = 0 #該個體被支配的解的個數初始化為0 即找到有多少好于該解的 解的個數for q in range(0, len(values11)):#遍歷每一個個體less = 0 #的目標函數值小于p個體的目標函數值數目equal = 0 #的目標函數值等于p個體的目標函數值數目greater = 0 #的目標函數值大于p個體的目標函數值數目for k in range(len(values)): # 遍歷每一個目標函數if values[k][p] > values[k][q]: # 目標函數k時,q個體值 小于p個體less = less + 1 # q比p 好if values[k][p] == values[k][q]: # 目標函數k時,p個體值 等于于q個體equal = equal + 1if values[k][p] < values[k][q]: # 目標函數k時,q個體值 大于p個體greater = greater + 1 # q比p 差if (less + equal == len(values)) and (equal != len(values)):n[p] = n[p] + 1 # q比p, 比p好的個體個數加1elif (greater + equal == len(values)) and (equal != len(values)):S[p].append(q) # q比p差,存放比p差的個體解序號#=====找出Pareto 最優解,即n[p]===0 的 個體p序號。=====if n[p]==0:rank[p] = 0 #序號為p的個體,等級為0即最優if p not in front[0]:# 如果p不在第0層中# 將其追加到第0層中front[0].append(p) #存放Pareto 最優解序號# =======劃分各層解========i = 0while (front[i] != []): # 如果分層集合為不為空Q = []for p in front[i]: # 遍歷當前分層集合的各個個體pfor q in S[p]: # 遍歷p 個體 的每個支配解qn[q] = n[q] - 1 # 則將fk中所有給對應的個體np-1if (n[q] == 0):# 如果nq==0rank[q] = i + 1if q not in Q:Q.append(q) # 存放front=i+1 的個體序號i = i + 1 # front 等級+1front.append(Q)del front[len(front) - 1] # 刪除循環退出 時 i+1產生的[]return front #返回各層 的解序號集合 # 類似[[1], [9], [0, 8], [7, 6], [3, 5], [2, 4]]front=fast_non_dominated_sort(values)#=============擁擠距離================ def crowding_distance(values,front):""":param values: 群體[目標函數值1,目標函數值2,...]:param front: 群體解的等級,類似[[1], [9], [0, 8], [7, 6], [3, 5], [2, 4]]:return: front 對應的 擁擠距離"""distance = np.zeros(shape=(pop_size, )) # 擁擠距離初始化為0for rank in front: # 遍歷每一層Pareto 解 rank為當前等級for i in range(len(values)): # 遍歷每一層函數值(先遍歷群體函數值1,再遍歷群體函數值2...)valuesi = [values[i][A] for A in rank] # 取出rank等級 對應的 目標函數值i 集合rank_valuesi = zip(rank, valuesi) # 將rank,群體函數值i集合在一起sort_rank_valuesi = sorted(rank_valuesi, key=lambda x: (x[1],x[0])) # 先按函數值大小排序,再按序號大小排序sort_ranki = [j[0] for j in sort_rank_valuesi] # 排序后當前等級ranksort_valuesi = [j[1] for j in sort_rank_valuesi] # 排序后當前等級對應的 群體函數值i#print(sort_ranki[0],sort_ranki[-1])distance[sort_ranki[0]] = np.inf # rank 等級 中 的最優解 距離為infdistance[sort_ranki[-1]] = np.inf # rank 等級 中 的最差解 距離為inf#計算rank等級中,除去最優解、最差解外。其余解的擁擠距離for j in range(1, len(rank) - 2):distance[sort_ranki[j]] = distance[sort_ranki[j]] + (sort_valuesi[j + 1] - sort_valuesi[j - 1]) / (max(sort_valuesi) - min(sort_valuesi)) # 計算距離# 按照格式存放distancesdistanceA = [[] for i in range(len(front))] #for j in range(len(front)): # 遍歷每一層Pareto 解 rank為當前等級for i in range(len(front[j])): # 遍歷給rank 等級中每個解的序號distanceA[j].append(distance[front[j][i]])return distanceAdistanceA=crowding_distance(values,front)#打印擁擠距離結果for i in range(len(front)):print('當前等級 解的序號為:',front[i])print('當前等級 解的擁擠距離為:',distanceA[i])4.精英選擇策略
????? 多目標優化過程中,得到父輩和子代解后,我們需要選擇優秀的群體進行下一代。
????? 這里采用精英選擇策略
4.1 精英選擇策略python 實現
#這里的front,distance,solution 數據格式同前文def elitism(self,front, distance, solution):"""精英選擇策略:param front: 父代與子代 組合構成的解的等級:param distance: 父代與子代 組合構成的解 擁擠距離:param solution: 父代與子代 組合構成的解:return: 返回群體解。群體數量=(父代+子代)//2"""X1index = [] # 存儲群體編號pop_size = len(solution) // 2 # 保留的群體個數 即(父輩+子輩)//2for i in range(len(front)): # 遍歷各層rank_distancei = zip(front[i], distance[i]) # 當前等級 與當前擁擠距離的集合sort_rank_distancei = sorted(rank_distancei, key=lambda x: (x[1], x[0]),reverse=True) # 先按擁擠距離大小排序,再按序號大小排序,逆序sort_ranki = [j[0] for j in sort_rank_distancei] # 排序后當前等級ranksort_distancei = [j[1] for j in sort_rank_distancei] # 排序后當前等級對應的 擁擠距離iif (pop_size - len(X1index)) >=len(sort_ranki): # 如果X1index還有空間可以存放當前等級i 全部解X1index.extend([A for A in sort_ranki])#print('已存放len(X1index)', len(X1index))#print('當前等級長度', len(sort_ranki))#print('需要存放的總長度,popsize)#num = pop_size-len(X1index)# X1index 還能存放的個數elif len(sort_ranki) > (pop_size-len(X1index)): # 如果X1空間不可以存放當前等級i 全部解num = pop_size - len(X1index) X1index.extend([A for A in sort_ranki[0:num]])X1 = [solution[i] for i in X1index]return X1總結
??????本文通定義兩個目標函數,然后生成初始解。利用初始解,解釋了核心知識點:快速非支配排序、擁擠距離、精英選擇策略。本文的文字部分有借鑒他人博客。代碼為自己純手打。下一步,將快速非支配排序、擁擠距離、精英選擇策略寫進遺傳算法,解決常規約束、帶復雜約束問題(可能需要修改下快速非支配排序代碼,因為復雜約束不僅僅考慮適應度,還有懲罰項在里面)構成多目標優化問題代碼模板。
多目標遺傳優化算法nsga2[python源碼實現]
多目標遺傳優化算法nsga2求解復雜約束問題【python源碼實現】
經實驗:快速非支配排序、擁擠距離、精英選擇策略 無法應用于粒子群算法。因為無法定義pbest,gbest。
作者:電氣-余登武。原創不易,禁止抄襲。
總結
以上是生活随笔為你收集整理的nsga2多目标优化之核心知识点(快速非支配排序、拥挤距离、精英选择策略)详解(python实现)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 一线城市的房价为什么这么高?
- 下一篇: 123万的房子,128..6平方,税费5