写在前面的话
前面五个问题,在遗传算法里面,都是非常简单入门问题,如果能够读懂前面这些算法,而且能够复现的话,说明你对遗传算法已经完成了入门过程,到这里就开始进入遗传算法的中级阶段了。
好了,先看看本节需要解决的问题:
本节中,我们将通过排列组合扑克牌,来完成这样的一个游戏:
如果给你从A(就算成是1吧)到10,这十张扑克牌,让你把这10张牌分成两组,第一组的数字之和,加起来等于36,第二组的数字之乘积,乘起来等于360。注意,十张牌,每张牌只能用一次,不得重复。
比如组合成下面这一组组合:(这几个数字是唯一的解法,但是如果是扑克牌的话,可以有不同的排列方式)
第一组:
2 + 7 + 8 + 9 + 10 = 36
第二组:
1 * 3 * 4 * 5 * 6 = 360
算法实现部分
首先还是导入包和构建基因库:
# 注意,这里新导入的两个包functools和operator是Python的两个默认库
# 是Python实现函数式编程的核心包——有兴趣的同学去了解一下函数式编程
# 虾神评价:很神奇……
import random,datetime,functools,operator
geneset = [i +1 for i in range(10)]
接下去构建健壮性类,这个类的作用是标识这次进化的健壮性的
实际上到今天为主,大家已经看出来了,作者是面向对象时代过来人,很多功能都喜欢直接用对象来进行描述,这样有其的好处,就是封装性非常好,所有的功能属性一个对象就全部包含了,但是对于写惯了标准Python编程脚本的人,有些不适应。
我们可以简单看看这个类的设计:
首先是一个构造函数,设计了三个成员变量:
Group1Sum用关于记录第一组分组的和
Group2Product用于记录第二组的乘积
TotalDifference用于记录两组的结果与最终期望结果的差距。
然后重写了两个比较函数,第一个是小于,仅比较真实结果与期望结果的的效果。第二个就是等于,用于比较真实结果是否等于期望结果。
最后是一个字符串转换函数,输出两组分组的结果(第一组的和,第二组的乘积)
class Fitness:
def __init__(self, group1Sum, group2Product):
self.Group1Sum = group1Sum
self.Group2Product = group2Product
sumDifference = abs(36 - group1Sum)
productDifference = abs(360 - group2Product)
self.TotalDifference = sumDifference + productDifference
def __gt__(self, other):
return self.TotalDifference < other.TotalDifference
def __eq__(self,other):
return self.TotalDifference == other.TotalDifference
def __str__(self):
return "sum: {} prod: {}".format(self.Group1Sum,
self.Group2Product)
然后是核心函数:健壮性检验:这里面的乘积方法,用了一个函数式编程的一个经典函数reduce(会hadoop或者Spark的同学,肯定很熟悉了),不过reduce的效率是不是比传统for循环自己写乘积方法快,还有待商榷(起码我这里,reduce方法比自己写for要慢)
def get_fitness(genes):
group1Sum = sum(genes[0:5])
group2Product = functools.reduce(operator.mul, genes[5:10])
return Fitness(group1Sum, group2Product)
显示函数,不解释:
def display(candidate, startTime):
timeDiff = datetime.datetime.now() - startTime
print("{} - {}\t{}\t{}".format(
', '.join(map(str, candidate.Genes[0:5])),
', '.join(map(str, candidate.Genes[5:10])),
candidate.Fitness,
timeDiff))
种群类,不解释:
class Chromosome:
def __init__(self, genes, fitness):
self.Genes = genes
self.Fitness = fitness
进化函数
在看本次的进化函数之前,先回想一下前面五节的进化函数,可以发现基本都是一样的,都是直接随机从基因库中提取一个基因,替换掉种群中的一个基因,这样变成了一个新的物种……这也是遗传算法里面的基本原理。
这里的进化函数,发生了一点点变化,并不仅仅是去变化一个(或者一组)基因,而是多去替换几组基因——比如我之类设置的4组,就是所谓的替换四组基因。
——这就是进化中另外一个核心的理论:我们可以加大每次基因的突变,这样有可能会加速进化的历程,不过是不是基因突变越大,进化越快,效果就越好呢?这个问题还是值得讨论的(大部分时候都不是)
def mutate(parent, geneset):
genes = parent.Genes[:]
for i in range(4):
indexA, indexB = random.sample(range(len(genes)), 2)
genes[indexA], genes[indexB] = genes[indexB], genes[indexA]
fitness = get_fitness(genes)
return Chromosome(genes, fitness)
然后是执行函数了:
流程与以往是一样的:初始化初代种群,如果初代种群就是最佳种群了,自然就结束进化了,否则进入进化流程——每次都用上一代最优的种群最为父本基因扔到进化函数里面去进化,之后对比进化出来的种群的健壮性是否更优秀,如果是,则替换父本,否则不变——直到进化到我们理想的目标为止:
def get_best():
startTime = datetime.datetime.now()
optimalFitness = Fitness(36, 360)
best = Chromosome(geneset, get_fitness(geneset))
if best.Fitness == optimalFitness:
return best
num = 0
while True:
num +=1
child = mutate(best,geneset)
if best.Fitness > child.Fitness:
continue
display(child,startTime)
if not child.Fitness > best.Fitness:
best = child
if child.Fitness == optimalFitness:
break
best = child
return (best,num)
这里用了一个num来计数,看看每次进化需要多少代,执行100次,生成的分布如下:
如果前面那些不变的进化算法被称为“经典基因遗传算法”,这种自定义突变的算法,被赋予了一个新的名称:“模因遗传算法”,用英文来表达就是“Memetic algorithms”,而Memetic这个词,是专门针对genetic(基因)这个词被创造出来的,目前在学术界还没有权威的翻译,一般而言,以英国演化理论学者理查德·道金斯的著作《自私的基因》为准,被翻译为模因的,比较多。
模因被认为是一种可以快速自我复制的文化基因,它存在于我们的大脑中,并且能通过模仿实现自我复制。它可以是一首歌曲、一本书的内容或者某些人的观点。这些模因通过人类的大脑进行复制传播,就好比基因通过身体复制传播一样。
下面是对于模因一段非常经典的解释:
当某一个人接触愤怒太久,他就会学会恨。他就会变成一个携带者,将嫉妒,贪婪,绝望… 将所有这些文化基因都传递下去. ——合金装备•崛起
那么这种进化算法,为什么会被叫做模因算法呢?我们后面会慢慢揭晓。