智能优化系列|第三期:10分钟教你快速get遗传算法(附python案例代码)
遗传算法(Genetic Algorithm), 简称 GA。是一种通过模拟自然进化过程搜索最优解的方法。在遗传算法中,每个个体通常由染色体表示(个体(潜在解)的二进制编码),通过选择、交叉和变异等遗传操作来模拟生物进化过程,以寻找最优解或近似最优解。遗传算法的核心要素有5个:参数编码、初始群体、适应度函数、遗传操作(选择、交叉和变异)、解码。
1.遗传算法简介
遗传算法(Genetic Algorithm), 简称 GA。是一种通过模拟自然进化过程搜索最优解的方法。在遗传算法中,每个个体通常由染色体表示(个体(潜在解)的二进制编码),通过选择、交叉和变异等遗传操作来模拟生物进化过程,以寻找最优解或近似最优解。
2.遗传算法核心思想
遗传算法的核心要素有5个:参数编码、初始群体、适应度函数、遗传操作(选择、交叉和变异)、解码。GA 算法最重要的就是明白什么是个体 和怎么样对个体进行评估 (他们的 Fitness)。
2.1参数编码
在遗传算法中,每个个体通常由染色体表示(个体(可能解)的二进制编码)。首先我们搞清楚可能解的形式是什么。以10座城市为例,城市编码(0、1、2........n),我们希望得到的解或许是3 5 4 8 6 7 9 0 2 1。因此,在遗传算法中,每个个体的形式可以用10个不重复数字的排列表示。好消息是这样一来不需要进行二进制编码解码了。
2.2初始群体
个体是指代表问题解空间中一个可能解的一个实体,群体则是这样的一组一组的可能解的集合。下面这小段代码可以生产一个初始群体:
self.city_num = 10 # 有10个城市
self.pop_size = 100 # 种群大小(个体个数)
# 初始化种群
def init_population(self):
path=np.array(range(self.city_num)) # 初始化1个个体
population = []
for i in range(self.pop_size):
x=path.copy()
np.random.shuffle(x) # 随机洗牌
population.append(x) # 初始化100个个体,生成群体
2.3适应度函数
适应度函数作用是对个体进行评估 (他们的 Fitness),评价一个个体的基因是否适合被遗传下来。这就需要计算个体的适应度,适应度越高的个体,被选择的概率就越大。在tsp问题中,我们希望一个个体的路径长度越短越好,即路径越短,适应度越大。因此适应度公式可以是这样的:
# 根据路径长度计算每个个体的适应度
self.fitness = []
for i in range(self.pop_size):
self.fitness.append(1 / (self.dis[i] ** 15))
2.4遗传操作
-
选择操作 (selection)-适者生存,不适者淘汰
-
交叉操作 (crossover)
-
变异操作 (mutation)
选择操作
适应度越大的个体被选中的可能性越大,用numpy.choice来实现。其他方式:也可以采用杰出父代+子代的方式来更新种群。
# 根据适应度进行选择,适应度大的被选择概率大
idx = np.random.choice(np.arange(self.pop_size), size=self.pop_size, replace=True,
p=(self.fitness) / (fitness_sum))
交叉操作
常见的3种交叉操作:部分映射交叉,顺序交叉,基于位置的交叉
我们这里可以选择顺序交叉,步骤如下:
变异操作
变异时随机选择两个不同的位置的基因进行交换。也可以采用三点变异法,随机生成abc三点,将ac基因片段与bc做交换。如果是二进制编码,主要采用单点变异,也叫位变异,即只需要对基因序列中某一个位进行变异,以二进制编码为例,即0变为1,而1变为0
3.遗传算法实现过程
第一步:初始化 i←0进化代数计数器;max_gen是最大进化代数(也可以没有);随机生成M个个体作为初始群体P(i);
第二步:个体评价 计算P(i)中各个个体的适应度;
第三步:交叉运算 将交叉算子作用于群体;-》在原始群体中,每对父母以一定概率生成一个新解,生成新群体
第四步:变异运算 将变异算子作用于群体,-》在交叉后的群体中,每个个体以一定概率发生突变,生成新群体
第五步:选择运算 将选择算子作用于群体;-》用轮盘赌的方式进行自然选择,fitness越大的被选择的概率越大
,经过以上运算后得到下一代群体P(i+ 1);
第六步:终止条件判断 i≦max_gen:
i← i+1 转到步骤2;
i>max_gen:终止 输出解。
4.遗传算法实现34个城市的TSP问题
核心算子的代码:
# 获得距离矩阵
def gen_dist_mat(self):
D = self.cities
city_cnt = self.cities_num
dist_mat = np.zeros((city_cnt, city_cnt))
for i in range(city_cnt):
for j in range(city_cnt):
if i == j:
# 相同城市不允许访问
dist_mat[i][j] = 1000000
else:
# 单位:km
dist_mat[i][j] = 6378.14 * math.acos(
math.cos(D[i][1]) * math.cos(D[j][1]) * math.cos(D[i][0] - D[j][0]) +
math.sin(D[i][1]) * math.sin(D[j][1]))
self.dist_mat = dist_mat
# 计算路径长度
def dis_cal(self, path):
"""
适应度函数,计算目标函数值.
:param path: 一个解
:return: 目标函数值,即总距离
"""
distance = 0
for i in range(len(path) - 1):
distance += self.dist_mat[path[i]][path[i + 1]]
distance += self.dist_mat[path[-1]][path[0]] # 从最后一个城市回到出发城市
return distance
# 初始化种群
def init_population(self):
path = np.array(range(self.cities_num))
self.population = []
for i in range(self.pop_size):
x = path.copy()
np.random.shuffle(x) # 随机洗牌
self.population.append(x)
# 自然选择,种群根据适应度进行更新
def select(self):
# 计算每条路径的长度,放入列表
self.dis = []
for i in range(self.pop_size):
self.dis.append(self.dis_cal(path=self.population[i]))
# 根据路径长度计算每个个体的适应度
self.fitness = []
for i in range(self.pop_size):
self.fitness.append(1 / (self.dis[i] ** 15))
# 适应度总和
fitness_sum = 0
for i in range(self.pop_size):
fitness_sum += self.fitness[i]
# 根据适应度进行选择,适应度大的被选择概率大
idx = np.random.choice(np.arange(self.pop_size), size=self.pop_size, replace=True,
p=(self.fitness) / (fitness_sum))
self.new_pop = []
for i in idx:
self.new_pop.append(self.population[i])
self.population = self.new_pop
# 交叉互换
def crossover(self):
self.new_pop = []
for father in self.population:
child = father # 初步让子代获得父本染色体
if np.random.rand() < self.cross_rate:
# 随机选择一个染色体作为母本
mother_index = np.random.randint(0, self.pop_size)
mother = self.population[mother_index]
# 确定切割点
left = np.random.randint(0, self.cities_num - 2)
right = np.random.randint(left + 1, self.cities_num - 1)
mother = mother.tolist()
father = father.tolist()
# 切割片段
gene = mother[left:right]
child1_c = father[right:] + father[:right]
child1 = child1_c.copy()
# 去除重复基因
for o in gene:
child1_c.remove(o)
child1[left:right] = gene
child1[right:] = child1_c[0:len(child1) - right]
child1[:left] = child1_c[(len(child1) - right):]
child = np.array(child1)
self.new_pop.append(child)
self.population = self.new_pop
# 变异
def mutation(self):
for i in range(self.pop_size):
if np.random.rand() < self.muta_rate:
child = self.population[i]
u = np.random.randint(0, self.cities_num - 2)
v = np.random.randint(u + 1, self.cities_num - 1)
child_x = child[u + 1:v]
child_x = child_x[::-1]
child = np.concatenate((child[0:u + 1], child_x, child[v:]))
self.population[i] = child
5.结果输出
the shortest path is:30-->29-->31-->28-->27-->26-->24-->25-->23-->19-->18-->20-->21-->22-->15-->14-->16-->17-->5-->8-->9-->11-->13-->12-->10-->6-->7-->4-->3-->2-->1-->0-->33-->32
the total distance is 16253.778217630528
6.相关阅读
1.遗传算法解决tsp问题(基于python)_遗传算法解决tsp问题python-CSDN博客
2.【建模算法】基于遗传算法求解TSP问题(Python实现)_城市序号x坐标y坐标-CSDN博客
3.干货 | 遗传算法(Genetic Algorithm) (附代码及注释)
相关视频会在后续发布,欢迎关注我的bilibili:无形忍者的个人空间-无形忍者个人主页-哔哩哔哩视频
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)