研究生研究的領域就是用啓發算法來解決超多目標優化問題,所謂的超多目標就是要同時優化目標維數超過3維且目標之間具備必定矛盾性的問題。例如從深圳到北京的交通路線,咱們若只考慮時間與價格這兩個目標問題(二維多目標問題),會存在時長最短可是價格昂貴的路線選擇(例如航班直達),也會存在價格最便宜可是很花時間的路線(例如展轉火車公交),不會存在時長最短價格最便宜的最優路線。解決多目標問題的啓發算法裏,遺傳算法和粒子羣算法的論文較多,今天講一下簡單的遺傳算法。git
遺傳算法主要是用達爾文進化論的觀點,優勝劣汰適者生存,能更好地適應環境的個體將會保留下來,這些擁有較好適應度的個體基因會傳給下一代,讓好基因廣爲流傳,活埋很差的基因。遺傳算法主要包括種羣初始化,交叉,變異和環境選擇四個主要操做。遺傳算法屬於隨機性算法,不能找到最優解,只能找到近優解,除非是很是簡單的問題。github
好比咱們要解決一個簡單的單目標問題,求解目標f的最大值,x一、x2變量有約束範圍算法
max f (x1, x2) = 21.5 + x1·sin(4 pi x1) + x2·sin(20 pi x2) s. t. -3.0 <= x1 <= 12.1 4.1 <= x2 <= 5.8
對於這類連續問題,其實能夠不採用網上最基礎的二進制編碼來進行單點交叉和單點變異的操做了。實數也能夠,只要把交叉和變異操做採用模擬二進制交叉simulated binary crossover(SBX)和多項式變異Polynomial mutation就行了。因此這裏的種羣個體能夠採用[x1,x2]的數值來表明一個個體。spring
首先進行種羣初始化,返回一個大小爲100的種羣列表,每一個元素表明一個由兩個變量構成的個體app
pop_size = 100 #種羣規模 population = [] dec_num = 2 #變量個數 dec_min_val = (-3, 4.1) dec_max_val = (12.1, 5.8) def init(population, pop_size, dec_num, dec_min_val, dec_max_val): rangeRealVal = [dec_max_val[i]-dec_min_val[i] for i in range(dec_num)] #範圍長度 for i in range(pop_size): tempIndividual=[] for j in range(dec_num): temp = random.uniform(0, 1)*rangeRealVal[j]+dec_min_val[j] #確保變量在範圍類內 tempIndividual.append(temp) population.append(tempIndividual) #population爲[[dec1,dec2],[],[]..[]]
初始化種羣後開始選擇種羣個體交配產生下一代,這裏採用SBX,是經過二進制單點交叉改進的。例如如今有個體A的x1=9,個體B的x1變量爲18,根據變量x1的約束範圍能夠算出x1的二進制編碼長度,假設長度爲6,則個體A的x1=001001,個體B有X1=010010,根據二進制單點交叉,選定隨機一個index,例如index=3,則將兩個個體的前三位互換,獲得new_x1=010001和new_x2=001010。而SBX則能夠進行實數編碼,其過程以下框架
~x1和~x2是根據父代x1和x2進行SBX後的子代,代碼以下,隨機挑選兩個父代併產生兩個子代。dom
Pc = 0.9 #交叉機率 小於這個機率的個體才進行交叉操做 Dc = 1 #交叉步長,步長越大,產生子代離父代越遠的機率越大 def SBXcross_2c(population, Pc, dec_num, dec_min_val, dec_max_val, Dc): offspring = copy.deepcopy(population) for i in range(0, len(population), 2): #隨機選兩個產生兩個 if random.random() > Pc: continue index1 = 0 index2 = 0 while index1 == index2: #保證選到的兩個父代不相同 index1 = random.randint(0, len(population) - 1) index2 = random.randint(0, len(population) - 1) #對兩個個體執行SBX交叉操做 for j in range(dec_num): #個體的第j個變量 lower = dec_min_val[j] uper = dec_max_val[j] parent1 = population[index1][j] parent2 = population[index2][j] r = random.random() if r <= 0.5: #betaq是採用了機率模型計算出來的,能夠不用管,照着套就行了 betaq = (2*r)**(1.0/(Dc+1.0)) else: betaq = (0.5/(1.0-r))**(1.0/(Dc+1.0)) child1 = 0.5*((1+betaq)*parent1+(1-betaq)*parent2) # child2 = 0.5*((1-betaq)*parent1+(1+betaq)*parent2) child1 = min(max(child1, lower), uper) #邊界保護,以防超出 child2 = min(max(child2, lower), uper) offspring[index1][j] = child1 offspring[index2][j] = child2 return offspring #返回新生成種羣
產生的子代須要進行變異操做,這能夠有必定概率讓解跳出局部最優,去尋找全局最優,多項式變異的過程以下異步
公式能夠不用理解,往上套!變異過程的代碼以下函數
Pm = 0.1 #變異機率,小於這個機率的個體進行變異 Dm = 1 #變異步長 def ploy_mutation(offspring, Pm, dec_num, dec_min_val, dec_max_val, Dm): for i in range(len(offspring)): for j in range(dec_num): r = random.random() if r <= Pm: y = offspring[i][j] #第i個個體的第j個變量 low = dec_min_val[j] up = dec_max_val[j] delta1 = 1.0*(y-low)/(up-low) #1.0保證精度不爲整數 delta2 = 1.0*(up-y)/(up-low) r = random.random() mut_pow = 1.0/(Dm+1.0) #最外層因子 if r <= 0.5: var = 1.0-delta1 lambda_ = 2.0*r+(1.0-2.0*r)*(var**(Dm+1.0)) deltaq = lambda_**mut_pow-1.0 else: var = 1.0-delta2 lambda_ = 2.0*(1.0-r)+2.0*(r-0.5)*(var**(Dm+1.0)) deltaq = 1.0-lambda_**mut_pow y = y+deltaq*(up-low) y = min(up, max(y, low)) offspring[i][j] = y
這樣進行交叉和變異操做後,咱們就獲得了父代種羣population和子代羣衆offspring.接下來的就是優勝劣汰的環境選擇。環境選擇的一部分太多不一樣的策略,基礎本採用輪盤賭的那種策略是交叉過程當中子代徹底取代父代,而後根據適應值大小來決定輪盤機率的大小。本身不多采用這種方式,採用的是穩態進化的方式。主流的有N+1,N+q,和N+N模式,N+1模式是N個父代產生一個個體的時候,就要N+1種羣裏淘汰一個最差的個體。N+N採用的是N個父代產生N個子代,而後從2N個種羣裏選擇最好的N個個體進入下一代,上面population+offspring就是N+N的模式了。優化
N+N環境選擇的代碼以下
popobj =[] #存儲個體的適應值,這裏爲目標函數y的大小,適應值越高越須要留下 merge = population + offspring def selection(merge, popobj, popsize,population): del popobj[:] del population[:] #用來存儲被保留的種羣進入下一代 for i in merge: #計算每一個個體的適應值大小 popobj.append(object_fun(i)) #根據適應值大小排序,取前N個擁有高適應值的個體存留下來 index = sorted(range(len(popobj)), key=lambda k: popobj[k], reverse=True) #從小到大的索引值 for i in range(popsize): population.append(merge[index[i]]) score = popobj[index[0]] #計算當前擁有最高適應值的個體的目標函數值y return score def object_fun(p): x1 = p[0] x2 = p[1] y = 21.5 + x1 * math.sin(4 * math.pi * x1) + x2 * math.sin(20 * math.pi * x2) return y
目標函數圖以下
經過遺傳算法的獲得的最大y值爲
這篇文章的代碼放在github頁面上:https://github.com/kugua233/G...代碼也寫了二進制單點模擬交叉,父代每次只產生一個子代的方式的交叉方式,差分進化的方式之後再補吧。代碼量比較少,沒有去優化,想用爲一個遺傳算法小框架的話,仍是要組織代碼結構,能夠進行不一樣的初始化,交叉,變異和環境選擇操做。