【python(deap庫)實現】GEAP 遺傳算法/遺傳編程 genetic programming +


前言

本文不介紹原理的東西,主要是實現進化算法的python實現
原理介紹可以看這里,能學習要很多,我也在這里寫了一些感受心得:
遺傳算法/遺傳編程 進化算法基於python DEAP庫深度解析講解

1.優化問題的定義

單目標優化

creator.create('FitnessMin', base.Fitness, weights=(-1.0, ))
  • 在創建單目標優化問題時,weights用來指示最大化和最小化。此處-1.0即代表問題是一個最小化問題,對於最大化,應將weights改為正數,如1.0。

  • 另外即使是單目標優化,weights也需要是一個tuple,以保證單目標和多目標優化時數據結構的統一。

  • 對於單目標優化問題,weights 的絕對值沒有意義,只要符號選擇正確即可。

多目標優化

creator.create('FitnessMulti', base.Fitness, weights=(-1.0, 1.0))
  • 對於多目標優化問題,weights用來指示多個優化目標之間的相對重要程度以及最大化最小化。如示例中給出的(-1.0, 1.0)代表對第一個目標函數取最小值,對第二個目標函數取最大值。

2.個體編碼

實數編碼(Value encoding):直接用實數對變量進行編碼。優點是不用解碼,基因表達非常簡潔,而且能對應連續區間。但是實數編碼后搜索區間連續,因此容易陷入局部最優。

實數編碼

from deap import base, creator, tools
import random
IND_SIZE = 5
creator.create('FitnessMin', base.Fitness, weights=(-1.0,)) #優化目標:單變量,求最小值
creator.create('Individual', list, fitness = creator.FitnessMin) #創建Individual類,繼承list

toolbox = base.Toolbox()
toolbox.register('Attr_float', random.random)
toolbox.register('Individual', tools.initRepeat, creator.Individual, toolbox.Attr_float, n=IND_SIZE)

ind1 = toolbox.Individual()
print(ind1)

# 結果:[0.8579615693371493, 0.05774821674048369, 0.8812411734389638, 0.5854279538236896, 0.12908399219828248]

二進制編碼

from deap import base, creator, tools
from scipy.stats import bernoulli

creator.create('FitnessMin', base.Fitness, weights=(-1.0,)) #優化目標:單變量,求最小值
creator.create('Individual', list, fitness = creator.FitnessMin) #創建Individual類,繼承list

GENE_LENGTH = 10

toolbox = base.Toolbox()
toolbox.register('Binary', bernoulli.rvs, 0.5) #注冊一個Binary的alias,指向scipy.stats中的bernoulli.rvs,概率為0.5
toolbox.register('Individual', tools.initRepeat, creator.Individual, toolbox.Binary, n = GENE_LENGTH) #用tools.initRepeat生成長度為GENE_LENGTH的Individual

ind1 = toolbox.Individual()
print(ind1)

# 結果:[1, 0, 0, 0, 0, 1, 0, 1, 1, 0]

序列編碼(Permutation encoding)

from deap import base, creator, tools
import random
creator.create("FitnessMin", base.Fitness, weights=(-1.0,))
creator.create("Individual", list, fitness=creator.FitnessMin)

IND_SIZE=10

toolbox = base.Toolbox()
toolbox.register("Indices", random.sample, range(IND_SIZE), IND_SIZE)
toolbox.register("Individual", tools.initIterate, creator.Individual,toolbox.Indices)
ind1 = toolbox.Individual()
print(ind1)

#結果:[0, 1, 5, 8, 2, 3, 6, 7, 9, 4]

粒子(Particles)

import random
from deap import base, creator, tools

creator.create("FitnessMax", base.Fitness, weights=(1.0, 1.0))
creator.create("Particle", list, fitness=creator.FitnessMax, speed=None,
               smin=None, smax=None, best=None)

# 自定義的粒子初始化函數
def initParticle(pcls, size, pmin, pmax, smin, smax):
    part = pcls(random.uniform(pmin, pmax) for _ in range(size))
    part.speed = [random.uniform(smin, smax) for _ in range(size)]
    part.smin = smin
    part.smax = smax
    return part

