經典遺傳算法(SGA)解非線性最優化問題的原理及其python(python3.6)代碼實現

1.多峯非線性最優化問題

非線性最優化問題一直是學者研究的熱點問題之一。下面考慮兩個變量的非線性最優化問題,這個問題具有多個局部解,是多峯問題。下圖是採用經典遺傳算法找到的最優解在目標函數的三維圖形中的分佈情況,由於多峯最優化問題具有多個局部解的特性,使用傳統的方法非常難獲得最優解。
s.t.maxf(x1,x2)=21.5+x1sin(4pix1)+x2sin(20pix2)3.0x112.14.1x25.8 s.t. \begin{matrix} max f(x_1,x_2)=21.5+x_1sin(4*pi*x_1)+x_2sin(20*pi*x_2) \\-3.0\leqslant x_1\leqslant 12.1 \\4.1\leqslant x_2\leqslant 5.8 \end{matrix}
在這裏插入圖片描述
目標函數的python代碼如下:

#目標函數
def aimfun(x):
    y=21.5+x[0]*math.sin(4*math.pi*x[0])+x[1]*math.sin(20*math.pi*x[1])
    return y

下面我們根據此問題介紹經典的遺傳算法。

2. 經典遺傳算法

2.1 遺傳算法概述

遺傳算法是一種基於生物遺傳機理,即生物進化(自然淘汰,交叉,變異等)現象的隨機搜索算法,它通過計算機模擬生物進化過程來進行搜索和進化,最後尋求最優解,是一種超啓發式算法,其中被稱爲個體的染色體(即研究對象的候選解)的集合能適應外部環境(研究對象的評價函數),並基於下面的規則在每代中生成新個體集合:
(1)越是適應性強的個體,其生存機率越高(自然選擇);
(2)以原有個體爲基礎通過遺傳操作生成新個體(遺傳現象)。
其中(1)可看做是概率搜索法,(2)是經驗搜索法。

2.2 染色體設計(編碼和解碼)

這裏採用二進制字符編碼法(binary-string encoding)來表示非線性優化的編碼,以下是算法的僞代碼,random[0,1]表示在[0,1]區間內隨機產生0或者1。

procedure:binary-string encoding
input:no.of required bits m
output:chromosome vk
begin
	for i=1 to m
		vk(i)<--random[0,1];
	output:chromosome vk;
end

編碼(種羣初始化)的python代碼如下:

#初始化種羣
def init(pop_num,chromo_len): 
    population=[]
    for i in range(pop_num):
        pop=''
        for k in chromo_len:
            for j in range(k):
                pop=pop+str(np.random.randint(0,2))
        population.append(pop)    
    return population

這裏以二進制字符串來表示決策變量x1,x2x_1,x_2。例如xjx_j的定義域爲[aj,bj][a_j,b_j],而要求精度的小數點後5位,這就要求xjx_j的定義域至少要劃分爲(bjaj)105(b_j-a_j)*10^5個空間。設變量xjx_j所需要的二進制字符串長爲mjm_j,則應滿足:
2mj1&lt;(bjaj)105&lt;2mj1 2^{m_j-1}&lt;(b_j-a_j)*10^5&lt;2^{m_j}-1
例如精度都設置爲小數點後5位,則兩個變量所需的串長分別爲18和15,總串長爲33,則設置初始染色體的長度爲33位。
獲得染色體長度的python代碼如下:

#獲得染色體長度
def getchromolen(delta, upper,lower):
    maxlen=25
    chromo_len=[]
    for j in range(len(upper)):
        a=(upper[j]-lower[j])/delta[j]
        for i in range(maxlen):
            if (2**i < a) and (a < (2**(i+1))):
                chromo_len.append(i+1)
                break
    return chromo_len

