首先了解一下達爾文進化論:人類在繁衍過程中,通過交配產生基因重組和變異,從而產生更好的個體,也可能是更差的個體,
每一代人接受大自然的考驗,優勝略汰,適應能力強的被保存下來,差的被淘汰,使得人類對環境的適應能力越來越強;
遺傳算法就是借鑒了人類的進化過程,更好地適應環境就是我們的目標(y),每一代人就是我們的可行解(取值范圍內的一組 x),遺傳算法就是通過不斷交叉變異 x 來獲取最優的 y,也可以理解為一種啟發式搜索算法
基本概念
粘過來的,主要看加粗的,廢話比較多
基因型(genotype):性狀染色體的內部表現;
表現型(phenotype):染色體決定的性狀的外部表現,或者說,根據基因型形成的個體的外部表現;
進化(evolution):種群逐漸適應生存環境,品質不斷得到改良。生物的進化是以種群的形式進行的。
個體(individual):指染色體帶有特征的實體;
種群(population):個體的集合,該集合內個體數稱為種群的大小。
適應度(fitness):度量某個物種對於生存環境的適應程度。
選擇(selection):以一定的概率從種群中選擇若干個個體。一般,選擇過程是一種基於適應度的優勝劣汰的過程。
復制(reproduction):細胞分裂時,遺傳物質DNA通過復制而轉移到新產生的細胞中,新細胞就繼承了舊細胞的基因。
交叉(crossover):兩個染色體的某一相同位置處DNA被切斷,前后兩串分別交叉組合形成兩個新的染色體。也稱基因重組或雜交;
變異(mutation):交叉時可能(很小的概率)產生某些復制差錯,變異產生新的染色體,表現出新的性狀。
編碼(coding):DNA中遺傳信息在一個長鏈上按一定的模式排列。遺傳編碼可看作從表現型到基因型的映射。 【對 x 的另一種表達,方便交叉變異】
解碼(decoding):基因型到表現型的映射。 【把編碼重新換算成 x,方便計算適應度】
算法框架
本圖是遺傳算法的整體流程,學會了之后看圖會比較清晰
以下為專業術語,吹牛逼專用
以決策變量的編碼作為運算對象,使得優化過程借鑒生物學中的概念成為可能
直接以目標函數作為搜索信息,確定搜索方向和范圍,屬於無導數優化
同時使用多個搜索點的搜索信息,算是一種隱含的並行性
是一種基於概率的搜索技術
具有自組織,自適應和自學習等特性
具體步驟--干貨
星表示重要度以及難度
編碼 ****
核心是 解碼后 能夠 覆蓋 x 的取值范圍;
編碼的方式對模型影響非常大,包括 算子的設計難度、模型的收斂效果等;
常用方法有 二進制編碼(110110)、浮點數編碼(12.3)、字符編碼(AcDa)
種群初始化 **
1. 最好保證初始編碼為可行解,這樣可以加速收斂,當然你也可以不保證,具體根據業務難易而定
2. 注意每個個體之間不能一樣,個體要有區別
適應度 *****
遺傳算法的核心,相當於達爾文進化論中的環境,負責對可行解進行篩選;
適應度分為 單目標和多目標函數,需要根據業務進行人工設計;
適應度變換 **
適應度變換主要是對適應度的一個衡量,如越大越好,還是越小越好,如果想求最小值,那就給適應度加個負號
生成新種群
核心思想是保證好的基因盡可能保留,並存在交叉和突變;
操作思路是通過選擇、交叉、變異,產生期望的可行解,淘汰不符合要求的可行解
注意事項
1. 演變過程中種群大小始終不變(如200個),每輪選擇+交叉+變異共同生成 200個 個體
2. 其實還有一個環節叫 復制,也就是說 直接選擇為 下一代,不做任何變化,其實和 選擇 作用差不多
3. 選擇比復制的功能更強大一點,比如我可以添加各種選擇策略,選上的才 復制
4. 一般來說先交叉再變異,直接復制的不會變異
5. 先選擇還是先交叉,視具體情況而定
6. 選擇、交叉、變異的方式都可自行設計,當然有一些常用的方法
7. 在編程時,一定要注意 可變變量,避免處理過程中,改變原個體,這樣會導致模型不穩定等各種小問題 【實戰中很容易出錯】
遺傳算子-選擇 ****
選擇的目的是讓 適應度高 的個體有更大概率保存下來,注意是更大概率保存,不是一定保存; 【也就是說選 200次,適應度最高的可能被選中 50次,但也可能選中了適應度最低的】
最常用的是 輪盤賭 選擇法;
更多請參考 我的博客;
遺傳算子-交叉 **
常用的有:單點交叉、多點交叉、均勻交叉、算術交叉等,都很簡單,也可自行設計
單點交叉
一般單點交叉加上合適的交叉概率就夠用了
多點交叉
均勻交叉(也稱一致交叉,Uniform Crossover):兩個配對個體的每個基因座上的基因都以相同的交叉概率進行交換,從而形成兩個新個體。
算術交叉(Arithmetic Crossover):由兩個個體的線性組合而產生出兩個新的個體。該操作對象一般是由浮點數編碼表示的個體
更多請參考 參考資料
遺傳算子-變異 **
基本位變異(Simple Mutation):對個體編碼串中以變異概率、隨機指定的某一位或某幾位基因座上的值做變異運算。
均勻變異(Uniform Mutation):分別用符合某一范圍內均勻分布的隨機數,以某一較小的概率來替換個體編碼串中各個基因座上的原有基因值。(特別適用於在算法的初級運行階段)
邊界變異(Boundary Mutation):隨機的取基因座上的兩個對應邊界基因值之一去替代原有基因值。特別適用於最優點位於或接近於可行解的邊界時的一類問題。
非均勻變異:對原有的基因值做一隨機擾動,以擾動后的結果作為變異后的新基因值。對每個基因座都以相同的概率進行變異運算之后,相當於整個解向量在解空間中作了一次輕微的變動。
高斯近似變異:進行變異操作時用符號均值為P的平均值,方差為P**2的正態分布的一個隨機數來替換原有的基因值。
實戰總結
一)需要區分不可行解和非最優解,不可行解是不滿足約束的解,非最優解是滿足約束但y非最優的解,對待他們的方式是不同的
二)遺傳算法的難點是 適應度函數 和 遺傳算子的設計,無固定套路,但可遵循如下原則
三)要注意交叉和變異的概率,以及交叉、變異、選擇的順序,目的是避免變換太頻繁,或者破壞最優解
四)要注意解空間的大小,解空間大很難收斂,或者模型不穩定,盡量把解空間大的問題分解成解空間小的問題,例如把 24小時規划 變成 24 個 1小時規划
時刻關注不可行解
1. 演變過程中要監控 解是否是可行解,過程產生的不可行解,可采用拒絕法直接淘汰,也可進行人工修復,對於可行但非最優解,可采取適當(小懲或者不懲)的淘汰策略
2. 交叉和變異 最好不要 產生不可行解,因為這種操作很可能被直接淘汰,這樣就沒有起到樣本多樣化的作用,白白浪費了一次迭代
約束處理
1. 拒絕法:直接拒絕所有不可行解,這種方式簡單粗暴,大部分時間效果不錯,但喪失了一定的樣本多樣性,適用於要求極其嚴格的約束
2. 人工修復法:把不可行解變成可行解,常用於 組合優化 問題
3. 獎懲函數法:類似於機器學習中的正則化,如
該方法直接把有約束問題變成了無約束問題,但該方法允許部分 不可行解 的存在
應用場景
遺傳算法的有趣應用很多,諸如尋路問題,8數碼問題,囚犯困境,動作控制,找圓心問題(在一個不規則的多邊形中,尋找一個包含在該多邊形內的最大圓圈的圓心),TSP問題,生產調度問題,人工生命模擬等
示例代碼
求一個表達式的最大值
import numpy as np import matplotlib.pyplot as plt from matplotlib import cm from mpl_toolkits.mplot3d import Axes3D DNA_SIZE = 24 POP_SIZE = 200 CROSSOVER_RATE = 0.8 MUTATION_RATE = 0.005 N_GENERATIONS = 50 X_BOUND = [-3, 3] Y_BOUND = [-3, 3] def F(x, y): # 目標函數 z = 3 * (1 - x) ** 2 * np.exp(-(x ** 2) - (y + 1) ** 2) - 10 * (x / 5 - x ** 3 - y ** 5) * np.exp( -x ** 2 - y ** 2) - 1 / 3 ** np.exp(-(x + 1) ** 2 - y ** 2) return z def plot_3d(ax): X = np.linspace(*X_BOUND, 100) Y = np.linspace(*Y_BOUND, 100) X, Y = np.meshgrid(X, Y) Z = F(X, Y) ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap=cm.coolwarm) ax.set_zlim(-10, 10) ax.set_xlabel('x') ax.set_ylabel('y') ax.set_zlabel('z') plt.pause(3) plt.show() def get_fitness(pop): # 適應度 x, y = translateDNA(pop) # 解碼 DNA pred = F(x, y) ### 求最大值 # 減去最小的適應度是為了防止適應度出現負數,通過這一步fitness的范圍為[0, np.max(pred)-np.min(pred)],最后在加上一個很小的數防止出現為0的適應度 # return (pred - np.min(pred)) + 1e-3 # 適應度函數變換 ### 求最小值 return (np.max(pred) - pred) + 1e-3 def translateDNA(pop): # pop表示種群矩陣,一行表示一個二進制編碼表示的DNA,矩陣的行數為種群數目 print(pop) x_pop = pop[:, 1::2] # 奇數列表示X y_pop = pop[:, ::2] # 偶數列表示y # pop:(POP_SIZE,DNA_SIZE)*(DNA_SIZE,1) --> (POP_SIZE,1) x = x_pop.dot(2 ** np.arange(DNA_SIZE)[::-1]) / float(2 ** DNA_SIZE - 1) * (X_BOUND[1] - X_BOUND[0]) + X_BOUND[0] y = y_pop.dot(2 ** np.arange(DNA_SIZE)[::-1]) / float(2 ** DNA_SIZE - 1) * (Y_BOUND[1] - Y_BOUND[0]) + Y_BOUND[0] return x, y def crossover_and_mutation(pop, CROSSOVER_RATE=0.8): # 復制、交叉、變異 new_pop = [] for father in pop: # 遍歷種群中的每一個個體,將該個體作為父親 child = father # 孩子先得到父親的全部基因(這里我把一串二進制串的那些0,1稱為基因) if np.random.rand() < CROSSOVER_RATE: # 產生子代時不是必然發生交叉,而是以一定的概率發生交叉 mother = pop[np.random.randint(POP_SIZE)] # 再種群中選擇另一個個體,並將該個體作為母親 cross_points = np.random.randint(low=0, high=DNA_SIZE * 2) # 隨機產生交叉的點 child[cross_points:] = mother[cross_points:] # 孩子得到位於交叉點后的母親的基因 mutation(child) # 每個后代有一定的機率發生變異 new_pop.append(child) return new_pop def mutation(child, MUTATION_RATE=0.003): if np.random.rand() < MUTATION_RATE: # 以MUTATION_RATE的概率進行變異 mutate_point = np.random.randint(0, DNA_SIZE) # 隨機產生一個實數,代表要變異基因的位置 child[mutate_point] = child[mutate_point] ^ 1 # 將變異點的二進制為反轉 def select(pop, fitness): # nature selection wrt pop's fitness # 輪盤賭選擇:適應度高的被選擇的概率更大 idx = np.random.choice(np.arange(POP_SIZE), size=POP_SIZE, replace=True, p=(fitness) / (fitness.sum())) # p實際是個數組,大小(size)應該與指定的a相同,用來規定選取a中每個元素的概率,默認為概率相同 return pop[idx] def print_info(pop): fitness = get_fitness(pop) max_fitness_index = np.argmax(fitness) print("max_fitness:", fitness[max_fitness_index]) x, y = translateDNA(pop) print("最優的基因型:", pop[max_fitness_index]) print("(x, y):", (x[max_fitness_index], y[max_fitness_index])) if __name__ == "__main__": fig = plt.figure() ax = Axes3D(fig) plt.ion() # 將畫圖模式改為交互模式,程序遇到plt.show不會暫停,而是繼續執行 plot_3d(ax) pop = np.random.randint(2, size=(POP_SIZE, DNA_SIZE * 2)) # matrix (POP_SIZE, DNA_SIZE) for _ in range(N_GENERATIONS): # 迭代N代 x, y = translateDNA(pop) print(locals()) if 'sca' in locals(): sca.remove() sca = ax.scatter(x, y, F(x, y), c='black', marker='o') plt.show() plt.pause(0.1) pop = np.array(crossover_and_mutation(pop, CROSSOVER_RATE)) # 生成新種群 # F_values = F(translateDNA(pop)[0], translateDNA(pop)[1])#x, y --> Z matrix fitness = get_fitness(pop) # 計算適應度 pop = select(pop, fitness) # 選擇生成新的種群 print_info(pop) # 計算最后種群的適應度,並取適應度最大的個體DNA,解碼成 xy plt.ioff() plot_3d(ax)
參考資料:
https://blog.csdn.net/daydream13580130043/article/details/87916668 遺傳算法 與 作業車間調度問題(C++實現)
https://blog.csdn.net/daydream13580130043/article/details/95927841 作業車間調度與遺傳算法Python/Java實現及應用:BitMES,基於Electron的作業車間調度系統
https://blog.csdn.net/u010451580/article/details/51178225 遺傳算法詳解(GA)
https://www.jianshu.com/p/ae5157c26af9 【算法】超詳細的遺傳算法(Genetic Algorithm)解析
https://blog.csdn.net/u012750702/article/details/54563515 遺傳算法中幾種交叉算子小結
https://www.omegaxyz.com/2018/04/21/crossover_mutation/ 遺傳算法的交叉變異詳解