TL; DR:我对猴子的行为进化进行了计算机模拟,继续阅读以了解最初在《自私的基因》中是如何陈述问题的。 第一部分显示了我的Java实现 ,第二部分显示了图表结果和结论。

问题

我最近读了理查德·道金斯 ( Richard Dawkins)的 《自私的基因》 ,尽管已经40岁,但它的开篇非常开阔。 尽管原始文本有时已经过时了(“ 您只能在头骨中装上几百个晶体管 -如今更像是数万亿 个晶体管 ,您好, 摩尔定律 ),但一般要求却像过去一样吸引人。

作为计算机工程师,有一章特别引起我注意的一章是关于动物进行社交修饰的 ,例如某些种类的猴子(请参见上图)。 让我引用相关章节 :

假设某个物种[…]被一种带有危险疾病的特别讨厌的壁虱所寄生。 非常重要的一点是,应尽快清除这些壁虱。 […]一个人也许无法伸手可及,但没有什么比让朋友为他做容易的事了。 以后,当朋友被自己寄生时,可以偿还善行。 […]这具有直接的直观意义。 任何有意识的远见的人都可以看出,相互背对背的安排是明智的。 […]

假设B在他的头顶上有一个寄生虫。 一个把它拉下来。 后来,时机到了,A头上有寄生虫。 他自然地寻找B,以便B可以偿还他的善行。 B只是转过鼻子走开。 B是一个骗子,一个人接受其他人的利他主义的利益,但不偿还或无偿还。 作弊要比不加选择的利他主义者做得更好,因为作弊者无需支付成本即可获得收益。 可以肯定的是,与去除寄生虫的好处相比,修饰另一个人的头部的花费似乎很小,但这并不是可以忽略的。 必须花费一些宝贵的精力和时间。

让总体由采用两种策略之一的个体组成。 […]称这两个策略为Sucker和Cheat。 吸盘会随意修饰需要的任何人。 骗子接受抽油机的利他主义,但他们从不修饰任何人,甚至以前没有修饰过的人。 […]作弊会比傻瓜做得更好。 即使整个人口濒临灭绝,也永远不会有比傻瓜做得更好的人。 因此,只要我们仅考虑这两种策略,就无法阻止吸盘的灭绝,也很可能阻止整个人口的灭绝。

但是现在,假设有第三种策略称为Grudger。 怨恨者会为陌生人和以前曾修饰过他们的人提供服务。 但是,如果有人欺骗他们,他们会记住事件并怀恨在心:他们将来拒绝修饰该人。 在充满怨恨和吮吸者的人群中,不可能分辨出哪个是哪个。 两种类型对其他所有人都有利他的表现[…]。 如果与作弊者相比,仇恨者很少,那么仇恨者基因将灭绝。 一旦仇恨者设法增加数量,使他们达到关键的比例,他们彼此相遇的机会就变得足够大,可以抵消他们在欺骗作弊方面的浪费。 当达到这个关键比例时,他们将开始平均获得比作弊更高的回报,并且作弊将以加速的速度被驱逐到灭绝。 […]

引自: 理查德·道金斯 ( Richard Dawkins)的 《自私的基因》 (ISBN 0-19-857519-X)。

随后,作者进行了一系列计算机模拟,以观察这三种策略在各种条件下如何共同发挥作用。 显然源代码不可用,我对此很满意。 首先,因为我有机会编写一些有趣的代码。 其次:这本书于1976年出版,距C发明4年,距C ++多年(使您了解),我真的不太想Fortran 。

实作

因此,我砍掉了几节课来模拟整个人群中的作弊者,吮吸者和怨恨者。 我想看看不同类型的行为如何影响人口规模。 哪种行为稳定并保证生存。 最后,如何减轻随机突变和死亡。 我决定尝试一些技术,即:

  1. 手工进行依赖注入,没有任何容器
  2. 完全单线程
  3. 逻辑时钟已显式改进以模拟时间流逝

