最近在学习组合优化系列的问题,接触到一系列的启发式算法,今天分享在学习过程中对于遗传算法的一些理解,手把手写了完整的遗传算法用于求解目标函数的最值问题。
遗传算法的主要步骤总共分为4个步骤:
1、初始化种群
2、选择
3、交叉
4、变异
然后就是循环步骤2、3、4,迭代寻找最优解。
这里以求解函数:y = x1 + x2^2 + sin(x3) + cos(x4)的最大值为例进行逐步分析。
1、初始化种群
函数中存在四个自变量x,因此每一代染色体需要四个基因,然后自己设置染色体的数量,用于一轮的交叉变异操作,迭代也是围绕着这个数量进行。代码如下:
def __init__(self,cross_rate,mutate_rate,n_population,n_iter,gen_size):
self.cross_rate = cross_rate
self.mutate_rate = mutate_rate
self.n_population = n_population
self.n_iter = n_iter
self.gen_size = gen_size
pop_total = np.random.randint(low=1,high=30,size=(self.n_population,self.gen_size))
pop = []
for i in range(len(pop_total)):
fit = self.f(pop_total[i])
pop.append({"gene":pop_total[i].tolist(),"fitness":fit})
self.pop = pop
self.bestind = self.selectBest(self.pop)
初始化的过程设置交叉的概率以及变异的概率增大随机性,初始化的父代以字典的形式放入到pop数组中,并计算适应度,这里就是指函数的值,在初代中保留适应读最大的用于后续交叉和比较。适应度函数如下:
def f(self,geneX):
x1 = geneX[0]
x2 = geneX[1]
x3 = geneX[2]
x4 = geneX[3]
y = x1 + x2**2 + np.sin(math.radians(x3)) + np.cos(math.radians(x4))
return y
这里将三角函数传入的角度值转换为弧度制计算。
选择适应最大:
def selectBest(self,pop):
start_gen = sorted(pop,key=itemgetter("fitness"),reverse=True)
return start_gen[0]
2、选择
按照适应度从大到小排序,适应度大的考虑大的概率作为交叉产生下一代,因此进入选择缓解,别的教程上都是计算概率和累计概率,思路大差不差,核心就是通过概率选择适应度大的,这里产生随机数,如果累计适应读区间则保留下来。代码如下:
def selection(self,individuals,k):
g_inds = sorted(individuals,key=itemgetter("fitness"),reverse=True)
sum_fits = sum(ind["fitness"] for ind in individuals)
chosen = []
for i in range(k):
r = random.random() * sum_fits
sum_per = 0
for ind in g_inds:
sum_per += ind["fitness"]
if sum_per >= r:
chosen.append(ind)
break
chosen = sorted(chosen,key=itemgetter("fitness"),reverse=False)
return chosen
3、交叉
选择完父代之后则是交叉产生子代,分为单点交叉和双点交叉,就是交换的数据多或少而已,这里选择双点交叉随机性更大一点。
def crossDNA(self,geneP):
gen1 = geneP[0]["gene"]
gen2 = geneP[1]["gene"]
dim = len(geneP[0]["gene"])
if dim ==1:
pos1 = 1
pos2 = 1
else:
pos1 = random.randrange(1,dim)
pos2 = random.randrange(1,dim)
newG1 = []
newG2 = []
for i in range(dim):
if(min(pos1,pos2)<=i<=max(pos1,pos2)):
newG1.append(gen1[i])
newG2.append(gen2[i])
else:
newG1.append(gen2[i])
newG2.append(gen1[i])
return newG1,newG2
4、变异
交叉的过程中有一定的概率会发生变异,目的就是创造跟大的随机性,这里采用单点变异,也就是随机变异基因中的一个值。
def mutation(self,crossoff):
dim = len(crossoff)
if dim==1:
pos = 0
else:
pos = random.randrange(0,dim)
crossoff[pos] = random.randint(1,30)
return crossoff
5、迭代
主函数主要负责上述过程的循环,主函数如下:
def GA_main(self):
print("++++++Evolution beginning ++++++")
for iter in range(self.n_iter):
print("=========genertation{}===========".format(iter))
GenSelect = self.selection(self.pop,self.n_population)
nextG = []
while len(nextG) != self.n_population:
genP = [GenSelect.pop() for _ in range(2)]
if random.random() < self.cross_rate:
newG1 , newG2 = self.crossDNA(genP)
if random.random() < self.mutate_rate:
mu1 = self.mutation(newG1)
mu2 = self.mutation(newG2)
fit_mu1 = self.f(mu1)
fit_mu2 = self.f(mu2)
nextG.append({"gene": mu1, "fitness": fit_mu1})
nextG.append({"gene": mu2, "fitness": fit_mu2})
else:
fit_cro1 = self.f(newG1)
fit_cro2 = self.f(newG2)
nextG.append({"gene": newG1, "fitness": fit_cro1})
nextG.append({"gene": newG2, "fitness": fit_cro2})
else:
nextG.extend(genP)
self.pop = nextG
fits = [ind["fitness"] for ind in self.pop]
best_ind = self.selectBest(self.pop)
if best_ind["fitness"] > self.bestind["fitness"]:
self.bestind = best_ind
print("best individual found is: {},{}".format(self.bestind["gene"],self.bestind["fitness"]))
print("max fitness of current pop : {}".format(max(fits)))
print("+++++++finish++++++++")
完整代码如下:
from operator import itemgetter
import random
import numpy as np
import math
class GeneAlgorithm(object):
def __init__(self,cross_rate,mutate_rate,n_population,n_iter,gen_size):
self.cross_rate = cross_rate
self.mutate_rate = mutate_rate
self.n_population = n_population
self.n_iter = n_iter
self.gen_size = gen_size
pop_total = np.random.randint(low=1,high=30,size=(self.n_population,self.gen_size))
pop = []
for i in range(len(pop_total)):
fit = self.f(pop_total[i])
pop.append({"gene":pop_total[i].tolist(),"fitness":fit})
self.pop = pop
self.bestind = self.selectBest(self.pop)
def f(self,geneX):
x1 = geneX[0]
x2 = geneX[1]
x3 = geneX[2]
x4 = geneX[3]
y = x1 + x2**2 + np.sin(math.radians(x3)) + np.cos(math.radians(x4))
return y
# 初始种群 后续放在了init部分便于迭代
# def init_population(self):
# pop_total = np.random.randint(low=1,high=30,size=(self.n_population,self.gen_size))
# pop = []
# for i in range(len(pop_total)):
# fit = self.f(pop_total[i])
# pop.append({"gene":pop_total[i].tolist(),"fitness":fit})
# return pop
def selectBest(self,pop):
start_gen = sorted(pop,key=itemgetter("fitness"),reverse=True)
return start_gen[0]
# 选择
def selection(self,individuals,k):
g_inds = sorted(individuals,key=itemgetter("fitness"),reverse=True)
sum_fits = sum(ind["fitness"] for ind in individuals)
chosen = []
for i in range(k):
r = random.random() * sum_fits
sum_per = 0
for ind in g_inds:
sum_per += ind["fitness"]
if sum_per >= r:
chosen.append(ind)
break
chosen = sorted(chosen,key=itemgetter("fitness"),reverse=False)
return chosen
# 双点交叉 genP为随机选择的父代
def crossDNA(self,geneP):
gen1 = geneP[0]["gene"]
gen2 = geneP[1]["gene"]
dim = len(geneP[0]["gene"])
if dim ==1:
pos1 = 1
pos2 = 1
else:
pos1 = random.randrange(1,dim)
pos2 = random.randrange(1,dim)
newG1 = []
newG2 = []
for i in range(dim):
if(min(pos1,pos2)<=i<=max(pos1,pos2)):
newG1.append(gen1[i])
newG2.append(gen2[i])
else:
newG1.append(gen2[i])
newG2.append(gen1[i])
return newG1,newG2
# 单点变异
def mutation(self,crossoff):
dim = len(crossoff)
if dim==1:
pos = 0
else:
pos = random.randrange(0,dim)
crossoff[pos] = random.randint(1,30)
return crossoff
def GA_main(self):
print("++++++Evolution beginning ++++++")
for iter in range(self.n_iter):
print("=========genertation{}===========".format(iter))
GenSelect = self.selection(self.pop,self.n_population)
nextG = []
while len(nextG) != self.n_population:
genP = [GenSelect.pop() for _ in range(2)]
if random.random() < self.cross_rate:
newG1 , newG2 = self.crossDNA(genP)
if random.random() < self.mutate_rate:
mu1 = self.mutation(newG1)
mu2 = self.mutation(newG2)
fit_mu1 = self.f(mu1)
fit_mu2 = self.f(mu2)
nextG.append({"gene": mu1, "fitness": fit_mu1})
nextG.append({"gene": mu2, "fitness": fit_mu2})
else:
fit_cro1 = self.f(newG1)
fit_cro2 = self.f(newG2)
nextG.append({"gene": newG1, "fitness": fit_cro1})
nextG.append({"gene": newG2, "fitness": fit_cro2})
else:
nextG.extend(genP)
self.pop = nextG
fits = [ind["fitness"] for ind in self.pop]
best_ind = self.selectBest(self.pop)
if best_ind["fitness"] > self.bestind["fitness"]:
self.bestind = best_ind
print("best individual found is: {},{}".format(self.bestind["gene"],self.bestind["fitness"]))
print("max fitness of current pop : {}".format(max(fits)))
print("+++++++finish++++++++")
if __name__ == "__main__":
run = GeneAlgorithm(cross_rate=0.9,mutate_rate=0.2,n_population=100,n_iter=1000,gen_size=4)
run.GA_main()