遺傳演算法庫DEAP的示例程式碼的學習和分析

張國平發表於2019-03-26

最近看到用遺傳演算法最佳化量化引數的文章,覺得還是有很先進,就開始學習。這個文章主要是針對遺傳演算法庫DEAP的示例程式碼的學習和分析。

程式碼連結:其實有不少針對這段程式碼文章,這裡主要說說自己學習和體會。

原文連結如下:


在分析程式碼前,先說說這段程式碼幹了什麼。這段程式碼就是如果有一個陣列有十個隨機陣列成,如何得到一組十個最小隨機數,其和是最小的。理論上,最小的就是組合就是10個0,但是因這裡面因為引入了變異,可以獲得負數,所以可以組合變異生成負數值。


1. 定義型別,方法creator.create(name,base,**kargs)

這個有點類似設計模式的工廠模式,這個方法建立一個新的類,名字為引數name,繼承的父類為base,這個類的屬性如之後的引數名稱和引數值。

creator.create("FitnessMin", base.Fitness, weights=(-1.0,))
對應的就是類建立程式碼:
class FitnessMin(base.Fitness):
    weights=(-1.0,)
        def __init__(self, values=()):
          super(FitnessMin, self).__init__(values=())
   
creator.create("Individual", list, fitness=creator.FitnessMin)
對應的就是類建立程式碼:
class Individul(list):
    self.fitness=FitnessMin
        def __init__(self):
            ….

     

        1.1 然後取看看base.Fitness這個父類,關鍵是weights這個屬性,主要是做比較用。分析程式碼,會發現把傳入的values序列和weights序列相乘,轉為元祖wvalues,返回的時候就相除返回values

defgetValues(self):
    returntuple(map(truediv,self.wvalues,self.weights))
 
defsetValues(self,values):
    try:
        self.wvalues=tuple(map(mul,values,self.weights))


  遺傳演算法庫DEAP的示例程式碼的學習和分析

2.     註冊方法;方法 toolbox.register (self,alias,function,*args,**kargs)

這個是一個比較核心的方法,引數第一個是別名,第二個是需要繫結的真正function,之後其實是針對這個方法的引數。這樣做就可以把方法和引數二次打包成一個新方法用於遺傳演算法,類似於方法覆蓋。


2.1 toolbox.register("attribute", random.random)

比如這個,就是給toolbox註冊了random.random,使用" attribute "別名,就是random.random()返回一個0-1直接隨機數,呼叫時候就是直接用toolbox.attr_float()

遺傳演算法庫DEAP的示例程式碼的學習和分析

2.2 toolbox.register("individual", tools.initRepeat, creator.Individual,

                 toolbox.attr_float, n=IND_SIZE)

這個更加難以理解 , 是註冊一個方法別名“ individual ”,關聯 tools 原生方法 iniRepeat ,並且呼叫了三個引數。其中creator.Individual的父類就是 list 而toolbox.attr_float實際就是 random.random toolbox . individual實際效果就是initRepeat(list, random.random, 10 )。initRepeat(container, func, n)定義就是用 func 生成 n 個返回值,放在 container 。顯示出來如下一個數列裡面放置 10 個隨機數。

遺傳演算法庫DEAP的示例程式碼的學習和分析

2.3 toolbox.register("population", tools.initRepeat, list, toolbox.individual)

有了上面基礎這個就理解,用一個list作為容器,然後防止toolbox.individual生成的隨機數list,組成雙層list。但是沒有給重複次數,所以要在呼叫時候給定。比如兩次的效果。

遺傳演算法庫DEAP的示例程式碼的學習和分析

 

3.     定義運算, 方法還是之前註冊 register 方法,最重要就是 evaluate 這個方法必須自己實現。


3.1 def evaluate(individual):

            return sum(individual),

        toolbox.register("evaluate", evaluate)

實際呼叫就是把之前individual的list中是個隨機數,算出來隨機數求和。

遺傳演算法庫DEAP的示例程式碼的學習和分析

3.2 toolbox.register("mate", tools.cxTwoPoint)

這個方法是交叉,輸入兩個長度相同資料,通常是陣列;返回兩個同樣長度,但是組成元素交叉互換的;類似於遺傳中的基因交叉。

遺傳演算法庫DEAP的示例程式碼的學習和分析

3.2 toolbox.register("mutate", tools.mutGaussian, mu=0, sigma=1, indpb=0.1)

這個方法是呼叫高斯變異,提供遺傳變異,高斯變異我也不懂。就像下圖顯示的,輸入一個陣列,返回的會陣列變動後如下。

