非理性繁荣!

遗传规划:一个新的失败

1月19日2009.申请下计算机科学遗传算法

之前我写过关于遗传算法,并简要介绍了遗传算法与遗传规划的区别。在这里,我们将更深入地研究基因编程。在我们开始愉快地浏览遗传程序示例之前,我承认存在偏见:我相信基因编程是解决复杂问题的一种神奇但虚构的方法1

基因编程的梦想

经过一个漫长的夜晚,清除互联网上的错误观点,你意识到你不能单枪匹马地保持它的神圣纯洁。你建立一个神经网络它接受博客条目作为输入并输出正确的操作解决这个问题,但它一直在返回在绝望中哭泣,没有多少训练能让神经网络正确地发出信号。激昂的演说模拟在Reddit当输入包含闪烁主题的博客条目时,比如Bitf * ck vs。计划,Vista糟透了Git拥有一定

听天由命地垂头丧气,你对自己说,如果我能写一个程序,可以为我写这个程序!等等——因为这真的很棒——如果我只是把一系列操作输入到一个遗传算法中,然后……

两周后,这家尚未破产的报纸宣布了你的成就:你,你编的程序,而你编的程序你编的程序已经把互联网清理干净了错误的,联合国已经命令那些讨厌的和不相干的博主剥夺公民权,并进入集中营。

哦,是的,世界上一切都好。

从一个稍微简单一点的问题开始

遗憾的是,我们需要从一个比拯救互联网更简单的问题开始。尽管如此,我们要解决的问题比我们在前一篇文章(创建一个数组N数字相等X当总结)。

在我们的新问题中,个人将由N从该列表中随机选择的元素(可以多次选择一个元素):

选项=(“+”,“- - -”,‘*’,' / ',' 0 ',' 1 ',' 2 ',“3”,“4”,“5”,“6”,“7”,“8”,“9”)

因此,我们可以用下面的代码创建一个个体。

随机进口用于产生基质的均匀分布的随机整数def部分():选项=(“+”,“- - -”,‘*’,' / ',' 0 ',' 1 ',' 2 ',“3”,“4”,“5”,“6”,“7”,“8”,“9”)返回选项[用于产生基质的均匀分布的随机整数(0,len(选项)-1)]def个人(长度=5):返回[部分()范围(长度)]

人口是这样产生的:

def人口(大小=1000,长度=5):返回[个人(长度=长度)x范围(大小)]

现在让我们来描述这个问题:我们想要一个个体——当它的元素被Python计算时eval函数,等于一个数X。并不是所有元素的组合都形成有效的Python语法(例如with)N = 5,然后5 5 * + 1是这样一个人),以及那些个体(即其元素在传递到eval)应该会得到一个很差的健康分数。对于这样做的人eval成功,那么他们的适应度得分就是他们与目标值的距离X

我们可以把这个定义形式化成这个适应度函数:

def健身(x,目标):试一试:瓦尔=eval(””加入(x))返回目标-腹肌(目标-瓦尔)除了:返回-100000

除了这些变化,代码与我们的基本相同以前实现遗传算法的代码。小的变化与我们新的适应度函数有关,其中高值代表强适应度,而在之前的适应度函数中,高值表示适应度较差。(你可以看到这个问题的初始代码)。

只需要最小程度的改变就可以证明遗传算法的普遍性;我们已经从用遗传算法解决一个极其简单的算术问题,到不用对算法做任何实质性的修改就能解决一个更复杂的遗传规划问题!也许我们应该给他们打电话普遍的问题解决者而不是仅仅遗传算法

让我们通过千百代人来验证我们的乐观主义。

>>>gp2进口*>>>人口,fitness_scores=发展()>>>fitness_scores[10:][-96200.27,-95499.98,-96000.02,-96200.006,-96300.002,-96599.83,-96599.904,-96000.06,-96500.001,-96800.001)>>>fitness_scores[-10:][-96300.45,-96500.04,-96200.51,-96000.008,-96300.04,-96000.01,-97099.97,-96200.091,-96400.01,-95099.96)

他妈的。他妈的。他妈的。什么那里发生的事吗?前十代的平均适合度是-96270.54,990到1000代人的平均适合度是-96210.11。这在统计上甚至没有意义。我叫你a普遍的问题解决者,然后你就这样对我?他妈的。让我休息几分钟。

过了短暂的过渡,拿着一个纸袋

好吧,所以我可能有点反应过度了,但是,您可能已经推断出了第一个重要信息:未调优的遗传算法可能执行得非常差(例如。直接失败)在一些问题上。特别是遗传算法,当依赖于一个没有信息的适应度函数时,表现得特别差,而我们目前的适应度函数是非常缺乏信息的。