toolbox = base.Toolbox()
toolbox.register("Particle", initParticle, creator.Particle, size=2, pmin=-6, pmax=6, smin=-3, smax=3) #為自己編寫的initParticle函數注冊一個alias "Particle",調用時生成一個2維粒子,放在容器creator.Particle中,粒子的位置落在(-6,6)中,速度限制為(-3,3)

ind1 = toolbox.Particle()
print(ind1)
print(ind1.speed)
print(ind1.smin, ind1.smax)

# 結果:[-2.176528549934324, -3.0796558214905]
#[-2.9943676285620104, -0.3222138308543414]
#-3 3

print(ind1.fitness.valid)

# 結果:False
# 因為當前還沒有計算適應度函數,所以粒子的最優適應度值還是invalid

3 初始種群建立

一般族群

  • 這是最常用的族群類型,族群中沒有特別的順序或者子族群。
from deap import base, creator, tools
from scipy.stats import bernoulli

# 定義問題
creator.create('FitnessMin', base.Fitness, weights=(-1.0,)) # 單目標,最小化
creator.create('Individual', list, fitness = creator.FitnessMin)

# 生成個體
GENE_LENGTH = 5
toolbox = base.Toolbox() #實例化一個Toolbox
toolbox.register('Binary', bernoulli.rvs, 0.5)
toolbox.register('Individual', tools.initRepeat, creator.Individual, toolbox.Binary, n=GENE_LENGTH)

# 生成初始族群
N_POP = 10
toolbox.register('Population', tools.initRepeat, list, toolbox.Individual)
toolbox.Population(n = N_POP)

# 結果:
# [[1, 0, 1, 1, 0],
# [0, 1, 1, 0, 0],
# [0, 1, 0, 0, 0],
# [1, 1, 0, 1, 0],
# [0, 1, 1, 1, 1],
# [0, 1, 1, 1, 1],
# [1, 0, 0, 0, 1],
# [1, 1, 0, 1, 0],
# [0, 1, 1, 0, 1],
# [1, 0, 0, 0, 0]]

同類群

  • 同類群即一個族群中包含幾個子族群。在有些算法中,會使用本地選擇(Local selection)挑選育種個體,這種情況下個體僅與同一鄰域的個體相互作用。
toolbox.register("deme", tools.initRepeat, list, toolbox.individual)

DEME_SIZES = 10, 50, 100
population = [toolbox.deme(n=i) for i in DEME_SIZES]

粒子群

  • 粒子群中的所有粒子共享全局最優。在實現時需要額外傳入全局最優位置與全局最優適應度給族群。
creator.create("Swarm", list, gbest=None, gbestfit=creator.FitnessMax)
toolbox.register("swarm", tools.initRepeat, creator.Swarm, toolbox.particle)

4 評價

  • 評價部分是根據任務的特性高度定制的,DEAP庫中並沒有預置的評價函數模版。

  • 在使用DEAP時,需要注意的是,無論是單目標還是多目標優化,評價函數的返回值必須是一個tuple類型。

from deap import base, creator, tools
import numpy as np
# 定義問題
creator.create('FitnessMin', base.Fitness, weights=(-1.0,)) #優化目標:單變量,求最小值
creator.create('Individual', list, fitness = creator.FitnessMin) #創建Individual類,繼承list

# 生成個體
IND_SIZE = 5
toolbox = base.Toolbox()
toolbox.register('Attr_float', np.random.rand)
toolbox.register('Individual', tools.initRepeat, creator.Individual, toolbox.Attr_float, n=IND_SIZE)

# 生成初始族群
N_POP = 10
toolbox.register('Population', tools.initRepeat, list, toolbox.Individual)
pop = toolbox.Population(n = N_POP)

# 定義評價函數
def evaluate(individual):
  return sum(individual), #注意這個逗號,即使是單變量優化問題,也需要返回tuple