要想將變量mjm_j由二進制轉爲十進制,可根據定義域範圍內2mj12^{m_j}-1個劃分點的位置,按下式計算:
xj=aj+decimal(substringj)(bjaj)/(2mj1) x_j=a_j+decimal(substring_j)*(b_j-a_j)/(2^{m_j}-1)
這裏decimal(substringj)decimal(substring_j)表示變量xjx_j的子串substringjsubstring_j的十進制值。
解碼的僞代碼如下:

procedure:binary-string decoding
input:no.of bariables n,
		no.of bits mj(j=1,2,...,n)
		range[aj,bj] of variable xj(j=1,2,...,n)
		chromosome vk
output:real number xj(j=1,2,...,n)
begin
	s<--0,t<--0;
	for j=1 to n
		s<--t+1;
		t<--t+mj;
		xj=aj+((bj-aj)/(2^mj-1))*sum_{i=s->t}(2^(mj-i)*vk(i));
	end
	output:real number xj(j=1,2,...,n);
end

解碼的python代碼如下:

#解碼
def decode(x,chromo_len,upper,lower):
    y=[]
    index=0
    for i in range(len(chromo_len)):
        a = lower[i]+(int(x[index:(index+chromo_len[i])],2))*(upper[i]-lower[i])/(2**chromo_len[i]-1)
        y.append(a)
        index = index+chromo_len[i]
    return y

2.3 遺傳操作

2.3.1輪盤賭模型

輪盤賭模型的基本原理是根據每個染色體的適值比例來確定該個體的選擇概率或生存概率。如下面的僞代碼那樣,個體適應度按比例轉換爲輪盤的面積並旋轉輪盤,最後選擇球落點位置所對應的個體。(此處輸入可以爲父代加子代,也可直接輸入子代,具體區別見後面的2.4算法流程)
這裏有一些參數說明:
l:染色體索引號
F:所有染色體的適值和
vk:第k個染色體
pk:第k個染色體的選擇概率
qk:前k個染色體的累計概率

procedure:roulette wheel selection
input:chromosome Pk,k=1,2,...,popsize+offsize
output:chromosome Pk in next generation
begin
	計算適應度函數值eval(Pk);
	計算累計適應度F=sum_{k=1->popsize+offsize}eval(Pk);
	計算選擇概率pk=eval(Pk)/F(k=1,2,...,popsize+offsize);
	計算累計概率qk=sum_{i=1->k}pi(k=1,2,...,popsize+offsize);
	for k=1 to popsize
		r<--random[0,1]
		if r<=q1 then
			Pl'<--Pk;
		else if qk-1<r<=qk then
			Pk'<--Pk;
	end
	output:chromosome Pk,k=1,2,...,popsize;
end

適應度值計算和輪盤賭的python代碼如下;

#適應度函數
def fitnessfun(population,aimfun,chromo_len,upper,lower,fun):
    value=[]
    for i in range(len(population)):
        valuea=aimfun(decode(population[i],chromo_len,upper,lower))
        value.append(valuea)
    if fun==0:
        minvalue=min(value)
        value=[(i-minvalue+0.0000001) for i in value]
    elif fun==1:
        maxvalue=max(value)
        value=[(maxvalue-i+0.0000001) for i in value]
    return value


#輪盤賭選擇
def roulettewheel(population,value,pop_num):
    fitness_sum=[]
    value_sum=sum(value)
    fitness=[i/value_sum for i in value]
    for i in range(len(population)):##
        if i==0:
            fitness_sum.append(fitness[i])
        else:
            fitness_sum.append(fitness_sum[i-1]+fitness[i])
    population_new=[]
    for j in range(pop_num):###
        r=np.random.uniform(0,1)
        for i in range(len(fitness_sum)):###
            if i==0:
                if r>=0 and r<=fitness_sum[i]:
                    population_new.append(population[i])
            else:
                if r>=fitness_sum[i-1] and r<=fitness_sum[i]:
                    population_new.append(population[i])
    return population_new

2.3.2單點交叉