首先,很少有个体使用有效的Python语法,所有无效个体的适应度值相同-10000年。这意味着我们目前的健身功能对大多数人的健身效果给出的反馈很差,就这样,在一个可怕的场面中失败了。没关系,因为我们可以提高我们目前的健身功能,对吧?

我们再看一下适应度函数。

def健身(x,目标):试一试:瓦尔=eval(””加入(x))返回目标-腹肌(目标-瓦尔)除了:返回-100000

如果语法不好,然后我们就没有得到关于语法与有效性有多接近的反馈。如果它是有效的,然后我们开始得到有用的反馈,但是,由于绝大多数个人都是无效的,所以我们有点困惑(下面我们将研究有效和无效之间的比例)。

在无法评估代码的情况下,我们有两种方法可以生成一些更具体的反馈:

  1. 计算基于的适应度函数代码在语法上有多接近正确,
  2. 计算基于的适应度函数参数与期望的正确解有多接近

在这个极其简单的例子中,第一个选项相当简单(格式必须在number和operation之间交替,必须以数字开头和结尾),但在更复杂的情况下,它会爆炸成一个相当可怕的问题。

第二个选择完全是假的,因为这意味着你通过教授一个你已经知道的答案来“解决”一个问题;在这种情况下,你既没有优化也没有解决任何问题,你在延续一场闹剧。

就像用手提钻掸灰一样

遗传算法是优化技术,但是看看健身我们可以看到我们的问题:首先,它试图解决一个搜索问题(寻找可行的解决方案,不管质量),然后尝试优化(提高发现的解决方案的质量)。很少有——如果有的话——算法能够在这两种情况下都表现良好搜索优化这个问题的各个方面。

事实上,在这个例子中,我们甚至没有逃避问题的搜索方面。我们可以通过修改grade_population函数的作用是计算在语法上有效的个体数量(而不是测量总体的平均适应度)。

我们将它从这个改变:

defgrade_population(流行,目标):总计=总和([健身(x,目标)x流行])avg=总计/(len(流行)*1.0)返回avg

这个(-100000年为语法无效个体的适应度):

defgrade_population(流行,目标):健身=[健身(x,目标)x流行]有效的=[xx健身如果x>-100000]返回len(有效的)

现在,我们可以重新运行上面的实验,并以更具体的方式了解我们是如何陷入这个问题的搜索部分的。

>>>gp2进口*>>>历史,健身=发展(一代又一代=1000)>>>健身[:10](37岁,37岁的34岁,37岁的33岁的27,21日,21日,33岁的36)>>>总和([:10])/10。0前十名的平均成绩31.6>>>[-10:][38,46岁,44岁的49岁,37岁的43岁的35岁,39岁,44岁的35)>>>总和([-10:])/10。0过去10年的平均成绩41.0

经过一千代人,有效个体的数量确实增加了,而是缓慢。出于好奇,让我们看看如果我们试着从一个几千块钱几千代。

>>>历史,健身=发展(一代又一代=5000)>>>健身[:10](37岁,42岁的37岁的46岁,41岁的40岁,34岁,33岁的35岁,34)>>>总和(健身[:10])/10。0前十名的平均成绩37.9>>>健身[-10:](30日43岁的42岁的53岁,44岁的45岁的42岁的35岁,37岁的36)>>>总和(健身[-10:])/10。0过去10年的平均成绩40.7

是的,从1000代增加到5000代没有任何明显的影响:我们肯定会陷入这个问题的搜索部分。不是简单地增加代数,方法中使用的参数发展函数。目前,我们只保留了前30%的参赛作品(因此,有相当数量的孩子超越了成功的父母),让我们试着把这一比例提高到保留前90%的个人。

>>>历史,健身=发展(一代又一代=1000,保留=0.9)>>>健身[:10](35岁,38岁的34岁,34岁,33岁的35岁,32岁的37岁的32岁的38)>>>总和(健身[:10])/10。034.8>>>健身[-10:][42岁34岁,36岁,26日,37岁的34岁,31日,44岁的36岁,41)>>>总和(健身[-10:])/10。036.1

我们当然没有看到任何定义的改进。如果将留存下来的个人比例降低到前10%,并将世代数量增加到3000人,情况会如何?

>>>历史,健身=发展(保留=0.1,一代又一代=3000)>>>健身[:10](35岁,28日,30.37岁的38岁的44岁的41岁的49岁,37岁的39)>>>总和(健身[:10])/10。037.8>>>健身[-10:](36岁,38岁的47岁的43岁的46岁,50岁,45岁的47岁的51岁,43)>>>总和(健身[-10:])/10。044.6

