[機器學習與深度學習] - No.2 遺傳算法原理及簡單實現

遺傳算法(Genetic Algorithm),顧名思義,就是模擬物種進化的過程,遵循“物競天擇,適者生存”的原則,隨機化搜索的算法。遺傳算法模擬種羣演化過程,經歷“選擇”,“基因交叉”,“變異”等過程。遺傳算法不保證一定能得到解,如果有解也不保證找到的是最優解,但若干代以後,理想狀態下,染色體的適應度可能達到近似最優的狀態。

遺傳算法的最大優點就是,我們不需要知道怎麼去解決一個問題,獲得最優解。你需要知道的僅僅是,怎麼將解空間中的值進行編碼,使得它能能被遺傳算法機制所利用,遺傳算法會爲我們挑選出可能最優的解。

遺傳算法組成如下:

  • 將染色體編碼
  • 計算種羣的適應度函數值
  • 進行選擇,交叉,變異,獲取新一代的染色體
  • 重複步驟2,直到迭代數或者適應度函數值收斂

1. 編碼:

使用遺傳算法首先就是要對問題的解,編碼成一定形式,才能使用遺傳算法。常用的二進制編碼方式是二進制編碼。例如:我們需要求解以下函數的在[0,9]上的極大值

y=x+10sin(5x)+7cos(4x)x[0,9]

假設我們要求精度爲0.0001,即小數點後四位,那麼我們將[0,9]區間分爲 (9 - 0 ) /0.0001 = 90000等分

我們使用二進制編碼。我們知道3位二進制編碼可以表示231 個數字。由於216<90000<217 ,所以我們至少需要17位二進制編碼來表示我們的解。每個解均被表示成17位的二進制數。

任意一個17位二進制數,可用如下公式解碼到[0,9]

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

一般化的解碼公式:

x = lower_bound + decimal(chromosome)×(upper_bound-lower_bound)/(2^chromosome_size-1)

2. 適應度函數

用於評價某個染色體的適應度,用f(x) 表示。有時需要區分染色體的適應度函數與問題的目標函數。適應度函數與目標函數是正相關的,可對目標函數作一些變形來得到適應度函數。但是,對於我們這種,求函數極大值的問題,可以將目標函數作爲適應度函數。如果一個染色體,目標函數值大,那麼它對應的適應度函數值也應該大。

3. 選擇

按照一定的策略,選擇一些染色體來產生下一代。輪盤法是一種 “比例選擇”,也就是個體被選中的概率與其適應度函數值成正比。適應性分數最高的成員不一定能選入下一代,但是它有最大的概率被選中。
這裏寫圖片描述
我們設{p(i)|i=1..n} 爲個體i 被選中的概率,那麼顯然 in(p(i))=1 。我們使用S() 集合來保存第n次選擇的結果,輪盤賭算法的(僞代碼)實現如下:

for i = 1:n
    r = rand()          %產生一個 [0 - 1] 之間的隨機數
    ps = 0              %輪盤初始轉動的位置,從0變化到1
    j = 1
    while ps < r        %終止條件爲輪盤轉動的位置超過色子位置
        ps = ps + p(j)  %輪盤轉動
        j = j + 1
    end
    s(i) = j - 1        %輪盤停止時色子停留位置所指示的個體

舉個例子:

染色體 A B C D E F
種羣適應度 5 7 9 12 15 30
種羣累計適應度 5 12 21 33 48 78
選中隨機數區間 [0 - 0.06] [0.06 - 0.15] [0.15-0.27] [0.27 - 0.42] [0.42 - 0.62 ] [0.62 - 1]
被選中概率 0.06 0.09 0.12 0.15 0.20 0.38

可以看出,種羣適應度越高的染色體越容易被留下產生下一代

精英(Elitist Strategy)選擇:爲了防止進化過程中產生的最優解被交叉和變異所破壞,可以將每一代中的最優解原封不動的複製到下一代中。

4. 交叉

我們從剛剛選擇的種羣中選出 2個染色體,同時生成其值在0到1之間一個隨機數,然後根據此數據的值來確定兩個染色體是否要進行雜交。一般設置雜交率爲0.6,如果該隨機數小與0.6,我們就對兩個染色體進行雜交。交叉算子分爲單點交叉、兩點交叉等。在程序中我選擇的是兩點交叉,隨機生成兩個在[0,chromosome_length] 之間的隨機數,作爲基因雜交的位置,將該位置的兩個基因進行交換,產生兩個子染色體

