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

<font size=3>

遗传算法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次,就能够完成全部硬币的翻面。

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

</font>

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,335评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,895评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,766评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,918评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,042评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,169评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,219评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,976评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,393评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,711评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,876评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,562评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,193评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,903评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,699评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,764评论 2 351