目录
概述
遗传算法(Genetic Algorithm, GA) 起源于对生物系统所进行的计算机模拟研究。它是模仿自然界生物进化机制发展起来的 随机全局搜索和优化方法,借鉴了达尔文的进化论和孟德尔的遗传学说。其本质是一种高效、并行、全局搜索的方法,能在搜索过程中自动获取和积累有关搜索空间的知识,并自适应地控制搜索过程以求得最佳解。
相关术语
-
基因型(genotype):性状染色体的内部表现;
-
表现型(phenotype):染色体决定的性状的外部表现,或者说,根据基因型形成的个体的外部表现;
-
个体(individual):指染色体带有特征的实体;
-
种群(population):个体的集合,该集合内个体数称为种群的大小
-
编码(coding):DNA中遗传信息在一个长链上按一定的模式排列。遗传编码可看作从表现型到基因型的映射。
-
解码(decoding):基因型到表现型的映射。
-
交叉(crossover):两个染色体的某一相同位置处DNA被切断,前后两串分别交叉组合形成两个新的染色体。也称基因重组或杂交;
-
变异(mutation):复制时可能(很小的概率)产生某些复制差错,变异产生新的染色体,表现出新的性状。
-
进化(evolution):种群逐渐适应生存环境,品质不断得到改良。生物的进化是以种群的形式进行的。
-
适应度(fitness):度量某个物种对于生存环境的适应程度。
-
选择(selection):以一定的概率从种群中选择若干个个体。一般,选择过程是一种基于适应度的优胜劣汰的过程。
-
复制(reproduction):细胞分裂时,遗传物质DNA通过复制而转移到新产生的细胞中,新细胞就继承了旧细胞的基因。
遗传算法的实现过程
遗传算法的实现过程实际上就像自然界的进化过程那样。首先寻找一种对问题潜在解进行“数字化”编码的方案,(建立表现型和基因型的映射关系)。然后用随机数初始化一个种群(那么第一批袋鼠就被随意地分散在山脉上),种群里面的个体就是这些数字化的编码。接下来,通过适当的解码过程之后(得到袋鼠的位置坐标),用适应性函数对每一个基因个体作一次适应度评估(袋鼠爬得越高,越是受我们的喜爱,所以适应度相应越高)。用选择函数按照某种规定择优选择(我们要每隔一段时间,在山上射杀一些所在海拔较低的袋鼠,以保证袋鼠总体数目持平。)。让个体基因变异(让袋鼠随机地跳一跳)。然后产生子代(希望存活下来的袋鼠是多产的,并在那里生儿育女)。遗传算法并不保证你能获得问题的最优解,但是 使用遗传算法的最大优点在于你不必去了解和操心如何去“找”最优解。(你不必去指导袋鼠向那边跳,跳多远。)而 只要简单的“否定”一些表现不好的个体就行了。(把那些总是爱走下坡路的袋鼠射杀,这就是遗传算法的精粹!)
遗传算法的一般步骤
开始循环直至找到满意的解。
-
1.评估每条染色体所对应个体的适应度。
-
2.遵照适应度越高,选择概率越大的原则,从种群中选择两个个体作为父方和母方。
-
3.抽取父母双方的染色体,进行交叉,产生子代。
-
4.对子代的染色体进行变异。
-
5.重复2,3,4步骤,直到新种群的产生。
结束循环。
代码描述
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
from mpl_toolkits.mplot3d import Axes3D
# DNA 编码长度 -- 对应解的精度
DNA_SIZE = 24 ## 2^23 < 6*10^7 < 2^24 精度为小数后 7 位
# 种群大小
POP_SIZE = 200
# 交叉概率
CROSSOVER_RATE = 0.8
# 变异概率
MUTATION_RATE = 0.005
# 进化代数
N_GENERATIONS = 40
# x,y 取值范围 --- 可行解的参数范围
X_BOUND = [-3, 3]
Y_BOUND = [-3, 3]
# 函数类型
def F(x, y):
return 3*(1-x)**2*np.exp(-(x**2)-(y+1)**2)- 10*(x/5 - x**3 - y**5)*np.exp(-x**2-y**2)- 1/3**np.exp(-(x+1)**2 - y**2)
解码
'''
函数说明: DNA解码
Parameters:
pop: 种群矩阵(种群的DNA信息)(二进制)
Returns:
x: 解码后x坐标值(十进制),size:[None,DNA_SIZE]
y: 解码后y坐标值(十进制),size:[None,DNA_SIZE]
'''
def translateDNA(pop): #pop表示种群矩阵,一行表示一个二进制编码表示的DNA,矩阵的行数为种群数目
# 偶数列表示x,奇数列表示y
x_pop = pop[:,1::2]
y_pop = pop[:,::2]
#pop:(POP_SIZE,DNA_SIZE)*(1,DNA_SIZE) --> (POP_SIZE,1) 向量点乘
x = x_pop.dot(2**np.arange(DNA_SIZE)[::-1])/float(2**DNA_SIZE-1)*(X_BOUND[1]-X_BOUND[0])+X_BOUND[0]
y = y_pop.dot(2**np.arange(DNA_SIZE)[::-1])/float(2**DNA_SIZE-1)*(Y_BOUND[1]-Y_BOUND[0])+Y_BOUND[0]
return x,y
相关问题
1、切片–取偶数,奇数列
2、生成序列数 0-N 再逆序 np.arange()[::-1]
3、一个二维数组与一个一维数组 进行数组乘法(dot)(含有一维数组,进行数组内积)
适应度函数
'''第二步
函数说明:求解个体在当前环境中的适应度(本次取最大值)
Parameters:
pop: 种群矩阵(种群的DNA信息)(二进制)
Returns:
fitness :个体在当前环境中的适应度1e-7~
'''
def get_fitness(pop):
x,y = translateDNA(pop)
pred = F(x, y)
# 减去最小适应度把适应度值的最小区间提升到从0开始(适应度不能为负值)
# 为了不出现0,最后在加上一个很小的正数
# 通过这一步fitness的范围为[1e-7, np.max(pred)-np.min(pred)]
## 适应度
fitness = pred - np.min(pred)+1e-7
return fitness
选择函数
'''第三步 依概率p选择(轮盘赌)
函数说明:选择优秀个体(适应度越高,被选择的机会越高,而适应度低的,被选择的机会就低)
Parameters:
pop: 种群矩阵(种群的DNA信息)(二进制)
fitness :个体在当前环境中的适应度1e-3~
Returns:
better_pop: 当前种群中优秀个体
'''
def select(pop, fitness): # nature selection wrt pop's fitness
idx = np.random.choice(np.arange(POP_SIZE), size=POP_SIZE, replace=True,
p=(fitness)/(fitness.sum()))
better_pop=pop[idx]
return better_pop
小问题:
better_pop=pop[idx] idx是一个一维数组 size=POP_SIZE ,保持种群大小不变
交叉、变异
'''第四步
函数说明: 交叉操作
Parameters:
pop: 种群矩阵(种群的DNA信息)(二进制)
CROSSOVER_RATE:交叉概率
Returns:
new_pop: 新种群
'''
def crossover_and_mutation(pop, CROSSOVER_RATE = 0.8):
new_pop = []
for father in pop: #遍历种群中的每一个个体,将该个体作为父亲
child = father #孩子先得到父亲的全部基因(这里我把一串二进制串的那些0,1称为基因)
# 产生子代时不是必然发生交叉,而是以一定的概率发生交叉 0~CROSSOVER_RATE
if np.random.rand() < CROSSOVER_RATE:
mother = pop[np.random.randint(POP_SIZE)] #再种群中选择另一个个体,并将该个体作为母亲
cross_points = np.random.randint(low=0, high=DNA_SIZE*2) #随机产生交叉的点
child[cross_points:] = mother[cross_points:] #孩子得到位于交叉点后的母亲的基因
#每个后代有一定的机率发生变异
child=mutation(child)
new_pop.append(child)
return new_pop
'''
函数说明:变异操作
Parameters:
child: 子代
MUTATION_RATE:变异率
Returns:
child: 变异后子代
'''
def mutation(child, MUTATION_RATE=0.003):
# 以MUTATION_RATE的概率进行变异
if np.random.rand() < MUTATION_RATE:
mutate_point = np.random.randint(0, DNA_SIZE*2) #随机产生一个实数,代表要变异基因的位置
# 将变异点的二进制为反转 按位
child[mutate_point] = child[mutate_point]^1
return child
相关问题
1、判断是否需要交叉—依概率:(0 ~ CROSSOVER_RATE) 交叉,(CROSSOVER_RATE ~ 1)不交叉,通过产生0-1直接的随机数,服从0-1均匀分布
2、随机选择个体作为母体,(随机生成一个整数)
3、等尺寸即可相互赋值
4、位操作(异或取反)
完整代码
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
from mpl_toolkits.mplot3d import Axes3D
# 编码长度
DNA_SIZE = 24
# 种群大小
POP_SIZE = 200
# 交叉概率
CROSSOVER_RATE = 0.8
# 变异概率
MUTATION_RATE = 0.005
# 进化周期
N_GENERATIONS = 50
# x,y 取值范围
X_BOUND = [-3, 3]
Y_BOUND = [-3, 3]
# 函数类型
def F(x, y):
return 3*(1-x)**2*np.exp(-(x**2)-(y+1)**2)- 10*(x/5 - x**3 - y**5)*np.exp(-x**2-y**2)- 1/3**np.exp(-(x+1)**2 - y**2)
'''
函数说明: DNA解码
Parameters:
pop: 种群矩阵(种群的DNA信息)(二进制)
Returns:
x: 解码后x坐标值(十进制),size:[None,DNA_SIZE]
y: 解码后y坐标值(十进制),size:[None,DNA_SIZE]
'''
def translateDNA(pop): #pop表示种群矩阵,一行表示一个二进制编码表示的DNA,矩阵的行数为种群数目
# 偶数列表示x,奇数列表示y
x_pop = pop[:,1::2]
y_pop = pop[:,::2]
#pop:(POP_SIZE,DNA_SIZE)*(DNA_SIZE,1) --> (POP_SIZE,1)
x = x_pop.dot(2**np.arange(DNA_SIZE)[::-1])/float(2**DNA_SIZE-1)*(X_BOUND[1]-X_BOUND[0])+X_BOUND[0]
y = y_pop.dot(2**np.arange(DNA_SIZE)[::-1])/float(2**DNA_SIZE-1)*(Y_BOUND[1]-Y_BOUND[0])+Y_BOUND[0]
return x,y
'''第二步
函数说明:求解个体在当前环境中的适应度(本次取最大值)
Parameters:
pop: 种群矩阵(种群的DNA信息)(二进制)
Returns:
fitness :个体在当前环境中的适应度1e-7~
'''
def get_fitness(pop):
x,y = translateDNA(pop)
pred = F(x, y)
# 减去最小适应度把适应度值的最小区间提升到从0开始(适应度不能为负值)
# 为了不出现0,最后在加上一个很小的正数
# 通过这一步fitness的范围为[1e-7, np.max(pred)-np.min(pred)]
fitness = pred - np.min(pred)+1e-7
return fitness
'''第三步
函数说明:选择优秀个体(适应度越高,被选择的机会越高,而适应度低的,被选择的机会就低)
Parameters:
pop: 种群矩阵(种群的DNA信息)(二进制)
fitness :个体在当前环境中的适应度1e-3~
Returns:
better_pop: 当前种群中优秀个体
'''
def select(pop, fitness): # nature selection wrt pop's fitness
idx = np.random.choice(np.arange(POP_SIZE), size=POP_SIZE, replace=True,
p=(fitness)/(fitness.sum()))
better_pop=pop[idx]
return better_pop
'''第四步
函数说明: 交叉操作
Parameters:
pop: 种群矩阵(种群的DNA信息)(二进制)
CROSSOVER_RATE:交叉概率
Returns:
new_pop: 新种群
'''
def crossover_and_mutation(pop, CROSSOVER_RATE = 0.8):
new_pop = []
for father in pop: #遍历种群中的每一个个体,将该个体作为父亲
child = father #孩子先得到父亲的全部基因(这里我把一串二进制串的那些0,1称为基因)
# 产生子代时不是必然发生交叉,而是以一定的概率发生交叉 0~CROSSOVER_RATE
if np.random.rand() < CROSSOVER_RATE:
mother = pop[np.random.randint(POP_SIZE)] #再种群中选择另一个个体,并将该个体作为母亲
cross_points = np.random.randint(low=0, high=DNA_SIZE*2) #随机产生交叉的点
child[cross_points:] = mother[cross_points:] #孩子得到位于交叉点后的母亲的基因
#每个后代有一定的机率发生变异
child=mutation(child)
new_pop.append(child)
return new_pop
'''
函数说明:变异操作
Parameters:
child: 子代
MUTATION_RATE:变异率
Returns:
child: 变异后子代
'''
def mutation(child, MUTATION_RATE=0.003):
# 以MUTATION_RATE的概率进行变异
if np.random.rand() < MUTATION_RATE:
mutate_point = np.random.randint(0, DNA_SIZE*2) #随机产生一个实数,代表要变异基因的位置
# 将变异点的二进制为反转 按位
child[mutate_point] = child[mutate_point]^1
return child
def plot_3d(ax):
X = np.linspace(*X_BOUND, 100)
Y = np.linspace(*Y_BOUND, 100)
X,Y = np.meshgrid(X, Y)
Z = F(X, Y)
# 绘制 3D图像
ax.plot_surface(X,Y,Z,rstride=1,cstride=1,cmap=cm.coolwarm)
ax.set_zlim(-10,10)
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
# 动态显示设置
plt.pause(3)
plt.show()
def print_info(pop):
fitness = get_fitness(pop)
max_fitness_index = np.argmax(fitness)
print("max_fitness:", fitness[max_fitness_index])
x,y = translateDNA(pop)
print("最优的基因型:", pop[max_fitness_index])
print("(x, y):", (x[max_fitness_index], y[max_fitness_index]))
if __name__ == "__main__":
fig = plt.figure()
ax = Axes3D(fig)
# 将画图模式改为交互模式,程序遇到plt.show不会暂停,而是继续执行
plt.ion()
plot_3d(ax)
'''第一步:创建出一个种群 '''
# pop表示种群矩阵,一行表示一个二进制编码表示的DNA,矩阵的行数为种群数目,DNA_SIZE为编码长度
# 将x,y编码在同一行,因此矩阵列数为编码长度的二倍
pop = np.random.randint(2, size=(POP_SIZE, DNA_SIZE*2)) #matrix (POP_SIZE, DNA_SIZE*2)
for _ in range(N_GENERATIONS):#迭代N代
x,y = translateDNA(pop)
if 'sca' in locals():
sca.remove()
sca = ax.scatter(x, y, F(x,y), c='black', marker='o');plt.show();plt.pause(0.1)
pop = np.array(crossover_and_mutation(pop, CROSSOVER_RATE))
#F_values = F(translateDNA(pop)[0], translateDNA(pop)[1])#x, y --> Z matrix
fitness = get_fitness(pop)
pop = select(pop, fitness) #选择生成新的种群
print_info(pop)
plt.ioff()
plot_3d(ax)
疑惑点(感谢大佬解答)
在主函数中,这个代码是啥意思,用做哪方面,没有这个会有什么影响?
个人修改代码(类形式)
import numpy as np
import matplotlib.pyplot as plt
class GA():
# 相关参数--DNA长度,种群大小,交叉概率,变异概率,进化代数
def __init__(self,DNA_SIZE,POP_SIZE,CROSSOVER_RATE,MUTATION_RATE,N_GENERATIONS,x,y):
self.DNA_SIZE=DNA_SIZE
self.POP_SIZE=POP_SIZE
self.CROSSOVER_RATE=CROSSOVER_RATE
self.MUTATION_RATE=MUTATION_RATE
self.N_GENERATIONS=N_GENERATIONS
self.xmin=x[0]
self.xmax=x[1]
self.ymin=y[0]
self.ymax=y[1]
self.fitness=0
self.pop=self.__Create_pop()
# 创建种群---二进制编码
def __Create_pop(self):
pop = np.random.randint(2, size=(self.POP_SIZE, self.DNA_SIZE*2))
return pop
'''
函数说明: DNA解码
Parameters:
pop: 种群矩阵(种群的DNA信息)(二进制)
Returns:
x: 解码后x坐标值(十进制),size:[None,DNA_SIZE]
y: 解码后y坐标值(十进制),size:[None,DNA_SIZE]
'''
def __translateDNA(self): #pop表示种群矩阵,一行表示一个二进制编码表示的DNA,矩阵的行数为种群数目
# 偶数列表示x,奇数列表示y
x_pop=self.pop[:,1::2]
y_pop=self.pop[:,::2]
#pop:(POP_SIZE,DNA_SIZE)*(DNA_SIZE,1) --> (POP_SIZE,1)
x=x_pop.dot(2**np.arange(self.DNA_SIZE)[::-1])/float(2**self.DNA_SIZE-1)*(self.xmax-self.xmin)+self.xmin
y=y_pop.dot(2**np.arange(self.DNA_SIZE)[::-1])/float(2**self.DNA_SIZE-1)*(self.ymax-self.ymin)+self.ymin
return x,y
'''第二步
函数说明:求解个体在当前环境中的适应度(本次取最大值)
Parameters:
pop: 种群矩阵(种群的DNA信息)(二进制)
Returns:
fitness :个体在当前环境中的适应度1e-7~
'''
def __get_fitness(self):
x,y = self.__translateDNA()
pred = self.__F(x, y)
# 减去最小适应度把适应度值的最小区间提升到从0开始(适应度不能为负值)
# 为了不出现0,最后在加上一个很小的正数
# 通过这一步fitness的范围为[1e-7, np.max(pred)-np.min(pred)]
self.fitness = pred - np.min(pred)+1e-7
# 适应度函数
def __F(self,x, y):
return 3*(1-x)**2*np.exp(-(x**2)-(y+1)**2)- 10*(x/5 - x**3 - y**5)*np.exp(-x**2-y**2)- 1/3**np.exp(-(x+1)**2 - y**2)
'''第三步
函数说明:选择优秀个体(适应度越高,被选择的机会越高,而适应度低的,被选择的机会就低)
Parameters:
pop: 种群矩阵(种群的DNA信息)(二进制)
fitness :个体在当前环境中的适应度1e-3~
Returns:
better_pop: 当前种群中优秀个体
'''
def __select(self): # nature selection wrt pop's fitness
self.__get_fitness()
idx = np.random.choice(np.arange(self.POP_SIZE), size=self.POP_SIZE, replace=True,
p=(self.fitness)/(self.fitness.sum()))
better_pop=self.pop[idx]
return better_pop
'''第四步
函数说明: 交叉操作
Parameters:
pop: 种群矩阵(种群的DNA信息)(二进制)
CROSSOVER_RATE:交叉概率
Returns:
new_pop: 新种群
'''
def __crossover_and_mutation(self):
new_pop = []
for father in self.pop: #遍历种群中的每一个个体,将该个体作为父亲
child = father #孩子先得到父亲的全部基因(这里我把一串二进制串的那些0,1称为基因)
# 产生子代时不是必然发生交叉,而是以一定的概率发生交叉 0~CROSSOVER_RATE
if np.random.rand() < self.CROSSOVER_RATE:
mother = self.pop[np.random.randint(self.POP_SIZE)] #再种群中选择另一个个体,并将该个体作为母亲
cross_points = np.random.randint(low=0, high=self.DNA_SIZE*2) #随机产生交叉的点
child[cross_points:] = mother[cross_points:] #孩子得到位于交叉点后的母亲的基因
#每个后代有一定的机率发生变异
child=self.__mutation(child)
new_pop.append(child)
return np.array(new_pop)
'''
函数说明:变异操作
Parameters:
child: 子代
MUTATION_RATE:变异率
Returns:
child: 变异后子代
'''
def __mutation(self,child):
# 以MUTATION_RATE的概率进行变异
if np.random.rand() < self.MUTATION_RATE:
mutate_point = np.random.randint(0, self.DNA_SIZE*2) #随机产生一个实数,代表要变异基因的位置
# 将变异点的二进制为反转 按位
child[mutate_point] = child[mutate_point]^1
return child
def run(self):
plt.ion() # 开启交互模式
plt.subplots()
for _ in range(self.N_GENERATIONS):
plt.clf() # 清空画布
plt.xlim(-3,3)
plt.ylim(-3,3)
x,y=self.__translateDNA()
self.pop=self.__crossover_and_mutation()
self.pop=self.__select()
ax = plt.scatter(x, y,c='black', marker='o')
plt.show()
plt.pause(0.1)
self.__print_info()
plt.ioff()
plt.show()
def __print_info(self):
max_fitness_index=np.argmax(self.fitness)
print("max_fitness:", self.fitness[max_fitness_index])
x,y=self.__translateDNA()
print('最优基因型',self.pop[max_fitness_index])
print("(x, y):", (x[max_fitness_index], y[max_fitness_index]))
import numpy as np
from GA_Object import GA
def main():
ga=GA(DNA_SIZE=24,POP_SIZE=200,CROSSOVER_RATE=0.8,MUTATION_RATE=0.01,N_GENERATIONS=80,x=[-3,3],y=[-3,3])
ga.run()
if __name__ == "__main__":
main()
运行结果
传送门
遗传算法详解(GA)(个人觉得很形象,很适合初学者)
遗传算法详解 附python代码实现
轮盘赌算法
代码相关
Python 中的range(),arange()函数
python中[-1]、[:-1]、[::-1]、[2::-1]的使用方法
python 取数组偶数/奇数位置的值
Python 的NumPy 库中dot()函数详解
numpy中min和max函数axis详细介绍
python,numpy中np.random.choice()的用法详解及其参考代码
np.random.choice方法
np.random.rand()函数、np.random.randn()函数
numpy.random.randint用法
Numpy学习—np.random.randn()、np.random.rand()和np.random.randint()
python中的位运算符
C语言中“ ~ ”按位取反 ~2的值为何是 -3