【遗传算法】——更高级的猜猜法
学习遗传算法有2个月了,最近也开始读汪民乐、高晓光、范阳涛三位先生编著的《先进遗传算法及其工程应用》,记录这几个月我对遗传算法的浅显理解。
1. 何为猜猜?如何理解遗传算法
我理解的遗传算法:无非是高级点的猜猜法。
先随机生成一堆方案,在迭代中随机更改这些方案的部分举措;
如果发现更好了(或更坏了),就随机保留或抛弃。在迭代最后选择一个最好的结果。
既然是猜猜法,那自然可以用在很多地方:物资调度、决策排序、选址、指派、路径规划等等。这些都是现阶段遗传算法适用的领域。
- 举个例子:
你要写一份5道选择题的试卷,你不知道答案,但是你可以反复提交这份试卷获得你的分数。
现在你在答题卡上随机写上AAAAA,获得分数2分。
这里的答案AAAAA,就是染色体的编码,这里的2分,对应着染色体的适应度。
你再改成AAAAB,分数高了1分。(染色体的遗传操作——变异)
这时候你知道,下次再改答案需要从AAAAB开始改了,就因为这个答案的分数高。
从遗传算法上看,这个染色体的适应度更高。我们通过选择留下适应度更高的染色体。
当然,和你一起答题的不止一人,恰如我们一开始给出的染色体数量不止一个。
有个同学随机的答案是CCCCC,你参考了他的前两个答案,得到一个新答案CCAAB,分数又高了1分。
这就是不同染色体之间的交叉操作。
就这么猜上100次,就会有极大的概率获得5分。
(最后我会给出源代码复现该场景)
至此,整合书中内容,我愿给出如下定义:
遗传算法是一种全局优化的自适应概率搜索算法,模拟自然选择和遗传进化的计算模型。
2. 为何高级?遗传算法的理论基础
和一般的猜猜法不同,遗传算法具有一定的数学基础理论,它可以得到一种模式定理:
遗传算法中,在选择、交叉、变异运算作用下,具有低阶、短定义长度、平均适应度高于种族适应度值的模式在子代中呈指数级增长。
通俗理解,就是物竞天择,适者生存。这可以确保遗传算法寻求最优样本的可能性。
但模式定理并不表明遗传算法一定能够找到最优样本,积木块假设却可以:
个体的基因通过选择、交叉、变异等遗传算法的作用,能够相互拼接在一起,形成适应度更高的个体编码串,最终接近最优样本。
由书中给出遗传算法隐含并行性原理推出可能存活的模式数目:
n
=
c
M
3
=
O
(
M
3
)
n = cM^3 = O(M^3)
n=cM3=O(M3)
其中M是我们给出的染色体数目,也就是一开始的方案数。故我们可以得到结论:
虽然在进化过程中遗传算法只处理了M个个体,但实际上并行处理了与M的二次方成正比例的模式数。这种并行性使得遗传算法可以快速搜索到一些比较好的模式。
总结:遗传算法比猜猜法高级。
3. 遗传算法的问题
当然,遗传算法也有其致命的问题,那就是欺骗性问题和收敛速度。
-
欺骗性问题:若评估适应度时存在多峰值,是有可能会陷入一个局部最优解,变得骄傲自大无法自拔。
措施:人们一般会综合其他智能算法,比如蚁群、粒子群、模拟退火等算法,尽量确保得到的结果是接近全局最优解的。
-
收敛问题:遗传算法的何时收敛也是人们关注的一个问题。
过早收敛——那叫“早熟”。过晚收敛——那叫“效率低下”。措施:人们一般根据具体的算法模型去调整最大迭代次数等参数,从而优化遗传算法的收敛速度。
4. 遗传算法框架代码(情景复现)
仅需引入一个random库,运行的结果多半是5分或者4分。
一起答题的同学越多,迭代次数越大,满分的概率就越大。
import random
# 试卷题目数量
num_questions = 5
# 每个染色体的长度:我们给出的答案长度
chromosome_length = num_questions
# 种群数量:一起答题的同学数量
population_size = 20
# 最大迭代次数:重复提交答案,校对修改的次数
max_iterations = 200
# 变异率:随机修改自己答案的概率
mutation_rate = 0.1
# 试卷答案:仅用于计算适应度,这里的染色体(每个同学)一开始并不知道答案
answers = "BCDAB"
# 适应度函数:计算答案的评分
# 实际问题中,我们一般采取路径的长度,成本的高低,执行的时间来作为适应度的评判标准。
def fitness(chromosome):
score = 0
for i in range(num_questions):
if chromosome[i] == answers[i]:
score += 1
return score
# 初始化种群:一开始随便写答案
population = []
for i in range(population_size):
chromosome = ""
for j in range(chromosome_length):
chromosome += random.choice(["A", "B", "C","D"])
population.append(chromosome)
# print("开始答案:" + chromosome)
# 遗传算法主循环
for iteration in range(max_iterations):
# 计算每个染色体的适应度:提交试卷得到分数
fitness_values = []
for chromosome in population:
fitness_values.append(fitness(chromosome))
# 选择操作,选择适应度高的染色体:尽量选择分数更大的试卷
total_fitness = sum(fitness_values)
selection_probabilities = [fitness_value / total_fitness for fitness_value in fitness_values]
selected_population = []
for i in range(population_size):
selected_chromosome = random.choices(population, weights=selection_probabilities)[0]
selected_population.append(selected_chromosome)
# 交叉操作,随机选取两个染色体进行交叉:这里随机选择两份试卷,随机抄答案
# 也可以设一个交叉概率限制一下
offspring_population = []
for i in range(population_size):
parent1 = random.choice(selected_population)
parent2 = random.choice(selected_population)
offspring = ""
for j in range(chromosome_length):
if random.random() < 0.5:
offspring += parent1[j]
else:
offspring += parent2[j]
offspring_population.append(offspring)
# 变异操作,对每个染色体按照一定概率进行变异:随机修改自己的答案
mutated_population = []
for chromosome in offspring_population:
mutated_chromosome = ""
for j in range(chromosome_length):
if random.random() < mutation_rate:
mutated_chromosome += random.choice(["A", "B", "C","D"])
else:
mutated_chromosome += chromosome[j]
mutated_population.append(mutated_chromosome)
# 更新种群:得到了一群新的答案,它们平均分数会趋向更高。
population = mutated_population
# 输出最终结果
best_chromosome = max(population, key=fitness)
best_fitness = fitness(best_chromosome)
print("Best Paper:", best_chromosome)
print("Best score:", best_fitness)