好吧,那也不太管用。也许我们走错了路。现在我们正在创造个人N(元素的数量)是5。让我们尝试减少N为3。(我们将把目标人数保持在15人。)

>>>历史,健身=发展(individ_size=3.)>>>健身[:10]〔162〕162年,141年,146年,140年,162年,148年,145年,152年,170]>>>(总和(健身[:10])*1.0)/10152.8>>>健身[-10:](149年,156年,157年,158年,165年,169年,174年,157年,159年,164]>>>(总和(健身[-10:])*1.0)/10160.8

用更少的组合(3^3而不是5^5)我们看到更多的语法健全的个体,但是这种百分比在几代人之间的增长实际上更糟。让我们做最后的努力。我们换个目标X6而不是15,只保留前10%的人,又使他们世世代代的人数加增到三千。

>>>历史,健身=发展(保留=0.1,individ_size=3.,目标=6,一代又一代=3000)>>>健身[:10](156年,147年,137年,135年,129年,153年,152年,141年,146年,149]>>>总和(健身[:10])/10。0144.5>>>健身[-10:](160年,166年,157年,152年,163年,170年,177年,167年,153年,161]>>>总和(健身[-10:])/10。0162.6

对于一个主要缺点是快速和短视优化的算法,这一进展的缺乏有力地表明,我们已经开始怀疑:遗传算法根本无法克服这个问题的搜索方面。

这个例子远远不够充分,但是,我将进一步说明,只有当您提供了解决方案的详细大纲时,遗传规划才能解决问题。遗传算法可以从提供的组件中优化解决方案,但遗传规划仅发现和优化解决方案是不够的。这是一个耻辱,因为这个概念有一定程度的优雅是很难被忽视的。

不难看出,这个想法是如何反复地诱使科学家们在这个问题上绞尽脑汁,直到他们扭曲了自己的大脑皮层和算法,从而相信它是可行的。如果遗传规划的结果能证明其概念之美就好了。

(你可以看到修改后的代码)。

有关二零零九年一月二十日的回应

许多评论者强烈认为,这种介绍不公平地歪曲了基因编程。基于他们所表达的深深的愤怒,这些人似乎都是在基因编程领域进行研究的人。总是那些离主题太近的,而且经常学者,对批评感到无理不安的人。当然,如果有人说我花了5年时间来追逐白日梦,我也会同样感到不安。

除了愤怒,最根本的问题是,我用一种随意组合元素的简单方法来生成个体,而不是创建一致生成语法正确的抽象语法树所需的相当复杂的基础设施。

在上面的文章中,我提到了两种对非信息性适应度函数的反应:

  1. 教健身函数如何编码。
  2. 教适应度函数的代码解决问题。

虽然单独列出,在尖叫声中,我不认为这两种解决方案是完全不同的。即使我们求助于沙发# 2在表演的前提下# 1,我们仍然在教授健身功能来创建我们想要的解决方案。如果我们接受遗传算法不能解决搜索领域的问题,不然怎么可能呢?即使仅限于有效代码,问题空间非常大;我们要么需要一个真正的搜索算法来发现答案,或者我们提供搜索方向的提示。

最后两个点:

  1. 这些文章的目的是向初学者介绍计算机科学的概念,在坚持使用Python语言的同时,最重要的是提供不依赖外部的完整的工作示例(例如,非标准)库。在解决方案中使用抽象语法树是不可能的,他们都不认为基因编程是不可达到的复杂,或者只局限于天体研究领域。

  2. 其中提到了一些未指明的问题领域,其中遗传规划特别优于遗传算法,也许还有其他算法。虽然上级的定义没有明确,和问题域一样,我很高兴能有这样一个比赛:

    1. 一个问题被选择,被认为是坚定地在问题领域内的遗传规划是最有竞争力的。
    2. 决定使用一种单一的公共编程语言(可能是Scheme或公共Lisp?)
    3. 编写代码时无需使用特定于算法的支持库(即生成随机数的库是可以接受的,但是一个支持遗传编程/遗传算法的库不是)。这似乎很重要,因为它揭示了算法的真正复杂性,而不是隐藏库背后的复杂性。
    4. 任何参赛者提交答案,以及它所使用的算法类别。
    5. 解决方案和测试用例分布在压缩文件中,因此我们都可以验证结果。
    6. 任何人都可以自由创建自己的评分标准,并以自己的偏见写出小组的结果。

最初的完整代码

