遺傳算法 Genetic Algorithm


2017-12-17 19:12:10

一、Evolutionary Algorithm

進化算法,也被成為是演化算法(evolutionary algorithms,簡稱EAs),它不是一個具體的算法,而是一個“算法簇”。進化算法的產生的靈感借鑒了大自然中生物的進化操作,它一般包括基因編碼,種群初始化,交叉變異算子,經營保留機制等基本操作。與傳統的基於微積分的方法和窮舉方法等優化算法相比,進化計算是一種成熟的具有高魯棒性和廣泛適用性的全局優化方法,具有自組織、自適應、自學習的特性,能夠不受問題性質的限制,有效地處理傳統優化算法難以解決的復雜問題(比如NP難優化問題)。

除了上述優點以外,進化算法還經常被用到多目標問題的優化求解中來,我們一般稱這類進化算法為進化多目標優化算法(MOEAs)。目前進化計算的相關算法已經被廣泛用於參數優化、工業調度、資源分配、復雜網絡分析等領域。

 

二、Genetic Algorithm

遺傳算法(Genetic Algorithm,簡稱GA)是一種最基本的進化算法,它是模擬達爾文生物進化理論的一種優化模型,最早由J.Holland教授於1975年提出。遺傳算法中種群分每個個體都是解空間上的一個可行解,通過模擬生物的進化過程,從而在解空間內搜索最優解。

遺傳算法的基本操作可以用下圖來描述:

個體的編碼方式確定以后,針對上圖操作的具體描述如下:

  Step 1 種群初始化:根據問題特性設計合適的初始化操作(初始化操作應盡量簡單,時間復雜度不易過高)對種群中的N個個體進行初始化操作;

  Step 2 個體評價:根據優化的目標函數計算種群中個體的適應值(fitness value);

  Step 3 迭代設置:設置種群最大迭代次數gmax,並令當前迭代次數g=1;

  Step 4 個體選擇:設計合適的選擇算子來對種群P(g)個體進行選擇,被選擇的個體將進入交配池中組成父代種群FP(g),用於交叉變換以產生新的個體。選擇策略要基於個體適應值來進行,假如要優化的問題為最小化問題,那么具有較小適應值的個體被選擇的概率相應應該大一些。常用的選擇策略有輪盤賭選擇,錦標賽選擇等。

  Step 5 交叉算子:根據交叉概率pm(預先指定,一般為0.8)來判斷父代個體是否需要進行交叉操作。交叉算子要根據被優化問題的特性來設計,它是整個遺傳算法的核心,它被設計的好壞將直接決定整個算法性能的優劣。

  Step 6 變異算子:根據變異概率pc(預先指定,一般為0.1)來判斷父代個體是否需要進行變異操作。變異算子的主要作用是保持種群的多樣性,防止種群陷入局部最優,所以其一般被設計為一種隨機變換。

  通過交叉變異操作以后父代種群FP(g)生成了新的子代種群P(g+1),令種群迭代次數g=g+1,進行下一輪的迭代操作(跳轉到Step 4),直至迭代次數達到最大的迭代次數。

需要注意的問題:

1)種群數目

太多會導致收斂很慢,太少會落入局部最優值。需要不斷嘗試得到一個合適的種群數目。

2)交叉繁殖概率

一般建議設置為0.8。

3)變異概率

建議值為1/Population,如果種群值為100,則建議為0.01。太大會破壞整個算法的正確執行,太小會導致種群的多樣性下降,會導致陷入局部最優。

4)挑選方式

  •  輪盤賭

一是最原始的輪盤競賽方式,也就是適應度高的在輪盤上的比例就高,比例值和其自身的比例值相對應。

有個問題就是,如果出現負值就沒法處理。可以改進一下,輪盤中的比例是固定的,只和排名有關。

  •  精英選擇(TOP-K)

舉個例子:

求解函數 f(x) = x + 10*sin(5*x) + 7*cos(4*x) 在區間[0,9]的最大值。

 

事實上,不管一個函數的形狀多么奇怪,遺傳算法都能在很短的時間內找到它在一個區間內的(近似)最大值。

1.編碼

實現遺傳算法的第一步就是明確對求解問題的編碼和解碼方式。

對於函數優化問題,一般有兩種編碼方式,各具優缺點

  • 實數編碼:直接用實數表示基因,容易理解且不需要解碼過程,但容易過早收斂,從而陷入局部最優
  • 二進制編碼:穩定性高,種群多樣性大,但需要的存儲空間大,需要解碼且難以理解
