引言
编程中很多算法都是基于一些严谨的理论来作为基础,从而进行编程实现,解决问题。但我认为遗传算法是比较特殊的一种。首先,它是基于生物进化理论来的,理论虽然已被证明,但总归觉得有一些概率,可以说是运气在里面。其实,往往使用遗传算法去解决问题时,和常规的直面问题,制定严谨的执行步骤去解决问题不同,遗传算法总是将问题往这个模型上靠,制定简单的进化规则,然后运行起来后,它就按照这些既定的简单理论开始自己进化,从而得到一个往往不是最优解的答案,但往往用它来解决一些问题,你总归是会得到一个可用的答案。
什么是遗传算法
如果要讲遗传算法(Genetic Algorithm),那要从生物进化理论来讲。一个种群,起初是由一定规模的生物个体组成,在自然界竞争中优胜劣汰,从而一代一代进化下来,能够成功进化到下一代的种群,肯定有一个帮它克服大自然,良好地适应于恶劣环境的特点。比如,人类进化过程中,刚开始都不会直立行走,但某一些第一个尝试直立行走的祖先当中,他们解放了双手,进行了更有效的生产劳动,从而得到了更多的生产资源,存活了下来,而那些未能尝试学习直立行走的部分,要么被淘汰,要么没有进化,种群没能良好的适应环境。而这群尝试直立行走的种群,得到了更多的生存机会,进一步进化过程中,那些强壮的个体,某一方面能力出众的个体,适应环境更好,得到了更多繁衍生息后代的机会,就这样一代一代进化成我们现在的样子。如果假设我们现在的状态为最佳状态(当然不是),第一批猴子为人类初始状态,那么人类进化的过程可以看作是人类进化过程中的一个解决方案,按照这条路走,总归会达到现在的样子,当然可能有更多更好的方法,但至少这种方法可以达到现在状态。
我们知道,人类的特性控制是由基因控制的,每一个性状都是一对基因的体现,基因也在不段的繁衍中,进行着各种杂交和突变,这才给了一些良好基因生存的机会,那些被证明杂交过程中留下来的基因,大多是更能适应环境的基因,而突变也给了基因一些闪亮的机会,并且最终突显出来。
根据上面的描述,不难看出,遗传过程中的几个重要步骤:
- 一个种群
- 种群中进行繁衍的亲本
- 基因杂交
- 基因突变
那这些与我们的编程算法有什么关系呢?
很多种实际问题,其实它们的解可以类比为基因的排序,比如一个机器人去走迷宫,从入口找到出口,那么它的街轨迹,就是找到出口的解,即类似于,上下左右,四个方向,左->上->左->右->下,这个问题的解就是类似于这样的一个序列,我们可以把它看作是一个染色体,每一步可以看作是一对基因,如果我们随机给出一个种群,种群中每个个体都是一套染色体,每个染色体就包含这样一个基因序列,即给出一组随机的基因序列,那我们怎么去解决这个问题呢:
- 先生产出一个群体,里面个体的基因序列随机;
- 让每个基因组(即机器人走迷宫的步骤),去尝试走迷宫,看看它能走多远;
- 根据结果对这组基因进行打分;
- 从结果中,按照一定规则选出两个亲本,可以按照打分情况来随机选,让分数高的更容易被选上;
- 用这两个亲本,进行杂交(比如随机选择一些片段整合一下),产生两个子代;
- 对这两个子代亲本,按照一定变异率进行基因变异(比如一定概率基因值取反);
- 把这两个子段放进下一代的种群中,依然再选择亲本,按照上面的方法产生子代,形成一个和上一代同等规模的种群;
- 让这个种群去走下迷宫,得到他们的分数,依次类推,看是否有哪一条基因找到了出口;
- 依次往复,直到得到答案。
关键点
从一个种群到下一个种群,适应下来的解,局部最优的解更容易得到繁衍的机会,而杂交算法的加入,会使得,这些优质的解可以取长补短,让子代有机会达到更佳,但局部最优可能不是全局最优,很有可能劣质基因的积累,让整个种群慢慢特征收敛,万一进入死胡同,将没有机会得到最优解,而变异就给了种群有了多样性的可能。这些算子的整合,就产生了一个完整的遗传算法。
代码演示
'''
* Created on: Oct 11, 2017
* Author: Heng Xiangzhong
*
* May the code be with you!
'''
import random
import copy
map = [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1],
[0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1],
[1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1],
[1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1],
[1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1],
[1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 5],
[1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]
map_shadow = [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1],
[0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1],
[1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1],
[1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1],
[1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1],
[1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 5],
[1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]
def _print_map(map):
for x,row in enumerate(map):
for y, i in enumerate(row):
if i == 999:
print("\033[0;32m* \033[0m", end="")
elif i > 1:
print("\033[0;31m%d \033[0m" % (i), end="")
elif x == 2 and y == 0:
print("\033[0;32m%d \033[0m" % (i), end="")
elif i == 0:
print(" ", end="")
elif i == 1:
print("+ ", end="")
print("")
class Genome:
def __init__(self):
self.bits = []
self.fitness = 0
for i in range(0, 100):
self.bits.append(random.randint(0, 1))
def clear(self):
self.bits.clear()
class GARobot:
def __init__(self):
self.genomes = []
self.pop_size = 200
self.mutation_rate = 0.001
self.chromo_length = 0
self.gene_length = 0
self.fittest_genome = 0
self.best_fitness_score = 0
self.total_fitness_score = 0
self.crossover_rate = 0.7
self.generation = 0
self.map = map
self.busy = False
for i in range(0, self.pop_size):
self.genomes.append(Genome())
def epoch(self):
self.__test_route()
new_genomes = []
for i in range(0, self.pop_size >> 1):
mum = self.__roulette_wheel_selection()
dad = self.__roulette_wheel_selection()
#print("mum (%f), dad (%f), best (%f)" % (mum.fitness, dad.fitness, self.best_fitness_score))
#Crossover
baby = self.__crossover(mum, dad)
baby_temp = baby[0]
self.__mutate(baby[0])
self.__mutate(baby[1])
self.__mutate(baby_temp)
new_genomes.append(baby[0])
new_genomes.append(baby[1])
self.genomes = new_genomes
def __decode_direction(self, a, b):
if a == 1 and b == 1:
return (-1, 0)
elif a == 1 and b == 0:
return (1, 0)
elif a == 0 and b == 0:
return (0, -1)
else:
return (0, 1)
def __show_path(self, genome):
start = ()
for x,row in enumerate(map):
for y,val in enumerate(row):
if val == 5:
start = (x, y)
temp_map = copy.deepcopy(map)
pos = start
for couple in range(0, len(genome.bits) >> 1):
# Check whether is wall
direction = self.__decode_direction(genome.bits[couple << 1], genome.bits[(couple << 1) + 1])
next_x = pos[0] + direction[0]
next_y = pos[1] + direction[1]
if next_x >= 0 and next_x < len(map) and next_y >=0 and next_y < len(map[next_x]) and map[next_x][next_y] != 1:
pos = (next_x, next_y)
temp_map[pos[0]][pos[1]] = 999
_print_map(temp_map)
def __test_route(self):
start = ()
for x,row in enumerate(map):
for y,val in enumerate(row):
if val == 5:
start = (x, y)
#print("%d, %d, (%d)" % (start[0], start[1], map[start[0]][start[1]]))
self.best_fitness_score = 0;
self.total_fitness_score = 0
for genome in self.genomes:
pos = start
for couple in range(0, len(genome.bits) >> 1):
# Check whether is wall
direction = self.__decode_direction(genome.bits[couple << 1], genome.bits[(couple << 1) + 1])
next_x = pos[0] + direction[0]
next_y = pos[1] + direction[1]
#print("direction(%d, %d), next_x(%d), next_y(%d)" % (direction[0], direction[1], next_x, next_y))
if next_x >= 0 and next_x < len(map) and next_y >=0 and next_y < len(map[next_x]) and map[next_x][next_y] != 1:
pos = (next_x, next_y)
genome.fitness = 1 / (abs(pos[0] - 2) + abs(pos[1] - 0) + 1)
if (genome.fitness == 1.0):
print("Fina a path!")
self.__show_path(genome)
exit(0)
self.total_fitness_score += genome.fitness
if genome.fitness > self.best_fitness_score:
self.best_fitness_score = genome.fitness
#print(genome.fitness)
if (map_shadow[pos[0]][pos[1]] == 0):
map_shadow[pos[0]][pos[1]] = 2
else:
map_shadow[pos[0]][pos[1]] += 1
def __roulette_wheel_selection(self):
slice = random.random() * self.total_fitness_score
#print(slice, self.total_fitness_score)
total = 0
for genome in self.genomes:
total += genome.fitness
if total > slice:
return genome
return self.genomes[0]
def __crossover(self, mum, dad):
baby1 = Genome()
baby2 = Genome()
baby1.clear()
baby2.clear()
if random.random() > self.crossover_rate or mum == dad:
baby1 = dad
baby2 = mum
return (baby1, baby2)
crossover_point = random.randint(0, len(mum.bits) - 1)
#print(crossover_point)
for i in range(0, crossover_point):
baby1.bits.append(mum.bits[i])
baby2.bits.append(dad.bits[i])
for i in range(crossover_point, len(mum.bits)):
baby1.bits.append(dad.bits[i])
baby2.bits.append(mum.bits[i])
return (baby1, baby2)
def __mutate(self, genome):
for i,val in enumerate(genome.bits):
if random.random() < self.mutation_rate:
genome.bits[i] = not genome.bits[i]
_print_map(map_shadow)
bob = GARobot()
for i in range(0, 1000):
map_shadow = copy.deepcopy(map)
bob.epoch()
_print_map(map_shadow)
#if map_shadow[2][0] > 0:
# break;
上面是 python 代码,用以演示前面提到的理论,代码仅作展示用,没有优化,本人对 python 也不熟,你当然可以选任何你喜欢的语言去实现,此代码只用于展示思路和可行性,不具有大的参考价值。
运行效果
运行程序,可以看到整个算法运行过程中,答案的收敛过程,以及发现可行路径的解,当然可以添加一些打印,观察不同算子对算法的影响,以及得到解的过程中,都是怎么进化的。