遗传算法虽然相比于深度学习,神经网络等新兴的人工智能算法过于原始,相比于有严格数学理论的优化算法又显得那么粗糙,但是作为一类思路独特的算法还是值得了解一下的。
介绍
遗传算法(Genetic Algorithm)是美国J.Holland教授于1975年首先提出的一种启发式优化方法,灵感来自于进化理论:它模拟了生物进化过程中的自然选择、遗传机制和变异规律,用于解决搜索和优化问题。
遗传算法的基本思想是将问题的解表示为一组基因序列,然后使用遗传操作(如选择、交叉和变异)对这些基因序列进行进化(迭代),使优秀的解逐渐从种群中浮现出来。
遗传算法作为一种启发式优化方法,具有如下特点:
- 适用性:遗传算法的计算框架是普适性的,可以应用于求解各种优化问题,特别是对于搜索空间庞大、复杂的优化问题,如组合优化、参数优化等
- 并行性:可以并行处理多个解决方案,提高搜索效率
- 全局或局部最优:由于具有随机性和多样性,遗传算法有助于跳出局部最优解,更有可能找到全局最优解,但是这依赖于初始种群的生成和参数的设置
- 灵活性/复杂性: 包含很多待定参数,需要根据问题的特点进行调整和改进,这既是算法的灵活性体现,也是算法自身的复杂性
- 维度问题: 遗传算法的计算策略是基于群体的进化而不是单个解的搜索,对于高维问题的计算量不会随着维度而剧增,但是可能需要更多的迭代次数
- 收敛性:数学上无法证明任何收敛速度,这是遗传算法的巨大劣势,同时由于算法的随机性,求解结果可能不稳定,有时难以给出可靠的结果
基本概念
遗传算法中有很多模仿生物进化理论的基本概念:
- 个体(Individual):可行解
- 种群(Population):可行域
- 染色体(Chromosome):可行解所对应的编码
- 基因(Gene):可行解编码的组成部分(染色体由基因组成)
- 适应性评估(Fitness Assignment):计算目标函数的函数值(希望目标函数最大化),得到适应度
- 选择(Selection):保留适应度更高的可行解(与进化论的生存斗争和适者生存相对应)
- 遗传/繁殖:用于在现有可行解的基础上产生新的可行解,包括两类方法(与生物遗传相对应)
- 交叉(Crossover):将两个可行解内的组分随机交换(染色体交换基因片段)
- 变异(Mutation):随机挑选可行解中的某些组分进行变异(基因突变)
- 进化迭代: 重复进行选择和遗传过程,不断生成新的个体群体,直到达到终止条件
遗传算法可以求解如下的一般最优化问题
$$
x^* = \text{argmax}_{x \in \Omega} f(x)
$$
其中目标函数$f$不需要任何假设,只需要保证全局最优解是存在的,并且可行解 $x \in \Omega$ 可以通过一种合适的方法转换为基因编码即可。
遗传算法的流程如下,通常设置固定的迭代次数来终止
求解示例
这里参考网上的教程,选择一个比较直观的一维连续优化问题
$$
x^* = \text{argmax}{x \in \Omega} f(x)
= \text{argmax}{x \in [0,9]} x+10\sin(5x)+7\cos(4x)
$$
$f$的函数曲线如下图,全局最优解在 $x^*=7.85$ 附近,注意到有很多局部最优解。
编码转换
我们需要将可行解(0-9的浮点数)转换为代表染色体的编码,例如转化为固定长度的01字符串,指定字符串的长度为$n$,则一共可以表示$2^n$个值,
我们选择$n=17$可以保证精度$\varepsilon = (9-0)/2^{17} \approx 0.0000686646$。
浮点数和染色体编码的转换公式如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
def encode(value, lower, upper, chromosome_size): normalized_value = (value - lower) / (upper - lower) decimal_value = int(normalized_value * (2**chromosome_size - 1)) binary_string = format(decimal_value, f"0{chromosome_size}b") return binary_string
def decode(chromosome, lower, upper): decimal_value = int(chromosome, 2) decoded_value = lower + decimal_value * (upper - lower) / (2 ** len(chromosome) - 1) return decoded_value
|
示例如下
1 2 3 4 5 6 7 8
| chromosome = "11010100110001" print("编码:", chromosome)
result = decode(chromosome, 0, 9) print("实数值:", result)
chromosome2 = encode(result, 0, 9, len(chromosome)) print("再次编码:", chromosome2)
|
其中编码函数通常是不需要的,随机初始生成的就是二进制编码。
初始化
我们可以随机生成初始的种群,需要设置染色体长度(即二进制编码长度)和种群数目这两个参数
1 2 3 4 5 6 7 8 9
| def initialize_population(population_size, chromosome_size): population = [] for _ in range(population_size): chromosome = "".join( [str(random.randint(0, 1)) for _ in range(chromosome_size)] ) population.append(chromosome) return population
|
适应性评估
实现代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| x_upper = 9.0 x_lower = 0.0
def fitness_function(x): global f_lower return x + 10 * np.sin(5 * x) + 7 * np.cos(4 * x)
def evaluate_fitness(population): global x_lower, x_upper fitness_values = [] for chromosome in population: x = decode(chromosome, x_lower, x_upper) fitness = fitness_function(x) fitness_values.append(fitness) return fitness_values
|
轮盘赌选择
实现代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
def select(population, fitness_values): selected = [] min_fitness = min(fitness_values) if min_fitness < 0: adjusted_fitness = [fitness - min_fitness for fitness in fitness_values] else: adjusted_fitness = fitness_values
total_fitness = sum(adjusted_fitness) probabilities = [fitness / total_fitness for fitness in adjusted_fitness]
for _ in range(len(population)): selected.append(population[np.random.choice(len(population), p=probabilities)]) return selected
|
交叉与变异
实现代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| def crossover(selected): new_population = [] for i in range(0, len(selected), 2): crossover_point = random.randint(1, len(selected[i]) - 1) child1 = selected[i][:crossover_point] + selected[i + 1][crossover_point:] child2 = selected[i + 1][:crossover_point] + selected[i][crossover_point:] new_population.extend([child1, child2]) return new_population
def mutate(population, mutation_rate): for i in range(len(population)): if random.random() < mutation_rate: mutation_point = random.randint(0, len(population[i]) - 1) population[i] = ( population[i][:mutation_point] + str(1 - int(population[i][mutation_point])) + population[i][mutation_point + 1 :] )
return population
|
主函数
遗传算法的主函数实现代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| def genetic_algorithm(population_size, chromosome_size, generations, mutation_rate): global x_lower, x_upper
population = initialize_population(population_size, chromosome_size)
for _ in range(generations): fitness_values = evaluate_fitness(population) selected = select(population, fitness_values) population = mutate(crossover(selected), mutation_rate)
def sort_func(x): return fitness_function(decode(x, x_lower, x_upper))
best_chromosome = max(population, key=sort_func) return decode(best_chromosome, x_lower, x_upper)
|
使用示例如下
1 2 3 4 5 6 7 8 9 10 11
| population_size = 400 chromosome_size = 17 generations = 100 mutation_rate = 0.1
best_solution = genetic_algorithm( population_size, chromosome_size, generations, mutation_rate )
print("找到的最优解 x:", best_solution) print("对应的适应度值 f(x):", fitness_function(best_solution))
|
运行结果如下
1 2
| 找到的最优解 x: 7.856657841932998 对应的适应度值 f(x): 24.85536152100544
|
求解封装
前面的示例是基于特定问题的,面向过程的程序,下面可以将遗传算法的主要过程封装成一个求解器类,使得求解过程更加简洁,并且可以适应一般性的问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
| import random import numpy as np
class GASolver: def __init__( self, fitness_func, decode_func, population_size, chromosome_size, generations, mutation_rate, ): self.fitness_func = fitness_func self.decode_func = decode_func self.population_size = population_size self.chromosome_size = chromosome_size self.generations = generations self.mutation_rate = mutation_rate
def _evaluate_fitness(self, population): fitness_values = [] for chromosome in population: x = self.decode_func(chromosome) fitness = self.fitness_func(x) fitness_values.append(fitness) return fitness_values
def _initialize_population(self): population = [] for _ in range(self.population_size): chromosome = "".join( [str(random.randint(0, 1)) for _ in range(self.chromosome_size)] ) population.append(chromosome) return population
def _select(self, population, fitness_values): selected = [] min_fitness = min(fitness_values) if min_fitness < 0: adjusted_fitness = [fitness - min_fitness for fitness in fitness_values] else: adjusted_fitness = fitness_values
total_fitness = sum(adjusted_fitness) probabilities = [fitness / total_fitness for fitness in adjusted_fitness]
for _ in range(len(population)): selected.append( population[np.random.choice(len(population), p=probabilities)] ) return selected
def _crossover(self, selected): new_population = [] for i in range(0, len(selected), 2): crossover_point = random.randint(1, len(selected[i]) - 1) child1 = selected[i][:crossover_point] + selected[i + 1][crossover_point:] child2 = selected[i + 1][:crossover_point] + selected[i][crossover_point:] new_population.extend([child1, child2]) return new_population
def _mutate(self, population): for i in range(len(population)): if random.random() < self.mutation_rate: mutation_point = random.randint(0, len(population[i]) - 1) population[i] = ( population[i][:mutation_point] + str(1 - int(population[i][mutation_point])) + population[i][mutation_point + 1 :] )
return population
def run(self): population = self._initialize_population()
for _ in range(self.generations): fitness_values = self._evaluate_fitness(population) selected = self._select(population, fitness_values) population = self._mutate(self._crossover(selected))
def sort_func(x): return self.fitness_func(self.decode_func(x))
best_chromosome = max(population, key=sort_func) best_x = self.decode_func(best_chromosome) best_value = self.fitness_func(best_x) return (best_x, best_value)
|
使用时需要提供适应度函数和解码函数,还有其它几个必要参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
|
def fitness_function(x): return x + 10 * np.sin(5 * x) + 7 * np.cos(4 * x)
def decode_function(chromosome): lower = 0.0 upper = 9.0 decimal_value = int(chromosome, 2) decoded_value = lower + decimal_value * (upper - lower) / (2 ** len(chromosome) - 1) return decoded_value
solver = GASolver( fitness_function, decode_function, population_size=400, chromosome_size=17, generations=100, mutation_rate=0.1, )
print("最优解:", solver.run())
|
运行结果如下
1
| 最优解: (7.856657841932998, 24.85536152100544)
|