diff --git a/12.md b/12.md index 3c41f345911c4ccb3b64907d1f9aac8291758b2f..37791dccc4f6be249cec0d8bfed08d20af863bb6 100644 --- a/12.md +++ b/12.md @@ -55,7 +55,7 @@ 事实证明,引发利他主义问题的囚徒困境,也可能有助于解决问题。 -## 12.3 囚徒困境锦标赛 +## 12.3 囚徒困境比赛 在 20 世纪 70 年代后期,密歇根大学的政治学家罗伯特阿克塞尔罗德(Robert Axelrod)组织了一场比赛来比较囚徒困境(PD)的策略。 @@ -63,9 +63,9 @@ 在 Axelrod 的比赛中,一个简单的策略出人意料地好,称为“针锋相对”,即 TFT,TFT 在第一轮迭代比赛中总是合作;之后,它会复制上一轮对手所做的任何事情。对手继续合作,TFT 保持合作,如果对手任何时候都背叛,下一轮 TFT 背叛,但如果对手变回合作,TFT 也会合作。 -这些锦标赛的更多信息,以及 TFT 为何如此出色的解释,请参阅以下视频:。 +这些比赛的更多信息,以及 TFT 为何如此出色的解释,请参阅以下视频:。 -看看这些锦标赛中表现出色的策略,Alexrod 发现了他们倾向于分享的特点: +看看这些比赛中表现出色的策略,Alexrod 发现了他们倾向于分享的特点: + 善良:表现好的策略在第一轮比赛中合作,并且通常会在随后的几轮中合作。 + 报复:始终合作的策略,并不如如果对手背叛就报复的策略好。 @@ -134,4 +134,125 @@ def mutate(self): 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`来决定哪些智能体繁殖。 + +我的模拟包含生存差异,就像第?章那样,但不包括繁殖差异。 你可以在本章的笔记本上看到细节。 作为练习之一,您将有机会探索繁殖差异的效果。 +