# 評價初始族群
toolbox.register('Evaluate', evaluate)
fitnesses = map(toolbox.Evaluate, pop)
for ind, fit in zip(pop, fitnesses):
  ind.fitness.values = fit
  print(ind.fitness.values)

# 結果:
# (2.593989197511478,)
# (1.1287944225903104,)
# (2.6030877077096717,)
# (3.304964061515382,)
# (2.534627558467466,)
# (2.4697149450205536,)
# (2.344837782191844,)
# (1.8959030773060852,)
# (2.5192475334239,)
# (3.5069764929866585,)

5 配種選擇

  • selTournament() 錦標賽選擇
  • selRoulette() 輪盤賭選擇(不能用於最小化或者適應度會小於等於0的問題)
  • selNSGA2() NSGA-II選擇,適用於多目標遺傳算法
  • selSPEA2() SPEA2選擇,目前版本(ver 1.2.2)的該函數實現有誤,沒有為個體分配距離,不建議使用。
  • selRandom() 有放回的隨機選擇
  • selBest() 選擇最佳
  • selWorst() 選擇最差
  • selTournamentDCD() Dominance/Crowding distance錦標賽選擇,目前版本的實現也有些問題
  • selDoubleTournament() Size+Fitness雙錦標賽選擇
  • selStochasticUniversalSampling() 隨機抽樣選擇
  • selLexicase() 詞典選擇,參考這篇文章
  • selEpsilonLexicase() 詞典選擇在連續值域上的擴展
from deap import base, creator, tools
import numpy as np
# 定義問題
creator.create('FitnessMin', base.Fitness, weights=(-1.0,)) #優化目標:單變量,求最小值
creator.create('Individual', list, fitness = creator.FitnessMin) #創建Individual類,繼承list

# 生成個體
IND_SIZE = 5
toolbox = base.Toolbox()
toolbox.register('Attr_float', np.random.rand)
toolbox.register('Individual', tools.initRepeat, creator.Individual, toolbox.Attr_float, n=IND_SIZE)

# 生成初始族群
N_POP = 10
toolbox.register('Population', tools.initRepeat, list, toolbox.Individual)
pop = toolbox.Population(n = N_POP)

# 定義評價函數
def evaluate(individual):
  return sum(individual), #注意這個逗號,即使是單變量優化問題,也需要返回tuple

# 評價初始族群
toolbox.register('Evaluate', evaluate)
fitnesses = map(toolbox.Evaluate, pop)
for ind, fit in zip(pop, fitnesses):
  ind.fitness.values = fit

# 選擇方式1:錦標賽選擇
toolbox.register('TourSel', tools.selTournament, tournsize = 2) # 注冊Tournsize為2的錦標賽選擇
selectedTour = toolbox.TourSel(pop, 5) # 選擇5個個體
print('錦標賽選擇結果:')
for ind in selectedTour:
  print(ind)
  print(ind.fitness.values)

# 選擇方式2: 輪盤賭選擇
toolbox.register('RoulSel', tools.selRoulette)
selectedRoul = toolbox.RoulSel(pop, 5)
print('輪盤賭選擇結果:')
for ind in selectedRoul:
  print(ind)
  print(ind.fitness.values)

# 選擇方式3: 隨機普遍抽樣選擇
toolbox.register('StoSel', tools.selStochasticUniversalSampling)
selectedSto = toolbox.StoSel(pop, 5)
print('隨機普遍抽樣選擇結果:')
for ind in selectedSto:
  print(ind)
  print(ind.fitness.values)
  
