遺傳算法-總體框架


首先了解一下達爾文進化論:人類在繁衍過程中,通過交配產生基因重組和變異,從而產生更好的個體,也可能是更差的個體,

每一代人接受大自然的考驗,優勝略汰,適應能力強的被保存下來,差的被淘汰,使得人類對環境的適應能力越來越強;

 

遺傳算法就是借鑒了人類的進化過程,更好地適應環境就是我們的目標(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/  遺傳算法的交叉變異詳解


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM