优化算法之遗传算法(Genetic Algorithm, GA)

概述

遗传算法(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

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值