我们将模拟一群猴子(从几只到几百万只),每只猴子都有独立的行为,随机的寿命等等。 使用诸如角色或至少线程之类的多代理解决方案似乎很明显。 但是, “反应式编程原理”课程告诉我这通常是过度设计。 从本质上讲,这样的模拟是一系列将来应该发生的事件:一只猴子应该在2年内出生,应该在5年内繁殖,并且应该在10年内死亡。当然,还有更多的此类事件和更多的猴子。 但是,将所有这些事件放入一个优先级队列中就足够了,在该队列中,将来最接近的事件将排在第一位。 这就是Planner的基本实现方式:

public abstract class Action implements Comparable<Action> {private final Instant schedule;public Action(Clock simulationTime, Duration delay) {this.schedule = simulationTime.instant().plus(delay);}@Overridepublic int compareTo(Action other) {return this.schedule.compareTo(other.schedule);}public abstract void run();}//...public class Planner implements Runnable {private final Queue<Action> pending = new PriorityQueue<>();private final SimulationClock simulationClock;public void schedule(Action action) {pending.add(action);}@Overridepublic void run() {while (!pending.isEmpty()) {Action nearestAction = pending.poll();simulationClock.advanceTo(nearestAction.getSchedule());nearestAction.run();}}
}

我们有一个具有预定义scheduleAction (应在何时执行)和一个pending的将来操作队列。 无需等待,我们只需选择将来的最近动作并提前模拟时间即可:

import java.time.Clock;public class SimulationClock extends Clock {private Instant simulationNow = Instant.now();@Overridepublic Instant instant() {return simulationNow;}public void advanceTo(Instant instant) {simulationNow = instant;}}

因此,我们实质上实现了一个事件循环 ,可以在队列中的任何位置(不一定在末尾)添加事件。 现在,当我们有了一个基本框架时,让我们实现猴子的行为:

public class Sucker extends Monkey {//...@Overridepublic boolean acceptsToGroom(Monkey monkey) {return true;}}public class Cheater extends Monkey {private final double acceptProbability;//...@Overridepublic boolean acceptsToGroom(Monkey monkey) {return Math.random() < acceptProbability;}
}public class Grudger extends Monkey {private final Set<Monkey> cheaters = new HashSet<>();@Overridepublic boolean acceptsToGroom(Monkey monkey) {return !cheaters.contains(monkey);}@Overridepublic void monkeyRejectedToGroomMe(Monkey monkey) {cheaters.add(monkey);}}

如您所见,这三个类捕获了三种不同的行为。 Sucker总是接受修饰请求, Cheater只是有时(在原始模拟中-从来没有,但是我使它可配置), Grudger记得谁之前拒绝了他们的请求。 猴子聚集在一个“ Population类中,这是一个小片段:

public class Population {private final Set<Monkey> monkeys = new HashSet<>();private final MonkeyFactory monkeyFactory;private Population addMonkey(Monkey child) {if (!full()) {newMonkey(child);}return this;}private boolean full() {return monkeys.size() >= environment.getMaxPopulationSize();}private void newMonkey(Monkey child) {monkeys.add(child);planner.scheduleMonkeyLifecycle(child, this);log.debug("New monkey in population {}total {}", child, monkeys.size());}//...
}

我们为每只新猴子安排其所谓的生命周期,即与繁殖,修饰和死亡有关的事件(在Planner ):

void scheduleMonkeyLifecycle(Monkey child, Population population) {askForGrooming(child, environment.getParasiteInfection().make(), population);scheduleBreedings(child, population);kill(child, environment.getLifetime().make(), population);
}void askForGrooming(Monkey child, Duration parasiteInfection, Population population) {schedule(new AskForGrooming(child, parasiteInfection, population));
}private void scheduleBreedings(Monkey child, Population population) {final int childrenCount = RANDOM.nextInt(environment.getMaxChildren() + 1);IntStream.rangeClosed(1, childrenCount).forEach(x -> breed(child, environment.getBreeding().make(), population));
}void kill(Monkey child, Duration lifetime, Population population) {schedule(new Kill(child, lifetime, population));
}private void breed(Monkey child, Duration breeding, Population population) {schedule(new Breed(child, breeding, population));
}