随机进口用于产生基质的均匀分布的随机整数,随机def部分():选项=(“+”,“- - -”,‘*’,' / ')选项+=元组(% d%xx范围(0,10))返回选项[用于产生基质的均匀分布的随机整数(0,len(选项)-1)]def个人(长度=5):返回[部分()范围(长度)]def人口(大小=1000,长度=5):返回[个人(长度=长度)x范围(大小)]def健身(x,目标):试一试:瓦尔=eval(””加入(x))返回目标-腹肌(目标-瓦尔)除了:返回-100000defgrade_population(流行,目标):总计=总和([健身(x,目标)x流行])avg=总计/(len(流行)*1.0)返回avgdef一代(流行,目标,保留=0.3,random_select=0.05,突变=0.01):分级=[(健身(x,目标),x)x流行]分级=[x[1]x排序(分级,反向=真正的)]保留长度=int(len(分级)*保留)父母=分级[:保留长度]#将其他个人随机添加到促进基因多样性个人分级[保留长度):如果随机()>random_select:父母附加(个人)变异一些个体个人父母:如果随机()>突变:pos_to_mutate=用于产生基质的均匀分布的随机整数(0,len(个人)-1)这种突变并不理想,因为它#限制可能值的范围,但是函数不知道最小/最大值#用来创建个体的值,个人[pos_to_mutate]=部分()跨界父母创造孩子parents_length=len(父母)所需长度=len(流行)-parents_length孩子们=[]len(孩子们)<所需长度:男性=用于产生基质的均匀分布的随机整数(0,parents_length-1)=用于产生基质的均匀分布的随机整数(0,parents_length-1)如果男性!=:男性=父母[男性]=父母[]一半=len(男性)/2孩子=男性[:一半]+[一半:]孩子们附加(孩子)父母扩展(孩子们)返回父母def发展(pop_size=1000,目标=15,individ_size=5,保留=0.3,一代又一代=1000,random_select=0.05,突变=0.01):p=人口(大小=pop_size,长度=individ_size)历史=[p,]fit_history=[grade_population(p,目标=目标)]范围(一代又一代):p=一代(p,目标,保留,random_select,突变)历史附加(p)fit_history附加(grade_population(p,目标=目标))返回历史,fit_history

精致的完整代码

随机进口用于产生基质的均匀分布的随机整数,随机def部分():选项=(“+”,“- - -”,‘*’,' / ')选项+=元组(% d%xx范围(0,10))返回选项[用于产生基质的均匀分布的随机整数(0,len(选项)-1)]def个人(长度=5):返回[部分()范围(长度)]def人口(大小=1000,长度=5):返回[个人(长度=长度)x范围(大小)]def健身(x,目标):试一试:瓦尔=eval(””加入(x))返回目标-腹肌(目标-瓦尔)除了:返回-100000defgrade_population(流行,目标):pop_fitness=[健身(x,目标)x流行]valid_syntax=[xxpop_fitness如果x>-100000]valid_count=len(valid_syntax)返回valid_countdef一代(流行,目标,保留=0.3,random_select=0.05,突变=0.01):分级=[(健身(x,目标),x)x流行]分级=[x[1]x排序(分级,反向=真正的)]保留长度=int(len(分级)*保留)父母=分级[:保留长度]#将其他个人随机添加到促进基因多样性个人分级[保留长度):如果随机()>random_select:父母附加(个人)变异一些个体个人父母:如果随机()>突变:pos_to_mutate=用于产生基质的均匀分布的随机整数(0,len(个人)-1)这种突变并不理想,因为它#限制可能值的范围,但是函数不知道最小/最大值#用来创建个体的值,个人[pos_to_mutate]=部分()跨界父母创造孩子parents_length=len(父母)所需长度=len(流行)-parents_length孩子们=[]len(孩子们)<所需长度:男性=用于产生基质的均匀分布的随机整数(0,parents_length-1)=用于产生基质的均匀分布的随机整数(0,parents_length-1)如果男性!=:男性=父母[男性]=父母[]一半=len(男性)/2孩子=男性[:一半]+[一半:]孩子们附加(孩子)父母扩展(孩子们)返回父母def发展(pop_size=1000,目标=15,individ_size=5,保留=0.3,一代又一代=1000,random_select=0.05,突变=0.01):p=人口(大小=pop_size,长度=individ_size)历史=[p,]fit_history=[grade_population(p,目标=目标)]范围(一代又一代):p=一代(p,目标,保留,random_select,突变)历史附加(p)fit_history附加(grade_population(p,目标=目标))返回历史,fit_history

  1. 如果这些计算机科学的条目被收集到一本书里,标题将包含单词有偏见的以粗体显示标记的感觉