【計算智能】目標優化智能算法之遺傳算法(Python實現)

遺傳算法(genetic algorithm, GA)是一種進化算法,其基本原理是仿效生物界中的“物競天擇,適者生存”的演化法則。遺傳算法是把問題參數編碼為染色體,再利用迭代的方式進行選擇、交叉以及變異等運算來交換種群中染色體的信息,最終生成符合優化目標的染色體。
更好閱讀體驗請訪問(https://tianle.me/2017/04/19/GA/

名詞解釋

在遺傳算法中,染色體對應的是數據或數組,通常是由一維的串結構數據來表示,串上各個位置對應基因的取值。基因組成的串就是染色體(chromosome),或者稱為基因型個體(individual)。一定數量的個體組成了群體(population)。群體中的個體數目稱為群體大小(population size),也成為群體規模。而各個個體對環境的適應程度叫適應度(fitness)。

基本步驟

編碼

GA在進行搜索之前先將解空間的解數據表示成遺傳空間的基因型串結構數據,這些串結構數據的不同組合便構成了不同的點。

初始群體的生成

隨機產生N個初始串結構數據,每個串結構數據稱為一個個體,N個個體構成了一個群體。GA以這N個串結構數據作為初始點開始進化。

適應度評估

適應度表明個體或解的優劣性。不同的問題,適應度函數的定義方式也不同。

選擇

選擇的目的是為了從當前群體中選出優良的個體,使它們有機會作為父代為下一代繁殖子孫。遺傳算法通過選擇過程體現這一思想,進行選擇的原則是適應度強的個體為下一代貢獻一個或多個后代的概率大。選擇體現了達爾文的適者生存原則。

交叉

交叉操作是遺傳算法中最主要的遺傳操作。通過交叉操作可以得到新一代個體,新個體組合了其父輩個體的特性。交叉體現了信息交換的思想。

變異

變異首先在群體中隨機選擇一個個體,對于選中的個體以一定的概率隨機地改變串結構數據中某個串的的值。同生物界一樣,GA中變異發生的概率很低,通常取值很小。

實例詳解

之前已經使用matlab實現了一次,由于現在又布置了作業,正好現在對python不是特別熟悉,那就寫個代碼練練手吧。

目標函數

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  
def func(self):
        self.decoding(self.code_x1, self.code_x2)
        self.y = 21.5 + self.x1 * math.sin(4 * math.pi * self.x1) + self.x2 * math.sin(20 * math.pi * self.x2)

二進制編碼

在剛剛提到的遺傳算法中,我們首先要將數據進行編碼,這里我們采用二進制的方式進行編碼。第一步,我們根據題目的介紹可以得知該函數含有兩個變量,以及各自的定義域。在二進制編碼中,我們首先要先計算它的編碼長度。計算公式如下:
$${2^{{m_j} - 1}} < ({b_j} - {a_j})*precision \le {2^{{m_j}}} - 1$$
其中precision為精度,如小數點后5位,則precision=10^5,mj為編碼長度,${x_j} \in [{a_j},{b_j}]$

二進制解碼

解碼即編碼的逆過程:
$${x_j} = {a_j} + {\rm{decimal}}(substrin{g_j}) \times \frac{{{b_j} - {a_j}}}{{{2^{{m_j}}} - 1}}$$

def decoding(self, code_x1, code_x2):
        self.x1 = self.bounds[0][0] + int(code_x1, 2) * (self.bounds[0][1] - self.bounds[0][0]) / (
        2 ** self.code_x1_length - 1)
        self.x2 = self.bounds[1][0] + int(code_x2, 2) * (self.bounds[1][1] - self.bounds[1][0]) / (
        2 ** self.code_x2_length - 1)

種群初始化

編碼完成那我們就開始對種群初始化吧,為了簡便我采用了隨機地方式進行初始化。

def __init__(self, bounds, precision):
        self.x1 = 1
        self.x2 = 1

        self.y = 0

        self.code_x1 = ''
        self.code_x2 = ''

        self.bounds = bounds

        temp1 = (bounds[0][1] - bounds[0][0]) * precision
        self.code_x1_length = math.ceil(math.log(temp1, 2))

        temp2 = (bounds[1][1] - bounds[1][0]) * precision
        self.code_x2_length = math.ceil(math.log(temp2, 2))

        self.rand_init()
        self.func()
        
def rand_init(self):
        for i in range(self.code_x1_length):
            self.code_x1 += str(random.randint(0, 1))

        for i in range(self.code_x2_length):
            self.code_x2 += str(random.randint(0, 1))

選擇

選擇我們采用輪盤賭方式進行選擇,主要思想是適應度高的,被選擇到的概率大。


沒怎么優化,用了一堆for循環。。。。

    def select(self):
        """
        輪盤賭選擇
        :return:
        """
        # calculate fitness function
        sum_f = 0
        for i in range(self.pop_size):
            self.pop[i].func()

        # guarantee fitness > 0
        min = self.pop[0].y
        for i in range(self.pop_size):
            if self.pop[i].y < min:
                min = self.pop[i].y
        if min < 0:
            for i in range(self.pop_size):
                self.pop[i].y = self.pop[i].y + (-1) * min

        # roulette
        for i in range(self.pop_size):
            sum_f += self.pop[i].y
        p = [0] * self.pop_size
        for i in range(self.pop_size):
            p[i] = self.pop[i].y / sum_f
        q = [0] * self.pop_size
        q[0] = 0
        for i in range(self.pop_size):
            s = 0
            for j in range(0, i+1):
                s += p[j]
            q[i] = s
        # start roulette
        v = []
        for i in range(self.pop_size):
            r = random.random()
            if r < q[0]:
                v.append(self.pop[0])
            for j in range(1, self.pop_size):
                if q[j - 1] < r <= q[j]:
                    v.append(self.pop[j])
        self.pop = v

變異

這里的變異,我們先以變異概率,從種群中選一個,然后對選中的個體,隨機選一個變異位點進行變異。

    def mutation(self):
        """
        變異
        :return:
        """
        for i in range(self.pop_size):
            if self.pm > random.random():
                pop = self.pop[i]
                # select mutation index
                index1 = random.randint(0, pop.code_x1_length-1)
                index2 = random.randint(0, pop.code_x2_length-1)

                i = pop.code_x1[index1]
                i = self.__inverse(i)
                pop.code_x1 = pop.code_x1[:index1] + i + pop.code_x1[index1+1:]

                i = pop.code_x2[index2]
                i = self.__inverse(i)
                pop.code_x2 = pop.code_x2[:index2] + i + pop.code_x2[index2+1:]

交叉

這里采用單點交叉法。隨機從種群中選兩個個體,然后再隨機選一個交叉點,交換位置。看圖 = . =

    def cross(self):
        """
        交叉
        :return:
        """
        for i in range(int(self.pop_size / 2)):
            if self.pc > random.random():
                # randon select 2 chromosomes in pops
                i = 0
                j = 0
                while i == j:
                    i = random.randint(0, self.pop_size-1)
                    j = random.randint(0, self.pop_size-1)
                pop_i = self.pop[i]
                pop_j = self.pop[j]

                # select cross index
                pop_1 = random.randint(0, pop_i.code_x1_length - 1)
                pop_2 = random.randint(0, pop_i.code_x2_length - 1)

                # get new code
                new_pop_i_code1 = pop_i.code_x1[0: pop_1] + pop_j.code_x1[pop_1: pop_i.code_x1_length]
                new_pop_i_code2 = pop_i.code_x2[0: pop_2] + pop_j.code_x2[pop_2: pop_i.code_x2_length]

                new_pop_j_code1 = pop_j.code_x1[0: pop_1] + pop_i.code_x1[pop_1: pop_i.code_x1_length]
                new_pop_j_code2 = pop_j.code_x2[0: pop_2] + pop_i.code_x2[pop_2: pop_i.code_x2_length]

                pop_i.code_x1 = new_pop_i_code1
                pop_i.code_x2 = new_pop_i_code2

                pop_j.code_x1 = new_pop_j_code1
                pop_j.code_x2 = new_pop_j_code2

算法主流程

至此,遺傳的主要框架已經完畢,下面展示主流程,及畫圖部分代碼。

    def ga(self):
        """
        算法主函數
        :return:
        """
        self.init_pop()
        best = self.find_best()
        self.g_best = copy.deepcopy(best)
        y = [0] * self.pop_size
        for i in range(self.max_gen):
            self.cross()
            self.mutation()
            self.select()
            best = self.find_best()
            self.bests[i] = best
            if self.g_best.y < best.y:
                self.g_best = copy.deepcopy(best)
            y[i] = self.g_best.y
            print(self.g_best.y)

        # plt
        plt.figure(1)
        x = range(self.pop_size)
        plt.plot(x, y)
        plt.ylabel('generations')
        plt.xlabel('function value')
        plt.show()

實驗結果圖

總結

在編碼的時候,我偷懶了一下,把兩個變量拆開寫,x1和x2,導致之后的操作變得異常復雜,并且不利于代碼重構。
程序中過多的使用了for循環,并沒有對此進行優化。
針對上述兩個問題,在此記錄一下。

程序完整代碼

Genetic-Algorithms

參考資料

《MATLAB智能算法-30個案例分析》

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容