遗传算法(Genetic Algorithm)是模拟达尔文生物进化论的自然选择和遗传学机理的生物进化过程的计算模型,是一种通过模拟自然进化过程搜索最优解的方法。
遗传算法是从代表问题可能潜在的解集的一个种群(population)开始的,而一个种群则由经过基因(gene)编码的一定数目的个体(individual)组成。每个个体实际上是染色体(chromosome)带有特征的实体。染色体作为遗传物质的主要载体,即多个基因的集合,其内部表现(即基因型)是某种基因组合,它决定了个体的形状的外部表现,如黑头发的特征是由染色体中控制这一特征的某种基因组合决定的。
因此,在一开始需要实现从表现型到基因型的映射即编码工作。由于仿照基因编码的工作很复杂,我们往往进行简化,如二进制编码,初代种群产生之后,按照适者生存和优胜劣汰的原理,逐代(generation)演化产生出越来越好的近似解,在每一代,根据问题域中个体的适应度(fitness)大小选择(selection)个体,并借助于自然遗传学的遗传算子(genetic operators)进行组合交叉(crossover)和变异(mutation),产生出代表新的解集的种群。这个过程将导致种群像自然进化一样的后生代种群比前代更加适应于环境,末代种群中的最优个体经过解码(decoding),可以作为问题近似最优解。(摘自百度百科)
假设我们需要找到喜马拉雅山的最高峰珠穆朗玛峰,有以下几种方法:
1.爬山算法:我们假设一只袋鼠,它并不知道珠穆朗玛峰的位置,但它一只向上爬,因此它可以到达一座山的最高峰,但这座山并不一定是最高峰——珠穆朗玛峰,因此能够求出局部的最高峰,也就是局部最优解。但不一定是全局的。
2.模拟淬火:假设有一只喝醉的袋鼠,它的清醒程度随时间而提高,当清醒度越低,则越有可能随机走,而清醒程度不断提高,越有可能向上走,最终也能找到一座山峰,但同样也不一定是最高峰。
3.遗传算法:我们随机在喜马拉雅山的各地部署袋鼠,因此有的所在的海拔高,有的海拔低。等过一段时间之后便从袋鼠中杀掉一些在低海拔的,保留高海拔的。因为高海拔的袋鼠被保留下来,它们便会繁殖,并且在繁殖时产生基因突变,使得繁殖出来的后代可能比其父母所在的海拔更高或者更低。当繁殖完成之后再继续击杀低海拔的袋鼠,随着时间的推移,最终袋鼠会聚集到同一座山顶。而这座山顶便极有可能是最高峰。
设计基因型与其对应的表现型:
假设袋鼠中有一条基因,控制袋鼠最喜欢居住的海拔。
假设有一只袋鼠中的基因为二进制串:
010100010100011111
其对应的十进制数 X 为此袋鼠所在的位置,并通过:
y=f(x)
计算得到此袋鼠所在的海拔y
初始化种群:
随机产生一定数量的种群,种群中的基因型为随机产生的二进制串。通过二进制串表示不同袋鼠所在的位置。
对种群进行评估(fitness):
通过基因型(袋鼠的二进制串)计算袋鼠的表现型(十进制数X),并通过目标函数计算袋鼠所在的海拔。并将此海拔作为此袋鼠的评估指标。
淘汰个体:
当我们对所有的袋鼠完成评估之后,选择种群中海拔低的袋鼠进行射杀。
种群繁殖(交叉)(crossover):
对两个不同的个体的基因进行重组,从而产生新的基因作为下一代的基因。
常见交叉算法:
单点交叉(One-point Crossover):指在个体编码串中只随机设置一个交叉点,然后再该点相互交换两个配对个体的部分染色体。
多点交叉(Multi-point Crossover):在个体编码串中随机设置了两个交叉点,然后再进行部分基因交换。
均匀交叉(Uniform Crossover):两个个体的对应位置的基因以一定概率进行交叉选择得到新的基因型。
个体变异
对个体的基因,由于基因(二进制串)中的某一位或多位产生变化,从而产生新的表现型。
当完成个体变异之后再次对种群进行评估并淘汰个体。当经过多代之后最终种群聚集在一小片区域内,并且此区域便被认为是所求解。
Rosenbrock函数是一个用来测试最优化算法性能的非凸函数,由Howard Harry Rosenbrock在1960年提出。也称为Rosenbrock山谷或Rosenbrock香蕉函数,也简称为香蕉函数。
香蕉函数在上述定义域内函数如图,Rosenbrock函数的每个等高线大致呈抛物线形,其全域最小值也位在抛物线形的山谷中(香蕉型山谷)。很容易找到这个山谷,但由于山谷内的值变化不大,要找到全域的最小值相当困难。其全域最小值位于 (x,y)=(1,1)点,数值为f(x,y)=0。有时第二项的系数不同,但不会影响全域最小值的位置。
#-*-coding:utf-8-*-
import os
import sys
import random
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
# 个体定义
class Individual(object):
"""docstring for individual"""
def __init__(self):
# 定义基因长度
self.__genetic_length=12
# x基因型
self.__genetic_x=[]
#y基因型
self.__genetic_y=[]
# 记录表现型
self.__phenotype=0
#记录适应度
self.__fitness=0
def random_create_genetic(self):
# 首先对x和y基因随机初始化
for i in range(self.__genetic_length):
self.__genetic_x.append(random.randint(0,1))
self.__genetic_y.append(random.randint(0,1))
pass
# 计算表现型
self.caculate_phenotype()
pass
def get_genetic(self):
return self.__genetic_x,self.__genetic_y
pass
def set_genetic(self,genetic_x,genentic_y):
self.__genetic_x=genetic_x
self.__genetic_y=genentic_y
self.caculate_phenotype()
pass
def caculate_phenotype(self):
#计算表现型
x=0
y=0
for i in range(self.__genetic_length):
x=x*2+self.__genetic_x[i]
y=y*2+self.__genetic_y[i]
x=(x-2048)/1000
y=(y-2048)/1000
# print(x," ",y)
self.__phenotype=100*(x**2-y)**2+(1-x)**2
return x,y
pass
def get_phenotype(self):
return self.__phenotype
pass
def mutation(self):
# 基因产生突变
x_mutation_num=random.randint(0,2)
y_mutation_num=random.randint(0,2)
for i in range(x_mutation_num):
position=random.randint(0,self.__genetic_length-1)
self.__genetic_x[position]=random.randint(0,1)
for i in range(y_mutation_num):
position=random.randint(0,self.__genetic_length-1)
self.__genetic_y[position]=random.randint(0,1)
self.caculate_phenotype()
pass
def set_fitness(self,fitness):
# 设置适应度
self.__fitness=fitness
pass
def get_fitness(self):
# 返回适应度
return self.__fitness
pass
class GeneticAlgorithm(object):
"""docstring for GeneticAlgorithm"""
def __init__(self,population_number):
self.__population_number=population_number
# 保存种群状态
self.__population=[]
# 初始化种群
self.init_population()
# 更新
self.update_fitness()
# 记录最大值的位置以及值
self.__max_x_array=[]
self.__max_y_array=[]
self.__max_z_array=[]
def init_population(self):
# 初始化种群
for i in range(self.__population_number):
individual=Individual()
individual.random_create_genetic()
self.__population.append(individual)
pass
# 更新适应度
def update_fitness(self):
total_fitness=0
# 计算总适应度
for individual in self.__population:
total_fitness=total_fitness+individual.get_phenotype()
# 更新个体适应度
for individual in self.__population:
individual_fitness=individual.get_phenotype()
individual.set_fitness(individual_fitness/total_fitness)
pass
#对种群进行选择
def selection(self,kill_number):
# # 计算需要删除的个体数量
# kill_number=(int)self.__population_number*random.uniform(0.2,0.5)
# 将种群按照从大到小排序
self.__population=sorted(self.__population,key= lambda individual:individual.get_fitness(),reverse=True)
# for i in self.__population:
# print(i.get_phenotype()," ",end='')
# 添加记录
position=self.__population[0].caculate_phenotype()
self.__max_x_array.append(position[0])
self.__max_y_array.append(position[1])
self.__max_z_array.append(self.__population[0].get_phenotype())
print("max:",self.__population[0].get_phenotype())
# 删除个体
self.__population=self.__population[0:self.__population_number-kill_number]
# 种群繁殖
def crossover(self,add_number):
# 采用单点交叉
current_pNumber=self.__population_number-add_number
# print("current_pNumber",current_pNumber)
for i in range(add_number):
# 随机选取
father_genetic_x,father_genetic_y=self.__population[random.randint(0,current_pNumber-1)].get_genetic()
mother_genetic_x,mother_genetic_y=self.__population[random.randint(0,current_pNumber-1)].get_genetic()
# 产生交叉点
crossover_point=random.randint(4,9)
genetic_x=father_genetic_x[0:crossover_point]+mother_genetic_x[crossover_point:12]
genetic_y=father_genetic_y[0:crossover_point]+mother_genetic_y[crossover_point:12]
current_individual=Individual()
current_individual.set_genetic(genetic_x,genetic_y)
current_individual.mutation()
self.__population.append(current_individual)
def start(self,num):
for i in range(num):
kill_number=(int)(self.__population_number*random.uniform(0.2,0.5))
print("迭代次数:",i)
# 删除种群
self.selection(kill_number)
# 添加个体
self.crossover(kill_number)
# 更新数值
self.update_fitness()
def return_max(self):
self.__population=sorted(self.__population,key= lambda individual:individual.get_fitness(),reverse=True)
for i in self.__population:
print(i.caculate_phenotype())
return self.__population[0].get_phenotype()
def draw_pic(self):
fig = plt.figure()
# 将figure变为3d
ax = Axes3D(fig)
# 定义x, y
x = np.arange(-2.048, 2.048, 0.05)
y = np.arange(-2.048, 2.048, 0.05)
# 生成网格数据
X, Y = np.meshgrid(x, y)
# 计算Z轴的高度
Z = np.array(100*(X**2-Y)**2+(1-X)**2) # 绘制3D曲面
ax.plot_surface(X, Y, Z, rstride = 1, cstride = 1, cmap = plt.get_cmap('rainbow'))
# 设置z轴的维度
ax.set_zlim(-1000,4000)
A=np.array(self.__max_x_array)
B=np.array(self.__max_y_array)
C=np.array(self.__max_z_array)
ax.plot(A, B, C,'b',label='parametric curve')
plt.show()
pass
#----------------------------------------------------------------------------------------------------
def main():
geneticAlgorithm=GeneticAlgorithm(50)
geneticAlgorithm.start(100)
print(geneticAlgorithm.return_max())
geneticAlgorithm.draw_pic()
pass
if __name__ == '__main__':
main()
蓝色线条为种群迭代过程中最大值的变化曲线。其最终终止在香蕉函数所在定义域的最大值位置。
虽然能够求出最大值,但是由于初始化种群时位置的随机性以及随着迭代进行,种群会不断聚集到相近的位置,因此有可能会聚集到上图左侧的最高点。