#結果:
#錦標賽選擇結果:
#[0.2673058115582905, 0.8131397980144155, 0.13627430737326807, 0.10792026110464248, 0.4166962522797264]
#(1.741336430330343,)
#[0.5448284697291571, 0.9702727117158071, 0.03349947770537576, 0.7018813286570782, 0.3244029157717422]
#(2.5748849035791603,)
#[0.8525836387058023, 0.28064482205939634, 0.9235436615033125, 0.6429467684175085, 0.5965523553349544]
#(3.296271246020974,)
#[0.5243293164960845, 0.37883291328325286, 0.28423194217619596, 0.5005947374376103, 0.3017896612109636]
#(1.9897785706041071,)
#[0.4038211036464676, 0.841374996509095, 0.3555644512425019, 0.5849111474726337, 0.058759891556433574]
#(2.2444315904271317,)
#輪盤賭選擇結果:
#[0.42469039733882064, 0.8411201950346711, 0.6322812691061555, 0.7566549973076343, 0.9352307652371067]
#(3.5899776240243884,)
#[0.42469039733882064, 0.8411201950346711, 0.6322812691061555, 0.7566549973076343, 0.9352307652371067]
#(3.5899776240243884,)
#[0.5448284697291571, 0.9702727117158071, 0.03349947770537576, 0.7018813286570782, 0.3244029157717422]
#(2.5748849035791603,)
#[0.630305953330188, 0.09565983206218687, 0.890691659939096, 0.8706091807317707, 0.19708949882847437]
#(2.684356124891716,)
#[0.5961060867498598, 0.4300051776616509, 0.4512760237511251, 0.047731561819711055, 0.009892120639829804]
#(1.5350109706221766,)
#隨機普遍抽樣選擇結果:
#[0.2673058115582905, 0.8131397980144155, 0.13627430737326807, 0.10792026110464248, 0.4166962522797264]
#(1.741336430330343,)
#[0.4038211036464676, 0.841374996509095, 0.3555644512425019, 0.5849111474726337, 0.058759891556433574]
#(2.2444315904271317,)
#[0.630305953330188, 0.09565983206218687, 0.890691659939096, 0.8706091807317707, 0.19708949882847437]
#(2.684356124891716,)
#[0.40659881466060876, 0.8387139101647804, 0.28504735705240236, 0.46171554118627334, 0.7843353275244066]
#(2.7764109505884718,)
#[0.42469039733882064, 0.8411201950346711, 0.6322812691061555, 0.7566549973076343, 0.9352307652371067]
#(3.5899776240243884,)

6 變異

  • cxOnePoint() 單點交叉 實數、二進制
  • cxTwoPoint() 兩點交叉 實數、二進制
  • cxUniform() 均勻交叉 實數、二進制
  • cxPartialyMatched() 部分匹配交叉PMX 序列
  • cxUniformPartialyMatched() PMX變種,改兩點為均勻交叉 序列
  • cxOrdered() 有序交叉 序列
  • cxBlend() 混合交叉 實數
  • cxESBlend() 帶進化策略的混合交叉
  • cxESTwoPoint() 帶進化策略的兩點交叉
  • cxSimulatedBinary() 模擬二值交叉 實數
  • cxSimulatedBinaryBounded() 有界模擬二值交叉 實數
  • cxMessyOnePoint() 混亂單點交叉 實數、二進制
from deap import base, creator, tools
import random
# 創建兩個序列編碼個體
random.seed(42) # 保證結果可復現
IND_SIZE = 8
creator.create('FitnessMin', base.Fitness, weights=(-1.0, ))
creator.create('Individual', list, fitness = creator.FitnessMin)

toolbox = base.Toolbox()
toolbox.register('Indices', random.sample, range(IND_SIZE), IND_SIZE)
toolbox.register('Individual', tools.initIterate, creator.Individual, toolbox.Indices)

ind1, ind2 = [toolbox.Individual() for _ in range(2)]
print(ind1, '\n', ind2)
# 結果:[1, 0, 5, 2, 7, 6, 4, 3] 
# [1, 4, 3, 0, 6, 5, 2, 7]

# 單點交叉
child1, child2 = [toolbox.clone(ind) for ind in (ind1, ind2)]
tools.cxOnePoint(child1, child2)
print(child1, '\n', child2)
#結果:[1, 4, 3, 0, 6, 5, 2, 7] 
# [1, 0, 5, 2, 7, 6, 4, 3]
# 可以看到從第四位開始被切開並交換了

