企业🤖AI Agent构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
## 十二、合作进化 > 原文:[Chapter 12 Evolution of cooperation](http://greenteapress.com/complexity2/html/thinkcomplexity2012.html) > 译者:[飞龙](https://github.com/wizardforcel) > 协议:[CC BY-NC-SA 4.0](http://creativecommons.org/licenses/by-nc-sa/4.0/) > 自豪地采用[谷歌翻译](https://translate.google.cn/) 在最后一章中,我们提出两个问题,一个来自生物学,一个来自哲学: + 在生物学中,“利他主义问题”是自然选择与利他主义之间的明显冲突,自然选择表明动物生存在不断竞争的状态中来生存和繁殖,利他主义是许多动物帮助其他动物的倾向,甚至是显然对他们不利。见 <https://en.wikipedia.org/wiki/Altruism_(biology)>。 + 在道德哲学中,人性问题是,人类是否从根本上是善良的,或者邪恶的,或者是由环境塑造的空白状态。见 <https://en.wikipedia.org/wiki/Human_nature>。 我们将用来解决这些问题的工具,(同样)是基于智能体的模拟和博弈论,博弈论是一组抽象模型,旨在描述智能体交互的各种方式。具体来说,我们会考虑囚徒困境。 本章的代码位于`chap12.ipynb`中,该书是本书仓库中的`Jupyter`笔记本。使用此代码的更多信息,请参见第?节。 ## 12.1 囚徒困境 囚徒困境是博弈论中的一个话题,但它不是一种有趣的博弈。相反,这种博弈揭示了人类的动机和行为。以下是来自维基百科的它的介绍(<http://en.wikipedia.org/wiki/Prisoner's_dilemma>): 两名犯罪团伙成员被逮捕并囚禁。每个囚犯都被单独监禁,无法与另一方交流。检察官缺乏足够的证据,来证明这两个人的主要指控。他们希望以较轻的指控被判处两年徒刑。同时,检察官为每个囚犯提供商量的余地。每个囚犯都有机会:(1)通过证明对方犯罪出卖对方,或(2)通过保持沉默与另一方合作。出价是: + 如果 A 和 B 各自背叛对方,每个人都服刑 2 年。 + 如果 A 背叛 B 但 B 保持沉默,A 将被释放,B 将被监禁 3 年(反之亦然)。 + 如果 A 和 B 都保持沉默,他们两人只会服刑 1 年(较轻的质控)。 很显然,这种情况是假想的,但它用于代表各种不同的互动,其中智能体必须选择是相互“合作”还是“背叛”,以及每个智能体的奖励(或惩罚)取决于他人的选择。 有了这套奖惩,我们很有可能说智能体应该合作,也就是说,双方都应该保持沉默。 但两个智能体不知道对方会做什么,所以每个人都必须考虑两种可能的结果。 首先,从 A 的角度来看它: + 如果 B 保持沉默,A 最好是背叛;他会无罪而不是服刑 1 年。 + 如果 B 背叛,A 最好也是背叛;他只会服刑 2 年而不是 3 年。 不管 B 做什么,A 最好都是背叛。 而且因为博弈是对称的,所以从 B 的角度来看这个分析是一样的:不管 A 做什么,B 最好也是背叛。 在这个博弈的最简单版本中,我们假设 A 和 B 没有考虑其他因素。 他们不能互相沟通,所以他们不能协商,作出承诺或相互威胁。 他们只考虑直接目标,最小化他们的判决;他们不考虑任何其他因素。 在这些假设下,两个智能体的理性选择都是背叛。 这可能是一件好事,至少在刑事司法方面是这样。 但对囚犯来说,这令人沮丧,因为显然,他们无法获得他们双方都想要的结果。 而且这种模式适用于现实生活中的其他场合,其中合作有更大的好处以及对于玩家来说都会更好。 研究这些场景以及摆脱困境的方法,博弈论研究者关注的焦点,但这不是本章的重点。 我们正朝着不同的方向前进。 ## 12.2 善良的问题 自 20 世纪 50 年代,囚徒困境被首次讨论以来,它一直是社会心理学研究的热门话题。根据前一节的分析,我们可以说一个理想的智能体应该做什么; 很难预测真正的人究竟做了些什么。 幸运的是,实验已经完成了 [1]。 > [1] 这里有一个最近的报告,提到以前的实验:Barreda-Tarrazona, Jaramillo-Gutiérrez, Pavan, and Sabater-Grande, “Individual Characteristics vs. Experience: An Experimental Study on Cooperation in Prisoner’s Dilemma", Frontiers in Psychology, 2017; 8: 596. <https://www.ncbi.nlm.nih.gov/pmc/articles/PMC5397528/>。 如果我们假设人们足够聪明地进行分析(或者在解释时理解它),并且他们通常为了自己的利益而行事,那么我们预计他们几乎总是背叛。 但他们没有。 在大多数实验中,受试者的合作远远超过理性的智能体模型的预测 [2]。 > [2] 有个不错的视频归纳了我们目前讨论的内容:<https://www.youtube.com/watch?v=t9Lo2fgxWHw>。 这个结果最明显的解释是,人们不是理性的智能体,这对任何人都不应该感到惊讶。 但为什么不是呢? 是因为他们不够聪明,无法理解这种情况,还是因为他们故意违背自己的利益行事? 根据实验结果,似乎至少有一部分解释是纯粹的利他主义:许多人愿意为了让别人受益而承担成本。现在,在你提出《Journal of Obvious Results》上发表的结论之前,让我们继续问为什么: + 为什么人们会帮助别人,即使自己会付出代价?至少部分原因是他们想这样;这让他们对自己和世界感觉良好。 + 为什么善良让人感觉良好?诱人的说法是,有人跟他们提出这是正确的,或者更普遍来说,他们被社会训练为想要做好事。但毫无疑问 [3],至少有一大部分利他主义是天生的;在不同程度上,利他主义的倾向是正常大脑发育的结果。 + 那么,为什么呢?大脑发育的内在部分,以及随后的个体特征,是基因的结果。当然,基因与利他主义的关系是复杂的,可能有许多基因与环境因素相互作用,导致人们在不同情况下或多或少是无私的。尽管如此,几乎可以肯定的是基因导致人们变得无私。 + 最后,为什么呢?如果在自然选择下,动物为了生存和繁殖而彼此不断竞争,似乎显然利他主义会适得其反。在一个种群中,有些人帮助别人,甚至是为别人伤害自己,其他人纯粹是自私的,似乎自私者会受益,利他者会受到影响,并且利他主义的基因将被驱逐而灭绝。 > [3] 我希望你能原谅我在这里用“毫无疑问”代替实验的参考资料,我想在本章中介绍一些理由,而不会陷入太深。 这个明显的矛盾是“利他主义问题”:为什么利他主义的基因没有消失? 在生物学家中,有很多可能的解释,包括互惠利他主义,性选择,亲属选择和群体选择。而在非科学家中,还有更多的解释。我把它交给你去探索别的假说;现在我想专注于一种解释,可以说是最简单的一种解释:也许利他主义是适应性的。换句话说,利他主义的基因可能使人们更容易生存和繁殖。 事实证明,引发利他主义问题的囚徒困境,也可能有助于解决问题。 ## 12.3 囚徒困境比赛 在 20 世纪 70 年代后期,密歇根大学的政治学家罗伯特阿克塞尔罗德(Robert Axelrod)组织了一场比赛来比较囚徒困境(PD)的策略。 他邀请参与者以计算机程序的形式提交策略,然后相互对抗并保持得分。具体来说,他们玩的是 PD 的迭代版本,其中智能体针对同一对手进行多轮比赛,因此他们的决定可以基于历史。 在 Axelrod 的比赛中,一个简单的策略出人意料地好,称为“针锋相对”,即 TFT,TFT 在第一轮迭代比赛中总是合作;之后,它会复制上一轮对手所做的任何事情。对手继续合作,TFT 保持合作,如果对手任何时候都背叛,下一轮 TFT 背叛,但如果对手变回合作,TFT 也会合作。 这些比赛的更多信息,以及 TFT 为何如此出色的解释,请参阅以下视频:<https://www.youtube.com/watch?v=BOvAbjfJ0x0>。 看看这些比赛中表现出色的策略,Alexrod 发现了他们倾向于分享的特点: + 善良:表现好的策略在第一轮比赛中合作,并且通常会在随后的几轮中合作。 + 报复:始终合作的策略,并不如如果对手背叛就报复的策略好。 + 宽恕:但是过于斗气的策略往往会惩罚自己以及对手。 + 不嫉妒:一些最成功的策略很少超过对手;他们成功了,因为他们对各种各样的对手都做得足够好。 TFT 具有所有这些属性。 Axelrod 的比赛为利他主义问题提供了部分可能的答案:也许利他主义的基因是普遍存在的,因为它们是适应性的。 许多社会互动可以建模为囚徒困境的变种,就这种程度而言,如果将一个大脑设定为善良,平衡报复和宽恕,就会在各种各样的情况下表现良好。 但是 Axelrod 比赛中的策略是由人们设计的;他们并不进化。 我们需要考虑,善良、报复和宽恕的基因是否可以通过突变出现,成功侵入其他策略的种群,并抵制后续突变的侵入。 ## 12.4 合作进化的模拟 合作进化是第一本书的标题,Axelrod 展示了来自囚徒困境比赛的结果,并讨论了利他主义问题的影响。 从那以后,他和其他研究人员已经探索了 PD 比赛的进化动态性,也就是说,PD 选手的总体中,策略的分布随时间如何变化。 在本章的其余部分中,我运行这些实验的一个版本并展示结果。 首先,我们需要一种将 PD 策略编码为基因型的方法。 在这个实验中,我考虑了一些策略,其中智能体每一轮的选择仅取决于前两轮中对手的选择。 我用字典来表示策略,它将对手的前两个选择映射为智能体的下一个选择。 以下是这些智能体的类定义: ```py class Agent: keys = [(None, None), (None, 'C'), (None, 'D'), ('C', 'C'), ('C', 'D'), ('D', 'C'), ('D', 'D')] def __init__(self, values, fitness=np.nan): self.values = values self.responses = dict(zip(self.keys, values)) self.fitness = fitness ``` `keys`是每个智能体的词典中的键序列,其中元组`('C', 'C')`表示对手在前两轮合作;`(None, 'C')`意味着只有一轮比赛并且对手合作;`(None, None)`表示还没有回合。 在`__init__`方法中,`values `是对应于键的一系列选项,`'C'`或`'D'`。 所以如果值的第一个元素是`'C'`,那就意味着这个智能体将在第一轮合作。 如果值的最后一个元素是`'D'`,那么如果对手在前两轮中背叛,该智能体将会背叛。 在这个实现中,总是背叛的智能体的基因型是`'DDDDDDD'`; 总是合作的智能体的基因型是`'CCCCCCC'`,而 TFT 的基因型是`'CCDCDCD'`。 `Agent`类提供`copy`,它使其它智能体具有相同的基因型,但具有一定的变异概率: ```py prob_mutate = 0.05 def copy(self): if np.random.random() > self.prob_mutate: values = self.values else: values = self.mutate() return Agent(values, self.fitness) ``` 突变的原理是,在基因型中选择一个随机值并从`'C'`翻转到`'D'`,或者相反: ```py def mutate(self): values = list(self.values) index = np.random.choice(len(values)) values[index] = 'C' if values[index] == 'D' else 'D' return values ``` 既然我们有了智能体,我们还需要比赛。 ## 12.5 `Tournament` `Tournament`类封装了 PD 比赛的细节: ```py payoffs = {('C', 'C'): (3, 3), ('C', 'D'): (0, 5), ('D', 'C'): (5, 0), ('D', 'D'): (1, 1)} num_rounds = 6 def play(self, agent1, agent2): agent1.reset() agent2.reset() for i in range(self.num_rounds): resp1 = agent1.respond(agent2) resp2 = agent2.respond(agent1) pay1, pay2 = self.payoffs[resp1, resp2] agent1.append(resp1, pay1) agent2.append(resp2, pay2) return agent1.score, agent2.score ``` `payoffs`是一个字典,将从智能体的选择映射为奖励。例如,如果两个智能体合作,他们每个得到 3 分。如果一个背叛而另一个合作,背叛者得到 5 分,而合作者得到 0 分。如果他们都背叛,每个都会得到 1 分。这些是 Axelrod 在他的比赛中使用的收益。 `play `运行几轮 PD 游戏。它使用`Agent`类中的以下方法: + `reset`:在第一轮之前初始化智能体,重置他们的分数和他们的回应的历史记录。 + `respond`:考虑到对手之前的回应,向每个智能体询问回应。 + `append`:通过存储选项,并将连续轮次的分数相加,来更新每个智能体。 在给定的回合数之后,`play`将返回每个智能体的总分数。我选择了`num_rounds = 6`,以便每个基因型的元素都以大致相同的频率访问。第一个元素仅在第一轮访问,或在六分之一的时间内访问。接下来的两个元素只能在第二轮中访问,或者每个十二分之一。最后四个元素在六分之一时间内访问,平均每次访问六次,或者平均每个六分之一。 `Tournament`提供了第二种方法,即`melee`,确定哪些智能体互相竞争: ```py def melee(self, agents, randomize=True): if randomize: agents = np.random.permutation(agents) n = len(agents) i_row = np.arange(n) j_row = (i_row + 1) % n totals = np.zeros(n) for i, j in zip(i_row, j_row): agent1, agent2 = agents[i], agents[j] score1, score2 = self.play(agent1, agent2) totals[i] += score1 totals[j] += score2 for i in i_row: agents[i].fitness = totals[i] / self.num_rounds / 2 ``` `melee`接受一个智能体列表和一个布尔值`randomize`,它决定了每个智能体每次是否与同一邻居竞争,或者匹配是否随机化。 `i_row`和`j_row`包含匹配的索引。 `totals`包含每个智能体的总分数。 在循环内部,我们选择两个智能体,调用`play`和更新`totals`。 最后,我们计算每个智能体获得的,每轮和每个对手的平均点数,并将结果存储在每个智能体的`fitness `属性中。 ## 12.6 `Simulation` 本章的`Simulation`类基于第?章的中的那个;唯一的区别是`__init__`和`step`。 这是`__init__`方法: ```py class PDSimulation(Simulation): def __init__(self, tournament, agents): self.tournament = tournament self.agents = np.asarray(agents) self.instruments = [] ``` `Simulation`对象包含一个`Tournament`对象,一系列的智能体和一系列的`Instrument`对象(就像第?章中一样)。 以下是`step`方法: ```py def step(self): self.tournament.melee(self.agents) Simulation.step(self) ``` 此版本的`step`使用`Tournament.melee`,它为每个智能体设置`fitness`属性;然后它调用父类的`step`方法,父类来自第?章: ```py # class Simulation def step(self): n = len(self.agents) fits = self.get_fitnesses() # see who dies index_dead = self.choose_dead(fits) num_dead = len(index_dead) # replace the dead with copies of the living replacements = self.choose_replacements(num_dead, fits) self.agents[index_dead] = replacements # update any instruments self.update_instruments() ``` `Simulation.step`将智能体的适应性收集到一个数组中; 然后它会调用`choose_dead`来决定哪些智能体死掉,并用`choose_replacements`来决定哪些智能体繁殖。 我的模拟包含生存差异,就像第?章那样,但不包括繁殖差异。 你可以在本章的笔记本上看到细节。 作为练习之一,你将有机会探索繁殖差异的效果。 ## 12.7 结果 假设我们从三个智能体开始:一个总是合作,一个总是背叛,另一个执行 TFT 策略。 如果我们在这个种群中运行`Tournament.melee`,合作者每轮获得 1.5 分,TFT 智能体获得 1.9 分,而背叛者获得 3.33 分。 这个结果表明,“总是背叛”应该很快成为主导策略。 但是“总是缺陷”包含着自我破坏的种子,如果更好的策略被驱使而灭绝,那么背叛者就没有人可以利用,他们的适应性下降,并且容易受到合作者的入侵。 根据这一分析,预测系统的行为不容易:它会找到一个稳定的平衡点,还是在基因型景观的各个位置之间振荡? 让我们运行模拟来发现它! 我以 100 个始终背叛的相同智能体开始,并运行 5000 个步骤的模拟: ```py tour = Tournament() agents = make_identical_agents(100, list('DDDDDDD')) sim = PDSimulation(tour, agents) ``` ![](https://img.kancloud.cn/d4/23/d4234109ee19f6f67b77ffb8e33525b7_454x305.png) 图 12.1:平均适应性(囚徒困境的每个回合的所得点数) 图?展示了随时间变化的平均适应性(使用第?章的`MeanFitness`仪器)。最初平均适应性是 1,因为当背叛者面对对方时,他们每轮只能得到 1 分。 经过大约 500 个时间步,平均适应性增加到近 3,这是合作者面对彼此时得到的。但是,正如我们所怀疑的那样,这种情况不稳定。在接下来的 500 个步骤中,平均适应性下降到 2 以下,回到 3,并继续振荡。 模拟的其余部分变化很大,但除了一次大的下降之外,平均适应性通常在 2 到 3 之间,长期平均值接近 2.5。 而且这还不错!它不是一个合作的乌托邦,每轮平均得 3 分,但距离始终背叛的乌托邦还很远。这比我们所期待的,自利智能体的自然选择要好得多。 为了深入了解这种适应性水平,我们来看看更多的仪器。`Niceness`在每个时间步骤之后测量智能体的基因型的合作比例: ```py class Niceness(Instrument): def update(self, sim): responses = np.array([agent.values for agent in sim.agents]) metric = np.mean(responses == 'C') self.metrics.append(metric) ``` ![](https://img.kancloud.cn/33/f8/33f8d257e01d6eb0feaeb2a4811995e7_704x305.png) 图 12.2:种群中所有基因组的平均友善度(左)和第一轮合作的种群比例(右) 图?(左图)展示结果:平均友善度从 0 迅速上升到 0.75,然后在 0.4 到 0.85 之间波动,长期平均值接近 0.65。 同样,这相当好! 具体看开始的移动,我们可以追踪第一轮合作的智能体的比例。 这是这个仪器: ```py class Retaliating(Instrument): def update(self, sim): after_d = np.array([agent.values[2::2] for agent in sim.agents]) after_c = np.array([agent.values[1::2] for agent in sim.agents]) metric = np.mean(after_d=='D') - np.mean(after_c=='D') self.metrics.append(metric) ``` 报复行为将所有基因组中的元素数量,其中对手背叛后智能体也背叛(元素 2, 4 和 6),与其中的元素数量,其中对手合作后智能体背叛相比较。正如你现在的预期,结果差异很大(你可以在笔记本中看到图形)。平均而言,这些分数之间的差异小于 0.1,所以如果智能体在对手合作后,30% 的时间中背叛,他们可能会在背叛后的 40% 时间中背叛。 这个结果为这个断言提供了较弱的支持,即成功的策略会报复。也许所有智能体甚至很多智能体都没有必要进行报复;如果整个种群中至少存在一定的报复倾向,那么这可能足以阻止高度报复策略的普及。 为了衡量宽恕,我再次定义了一个工具,来查看在前两轮之后,智能体是否更有可能在 D-C 之后进行合作,与 C-D 相比。在我的模拟中,没有证据表明这种特殊的宽恕。另一方面,这些模拟中的策略在某种意义上是必然的宽容,因为它们只考虑前两轮的历史。 ## 12.8 总结 Axelrod 的比赛提出了解决利他主义问题的一个可能的解决办法:或许善良,但不是太善良,是适应性的。但是原始比如中的策略是由人们,而不是进化论设计的,并且策略的分布在比赛过程中没有改变。 所以这就提出了一个问题:像 TFT 这样的策略可能会在固定的人为设计策略中表现良好,但它们是否会进化?换句话说,他们是否可以通过变异出现在种群中,与祖先竞争成功,并抵抗他们的后代的入侵? 本章中的模拟表明: + 背叛者种群容易受到更善良的策略的入侵。 + 过于善良的种群容易受到背叛者的入侵。 + 所以,善良的平均程度有所波动,但善良的平均数量普遍较高,而平均适应程度一般更接近合作乌托邦而不是偏差异议程度。 + 在 Alexrod 的比赛中,TFT 是一项成功的战略,但对于不断发展的种群来说,这似乎不是一个最佳策略。事实上,可能没有稳定的最佳策略。 + 某种程度的报复可能是适应性的,但对所有智能体来说,可能没有必要进行报复。 如果在整个种群中有足够的报复行为,这可能足以防止背叛者入侵 [4]。 > [4] 这就引入了博弈论中一个全新的话题 - 搭便车问题(见 <https://en.wikipedia.org/wiki/Free-rider_problem>)。 很明显,这些模拟中的智能体很简单,而囚徒困境是一种有限范围的社交互动的高度抽象模型。 尽管如此,本章的结果对人性提供了一些见解。 也许我们对合作,报复和宽恕的倾向是天生的,至少部分是。 这些特征是我们的大脑的工作机制的结果,至少部分是由我们的基因控制的。 也许我们的基因这样来构建我们的大脑,因为在人类进化史上,自私的大脑的基因不太可能传播。 所以也许这就是为什么自私基因会建立无私的大脑。 ## 12.9 练习 本章的代码位于本书仓库的 Jupyter 笔记本`chap12.ipynb`中。打开笔记本,阅读代码并运行单元个。你可以使用这个笔记本来练习本章的练习。我的解决方案在`chap12soln.ipynb`中。 练习 1 本章中的模拟取决于我任意选择的条件和参数。作为练习,我鼓励你去探索其他条件,看看他们对结果有什么影响。这里有一些建议: 1. 改变初始条件:不要从所有背叛者开始,看看如果从所有合作者,所有 TFT 或随机智能体开始会发生什么。 1. 在`Tournament.melee`中,我在每个时间步骤开始时洗牌,所以每个玩家对抗两个随机选择的玩家。如果你不洗牌会怎么样?在这种情况下,每个智能体都会反复与相同的邻居进行比赛。这可能会让少数人的战略,更容易通过利用局部性入侵大多数。 1. 由于每个智能体只与另外两个智能体进行比赛,因此每轮比赛的结果都是非常不同的:在任何一轮比赛中,胜过大部​​分智能体的智能体可能会运气不好,或者相反。如果增加每个智能体在每轮中的对手数量会发生什么?或者如果在每一步结束时,智能体的适应性是上一轮结束时其当前得分和适应性的平均值,会怎么样? 1. 我为`prob_survival`函数选择的值从 0.7 到 0.9 不等,所以适应性最差的智能体`p = 0.7`,生存了 3.33 个时间步骤,适应性最强的智能体生存了 10 个。如果你使`prob_survival`更加或更加不“激进”,会发生什么情况。 1. 我选择了`num_rounds = 6`,以便基因组的每个元素对比赛的结果具有大致相同的影响。 但这比 Alexrod 在他的比赛中使用的值要小得多。 如果增加`num_rounds`会发生什么? 注意:如果你研究这个参数的效果,你可能想修改`Niceness`来衡量基因组中最后4个元素的友善度,随着`num_rounds`的增加,它会受到更多的选择性压力。 1. 我的实现拥有生存差异和随机繁殖。 如果添加繁殖差异会发生什么? 练习 2 在我的模拟中,种群从未收敛到一个状态,其中多数人共享相同的,据推测是最佳的基因型。对于这个结果有两种可能的解释:一是没有最佳策略,因为无论何时种群被大多数基因型控制,这种状况为少数人入侵提供了机会;另一种可能性是,突变率高到足以维持多种基因型,即使多数是非最佳的。为了辨别这些解释,请尝试降低突变率来查看发生了什么。或者,从随机种群开始,并且不带突变来运行,直到只有一个基因型存活。或者带突变来运行,直到系统达到稳定状态;然后关闭突变并运行,直到只有一个幸存的基因型。这些情况下基因型的特征是什么? 练习 3 我的实验中的智能体是“反应型”的,因为他们在每轮中的选择只取决于对手在前几轮中的做法。考虑探索一些策略,它们也考虑到智能体过去的选择。这样的策略将能够区分报复性对手,和没有挑衅而背叛的对手。