其中引數mu按照介紹是均值;sigma是標準差,這個值決定變異強度,如果sigma為0時候,沒有變異;indpb是獨立機率(Independent probability),決定變異機率,如果為1時候,所有元素都會變異,如果是0.1,就是0.1機率;

遺傳演算法庫DEAP的示例程式碼的學習和分析

3.3 toolbox.register("select", tools.selTournament, tournsize=3)

這個方法呼叫的是

def selTournament(individuals, k, tournsize, fit_attr= "fitness" ):

輸入一個資料組集合,比如有 10 individual 資料組; k 是舉行多少次選拔,每舉行一次選拔得到一個最優解,如果 k len(pop1) ,最後返回就是同樣長度陣列; tournsize 是每次在十個裡面選擇多少個個參加選拔,這裡選擇是隨機的,所以結果也是也看得到,雖然選擇最小,但是有 5.188 這樣明顯很大的。還有就是就算 tournsize 改成 10 ,返回應該就是最小單一值把,試了發現還不是,每次選參賽 individual 是可以重複的,就是可以有一個 individual 重複參加,算是一個邏輯 bug

遺傳演算法庫DEAP的示例程式碼的學習和分析

還有就是就算 tournsize 改成 10情況

  遺傳演算法庫DEAP的示例程式碼的學習和分析

4.     演算法,前面所有要求的都定義好,後面就是一步步繁殖變異篩選了

pop = toolbox.population(n=50)

首先使用polulation方法生成一個50個individual例項,就像前面說的individual是一個有10個0-1隨機陣列成陣列。

fitnesses = map(toolbox.evaluate, pop)

利用map方法,對於pop這個list中每個individual操作,計算出每個individual是個隨機數的和,返回一個元祖。50個individual返回就是50個元祖。

 

for ind, fit in zip(pop, fitnesses):

ind.fitness.values = fit

這個使用了zip方法,把兩個陣列組合成一個新的元祖陣列,這個元祖包括一個individual陣列和fitness元祖,然後用ind,和fit遍歷,再把fit值賦值到individual上。

上面完成了第一代50個inidividual維護。

  遺傳演算法庫DEAP的示例程式碼的學習和分析

後面開始繁殖,首先定義出一些引數,後面用到時候說明。

CXPB, MUTPB, NGEN = 0.5, 0.2, 40

後面就是一個很長的邏輯,首先第一個定義了NGEN,進行多少代繁殖篩選,這裡NGEN是40代。

然後使用select方法,進行50次選拔,每次三個,保留其中individual fitnes最新的那個。

第一遍的select只是引用,所以還要克隆。

然後利用zip,把offspring中兩個不同individual組合成一個元祖陣列,這個元祖有兩個不同individual,然後有50%機會進行交叉繁殖。

同時有20%機會進行突變。

突變和繁殖後,這些individual都沒有fitness,後面就是就是給這些繁殖和突變的individual計算fitness並繫結。

然後用所以的下一代代替上一代。最後經過四十代繁殖返回pop

    for g in range(NGEN):
        # Select the next generation individuals
        offspring = toolbox.select(pop, len(pop))
        # Clone the selected individuals
        offspring = map(toolbox.clone, offspring)
 
        # Apply crossover and mutation on the offspring
        for child1, child2 in zip(offspring[::2], offspring[1::2]):
            if random.random() < CXPB:
                toolbox.mate(child1, child2)
                del child1.fitness.values
                del child2.fitness.values
 
        for mutant in offspring:
            if random.random() < MUTPB:
                toolbox.mutate(mutant)
                del mutant.fitness.values
 
        # Evaluate the individuals with an invalid fitness
        invalid_ind = [ind for ind in offspring if not ind.fitness.valid]
        fitnesses = map(toolbox.evaluate, invalid_ind)
        for ind, fit in zip(invalid_ind, fitnesses):
            ind.fitness.values = fit
 
        # The population is entirely replaced by the offspring
        pop[:] = offspring

 

5.    執行 main() 就可以等到 40 代以後的 pop ,然後可以用遍歷發現各個 sum 都是負數,由於原來隨機並不會產生負數,應該都是高斯變異突變效果,而這些突變也很好保留下來。

最後, best_ind = tools.selBest(pop, 1 )[ ] 可以等到最好的一個 individual ,發現 individual 裡面都是負數,都是突變的值。

    pop = main()
    for individual in pop:
        print(individual.fitness.values)
    print("-- End of (successful) evolution --")
    best_ind = tools.selBest(pop, 1)[0]
    print best_ind, best_ind.fitness.values


最後原始碼可以在我的GitHub裡面找到,

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/22259926/viewspace-2639405/,如需轉載,請註明出處,否則將追究法律責任。

相關文章