# 兩點交叉
child1, child2 = [toolbox.clone(ind) for ind in (ind1, ind2)]
tools.cxTwoPoint(child1, child2)
print(child1, '\n', child2)
# 結果:[1, 0, 5, 2, 6, 5, 2, 3] 
# [1, 4, 3, 0, 7, 6, 4, 7]
# 基因段[6, 5, 2]與[7, 6, 4]互換了

# 均勻交叉
child1, child2 = [toolbox.clone(ind) for ind in (ind1, ind2)]
tools.cxUniform(child1, child2, 0.5)
print(child1, '\n', child2)
# 結果:[1, 0, 3, 2, 7, 5, 4, 3] 
# [1, 4, 5, 0, 6, 6, 2, 7]

# 部分匹配交叉
child1, child2 = [toolbox.clone(ind) for ind in (ind1, ind2)]
tools.cxPartialyMatched(child1, child2)
print(child1, '\n', child2)
# 結果:[1, 0, 5, 2, 6, 7, 4, 3] 
# [1, 4, 3, 0, 7, 5, 2, 6]
# 可以看到與之前交叉算子的明顯不同,這里的每個序列都沒有沖突

# 有序交叉
child1, child2 = [toolbox.clone(ind) for ind in (ind1, ind2)]
tools.cxOrdered(child1, child2)
print(child1, '\n', child2)
# 結果:[5, 4, 3, 2, 7, 6, 1, 0] 
# [3, 0, 5, 6, 2, 7, 1, 4]

# 混亂單點交叉
child1, child2 = [toolbox.clone(ind) for ind in (ind1, ind2)]
tools.cxMessyOnePoint(child1, child2)
print(child1, '\n', child2)
# 結果:[1, 0, 5, 2, 7, 4, 3, 0, 6, 5, 2, 7] 
# [1, 6, 4, 3]
# 注意個體序列長度的改變

7 突變

from deap import base, creator, tools
import random
# 創建一個實數編碼個體
random.seed(42) # 保證結果可復現
IND_SIZE = 5
creator.create('FitnessMin', base.Fitness, weights=(-1.0, ))
creator.create('Individual', list, fitness = creator.FitnessMin)

toolbox = base.Toolbox()
toolbox.register('Attr_float', random.random)
toolbox.register('Individual', tools.initRepeat, creator.Individual, toolbox.Attr_float, IND_SIZE)

ind1 = toolbox.Individual()
print(ind1)
# 結果:[0.6394267984578837, 0.025010755222666936, 0.27502931836911926, 0.22321073814882275, 0.7364712141640124]

# 高斯突變
mutant = toolbox.clone(ind1)
tools.mutGaussian(mutant, 3, 0.1, 1)
print(mutant)
# 結果:[3.672658632864655, 2.99827700737295, 3.2982590920597916, 3.339566606808737, 3.6626390539295306]
# 可以看到當均值給到3之后,變異形成的個體均值從0.5也增大到了3附近

# 亂序突變
mutant = toolbox.clone(ind1)
tools.mutShuffleIndexes(mutant, 0.5)
print(mutant)
# 結果:[0.22321073814882275, 0.7364712141640124, 0.025010755222666936, 0.6394267984578837, 0.27502931836911926]

# 有界多項式突變
mutant = toolbox.clone(ind1)
tools.mutPolynomialBounded(mutant, 20, 0, 1, 0.5)
print(mutant)
# 結果:[0.674443861742489, 0.020055418656044655, 0.2573977358171454, 0.11555018832942898, 0.6725269223692601]

# 均勻整數突變
mutant = toolbox.clone(ind1)
tools.mutUniformInt(mutant, 1, 5, 0.5)
print(mutant)
# 結果:[0.6394267984578837, 3, 0.27502931836911926, 0.22321073814882275, 0.7364712141640124]
# 可以看到在第二個位置生成了整數3

8 環境選擇

DEAP中沒有設定專門的reinsertion操作。可以簡單的用python的list操作來完成選擇


免責聲明!

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



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