雜交位置爲(3,5)
雜交前
111222333
aaabbbccc
雜交後:
111bbb333
aaa222ccc

5. 變異

遺傳算法中的變異運算,是指將個體染色體編碼串中的某些位置上的基因值用該位置上其它等位基因來替換,從而形成以給新的個體。在我們程序的視線中,基因變異即將該位置的基因由0變爲1,或由1變爲0

我們選出任意一個染色體,同時生成其值在0到1之間一個隨機數,然後根據此數據的值來確定該染色體是否要進行變異。變異率一般設爲0.02

我們以上面提到的函數爲例,編寫程序,計算極大值:

函數圖像:
這裏寫圖片描述
程序運行截圖:
這裏寫圖片描述
可以看到,使用scipy計算得到的值爲21.4475874266,遺傳算法得到的值爲21.4475874267,可以看作爲近似最優

完整程序:

# -*- coding: utf-8 -*-
# @Author  :yenming
# @Time    :2018/4/10  16:10

import numpy as np
import math
from scipy.optimize import fmin,fminbound

def obj_function(x):
    return x + 10 * math.sin(5*x) -7 * math.cos(4*x)

#二進制轉十進制
def bin2dec(bin):
    res = 0
    max_index = len(bin) - 1
    for i in range(len(bin)):
        if bin[i] == 1:
            res = res + 2**(max_index-i)
    return res

#獲取特定精度下,染色體長度
def get_encode_length(low_bound,up_bound,precision): # e.g 0,9,0.01
    divide = (up_bound - low_bound)/precision
    for i in range(10000):
        if 2**i < divide < 2**(i + 1):
            return i+1
    return -1

#將二進制的染色體解碼成[low,up]空間內的數
def decode_chromosome(low_bound,up_bound, length, chromosome):
    return low_bound + bin2dec(chromosome)*(up_bound - low_bound)/(2**length -1)

#定義初始染色體
def intial_population(length,population_size):
    chromosomes = np.zeros((population_size,length),dtype=np.int8)
    for i in range(population_size):
        chromosomes[i] = np.random.randint(0,2,length)              #隨機數[0,2)之間的整數
    return chromosomes


##########hyperparameter##############
# f(x) = x + 10*sin(5x) -7*cos(4x)
population_size = 500   #種羣大小
iselitist = True        #是否精英選擇
generations = 1000      #演化多少代
low_bound = 0           #區間下界
up_bound = 9            #區間上界
precision = 0.0000001      #精度
chromosome_length = get_encode_length(low_bound,up_bound,precision)  #染色體長度
populations = intial_population(chromosome_length,population_size)   #初始種羣
best_fit = 0            #獲取到的最大的值
best_generation = 0     #獲取到最大值的代數
best_chromosome = [0 for x in range(population_size)]               #獲取到最大值的染色體
fitness_average = [0 for x in range(generations)]                   #種羣平均適應度
cross_rate = 0.6                                                    #基因交叉率
mutate_rate = 0.01                                                  #基因變異率
######################################

#計算種羣中每個染色體的適應度函數值(在這個問題中,適應度函數就是目標函數)
def fitness(populations):
    fitness_val = [0 for x in range(population_size)]
    for i in range(population_size):
        fitness_val[i] = obj_function(decode_chromosome(low_bound,up_bound,chromosome_length,populations[i]))
    return fitness_val

