詳解如何在Python中實現(xiàn)遺傳算法
前言
遺傳算法是一種模擬自然進化過程與機制來搜索最優(yōu)解的方法,它由美國 John Holland 教授于20世紀70年代提出。遺傳算法的主要思想來源于達爾文生物進化論和孟德爾的群體遺傳學(xué)說,通過數(shù)學(xué)的方式,將優(yōu)化問題轉(zhuǎn)換為類似生物進化中的染色體基因的交叉和變異等過程,因此具有堅實的生物學(xué)基礎(chǔ)和鮮明的認知學(xué)意義。和許多傳統(tǒng)優(yōu)化算法尤其是基于梯度的算法相比,遺傳算法通過交叉和變異引入的隨機性減少了陷入局部最優(yōu)解的概率。同時遺傳算法以適應(yīng)度函數(shù)為導(dǎo)向,無需計算目標函數(shù)的導(dǎo)數(shù)和其他信息,這使得它適用于復(fù)雜的非線性和多維優(yōu)化問題,因此被廣泛應(yīng)用于模式識別、圖像處理、自動控制、工程設(shè)計和機器人等領(lǐng)域,具有廣泛的應(yīng)用價值。
雖然遺傳算法表現(xiàn)優(yōu)異,但是它的各個參數(shù)對其性能有著重要的影響。對于一個優(yōu)化問題,如果沒有找到合適的遺傳算法參數(shù),可能導(dǎo)致算法早熟或者收斂性能差的問題,需要迭代多個世代才能找到全局最優(yōu)解。這些問題毫無疑問會阻礙遺傳算法的落地應(yīng)用,因此研究各個參數(shù)對算法產(chǎn)生了怎樣的影響,進而確定合適的參數(shù)選取準則,對于改善遺傳算法的搜索能力和收斂速度有著重要的意義。
本文通過遺傳算法求解 Rastrigin 函數(shù)在二維空間的最小值問題,研究算法的各個參數(shù)的變化對最優(yōu)解和收斂速度的影響,進而給出遺傳算法參數(shù)選取準則,使得遺傳算法在具體問題上能有更優(yōu)異的表現(xiàn)。
遺傳算法
遺傳算法將優(yōu)化問題的可行解編碼為二進制形式,稱為染色體。對于多維空間下的可行解,每個維度被編碼為二進制形式的基因,共同組成了一條染色體。
遺傳算法的流程如下圖所示,可以分為以下幾個過程:
- 初始化種群。在種群個體數(shù)為N和基因長度為Lc的情況下,隨機生成N條染色體,作為初始種群X0。
- 個體評價。使用適應(yīng)度函數(shù)計算種群中各條染色體Ci的適應(yīng)度fi。
- 終止條件判斷。如果適應(yīng)度變化幅度小于指定的閾值或者迭代次數(shù)達到最大值,結(jié)束遺傳算法,將種群中適應(yīng)度值最大的染色體解碼,可以得到最優(yōu)解;否則繼續(xù)下一步。
- 選擇。根據(jù)各條染色體適應(yīng)度的值,計算染色體的選擇概率fi=fi/∑fi,使用輪盤賭算法隨機選擇出M=(1−α)條染色體,其中α代表保留的適應(yīng)度值最大的染色體的比例,這部分保留的染色體不會參與下面的交叉和變異過程,而是作為優(yōu)秀個體被保留到下一次迭代。
- 交叉。對輪盤賭算法選出的M條染色體中的每一條染色體Cf,在交叉概率為Pc的情況下隨機選擇另一條染色體Cm和一個交叉位置 k,將Cf[0:k−1]和Cm[k:]進行拼接,生成新染色體Cc。
- 變異。對M條染色體分別獨立依概率Pm進行一個隨機比特的翻轉(zhuǎn)。
- 回到步驟 2 計算新種群的個體適應(yīng)度。
可以看到,遺傳算法的上述流程中共有6個參數(shù)需要調(diào)節(jié),各個參數(shù)如下表所示:
符號 | 說明 |
---|---|
N | 種群規(guī)模,即種群染色體的數(shù)量 |
Lc | 基因長度 |
α | 保留的優(yōu)秀染色體比例 |
Pc | 交叉概率 |
Pm | 變異概率 |
ε | 適應(yīng)度變化閾值或迭代次數(shù) |
代碼實現(xiàn)
使用 Python 實現(xiàn)的遺傳算法如下所示,使用 get_solution()
方法可以對目標函數(shù)求極值:
class GA: """ 遺傳算法 """ def __init__(self, pop_size=200, dna_size=20, top_rate=0.2, crossover_rate=0.8, mutation_rate=0.01, n_iters=100): """ Parameters ---------- pop_size: int 種群大小 dna_size: int 染色體大小 top_rate: float 保留的優(yōu)秀染色體個數(shù) crossover_rate: float 交叉概率 mutation_rate: float 變異概率 n_iters: int 迭代次數(shù) """ self.pop_size = pop_size self.dna_size = dna_size self.top_rate = top_rate self.crossover_rate = crossover_rate self.mutation_rate = mutation_rate self.n_iters = n_iters def get_solution(self, obj_func, bound: List[tuple], is_min=True): """ 獲取最優(yōu)解 Paramters --------- obj_func: callable n 元目標函數(shù) bound: List[tuple] 維度為 `(n, 2)` 的取值范圍矩陣 is_min: bool 尋找的是否為最小值 Returns ------- index: int 最優(yōu)解的下標 Xm: list 歷次迭代的最優(yōu)解 Ym: list 歷次迭代的最優(yōu)值 """ Xm, Ym = [0], [0] # 初始化種群 pop = np.random.randint( 2, size=(self.pop_size, self.dna_size*len(bound))) Xm[0], Ym[0] = self._get_best(pop, obj_func, bound, is_min) # 迭代求最優(yōu)解 for _ in range(self.n_iters): X = self._decode(pop, bound) fitness = self._fitness(obj_func, X, is_min) keep, selected = self._select(pop, fitness) new = self._crossover_mutation(selected) pop = np.vstack((keep, new)) xm, ym = self._get_best(pop, obj_func, bound, is_min) Xm.append(xm) Ym.append(ym) idx = np.argmin(Ym) if is_min else np.argmax(Ym) return idx, Xm, Ym def _decode(self, pop: np.ndarray, bound: List[tuple]): """ 將二進制數(shù)解碼為十進制數(shù) """ result = [] N = 2**self.dna_size-1 pows = 2**np.arange(self.dna_size, dtype=float)[::-1] for i, (low, high) in enumerate(bound): X = pop[:, i*self.dna_size:(i+1)*self.dna_size] X = low + (high-low)*X.dot(pows)/N result.append(X) return result def _fitness(self, obj_func, X: tuple, is_min=True): """ 適應(yīng)度函數(shù) """ y = obj_func(*X) e = 1e-3 return -y+np.max(y)+e if is_min else y-np.min(y)+e def _select(self, pop: np.ndarray, fitness: np.ndarray): """ 根據(jù)使用度選擇染色體 """ # 保留一定比例的優(yōu)秀染色體 n_keep = int(self.pop_size*self.top_rate) idx_keep = (-fitness).argsort()[:n_keep] # 按照適應(yīng)度值隨機挑選出剩下的染色體 n_select = self.pop_size-n_keep p = fitness/fitness.sum() idx = np.random.choice(np.arange(self.pop_size), n_select, True, p) return pop[idx_keep], pop[idx] def _crossover_mutation(self, pop: np.ndarray): """ 交叉變異 """ result = [] for child in pop: # 交叉 if np.random.rand() < self.crossover_rate: mother = pop[np.random.randint(len(pop))] point = np.random.randint(pop.shape[1]) child[point:] = mother[point:] # 變異 if np.random.rand() < self.mutation_rate: pos = np.random.randint(pop.shape[1]) child[pos] = 1 - child[pos] result.append(child) return np.array(result) def _get_best(self, pop: np.ndarray, obj_func, bound: List[tuple], is_min): """ 獲取最優(yōu)解及其目標函數(shù)值 """ X = self._decode(pop, bound) Y = obj_func(*X) idx = np.argmin(Y) if is_min else np.argmax(Y) xm = [x[idx] for x in X] return xm, Y[idx] def f(x, y): """ 目標函數(shù) """ return 20+x*x+y*y-10*(np.cos(2*np.pi*x)+np.cos(2*np.pi*y)) if __name__ == '__main__': ga = GA(dna_size=50) idx, Xm, Ym = ga.get_solution(f, [(-5, 5), (-5, 5)]) print("最優(yōu)解:", Xm[idx], "目標函數(shù)值:", Ym[idx]) plt.plot(np.arange(ga.n_iters+1), Ym) plt.show()
實驗
本文使用遺傳算法求取 Rastrigin 函數(shù)在二維空間的最小值問題,優(yōu)化問題可描述為:
minf(x1,x2)=20+x12+x22−10(cos2πx1+cos2πx2), s.t.−5≤x1,x2≤5
觀察函數(shù)圖像可知 Rastrigin 函數(shù)具有多個局部最小值點,在 (0,0)處是最優(yōu)全局最優(yōu)解,此時最優(yōu)目標值為f∗(x1,x2)=0。為了更好地研究各個參數(shù)的變化對遺傳算法的搜索能力和收斂速度的影響,本文在 N=200, Lc=20, α=0.2, Pc=0.8, Pm=0.01, ε=100(使用迭代次數(shù)作為結(jié)束條件)的條件下分別調(diào)整每個參數(shù),仿真并分析每個參數(shù)值下目標函數(shù)值曲線的變化。為了減少隨機性造成的干擾,每個參數(shù)下進行 10 次仿真,并取平均值作為最終結(jié)果。
調(diào)整種群規(guī)模
當(dāng)種群規(guī)模在 [20,260][20,260] 區(qū)間內(nèi)變化時,迭代曲線如下圖所示??梢钥吹?,當(dāng)種群規(guī)模 較小時,種群多樣性會比較少,會造成近親繁殖的現(xiàn)象,算法容易陷入局部最優(yōu)解且收斂速 度較慢。隨著種群規(guī)模的擴大,多樣性逐漸提升,交叉和變異更有可能繁殖出更優(yōu)秀的個體, 算法很快就收斂于全局最優(yōu)解。對比N=180和N=260的迭代曲線,發(fā)現(xiàn)二者的收斂速度 差異并不大,所以種群數(shù)量不能設(shè)置過大,一味增大種群數(shù)量只會造成運算開銷的上升。
調(diào)整基因長度
基因長度對迭代曲線的影響如下圖所示,由圖可知,當(dāng)基因長度為 Lc=5時,二進制數(shù)據(jù)分辨率過低,算法很快收斂到解 (1.129,1.129)。有趣的是當(dāng)基因長度Lc∈{35,40,45}時的收斂到最優(yōu)解的速度并沒有快于Lc=20,原因在于染色體長度越大,分辨率就越高,這減小了隨機交叉和變異對染色體的十進制數(shù)的影響。
調(diào)整保留的最優(yōu)染色體比率
保留最優(yōu)染色體的比率在 [0.1,0.9]之間變化時,迭代曲線的對比如下圖所示。由圖可知,如果保留的最優(yōu)染色體過多,會導(dǎo)致用于交叉和變異的染色體數(shù)量變少,減少了算法從局部最優(yōu)解跳到全局最優(yōu)解的可能性,因此α=0.9時算法收斂速度明顯慢于其他曲線。而α=0.1時保留的最優(yōu)個體太少,過多的交叉和變異可能導(dǎo)致算法在多個局部最優(yōu)解之間跳 動,只有α∈{0.2,0.3,0.4}時算法快速地收斂于全局最優(yōu)解。
調(diào)整交叉概率
交叉概率在[0.1,1.0]之間變化時,迭代曲線的對比如下圖所示??梢园l(fā)現(xiàn),交叉概率較小時,種群內(nèi)的染色體通過交叉繁衍出更優(yōu)秀個體的概率也更低,因此算法收斂速度較慢。而交叉概率取 0.8 以上的值時,種群繁衍出優(yōu)秀個體的概率變大,收斂速度明顯加快。
調(diào)整變異概率
為了更好地體現(xiàn)變異概率變化對算法表現(xiàn)的影響,本文分別在種群規(guī)模為 40 和 200 的 情況下仿真[0.02,0.20]范圍內(nèi)的Pm對應(yīng)的迭代曲線。觀察圖 (a) 可以發(fā)現(xiàn)種群規(guī)模較小時,變異概率越高,染色體進化到更優(yōu)秀個體的概率也越大,算法收斂速度越快。當(dāng)種群規(guī)模擴大到 200 時,過大的變異概率反而會影響算法收斂到全局最優(yōu)解。
調(diào)整迭代次數(shù)
本文使用迭代次數(shù)作為算法的結(jié)束條件,由下圖可知當(dāng)?shù)螖?shù)較少時,種群還未成熟,算法沒有收斂到全局最優(yōu)解。當(dāng)?shù)螖?shù)達到 60 次時,迭代曲線取向平緩,表明種群已成熟,算法已收斂到全局最優(yōu)解,此后的迭代對算法的貢獻很小,卻帶來了一定的計算開銷。
結(jié)論
本文針對遺傳算法的參數(shù)調(diào)節(jié)問題,開展基于遺傳算法的 Rastrigin 函數(shù)優(yōu)化問題研究。 仿真結(jié)果表明,種群規(guī)模、基因長度、保留的最優(yōu)染色體比例和迭代次數(shù)不能過小或過大,更大的交叉概率可以增大進化出更優(yōu)秀個體的概率,而更大的變異概率在種群規(guī)模較小時也能加速算法收斂到全局最優(yōu)解的速度。對于遺傳算法在其他場景上的應(yīng)用,可以在這個調(diào)參準則的基礎(chǔ)上進行參數(shù)選擇,能讓遺傳算法有更好的表現(xiàn)。
到此這篇關(guān)于詳解如何在Python中實現(xiàn)遺傳算法的文章就介紹到這了,更多相關(guān)Python遺傳算法內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
基于Python和PyYAML讀取yaml配置文件數(shù)據(jù)
這篇文章主要介紹了基于Python和PyYAML讀取yaml配置文件數(shù)據(jù),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-01-01解決使用Pycharm導(dǎo)入conda?environment時找不到python.exe
今天在使用conda創(chuàng)建環(huán)境之后,使用pycham發(fā)現(xiàn)找到自己的python環(huán)境但是找不到環(huán)境對應(yīng)的python.exe,這篇文章主要給大家介紹了關(guān)于如何解決使用Pycharm導(dǎo)入conda?environment時找不到python.exe的相關(guān)資料,需要的朋友可以參考下2023-10-10Python使用SQLAlchemy操作Mysql數(shù)據(jù)庫的操作示例
SQLAlchemy是Python的SQL工具包和對象關(guān)系映射(ORM)庫,它提供了全套的企業(yè)級持久性模型,用于高效、靈活且優(yōu)雅地與關(guān)系型數(shù)據(jù)庫進行交互,這篇文章主要介紹了Python使用SQLAlchemy操作Mysql數(shù)據(jù)庫,需要的朋友可以參考下2024-08-08基于Python實現(xiàn)股票數(shù)據(jù)分析的可視化
在購買股票的時候,可以使用歷史數(shù)據(jù)來對當(dāng)前的股票的走勢進行預(yù)測,這就需要對股票的數(shù)據(jù)進行獲取并且進行一定的分析。本文將介紹如何通過Python實現(xiàn)股票數(shù)據(jù)分析的可視化,需要的可以參考一下2021-12-12