隨機選擇父母雙方染色體上的一個點,並指定爲“交叉點”。在該點右側的位在兩個親本染色體之間進行交換。這樣導致每個後代都攜帶了來自父母的一些遺傳信息。此算法父代的雙親個體有可能被多次選擇,也可能一次都未選到,即不滿足完備性(父代中的個體不是所有都被選擇)。此算法也可以讓父代的雙親個體都被選擇,即滿足完備性。以下的算法僞代碼爲前者,實現的python代碼爲後者。
參數說明:
pc:交叉概率
p:交叉點
L:爲染色體長度

input: pc, parent Pk, k=1, 2, ..., popsize 
output: offspring Ck
begin
	for k <-- 1 to popsize/2 do
		if pc≥random[0,1] then
			i<--0;
			j<--0;
			repeat
				i<--random[1,popsize];
				j<--random[1,popsize];
			until(i≠j)                
			p<--random[1,L-1]; 
			Ck<--Pi[1:p-1]//Pj[p:L];
			C(k+popsize/2)<--Pj[1:p-1]//Pi[p:L];
		end
	end
	output:offspring Ck;
end

單點交叉的python代碼如下:

#單點交叉
def crossover(population_new,pc,ncross):
    a=int(len(population_new)/2)
    parents_one=population_new[:a]
    parents_two=population_new[a:]
    np.random.shuffle(parents_one)
    np.random.shuffle(parents_two)
    offspring=[]
    for i in range(a):
        r=np.random.uniform(0,1)
        if r<=pc:
            point=np.random.randint(0,int(len(parents_one[i])/2))
            off_one=parents_one[i][:point]+parents_two[i][point:]
            off_two=parents_two[i][:point]+parents_one[i][point:]
            ncross = ncross+1
        else:
            off_one=parents_one[i]
            off_two=parents_two[i]
        offspring.append(off_one)
        offspring.append(off_two)
    return offspring

2.3.3反轉變異(單點變異)

實現單點變異算子的常用方法1爲每條染色體隨機生成一個數,此數指示該染色體是否需要變異。如果該染色體需要變異,爲其生成一個隨機變量,此隨機變量指示修改該染色體的哪一位。其算法的僞代碼如下:

procedure:mutation1
input: pm, parent Pk, k=1, 2, ..., popsize              // pm:變異概率
output: offspring Ck
begin
	for k <-- 1 to popsize do	                       
		if pm <-- random [0, 1] then
			point<--random[0,L-1]	          // L: 染色體長度
			if point = 0
				Pk <-- 1-Pk [ 0 ] // Pk[ 1: L ];	
			else
				Pk <-- Pk [1: point-1] // 1-Pk [ point ] // Pk[ point+1: L ];	
			end
		end
		Ck =Pk;
	end
   	output:offspring Ck;
end

反轉變異1的python代碼如下:

#單點變異1
def mutation1(offspring,pm,nmut):
    for i in range(len(offspring)):
        r=np.random.uniform(0,1)
        if r<=pm:
            point=np.random.randint(0,len(offspring[i]))
            if point==0:
                if offspring[i][point]=='1':
                    offspring[i]='0'+offspring[i][1:]
                else:
                    offspring[i]='1'+offspring[i][1:]
            else:
                if offspring[i][point]=='1':
                    offspring[i]=offspring[i][:(point-1)]+'0'+offspring[i][point:]
                else:
                    offspring[i]=offspring[i][:(point-1)]+'1'+offspring[i][point:]
            nmut = nmut+1
    return offspring

實現單點變異算子的常用方法2爲染色體數組中的每個位生成隨機變量。此隨機變量指示是否將修改特定位。其算法的僞代碼如下:

procedure:mutation2
input: pm, parent Pk, k=1, 2, ..., popsize              // pm:變異概率
output: offspring Ck
begin
	for k <-- 1 to popsize do	                       
		for j <-- 1 to L do(基因串長度)              // L: 染色體長度
			if pm <-- random [0, 1] then	          
				Pk <-- Pk [1: j-1] // 1-Pk [ j ] // Pk[ j+1: L ];	
			end
		end
		Ck =Pk;
	end
    output:offspring Ck;
