2.2.1归一化
使用种群中最小适应度值的个体作为归一化的最小值。这被称为“最小-最大规范化”(Min-Max Normalization)或“线性比例缩放”(Linear Proportional Scaling)。具体地,对于一个种群,可以通过以下步骤进行归一化:
:其中f(x)是个体的原始适应度值。
:这样得到的归一化适应度值f’(x)会被限制在[0, 1]之间,并且种群中适应度值最小的个体的归一化适应度值将为0
问题:在使用最小-最大规范化对适应度进行归一化时,如果存在某个个体的适应度值为0,则它的归一化适应度值也将为0。这可能会导致一些问题,因为在选择过程中,只有归一化适应度值比较大的个体才会被选中,而归一化适应度值为0的个体则无法被选中。
解决方法:有一种解决方案是添加一个非零的偏移量例如,在计算f’(x)时,我们可以使用以下公式:
其中epsilon是一个足够小的正数,用来避免出现分母为0的情况。通过添加epsilon,所有的适应度值都可以被成功归一化,而且可以继续使用遗传算法等选择方法。
需要注意的是,选择过程不仅依赖于适应度值的大小,还涉及到其他因素,如选择操作的参数、选择算子的类型等。因此,在使用归一化适应度值进行选择时,需要谨慎地选择合适的方法,并进行实验测试和验证。
上面代码略 上面的代码在文章上面 def roulette_wheel_selection(population, fitness_values): # 计算总适应度 total_fitness = sum(fitness_values) # 计算适应度最大值的偏移量 offset = abs(min(fitness_values)) # 计算相对适应度 relative_fitness = [(f + offset) / total_fitness for f in fitness_values] # 计算累积概率 probabilities = [sum(relative_fitness[:i + 1]) for i in range(len(relative_fitness))] # 选择个体 selected = [] for i in range(len(population)): r = random.random() for j, p in enumerate(probabilities): if r <= p: selected.append(population[j]) break return selected def normalize_fitness(fitness_list, population): # Calculate the minimum and maximum fitness values min_f = min(fitness_list) max_f = max(fitness_list) # Add a small constant to avoid division by zero epsilon = 1e-8 # Normalize the fitness values for each individual in the population normalized_fitness = [] for i in range(len(population)): fitness = fitness_list[i] norm_fitness = (fitness - min_f + epsilon) / (max_f - min_f + epsilon) normalized_fitness.append(norm_fitness) return normalized_fitness # 归一化适应度 fitness_values_nor = normalize_fitness(fitness_values,population_2) print("归一化的适应度值",fitness_values_nor) # 轮盘赌选择父代 select = roulette_wheel_selection(population_2, fitness_values_nor) print(select)
输出:
重要知识点:在轮盘赌选择算法中,每个个体在被选中的概率与其适应度成正比。因此,如果得到的个体重复出现,那么它们必须被视为不同的个体,并分别参与下一代的繁殖。
一般来讲,我们选择种群的个数和初始种群个数保持一致。
3.交叉操作
交叉操作是遗传算法中的一种重要操作,用于产生新的个体。其原理是通过对两个或多个个体的某些基因位点进行交换,从而产生具有不同基因组合的新个体。
3.1单点交叉
图片来自: 博客:遗传算法中的一些交叉算子
单点交叉是其中一种常见的交叉方式,它是指在随机选择的一个交叉点处将两个个体的染色体分为两段,然后将这两段互换,产生两个新个体。
由上面选择操作,选择到了种群一些优秀个体作为父代基因,将这些优秀个体随机两两交叉。
这样做的好处是:
在使用已经选择出来的父代个体进行交叉操作时,通常是随机选取两个不同的父代进行交叉操作。这样可以增加交叉的随机性,从而更好地避免陷入局部最优解。
如果您采用逐一对每一对父代进行交叉操作,则可能会导致某些父代个体之间没有交叉,或者某些个体交叉的次数过多,从而影响算法效果。因此,随机选择两个不同的父代进行交叉是比较合适的做法。
代码:
# 单点交叉 def single_point_crossover(parents): # 随机选择两个不同的父代基因进行交叉,并产生相应数量的子代基因 children = [] for i in range(int(len(parents) / 2)): parent1, parent2 = random.sample(parents, 2) crossover_point = random.randint(1, len(parent1) - 1) parent1_left, parent1_right = parent1[:crossover_point], parent1[crossover_point:] parent2_left, parent2_right = parent2[:crossover_point], parent2[crossover_point:] child1 = parent1_left + parent2_right child2 = parent2_left + parent1_right children.append(child1) children.append(child2) # 返回交叉后的子代基因 return children # 轮盘赌选择父代 children = single_point_crossover(select) print(children)
输出:
3.2 多点交叉
看图就能理解吧,就不解读了=========
3.3部分交叉
看这篇博客吧:遗传算法一些交叉算子
4.变异操作
变异操作是遗传算法中的一种重要操作,它可以引入新的基因组合,帮助种群避免局部最优解并提高全局搜索能力。在遗传算法中,变异操作通常通过以下三个步骤来实现:
- 随机选择一个个体:从当前种群中随机选择一个个体作为变异对象。
- 选择一个基因位点:从该个体的染色体序列中随机选择一个基因位点(即染色体上的一个基因)。
- 改变基因值:根据预定义的概率将该基因值进行改变。这个概率通常很小,通常设置为0.1或更小,以确保变异不会太频繁影响种群稳定性。
变异操作的实现方式有很多种,具体取决于问题的特性和目标函数的形式。例如,在二进制编码的问题中,变异操作可以通过随机翻转某些基因位来实现;在实数编码的问题中,变异操作可以通过对染色体上的某些基因进行随机加减来实现。
需要注意的是,变异操作应该谨慎使用,以防止过度变异导致种群的多样性降低。同时,由于变异操作通常会增加种群的多样性,因此应该控制变异概率,以确保种群能够平衡地进行探索和利用。
# 变异操作 def mutation(population, mutation_rate): mutated_population = [] for individual in population: if random.random() < mutation_rate: mutated_individual = list(individual) gene_index = random.randint(0, len(mutated_individual) - 1) mutated_individual[gene_index] ^= 1 mutated_population.append(mutated_individual) else: mutated_population.append(individual) return mutated_population mutated_children = mutation(children,0.01) print("变异后的子代种群", mutated_children)
输出:
5.评估操作
这一步很简单,就是将子代的适应度求一下。
看到这了,其实你已经掌握了90%的遗传算法了,
我们将上面的操作进行一下可视化,看一下我们第一次迭代得到的效果:
这里,我把之前的代码中的种群个数修改为20个,提高可视化的可读性
# Generate data for the fitness function curve x_fit = np.linspace(-5, 5, 100) y_fit = [fitness(x) for x in x_fit] # Plot the fitness function curve plt.plot(x_fit, y_fit, label='Fitness Function') # Plot the population and offspring for i, population in enumerate([population_2, mutated_children]): x = binary_to_x(population, -5, 5, n) x = [round(num, 1) for num in x] fitness_values = [fitness(x) for x in x] if i == 0: label = 'Population' color = 'blue' else: label = 'Offspring' color = 'red' plt.scatter(x, fitness_values, c=color, label=label) plt.legend() plt.xlabel('x') plt.ylabel('Fitness') plt.show()
输出:
看到了把,子代函数值要大于父代,接近成功了,接下来,我们仅仅需要将上述操作迭代下去,就能逼近那个函数的最大值。
6.终止操作
进行完上述步骤之后,需要检查是否满足终止条件,如达到最大迭代次数或目标函数值足够小等。如果不满足,则继续执行上述步骤,直到满足终止条件为止。
根据终止条件的不同,大致可以分为以下几种终止条件设置方法:
- 迭代次数:可以设置算法运行的最大迭代次数,超过迭代次数则停止运行。
- 精度要求:可以设置算法达到一定的精度要求后,停止运行。例如,对于优化问题,当目标函数值变化很小或者收敛到一个特定值时,算法就可以停止。
- 计算时间:可以设置算法运行的最大时间,当超过这个时间限制时,算法就停止。
- 变异率:可以根据变异率来判断算法何时停止。如果变异率已经非常小,那么算法就可以停止。
- 种群适应性:可以设置种群适应性的阈值,当种群适应性达到一定水平时,算法就停止。
需要注意的是,终止条件的设置应该合理,否则可能会导致算法早停或持续运行而无法得到最优解。因此,在实际应用中,需要根据具体情况进行细致的分析和设置。
删除线:基础篇就不写了,有时间的话,放到进阶篇讲
6.1 设置迭代次数
很好理解,就是设置迭代次数,次数到了,自然就结束了。
代码:
# 二进制编码 n位二进制的选择 n = calculate_n(1, -5, 5) print("n:", n) # 初始化种群 population_2 = initialize_population(20, n) print("初始化种群", population_2) # 方便操作 population = population_2 def xunhuan(population_2, mutation_rate, a, b, n): x = binary_to_x(population_2, a, b, n) x = [round(num, 1) for num in x] fitness_values = [fitness(x) for x in x] fitness_values_nor = normalize_fitness(fitness_values, population_2) select = roulette_wheel_selection(population_2, fitness_values_nor) children = single_point_crossover(select) mutated_children = mutation(children, 0.01) return mutated_children stop_flag = False max_iterations = 1000 iteration_count = 0 while not stop_flag: population = xunhuan(population, 0.01, -5, 5, n) iteration_count += 1 if iteration_count >= max_iterations: stop_flag = True mutated_children = population # Generate data for the fitness function curve x_fit = np.linspace(-5, 5, 100) y_fit = [fitness(x) for x in x_fit] # Plot the fitness function curve plt.plot(x_fit, y_fit, label='Fitness Function') # Plot the population and offspring for i, population in enumerate([population_2, mutated_children]): x = binary_to_x(population, -5, 5, n) x = [round(num, 1) for num in x] fitness_values = [fitness(x) for x in x] if i == 0: label = 'Population' color = 'blue' else: label = 'Offspring' color = 'red' plt.scatter(x, fitness_values, c=color, label=label) plt.legend() plt.xlabel('x') plt.ylabel('Fitness') plt.show()
输出:
最后看到子代的基因几乎都为一种,恒定。这个就是我们找到的最大解。