AskForGroomingKillBreed等是已经提到的Action类的实例,例如Kill

public class Kill extends MonkeyAction {private final Population population;public Kill(Monkey monkey, Duration lifetime, Population population) {super(monkey, lifetime);this.population = population;}@Overridepublic void run(Monkey monkey) {population.kill(monkey);}
}

我将所有模拟参数封装在一个简单的值类Environment ,许多参数(例如parasiteInfectionlifetimebreeding不是常量,而是RandomPeriod类的实例:

@Value
public class RandomPeriod {private static final Random RANDOM = new Random();Period expected;Period stdDev;public Duration make() {final long shift = Periods.toDuration(expected).toMillis();final long stdDev = Periods.toDuration(this.stdDev).toMillis();final double gaussian = RANDOM.nextGaussian() * stdDev;double randomMillis = shift + gaussian;return Duration.ofMillis((long) randomMillis);}}

这使我能够捕捉具有期望值,标准偏差和正态分布的随机时间段的概念。 make()方法仅生成一个这样的随机周期。 我不会探索该模拟的完整源代码, 可以在GitHub上找到 。 现在终于可以运行几次,观察种群如何增长(或灭绝)。 顺便说一下,我使用相同的计划程序和操作机制来查看发生的情况:我只是每年一次(逻辑时间!)注入Probe动作并输出当前的人口规模。

就像许多事件循环一样,访问事件必须只有一个线程。 我们遵循这种做法,模拟是单线程的,因此根本不需要执行任何同步或锁定,我们还可以使用标准,不安全但速度更快的集合。 较少的上下文切换和改进的缓存局部性也有帮助。 同样,我们可以轻松地将模拟状态转储到磁盘上,例如以后将其还原。 当然有缺点。 由于有成千上万的猴子,因此模拟速度变慢,除了谨慎的优化和购买更快的CPU(甚至没有更多的CPU!)外,我们无能为力。

本实验

作为对照组,我们从一个很小的(10个样本)种群开始,这些种群仅由吸盘以及吸盘和怨恨者的混合物组成。 在没有作弊者的情况下,这两种行为是无法区分的。 我们关闭突变(一个有傻瓜和怨恨的孩子成为骗子,而不是后来成为傻瓜或怨恨的孩子的可能性),然后查看种群的增长方式(X轴代表时间,Y轴代表种群数量):

请注意,由于这两种行为的行为完全相同,因此吸盘和怨恨者的比例波动约50%。 仅用很少的作弊者进行模拟是没有意义的。 由于他们通常不互相修饰,因此他们很快死去,擦除了“ 作弊基因 ”。 另一方面,仅吸盘(无突变)呈指数增长(您可以清楚地看到高原后出生的新生代):

但是,如果我们模拟一个有100个吮吸者和5个作弊者的种群,会发生什么情况? 突变再次被关闭以保持仿真的干净:

有两种可能的情况:作弊者基因消失或扩散,导致种群灭绝。 这有点自相矛盾-如果这个特定的基因获胜,整个人口(包括那个基因!)注定要失败。 现在让我们模拟一些更有趣的东西。 我们打开了5%的突变概率,并要求作弊者在10例中的9例中进行修饰(因此他们的行为有点像傻瓜)。 我们从拥有5个吮吸者和5个怨恨者的健康人群开始:

您是否看到仇恨者如何Swift扩张,并且几乎总是数量超过吸盘? 这是因为在有时由于突变而导致作弊者出现的环境中,吮吸者更容易受到伤害。 您是否注意到每当出现少量吸盘时,人口如何Swift减少? 怨恨者也很脆弱:他们在欺骗新生的骗子时不知道自己是谁。 但是,他们不会像傻瓜那样重复此错误。 这就是为什么吸盘总是松动的原因,但是它们并没有完全灭绝,因为它们在某种程度上受到怨恨者的保护。 仇恨者不直接这样做,而是不对它们进行修饰来杀死作弊者,从而减少了威胁。 这就是不同的行为如何合作的方式。 那么为什么人口最终灭绝了呢? 仔细查看图表的末尾,在某些时候,由于某种随机原因,吮吸者的人数超过了怨恨者-在当时没有作弊者的情况下,这尤其可能发生。 所以发生了什么事? 由于突变,一些作弊者突然出现了,这个猴子社会注定要失败。

现在让我们研究另一个例子:一个已经生活着100个吸盘的成熟社会,没有观察到其他行为。 当然由于突变,仇恨者和作弊者很快出现。 大多数情况下,这种模拟很快就结束了,几代之后就结束了。 作弊者出生的可能性与怨恨者的可能性相同,但是我们需要更多的怨恨者来保护吮吸者。 因此人口死亡。 但是我设法进行了几次模拟,这些社会实际上幸存了一段时间:

有趣的是,吸盘在整个人口中占据了相当长的一段时间,但是又一次流行的作弊者袭击杀死了大多数吸盘。 不幸的是,骗子的最后增长使怨恨者的数量减少到了无法再保护吮吸者的地步,所有人都突然死亡。

摘要

从实现的角度来看,尽管难以扩展,但使用单线程模型和操作队列确实非常有效。 但是生物学结论更加有趣:

  • 只要没有作弊者,无私的人(只有傻瓜 )可以永远快乐地生活,试图利用该系统
  • 利他主义群体中只有一个作弊者会导致这种群体崩溃
  • 人口需要防止作弊者的守卫( 怨恨者)
  • 即使在守卫在场的情况下,仍有少量骗子未被发现的地方
  • 如果作弊者的数量超过某个临界比例,那么人们将无法保护自己并放弃。 每个标本都会死亡,包括作弊者
  • 即使对整个种群都造成了不利影响的基因,也普遍存在对已灭绝的种群有害的基因

您可以自由地将结论推广到人类社会。

  • 完整的源代码可在GitHub上获得,随时尝试,也欢迎拉取请求!

翻译自: https://www.javacodegeeks.com/2015/04/biological-computer-simulation-of-selfish-genes.html

自私基因的生物计算机模拟相关推荐

  1. 自私的基因_自私基因的生物计算机模拟

    自私的基因 TL; DR:我对猴子的行为进化进行了计算机模拟,继续阅读以了解最初在<自私的基因>中是如何陈述问题的. 第一部分显示了我的Java实现 ,第二部分显示了图表结果和结论. 问题 ...

  2. 李嘉诚的“自负指数”与盖茨的“自私基因”看成功需要什么?

    李嘉诚:我有一个"自负指数"--"自负指数"是一套衡量检讨自我意识.态度和行为的简单心法 下文为李嘉诚6月26日出席汕头大学毕业典礼时的演讲: 首先让我代表校董 ...

  3. (转)基因芯片数据GO和KEGG功能分析

    随着人类基因组计划(Human Genome Project)即全部核苷酸测序的即将完成,人类基因组研究的重心逐渐进入后基因组时代(Postgenome Era),向基因的功能及基因的多样性倾斜.通过 ...

  4. 基因大数据的集成分析

    基因大数据的集成分析 胡湘红1, 彭衡2, 杨灿3, 张纵辉1, 万翔1, 罗智泉1 1 深圳市大数据研究院,广东 深圳 518172 2 香港浸会大学数学系,香港 999077 3 香港科技大学数学 ...

  5. 中国CAR-T细胞疗法成果首登Nature,我们与背后公司聊了聊技术进展|量子位·对撞派 × 邦耀生物...

    量子位智库 发自 凹非寺 量子位|公众号 QbitAI CRISPR/Cas9等新型基因编辑凭借着成本低廉,操作方便,效率高等优点,迅速引起学界和产业界的关注,是继ZFN.TALEN之后出现的第三代基 ...

  6. 基于单细胞多组学数据无监督构建基因调控网络

    在单细胞分辨率下识别基因调控网络(GRNs,gene regulatory networks)一直是一个巨大的挑战,而单细胞多组学数据的出现为构建GRNs提供了机会. 来自:Unsupervised ...

  7. 易基因:多组学关联分析及组学分子实验验证方法(表观组+转录组+微生物组)|干货系列

    大家好,这里是专注表观组学十余年,领跑多组学科研服务的易基因. 生物过程具有复杂性和整体性,单组学数据难以系统全面解析复杂生理过程的分子调控机制.而多组学(Multi-omics)联合分析可同时实现从 ...

  8. 基因编辑食品,能否端上我们的餐桌?

    来源|脑极体 作者|海怪 对匮乏恐惧的本能,一直深入我们的基因当中. 站在2021年初,我们不由得感慨过去一年来新冠病毒肆虐所造成的全世界的支离破碎,同样也庆幸我们很快将暴风骤雨的中心变成这个世界上少 ...

  9. 【听】自私的基因,生物进化的本质

    自私的基因,一个特殊而又合理的角度讲述了生物学中的自然选择学,生物进化等理论,人生来就是自私的,由基因,自私基因决定. 开头以自私的基因,解释了物种的起源,的确是一个很好,很通俗的例子,尤其是用来描述 ...

最新文章

  1. Ubuntu系统环境变量配置文件(转)
  2. java获取系统语言(区分简体中文和繁体中文)
  3. linux中怎么添加附属组,Linux中如何使用附属属组创建文件
  4. 初学者计算机_初学者极客:如何在计算机上重新安装Windows
  5. 猫、狗与Java的多态
  6. sms 2003 Service Pack 3 Open Beta
  7. UVA 11107 Life Forms——(多字符串的最长公共子序列,后缀数组+LCP)
  8. 汉化pycharm,中文
  9. DSP学习笔记(三)——TMS320F28335硬件结构
  10. Winxp 中文版 使用 IDM,补丁
  11. 虚拟机Ubuntu21.04全屏显示
  12. Educational Codeforces Round 51 (Rated for Div. 2).B. Relatively Prime Pairs(水题)
  13. FormData兼容性问题
  14. java三次指数平滑_时间序列挖掘-预测算法-三次指数平滑法(Holt-Winters)
  15. 学习笔记:Java 并发编程①_基础知识入门
  16. ❤️数据可视化❤️:基于Echarts + GeoJson实现的地图视觉映射散点(气泡)组件【10】 - 黑龙江省
  17. 早年黑网吧特供游戏《血战上海滩》如何在Win10运行?
  18. 刷爆网络的动态条形图,3行Python代码就能搞定
  19. 新的一年,您在学习和工作上,想坚守所学,还是尝试转型呢?
  20. IDE硬盘的容量限制

热门文章

  1. iOS-UIAccessibility旁白适配
  2. 小米wifi链android,小米WiFi链app是什么有什么用
  3. python selenium 下拉框 页面变化_selenium + python 处理 select 标签下拉框的选项
  4. g2o: 如何使用g2o的例子
  5. 花钱买快乐 文 / 弗雷德里.美娜德
  6. Java进阶(十三)网络编程
  7. 23 BeautifulSoup 方法选择器find()方的使用
  8. html标题过长如何隐藏,CSS实现标题文字过长部分显示省略号的方法
  9. 四大错误-------为何会拿好人卡(十)这些事情也请不要做
  10. 100 道 JavaScript 面试题及答案汇总(下)