end

反轉變異2的python代碼如下:

#單點變異2
def mutation2(offspring,pm,nmut):
    for i in range(len(offspring)):
        for j in range(len(offspring[i])):
            r=np.random.uniform(0,1)
            if r<=pm:
                if j==0:
                    if offspring[i][j]=='1':
                        offspring[i]='0'+offspring[i][1:]
                    else:
                        offspring[i]='1'+offspring[i][1:]
                else:
                    if offspring[i][j]=='1':
                        offspring[i]=offspring[i][:(j-1)]+'0'+offspring[i][j:]
                    else:
                        offspring[i]=offspring[i][:(j-1)]+'1'+offspring[i][j:]
                nmut = nmut+1
    return offspring

2.4 算法流程

應用經典遺傳算法對多峯非線性問題求解的總體流程僞代碼如下:(此處由於對輪盤賭操作的輸入種羣不同,因此列出兩種代碼,請讀者加以區別。)
GA的算法流程爲僞代碼如下:

procedure:simple GA
input: GA parameters
output: the best solution
begin
	t<-- 0	                       // t:遺傳代數
	initialize P(t) by encoding routine;         //P(t):染色體種羣 
	fitness eval(P) by decoding routine;
	while (not termination condition) do
		crossover P(t) to yield C(t);     //C(t):offspring
		mutation   P(t) to yield C(t);
		fitness eval(C) by decoding routine;
		select P(t+1) from P(t) and C(t);
		t<--t+1;
	end
    output:the best solution;
end

GA的python代碼1如下(輪盤賭的輸入爲父代加子代):

import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import math
#參數設置-----------------------------------------------------------------------
gen=1000#迭代次數
upper=[12.1,5.8]#自變量上界
lower=[-3.0,4.1]#自變量下界
pc=0.25#交叉概率
pm=0.01#變異概率
pop_num=10#種羣大小
delta=[0.0001,0.0001]#編碼精度
fun=0#0最大化,1最小化
#初始化-------------------------------------------------------------------------
#獲得編碼長度
chromo_len = getchromolen(delta,upper,lower)
#初始化種羣
population=init(pop_num,chromo_len)
#初始化交叉個數
ncross=0
#初始化變異個數
nmut=0
#儲存每代種羣的最優值及其對應的個體
t=[]
best_ind=[]
last=[]#儲存最後一代個體的函數值
realvalue=[]#儲存最後一代解碼後的值
#循環---------------------------------------------------------------------------
for i in range(gen):
    print("迭代次數:")
    print(i)
    #交叉
    offspring_c=crossover(population,pc,ncross)
    #變異
    #offspring_m=mutation1(offspring,pm,nmut)
    offspring_m=mutation2(offspring_c,pm,nmut)
    mixpopulation=population+offspring_m
    #適應度函數計算
    value = fitnessfun(mixpopulation,aimfun,chromo_len,upper,lower,fun)
    #輪盤賭選擇
    population=roulettewheel(mixpopulation,value,pop_num)
    #儲存當代的最優解
    result=[]
    if i==gen-1:
        for j in range(len(population)):
            bb = decode(population[j],chromo_len,upper,lower)
            result.append(aimfun(bb))
            realvalue.append(bb)
        last=result
    else:
        for j in range(len(population)):
            result.append(aimfun(decode(population[j],chromo_len,upper,lower)))
    maxre=max(result)
    h=result.index(max(result))
    #將每代的最優解加入結果種羣
    t.append(maxre)
    best_ind.append(population[h])

GA的python代碼2如下(輪盤賭的輸入爲子代):

