生物进化思想与程序设计思想的比较

阮奇桢
我的职业是软件工程师,编程序的。研究生专业是生物医学。我对计算机和生物都挺感兴趣的,所以平时这两方面的书都喜欢看。这两门学科虽然相差较远,但有些思想还是可以相互借鉴的。这篇文章就是介绍一下进化论的各种理论对我的程序设计思想产生的影响,以及在学习了编程思想后对进化论的反思。
一、基因突变、物竞天择思想在编程算法中的应用
有一门学科叫遗传算法,里面详细阐明了把各种遗传、进化思想引入到计算机科学的理论,比我研究的透彻多了。但是,在这里我还是叙述一下我在接触这门学科之前的一些朴素的想法。
最初遇到这个问题是这样的:我一个朋友刚学了黑白棋(Othello),找我来下。我不会玩,虽然他的水平也很臭,可我还是下不过他。后来我就想,我下棋水平不行,编程水平还可以,能不能编个程序赢了他呢?问题是我又不太懂黑白棋,如何使编出的程序如何比我的水平还好呢?
首先,黑白棋比较简单,算法容易实现。计算机算得比我快,按照我同样的思路下棋,也应该比我下得好一点。
其次,我就在考虑如何能让计算机通过学习,自己不断的提高技术。我当时就是受了基因突变、物竞天择这个思想的启发:让不同的程序之间互相下棋比试,输了的被淘汰,赢了的就有机会繁殖后代,然后再在后代之间比较。理论上,程序的棋力应当一代比一代强。
具体实现方法就是先提取下棋时可能对输赢产生影响的参数,比如落子的位置,每个子周围的空格数,己方可落子的位置数、低方可落子的位置数等等,类似的能列出几十条。我不确定这些因素是否都会对结果产生影响,因为自己的下棋水平就很差。但是这不要紧,这些参数统统列出来好了,多多益善。这些参数就相当于生物体的基因。每个下棋程序的框架都是一样的,只是这些参数的值不同。
组织几十个下棋程序进行循环赛。名次排在后一半的都淘汰。成绩最好的那些程序有机会把全部基因保留下来,还可以和其他程序交换一定的基因,还可能有一定的随机突变。最后又生成了几十个程序,这是第二代。换句话说,成绩好的程序,后代多一些;成绩一般的,后代少一些;成绩差的美后代。然后再它们的后代之间再进行比赛,选拔,又生成下一代。这样循环反复。程序的水平就越来越高了。
在实践中,我就是用这个程序经过一定时间的子学习,赢了我的朋友,很有成就感
不过用这重思想样写出来的程序是有一些明显缺陷的,主要就是效率太低了。程序的下棋水平进步的非常慢。这点想一想生物界也可以理解。生物进化了几十亿年,才到现在的水平。就算我这个程序比生物进化的效率高,取得个明显进步也得个上百年吧。
再有,我程序每一代的样本也太少,只有几十个。生物界里,每种生物一代一般也得有几百万,几千万的个体。而我的程序等于全都是近亲结婚,弄不好还会水平一代比一代差。这两个问题的解决受限于计算机的运算和存储能力。
遗传算法上介绍的算法比我的解决方案成熟多了,但是我刚刚提到的的缺陷也同样存在于遗传算法中。有兴趣的可以看看这方面的书。
二、对进化论的反思
我的采用基因突变、物竞天择思想编出来的下黑白棋的程序,如果就让他这样运行下去,过个几万年有没有可能进化成下五子棋或是其他什么棋的程序呢?
达尔文的 进化论,在很多国家,只是算作假说。而在我国,由于政治的原因,是当它做真理来传授的。
达尔文在他的《物种起源》里提出了两大假说,其一是“同源说”,认为地球上的所有物种都起源于同一个细胞。同源说基本上被大家接受了,在学术界上反对的声音相对较小。而另一假说,是用来解释现在的物种多样性的,就是基因突变、物竞天择的进化论。但是它从一开始就受到了强烈的质疑。
达尔文的时代还没人能证明基因的存在,50年前DNA的发现给了进化论以强大的支持。但是把物种的多样性归结为基因突变、物竞天择我觉得还是有很多不妥之处。
进化论有三大证据: 比较解剖学、古生物学(化石)和胚胎发育的重演律。
比较解剖学发现人和猴子的骨胳相类似,所以证明人和猴子是共同祖先演化来的。有人认为这是循环论证,因为如果有人问为什么人和猴子的骨胳为什么相似,原因是人和猴子有共同祖先。我到不这么认为,原因是人和猴子的骨胳相似是现象,而不是结论。现象不需要证明,不论进化论对这一现象的解释合理不合理,都不影响这个现象作为事实存在。这一点可以用来证明同源说,但解释不了生物演变的过程。同源说在发现基因后得到了进一步证明,不用生物间,即便是人类和单细胞细菌间也存在高度同源的基因。但这一点对我们编程序影响不大。
按照基因突变的理论,生物进化的进程应该是一个均匀连续的过程。然而,在出土的化石中,基本都是物种分明的,很难找到介于不同物种的中间过渡类型。能作为过渡类型的化石只有始祖鸟等极少的几个。相对于其他的成千上万的物种的化石显得太微乎其微了。
大多数化石展示的都是突然出现的某种有机体,这些有机体一旦出现,基本上就不再变了,直至消亡,哪怕这个过程要历经几亿年。例如:在30亿年前到5亿年前之间的化石全部都是单细胞生物的。然而,在5亿年前,似乎一夜之间,各式各样的多细胞生物就出现在了海洋里。所有可以找到的化石都属于这些物种,根本没有介于他们之间的过渡类型。动物界有十个门,脊索动物门是哪来的?是从原生动物门进化来的,还是从棘皮动物门进化来的?像类似这些问题,在化石里一点线索都找不到。因此,但从化石来看,倒是更能证明基因突变的理论的不完善。
再想一下本段开头的问题,我黑白棋的程序,如果就让他这样不停的基因变化下去,他会进化成下五子棋或是其他什么棋的程序吗?我认为不会。
至于胚胎发育的重演律则更有问题。既然基因已经突变,就不应该再保留原有的信息,失去了原有信息就不应该有重演。除非生物进化不是基因突变引起的。
所以这三大证据,均不能证明基因突变的理论。
三、程序是如何“进化”的
我的黑白棋自己是不能进化成五子棋的,但在我的帮助下却是可以的。
我程序使用C++编写的。熟悉C++的人即使是个小程序也会把面向对象、泛型编程这样的思想用上,我也是这样。
比如我又打算编一个下五子棋的程序,该怎么写呢?两种棋有很多相似的地方,比如棋盘的类型,棋子等等。全部从头再创建一个五子棋程序显然是不合适的。
最土的利用源代码的方法是把原来的黑白棋程序拷贝一份,然后在其基础上改写一下。这样做的缺点是,我的两份程序中有重复的代码,这样不利维护。比如我改进了黑白棋中一部分代码的性能,我还得再想办法把他移植到五子棋上来。相同部分的代码应该只有一份,就比较利于维护了。
有了面向对象的编程方法,这个问题就好解决了。我可以把黑白棋中创建的类作为基类,派生出五子棋的类。五子棋中不同于黑白棋的属性和方法,可以用新的属性和方法把它覆盖(override)掉。这样做,被覆盖的属性和方法他们的代码还在,但在执行时已不起作用了。但有些父类的代码,还会继续起作用。比如,子类的构造函数通常会先调用父类的构造函数,然后再执行自己的代码。这样一来,父类的某些特征就会在子类实例中体现出来。尤其是在构造子类实例的过程中。
这像不像胚胎的重演过程?
四、生物的进化的新假说
参考软件自动进化的规律,我提出一个假说:基因突变只会导致同一物种的小变化范围内的改良。而能够导致物种变化的主要是基因上类似“继承”的操作。
按照现在的研究成果,高等生物体的染色体都存在有大量的“无用”基因片断。这些基因片断在生物进化的历史上或许并不是无用的,他们曾经是对这种生物发育起到作用的。但是,随着新的基因片断被加入到该生物体内,某些新基因片断可能与旧有基因片断存在着类似的功能,但优先级更高,抑制了原来基因片断的活性。之后,生物体所体现出来的行为就都是由新的基因片断决定的,原有的片段就成了无用片断了。
至于新片断是哪来的,我想首先可能来自于有性繁殖。有性繁殖除了把父母的两条染色体合成一对,有时也许还会带入全新的基因片断。其次,低等生物,比如细菌,个体间会有基因交换行为。新片断也可能来自于病毒的传播,或者通过饮食等途径带进来了。
五、未来软件的发展趋势
生物进化了30亿年,才达到现在的程度。计算机软件出现了不过50年。这两者目前似乎没有太多的可比性,但是我想在人类智慧的帮助下,软件的进化速度会大大高于生物的进化速度。出现拥有智慧的,可以自我完善软件也许只需几百年,是生物进化速度的 10,000,000 倍。我们不妨大胆预测一下未来软件的发展趋势。软件未来的发展也一定会与生物进化有某些类似的地方。
最近我开始着手更新一部分程序代码。这部分代码存在于公司一个非常大型的应用软件中,我需要修改的这段代码已经有很多年的历史了。其风格,按现在的观点来看,是非常差的。比如说变量名都是用单个字符表示的、存在大量硬编码等等。如果是年轻几岁的我,看到这段代码会认为根本没有维护它的必要,就应该把他们彻底抛弃,重写一遍。事实上,公司里确实有人曾经试图这样做过:刚看到很原始的代码,对他嗤之以鼻,又对自己的能力充满自信,于是开始对部分代码重写。但最终,这些尝试都以失败告终。
在一个有些历史大型软件中,遗留着着几十年前的代码是很正常的。这些代码不是不可以被改写,但是如果综合考虑代价和收益,可能改写他们是非常不合算的。一个大型的软件,经过几十年,被成百上千的人改动过,它当中相当一部分代码都会被软件中其它的地方引用到。到最后,没人能够说得清楚这段代码全部用途了。如果只做了少部分验证,就贸然把这段代码重写,新写的代码在功能上很难保持与原代码完全一致。将来会慢慢发现,新代码引入的bug和潜在危险远比它带来的好处大。
现在生物学上,研究基因片段功能的方法主要是,先把一段基因敲掉,然后看看他对生物体带来了什么影响。然而大多数基因被敲掉以后,却看不出生物体受到了明显的影响,这就是那些“无用”基因片断。这一是因为上面提到的基因有备份,备用基因会及时发挥作用,弥补缺失的基因的功能。还有就是很多基因的功能是和其他很多基因共同作用才能显现出来的,或者是在某些特殊情况下才会被显现出来的。这些基因的功能不是那么容易被观察到的,他们缺失了,或者被添加到一个本来不具备这种基因的生物体上,它们的潜在的影响或许要过很多年才能被发现。现在大家对转基因作物的戒心,这也是原因之一。(对转基因食物更大的担心可能是在于,被植入的基因片段不够稳定,更容易整合到人体基因中去,导致人类的基因被转化。)
那么针对这样一段老的代码,需要修改其bug,或是增强某些功能,最好的办法不是重写它,而是把它原封不动的留着,在它的上层再写一层外壳。在这层包装层内对源代码的功能加以修正。以后再用到这段代码的功能时,调用包装层提供的函数。
比如说源代码是个除法运算,但是没考虑除零的特殊情况,我们不是去改动源代码来判断参数是否为零,而是为它包一层新的函数。在新函数中先检查除数是否为零,如果是,进行错误处理;否则调用原代码进行除法运算。新的程序再用到除法功能就试用新写的函数。这样做的好处是:原本那个除法可能被用在了系统中的很多地方,或许某个地方就是要利用它的除零错误。你不能百分之百确定没有这种情况的出现,就贸然改变这个函数的行为。结果在系统的其它部位又引起了新的bug。这个新的bug更危险,因为你还没有意识到它的存在。
如果要改进的是个类,那么就在原来类的基础上派生出一个新的类。在新类里覆盖或添加一些功能。原有的类也是保持不变。
这种做法是有明显缺点的,就是空间效率太差。用这种方法维护的程序,体积会飞速膨胀。但是随着科技的发展,计算机存储设备开销,相对于软件维护来说会越来越便宜。迟早有一天,软件的大小将不再是一个需要考虑的问题。
程序越大,bug越多,相信永远都不会出现所谓的“完美程序”。随着软件规模原来越大,花再多的时间测试,也不能保障一个应用系统没有出现故障的可能。对于某些关键部位,一旦就问题出现,损失可能是不可接受的。那么在这些部位,最好的解决办法就是备份。硬件系统设计中,备份是很普遍的。软件系统承担起越来越多的工作后,也必然要采取这个方案。对同一功能,按不同的思路写两套实现代码。系统平时只用其中一份,一旦出现异常,系统就把另一份调出来使用。
生物的基因都是成对的,这不仅是为了繁衍后代,更是为了备份。如果某一条基因出现的缺损,另一条上的对应部位可以立即接替相应的功能,保证整个生物体的正常运作。
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值