遗传算法Python实战 002.翻硬币

遗传算法Python实战 002.翻硬币

还是从游戏谈起

现在有这样一个游戏,准备10枚(当然,也可以更多)硬币,正反两面分别被涂上了黄色和绿色,然后把这些硬币都扔在桌子上。

现在把你的眼睛蒙上,然后去翻硬币,赢的条件是把所有硬币都翻到绿色一面朝上。

当你翻正确的时候,旁边人会告诉你,你翻对了;而你翻错了的时候,他会告诉你错了,并且把这个硬币翻回去……当然,他还需要把这个硬币移到另外的地方,防止你能够马上改正错误。

然后两个人轮流玩,以此比较,谁的次数最少。

在这种规则下,区区十枚硬币,极有可能要翻几十次甚至几百次(翻对了的有可能又会被翻回去)……实在是一场坚苦卓绝的比赛。

这也是本节所要给大家介绍的算法和实现。
导入需要的包:

import numpy,datetime,random
import matplotlib.pyplot as plt

还是一样,先定义遗传算法的各种基础组件,先是定义一个健壮性验证函数:

def get_fitness(genes):
    return genes.count(1)

定义一个类,用来存储当前进化到的这一代物种的染色体和健壮性:

class Chromosome:
    def __init__(self, genes, fitness):
        self.Genes = genes
        self.Fitness = fitness

继续定义变异、进化的方法:从父本里面随机选择一个基因进行变化,生成下一代种群

def mutate(parent):
    childGenes = parent.Genes
    index = random.randrange(0, len(parent.Genes))
    newGene, alternate = random.sample([1,0], 2)
    childGenes[index] = alternate if newGene == childGenes[index] else newGene
    fitness = get_fitness(childGenes)
    return Chromosome(childGenes, fitness)

这是一个显示的方法,用来显示前15个元素和最后15个,不100个元素会显示很长:

def display(candidate, startTime):
    timeDiff = datetime.datetime.now() - startTime
    print("{}...{}\t{:3.2f}\t{}".format(
        ''.join(map(str, candidate.Genes[:15])),
        ''.join(map(str, candidate.Genes[-15:])),
        candidate.Fitness,
        timeDiff))

然后就是执行方法了:
首先初始化一个祖先……用numpy的randint来生成一个100个元素的0和1的整数数组。如果这个祖先函数直接就全部是1,就不用玩了……之后进入进化,一直进化到全部都是1为止,其中num是我添加在里面的计数器。

def get_best():
    random.seed()
    startTime = datetime.datetime.now()
    c1 = numpy.random.randint(0,2,(100)).tolist()
    bestParent = Chromosome(c1,get_fitness(c1))
    if bestParent.Fitness >= 100:
        return bestParent
    num = 0
    while True:
        num +=1
        child = mutate(bestParent)
        if bestParent.Fitness >= child.Fitness:
            continue
        display(bestParent,startTime)
        if child.Fitness >= 100:
            display(child,startTime)
            return (child,num)
        bestParent = child

执行函数:

b = get_best()
print(b[0].Fitness,",",b[1])

执行结果如下:

111110111000110...110111100101100	47.00	0:00:00
111110111000110...110111100101100	55.00	0:00:00
111110111000110...110111100101100	56.00	0:00:00
111110111000110...110111100101100	57.00	0:00:00.000997
111110111000110...110111100101100	58.00	0:00:00.000997
111110111000110...110111110101100	59.00	0:00:00.000997
……
111111111011111...111111111111111	96.00	0:00:00.012965
111111111111111...111111111111111	97.00	0:00:00.013963
111111111111111...111111111111111	98.00	0:00:00.014960
111111111111111...111111111111111	99.00	0:00:00.016993
111111111111111...111111111111111	100.00	0:00:00.016993
100 , 414

一次执行,用了0.16秒,总共翻了414次硬币,才全部翻正确(因为随机的关系,每次执行,结果会不一样)

现在我们来迭代1000次,看看统计信息:

n = []
for i in range(1000):
    n.append(get_best()[1])
plt.hist(n)

结果如下:

频率

可以看见,一般说,最少需要200次,而最多需要1000多次,才能全部翻对,平均也得451次才能完成,可见真是一个艰难的游戏。

但是,我们要是修改一下游戏规则:

比如,再你翻对一枚硬币的时候,就把这枚硬币从桌子上拿下来。这时候桌子上剩下的就是你翻错的,或者还没有翻的硬币了,这样看起来是不是就容易很多了啊。因为你不用重复去翻你已经翻对过的硬币了。

具体的转换到算法上,就是要记录你翻对过的硬币在哪里——翻对过,从桌上已经拿掉了,就等于你不用再去翻一次了,下面来看看这种规则下的游戏:

修改基因进化变异函数,传入一个记录数组,如果你随机的索引已经是翻过且正确的(拿掉的硬币,则重新选择一个索引)

def mutate(parent,idxlist):
    childGenes = parent.Genes[:]
    while True:
        index = random.randrange(0, len(parent.Genes),1)
        if index in idxlist:
            pass
        else:
            break
    newGene, alternate = random.sample([0,1], 2)
    childGenes[index] = alternate if newGene == childGenes[index] else newGene
    fitness = get_fitness(childGenes)
    return (Chromosome(childGenes, fitness),index)

开始运行之前,设定一个list,用来存储拿掉的那些硬币

def get_best():
    oneIndex = []
    random.seed()
    startTime = datetime.datetime.now()
    c1 = numpy.random.randint(0,2,(100)).tolist()
    bestParent = Chromosome(c1,get_fitness(c1))
    if bestParent.Fitness >= 100:
        return bestParent
    num = 0
    while True:
        num +=1
        child,idx = mutate(bestParent,oneIndex)
        if bestParent.Fitness >= child.Fitness:
            continue
        oneIndex.append(idx)
        #display(bestParent,startTime)
        if child.Fitness >= 100:
            display(child,startTime)
            return (child,num)
        bestParent = child
    display(bestParent,startTime)

执行1000次之后,结果如下:
频率

可以看见,效果显著——平均仅需要274次,就能够完成全部硬币的翻面。

这种算法,在游戏中经常用到,你不用管开局是什么样子的,只需要知道你这样做是更好还是更坏,就可以最终达到最佳效果。

猜你喜欢

转载自www.cnblogs.com/godxia/p/12932375.html
今日推荐