這里采用2進制編碼,假設解的精度為小數點后4位,可以將x的解空間划分為 (9-0)×(1e+4)=90000個等分。2^16<90000<2^17,需要17位二進制數來表示這些解。換句話說,一個解的編碼就是一個17位的二進制串。

一開始,這些二進制串是隨機生成的。一個這樣的二進制串代表一條染色體串,這里染色體串的長度為17。對於任何一條這樣的染色體chromosome,如何將它復原(解碼)到[0,9]這個區間中的數值呢?

對於本問題,我們可以采用以下公式來解碼:

 x = 0 + decimal(chromosome)×(9-0)/(2^17-1)

 更一般的公式為:

f(x), x∈[lower_bound, upper_bound]
x = lower_bound + decimal(chromosome)×(upper_bound-lower_bound)/(2^chromosome_size-1)
lower_bound: 函數定義域的下限
upper_bound: 函數定義域的上限
chromosome_size: 染色體的長度

 2.個體和種群

『染色體』表達了某種特征,這種特征的載體,稱為『個體』。

對於本次實驗所要解決的一元函數最大值求解問題,個體可以用上一節構造的染色體表示,一個個體里有一條染色體。

許多這樣的個體組成了一個種群,其含義是一個一維點集(x軸上[0,9]的線段)。

3.適應度函數

遺傳算法中,一個個體(解)的好壞用適應度函數值來評價,在本問題中,f(x)就是適應度函數。

適應度函數值越大,解的質量越高。

適應度函數是遺傳算法進化的驅動力,也是進行自然選擇的唯一標准,它的設計應結合求解問題本身的要求而定。

4.遺傳算子

我們希望有這樣一個種群,它所包含的個體所對應的函數值都很接近於f(x)在[0,9]上的最大值,但是這個種群一開始可能不那么優秀,因為個體的染色體串是隨機生成的。

如何讓種群變得優秀呢?

不斷的進化。

每一次進化都盡可能保留種群中的優秀個體,淘汰掉不理想的個體,並且在優秀個體之間進行染色體交叉,有些個體還可能出現變異。

種群的每一次進化,都會產生一個最優個體。種群所有世代的最優個體,可能就是函數f(x)最大值對應的定義域中的點。

如果種群無休止地進化,那總能找到最好的解。但實際上,我們的時間有限,通常在得到一個看上去不錯的解時,便終止了進化。

對於給定的種群,如何賦予它進化的能力呢?

  • 首先是選擇(selection)
    • 選擇操作是從前代種群中選擇***多對***較優個體,一對較優個體稱之為一對父母,讓父母們將它們的基因傳遞到下一代,直到下一代個體數量達到種群數量上限
    • 在選擇操作前,將種群中個體按照適應度從小到大進行排列
    • 采用輪盤賭選擇方法(當然還有很多別的選擇方法),各個個體被選中的概率與其適應度函數值大小成正比
    • 輪盤賭選擇方法具有隨機性,在選擇的過程中可能會丟掉較好的個體,所以可以使用精英機制,將前代最優個體直接選擇
  • 其次是交叉(crossover)
    • 兩個待交叉的不同的染色體(父母)根據交叉概率(cross_rate)按某種方式交換其部分基因
    • 采用單點交叉法,也可以使用其他交叉方法
  • 最后是變異(mutation)
    • 染色體按照變異概率(mutate_rate)進行染色體的變異
    • 采用單點變異法,也可以使用其他變異方法
#encoding=utf-8

import math
import random
import operator

