基于DEAP库的NSGA2代码详解
源代碼
??完整代碼如下所示,評價函數使用的是經典的ZDT3測試函數。
import numpy as np from deap import base, tools, creator, algorithms import random import matplotlib.pyplot as plt #定義問題 creator.create('MultiObjMin',base.Fitness,weights=(-1.0,-1.0))#兩個目標,都求最小值 creator.create('Individual',list,fitness = creator.MultiObjMin)#創建individual類def genInd(low,up):return [random.uniform(low[0],up[0]),random.uniform(low[1],up[1])]#實數編碼,一個個體里兩個基因toolbox = base.Toolbox() Ndim = 2#兩個變量 low = [0,0]#兩個變量的下界 up = [1,1]#兩個變量的上界toolbox.register('genInd',genInd,low,up) toolbox.register('individual',tools.initIterate,creator.Individual,toolbox.genInd)#注冊個體生成工具 #print(toolbox.individual())#打印一個個體檢查def ZDT3(ind):#測試函數,注意這里與單目標比最明顯的區別是有兩個返回值(兩個目標)n = len(ind)f1 = ind[0]g = 1 + 9 * np.sum(ind[1:])/(n-1)f2 = g * (1 - np.sqrt(ind[0]/g) - ind[0]/g * np.sin(10*np.pi*ind[0]))return f1,f2#返回在兩個函數上的值 toolbox.register('evaluate',ZDT3)#注冊評價函數N_POP = 200#種群內個體數量,參數過小,搜索速度過慢 toolbox.register('population',tools.initRepeat,list,toolbox.individual)#注冊種群生成工具 pop = toolbox.population(n=N_POP)#建立種群pop #for ind in pop:#打印一個種群檢查 # print(ind)toolbox.register('selectGen1', tools.selTournament, tournsize=2) toolbox.register('select', tools.emo.selTournamentDCD) # 該函數是binary tournament,不需要tournsize toolbox.register('mate', tools.cxSimulatedBinaryBounded, eta=20.0, low=low, up=up)#交叉與變異方式選取較為講究,隨意換成其他方式效果不佳 toolbox.register('mutate', tools.mutPolynomialBounded, eta=20.0, low=low, up=up, indpb=1.0/Ndim)NGEN = 200#迭代步數,參數過小,在收斂之前就結束搜索 CXPB = 0.8#交叉概率,參數過小,族群不能有效更新 MUTPB = 0.2#突變概率,參數過小,容易陷入局部最優#開始迭代 #第一代與第二代之后的代數操作不同 fitnesses = toolbox.map(toolbox.evaluate,pop)#為初始種群進行評價,得到適應度 for ind,fitness in zip(pop,fitnesses):ind.fitness.values = fitness#這里先有一個適應度才能進行快速非支配排序fronts = tools.emo.sortNondominated(pop,k=N_POP,first_front_only=False)#快速非支配排序,得到不同前沿的pareto層集合fronts #print(fronts)#打印一個pareto層集合fronts檢查,每層前沿都是一個列表for idx,front in enumerate(fronts):#使用枚舉循環得到各層的標號與pareto解#print(idx,front)#打印檢查前沿標號與每層的pareto解for ind in front:ind.fitness.values = (idx+1),#將個體的適應度設定為pareto解的前沿次序offspring = toolbox.selectGen1(pop,N_POP)#進行選擇 offspring = algorithms.varAnd(offspring,toolbox,CXPB,MUTPB)#只做一次交叉與變異操作#從第二代開始循環 for gen in range(1,NGEN):combinedPop = pop + offspring#將子代與父代結合成一個大種群fitnesses = toolbox.map(toolbox.evaluate,combinedPop)#對該大種群進行適應度計算for ind,fitness in zip(combinedPop,fitnesses):ind.fitness.values = fitnessfronts = tools.emo.sortNondominated(combinedPop,k=N_POP,first_front_only=False)#對該大種群進行快速非支配排序for front in fronts:tools.emo.assignCrowdingDist(front)#計算擁擠距離pop = []for front in fronts:pop += frontpop = toolbox.clone(pop)pop = tools.selNSGA2(pop,k=N_POP,nd='standard')#基于擁擠度實現精英保存策略offspring = toolbox.select(pop,N_POP)#選擇offspring = toolbox.clone(offspring)offspring = algorithms.varAnd(offspring,toolbox,CXPB,MUTPB)#交叉變異bestInd = tools.selBest(pop,1)[0]#選擇出種群中最優個體 bestFit = bestInd.fitness.values print('best solution:',bestInd) print('best fitness:',bestFit)front = tools.emo.sortNondominated(pop,len(pop))[0]#返回的不同前沿的pareto層集合fronts中第一個front為當前最優解集 #圖形化顯示 for ind in front:plt.plot(ind.fitness.values[0],ind.fitness.values[1],'r.',ms=2) plt.xlabel('f1') plt.ylabel('f2') plt.tight_layout() plt.show()問題定義
??測試函數為ZTD3,取n=2,也就是有兩個目標。兩個目標都取最小值,因此代碼中目標參數要寫成weights=(-1.0,-1.0)。
creator.create('MultiObjMin',base.Fitness,weights=(-1.0,-1.0))#兩個目標,都求最小值 creator.create('Individual',list,fitness = creator.MultiObjMin)#創建individual類個體編碼與生成
??測試函數ZTD3要求自變量的取值范圍是[0,1],我們有兩個變量,說明一個個體中含有兩個基因。考慮到基因為復數并且對精度沒有特殊需求,因此使用實數編碼法生成個體。
??以下函數的編寫是常見的實數編碼法應用,在一個列表中生成兩個均勻隨機分布的實數。
??編碼完畢之后設定變量的上界與下界,再注冊到工具箱中即可。
def genInd(low,up):return [random.uniform(low[0],up[0]),random.uniform(low[1],up[1])]#實數編碼,一個個體里兩個基因toolbox = base.Toolbox() Ndim = 2#兩個變量 low = [0,0]#兩個變量的下界 up = [1,1]#兩個變量的上界toolbox.register('genInd',genInd,low,up) toolbox.register('individual',tools.initIterate,creator.Individual,toolbox.genInd)#注冊個體生成工具 #print(toolbox.individual())#打印一個個體檢查評價函數(ZTD3)
??ZTD3是一個常用來測試代碼性能的函數,ZTD3的內容如下圖所示:
??
??這里取m=2(無視上圖中的m=30,那樣目標太多難以使用圖像直觀觀察)。從函數中可以看到,雙目標的求解其實是計算出一組解,令其在f1,f2上求得最小值。
??此外,和單目標程序最明顯的區別為,評價函數返回了兩個值。這樣的表現也可以理解為該程序有兩個評價函數,分別用于評價兩個目標上的適應度。
生成種群
??種群的生成方法與單目標程序無異:
N_POP = 200#種群內個體數量,參數過小,搜索速度過慢 toolbox.register('population',tools.initRepeat,list,toolbox.individual)#注冊種群生成工具 pop = toolbox.population(n=N_POP)#建立種群pop #for ind in pop:#打印一個種群檢查 # print(ind)注冊工具與超參數設置
??注冊工具中注冊了兩個選擇函數,一個是selectGen1,一個是select。selectGen1函數是第一次進行選擇時使用的,而第二代之后的選擇就需要考慮到擁擠度比較了,因此需要另一個選擇函數tools.emo.selTournamentDCD。
??此外,在測試了其他的交叉,變異函數后發現運行結果并不理想,說明該多目標算法對交叉,變異的手段有特定的需求,這一點將在今后繼續研究。
??填坑,后續的項目測試證明,個體采用實數編碼,有取值范圍的情況,變異與交叉應當采用含邊界方式的變異與交叉方法。
第一次迭代
??在NSGA2代碼中,第一次迭代與后續的迭代操作不同,主要區別有:
??(1)第一次迭代不計算擁擠距離。
??(2)第一次迭代中選擇的依據——適應度,用的是個體對應的pareto解的前沿次序。
??如果熟悉之前deap程序的話,可以看到多了些陌生但在NSGA2程序中常見的變量:fronts、front。
??首先解釋一下fronts,fronts是一個列表,列表內記錄了一個種群中個體按照非支配排序后形成的不同層的前沿pareto解。由下圖所示,完整的整個列表就是fronts。紅框處框選了一個列表,該列表正是第一層pareto解,也就是pareto最優解,其中又包含了兩個小列表,每個小列表顯然是一個解。
??也就是說,該fronts里面的第一層pareto解里面有兩個解,同理可以看出,第二層里有4個解,第三層里有4個解。
??
??
??front的概念就更好理解了,front就是fronts中某一層pareto解的集合,以上圖舉例,紅框框選的那個列表就是一個front,該front里面有兩個個體(ind)。
??在程序中按照枚舉循環的方式打印fronts可以看到內容如下圖所示(這里換了一個種群):
??
??
??前方箭頭指向的數字0~5就是pareto解的層數,該種群中總共有6層pareto解。數字之后的列表就是每個front的內容,可以看到,第一層front有3個解(個體),第二層front有5個解(個體)等等……
第二次及之后的迭代
??在NSGA2代碼中,后續的迭代與第一次迭代操作不同,主要區別有:
??(1)引入了父代子代合并后選擇的精英選擇機制。
??(2)計算擁擠距離,利用擁擠距離進行種群內選擇。
??
圖形化顯示
front = tools.emo.sortNondominated(pop,len(pop))[0]#返回的不同前沿的pareto層集合fronts中第一個front為當前最優解集 #圖形化顯示 for ind in front:plt.plot(ind.fitness.values[0],ind.fitness.values[1],'r.',ms=2) plt.xlabel('f1') plt.ylabel('f2') plt.tight_layout() plt.show()??可以看到pareto最優解集將ztd3函數的部分輪廓較為完整地勾勒出來。
??
??
后記
??最近項目中可用的優化影響因素較少,因此暫時使用單目標優化的程序,更多的約束打算放在懲罰函數中處理,多目標優化的學習要暫時耽擱。
??
總結
以上是生活随笔為你收集整理的基于DEAP库的NSGA2代码详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: springboot接收json参数_S
- 下一篇: 如何设置照片的高度没有滚条_基金定投选几