import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import math
#參數設置-----------------------------------------------------------------------
gen=1000#迭代次數
upper=[12.1,5.8]#自變量上界
lower=[-3.0,4.1]#自變量下界
pc=0.25#交叉概率
pm=0.01#變異概率
pop_num=10#種羣大小
delta=[0.0001,0.0001]#編碼精度
fun=0#0最大化,1最小化
#初始化-------------------------------------------------------------------------
#獲得編碼長度
chromo_len = getchromolen(delta,upper,lower)
#初始化種羣
population=init(pop_num,chromo_len)
#初始化交叉個數
ncross=0
#初始化變異個數
nmut=0
#儲存每代種羣的最優值及其對應的個體
t=[]
best_ind=[]
last=[]#儲存最後一代個體的函數值
realvalue=[]#儲存最後一代解碼後的值
#循環---------------------------------------------------------------------------
for i in range(gen):
    print("迭代次數:")
    print(i)
    #適應度函數計算
    value = fitnessfun(population,aimfun,chromo_len,upper,lower,fun)
    #輪盤賭選擇
    population=roulettewheel(population,value,pop_num)
    #交叉
    offspring_c=crossover(population,pc,ncross)
    #變異
    #offspring_m=mutation1(offspring,pm,nmut)
    offspring_m=mutation2(offspring_c,pm,nmut)
    population=offspring_m
    #儲存當代的最優解
    result=[]
    if i==gen-1:
        for j in range(len(population)):
            bb = decode(population[j],chromo_len,upper,lower)
            result.append(aimfun(bb))
            realvalue.append(bb)
        last=result
    else:
        for j in range(len(population)):
            result.append(aimfun(decode(population[j],chromo_len,upper,lower)))
    maxre=max(result)
    h=result.index(max(result))
    #將每代的最優解加入結果種羣
    t.append(maxre)
    best_ind.append(population[h])

3. 實驗結果

本實驗參數設置如下:
1.gen=1000#迭代次數
2.upper=[12.1,5.8]#自變量上界
3.lower=[-3.0,4.1]#自變量下界
4.pc=0.25#交叉概率
5.pm=0.01#變異概率
6.pop_num=10#種羣大小
7.delta=[0.0001,0.0001]#編碼精度
根據上述參數設置和原理分析,我們編程得到最後一代解的分佈如下圖所示。
在這裏插入圖片描述
其每代最優的函數值隨進化代數的變化如下圖所示。
在這裏插入圖片描述
很明顯算法最後可以收斂到一個較好的解,如果想要得到最優的解,還需要做大量的實驗從而選擇較合適的參數,建議讀者可以多多進行嘗試。
畫圖的python代碼如下:(接到主程序後就行)

#輸出結果-----------------------------------------------------------------------
best_value=max(t)
hh=t.index(max(t))
print(best_value)
print(decode(best_ind[hh],chromo_len,upper,lower))
#畫出收斂曲線
plt.plot(t)
plt.title('The curve of the optimal function value of each generation with the number of iterations',color='#123456')
plt.xlabel('the number of iterations')
plt.ylabel('the optimal function value of each generation')
#畫出函數與解的分佈 
fig = plt.figure()
ax = Axes3D(fig)
x1 = np.arange(-3,13,0.4)
x2 = np.arange(4,6,0.0460)
X, Y = np.meshgrid(x1,x2)#網格的創建,這個是關鍵
Z=21.5+X*np.sin(4*np.pi*X)+Y*np.sin(20*np.pi*Y)
plt.xlabel('x1')
plt.ylabel('x2')
ax.set_zlabel('f(x1,x2)') 
ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap='rainbow')
ax.scatter(np.array(realvalue)[:,0], np.array(realvalue)[:,1], np.array(last),marker='o', c='k')
plt.show()

完整的python代碼如下:https://download.csdn.net/download/qq_40434430/10742475
由於作者水平有限,以上內容難免有錯誤之處,如果讀者有所發現,請在下面留言告知我,鄙人將不勝感激!
[1]:(日)玄光男, 林林等著. 網絡模型與多目標遺傳算法[M]. 於歆傑, 梁承姬譯. 北京: 清華大學出版社, 2017

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章