class GA():
    def __init__(self, length, count):
        # 染色體長度
        self.length = length
        # 種群中的染色體數量
        self.count = count
        # 隨機生成初始種群
        self.population = self.gen_population(length, count)

    def evolve(self, retain_rate=0.2, random_select_rate=0.5, mutation_rate=0.01):
        """
        進化
        對當前一代種群依次進行選擇、交叉並生成新一代種群,然后對新一代種群進行變異
        """
        parents = self.selection(retain_rate, random_select_rate)
        self.crossover(parents)
        self.mutation(mutation_rate)

    def gen_chromosome(self, length):
        """
        隨機生成長度為length的染色體,每個基因的取值是0或1
        這里用一個bit表示一個基因
        """
        chromosome = 0
        for i in xrange(length):
            chromosome |= (1 << i) * random.randint(0, 1)
        return chromosome

    def gen_population(self, length, count):
        """
        獲取初始種群(一個含有count個長度為length的染色體的列表)
        """
        return [self.gen_chromosome(length) for i in xrange(count)]

    def fitness(self, chromosome):
        """
        計算適應度,將染色體解碼為0~9之間數字,代入函數計算
        因為是求最大值,所以數值越大,適應度越高
        """
        x = self.decode(chromosome)
        return x + 10*math.sin(5*x) + 7*math.cos(4*x)

    def selection(self, retain_rate, random_select_rate):
        """
        選擇
        先對適應度從大到小排序,選出存活的染色體
        再進行隨機選擇,選出適應度雖然小,但是幸存下來的個體
        """
        # 對適應度從大到小進行排序
        graded = [(self.fitness(chromosome), chromosome) for chromosome in self.population]
        graded = [x[1] for x in sorted(graded, reverse=True)]
        # 選出適應性強的染色體
        retain_length = int(len(graded) * retain_rate)
        parents = graded[:retain_length]
        # 選出適應性不強,但是幸存的染色體
        for chromosome in graded[retain_length:]:
            if random.random() < random_select_rate:
                parents.append(chromosome)
        return parents

    def crossover(self, parents):
        """
        染色體的交叉、繁殖,生成新一代的種群
        """
        # 新出生的孩子,最終會被加入存活下來的父母之中,形成新一代的種群。
        children = []
        # 需要繁殖的孩子的量
        target_count = len(self.population) - len(parents)
        # 開始根據需要的量進行繁殖
        while len(children) < target_count:
            male = random.randint(0, len(parents)-1)
            female = random.randint(0, len(parents)-1)
            if male != female:
                # 隨機選取交叉點
                cross_pos = random.randint(0, self.length)
                # 生成掩碼,方便位操作
                mask = 0
                for i in xrange(cross_pos):
                    mask |= (1 << i) 
                male = parents[male]
                female = parents[female]
                # 孩子將獲得父親在交叉點前的基因和母親在交叉點后(包括交叉點)的基因
                child = ((male & mask) | (female & ~mask)) & ((1 << self.length) - 1)
                children.append(child)
        # 經過繁殖后,孩子和父母的數量與原始種群數量相等,在這里可以更新種群。
        self.population = parents + children

    def mutation(self, rate):
        """
        變異
        對種群中的所有個體,隨機改變某個個體中的某個基因
        """
        for i in xrange(len(self.population)):
            if random.random() < rate:
                j = random.randint(0, self.length-1)
                self.population[i] ^= 1 << j


    def decode(self, chromosome):
        """
        解碼染色體,將二進制轉化為屬於[0, 9]的實數
        """
        return chromosome * 9.0 / (2**self.length-1)

    def result(self):
        """
        獲得當前代的最優值,這里取的是函數取最大值時x的值。
        """
        graded = [(self.fitness(chromosome), chromosome) for chromosome in self.population]
        graded = [x[1] for x in sorted(graded, reverse=True)]
        return ga.decode(graded[0])     


if __name__ == '__main__':
    # 染色體長度為17, 種群數量為300
    ga = GA(17, 300)

    # 200次進化迭代
    for x in xrange(200):
         ga.evolve()

    print ga.result()

 

三、Genetic Programming

遺傳編程或稱基因編程,簡稱GP,是一種從生物演化過程得到靈感的自動化生成和選擇計算機程序來完成用戶定義的任務的技術。從理論上講,人類用遺傳編程只需要告訴計算機“需要完成什么”,而不用告訴它“如何去完成”,最終可能實現真正意義上的人工智能:自動化的發明機器。

GP的輸出是一個程序,可以用來控制機器人,進行函數回歸等。

GP中有兩個重要的概念:

1)Terminal Set,包含變量和常量等;

2)Function Set,主要是一些函數,可以是加減乘除,也可以是一些復雜的函數;

GP里進化的實際上是一顆樹,如圖。

1.Crossover雜交(允許自雜交)

2.Mutation變異

舉個例子:

使用GP進行回歸,具體來說是生成一些點,之后使用GP來訓練得到符合這些點的函數。適應度定義為差值的總和。

 

 

 

 

 

 

 

 

 

 


免責聲明!

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



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