#對種羣染色體根據適應度函數進行排序,適應度函數值高的在最後
def rank(fitness_val,populations,cur_generation):
    global best_fit,best_generation,best_chromosome
    global fitness_average
    fitness_sum = [0 for x in range(len(populations))]              #初始化種羣累計適應度
    #population_size,length = populations.shape
    for i in range(len(populations)):                               #冒泡排序按照種羣適應度從小到大
        min_index = i
        for j in range(i+1,population_size):
            if fitness_val[j] < fitness_val[min_index]:
                min_index = j
        if min_index!=i:
            tmp = fitness_val[i]
            fitness_val[i] = fitness_val[min_index]
            fitness_val[min_index] = tmp

            tmp_list = np.zeros(chromosome_length)
            for k in range(chromosome_length):
                tmp_list[k] = populations[i][k]
                populations[i][k] = populations[min_index][k]
                populations[min_index][k] = tmp_list[k]

    #########種羣適應度從小到大排序完畢#########
    for l in range(len(populations)):                               #獲取種羣累計適應度
        if l == 1:
            fitness_sum[l] = fitness_val[l]
        else:
            fitness_sum[l] = fitness_val[l] + fitness_val[l-1]

    fitness_average[cur_generation] = fitness_sum[-1]/population_size   #每一代的平均適應度,在這個算法程序中沒有使用到,僅作記錄

    if fitness_val[-1] > best_fit:                                  #更新最佳適應度及其對應的染色體
        best_fit = fitness_val[-1]
        best_generation = cur_generation
        for m in range(chromosome_length):
            best_chromosome[m] = populations[-1][m]
    return fitness_sum

#根據當前種羣,按照輪盤法選擇新一代染色體
def select(populations,fitness_sum,iselitist): #輪盤選擇法,實現過程可看爲二分查找
    population_new = np.zeros((population_size, chromosome_length), dtype=np.int8)
    for i in range(population_size):
        rnd = np.random.rand()*fitness_sum[-1]
        first = 0
        last = population_size-1
        mid = (first+last)//2
        idx = -1
        while first <= last:
            if rnd >fitness_sum[mid]:
                first = mid
            elif rnd < fitness_sum[mid]:
                last = mid
            else:
                idx = mid
                break
            if last - first == 1:
                idx = last
                break
            mid = (first + last) // 2

        for j in range(chromosome_length):
            population_new[i][j] = populations[idx][j]
    if iselitist == True: #是否精英選擇,精英選擇強制保留最後一個染色體(適應度函數值最高)
        p = population_size - 1
    else:
        p = population_size
    for k in range(p):
        for l in range(chromosome_length):
            populations[k][l] = populations[k][l]

#基因交叉
def crossover(populations):
    for i in range(0,population_size,2):
        rnd = np.random.rand()
        if rnd < cross_rate:
            rnd1 = int(math.floor(np.random.rand()*chromosome_length))
            rnd2 = int(math.floor(np.random.rand()*chromosome_length))
        else:
            continue
        if rnd1 <= rnd2:
           cross_position1 = rnd1   #這裏我選擇了一個基因片段,進行兩點的交叉
           cross_position2 = rnd2
        else:
           cross_position1 = rnd2
           cross_position2 = rnd1
        for j in range(cross_position1,cross_position2):
            tmp = populations[i][j]
            populations[i][j] = populations[i+1][j]
            populations[i+1][j] = tmp
#基因變異
def mutation(populations):
    for i in range(population_size):
        rnd = np.random.rand()
        if rnd < mutate_rate:
            mutate_position = int(math.floor(np.random.rand()*chromosome_length))
        else:
            continue
        populations[i][mutate_position] = 1 - populations[i][mutate_position]

#演化generations代
for g in range(generations):
    print("generation {} ".format(g))
    fitness_val = fitness(populations)
    fitness_sum = rank(fitness_val,populations,g)
    select(populations,fitness_sum,iselitist)
    crossover(populations)
    mutation(populations)
    print("best chromosome", best_chromosome)
    print("best_generation", best_generation)
    print("best_fit", best_fit)

print("####################Done######################")
print("best chromosome",best_chromosome)
print("best_generation",best_generation)
print("best_fit",best_fit)



print("####################Actual###################")


def func(x):#使用fmincound 將函數取負,求最小值
    return -obj_function(x)


min_global=fminbound(func,0,9)#這個區域的最小值
print("The actual max value",-func(min_global))
x = [0,1,2,3,4,5,6,7,8,9]
y = [i + 10 * np.sin(i*5) -7 * np.cos(i*4) for i in x]
import matplotlib.pyplot as plt
plt.plot(x,y)
plt.show()

參考文章:
https://www.zhihu.com/question/23293449
http://blog.jobbole.com/110913/
https://blog.csdn.net/zzwu/article/details/561620

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章