定义『人类』

如何构建『人类』

首先,『人类』应该是一个 类 (class),而不是一个 函数。
类 像是一个整理用的空间(NameSpace),可以有自己的子函数、子属性。其实,子函数本身也是一个子属性,记得我们在 《FirstWeb》中反复强调的,函数也是一种变量。


暂停下:我们要构建一个『人类』的类 ,回顾下 鹰鸽博弈,如果是你,会如何设计这个 类 呢?


本章节的内容,并不是 How to 的性质,我们的目的只是为了阅读代码,从而去理解『普通人的自然逻辑思维,如何在程序的世界中得以呈现』。
你也应该在这个过程中保持思考:如果是自己实现这个博弈模型,具体怎么处理会比较好?

一些全局的变量

我们需要给一些固定的变量,这样后续程序进行调整的时候,只要改变这些变量就可以了。

INIT_HUMAN_NUMBER = 100
DEFAULT_PIGEON_SCORE_RANGE = (0.4, 0.6)

INIT_MONEY = 5
PER_HUMAN_COST_BASE = 10

MILESTORE_TIMES = 100000
PIGEON_SCORE_PER_ROUND = 0.1

DOUBLE_WIN_AWARD = 1.5

在量化一个人的鸽性,我们会给它一个指数,叫鸽派指数,如果大于 0.5 就是合作倾向的,如果小于 0.5 就是掠夺倾向的,如果等于 0.5 就刚好与世无争了。
自然,0.2 系数的肯定比 0.45 的野蛮很多,而 0.9 系数的肯定比 0.55 的更加倾向于合作。
为了让一个人有资源可以被掠夺,所以,每个人都有一个属性,叫money。

这些变量的说明:

  • INIT_HUMAN_NUMBER,初始的人类数量
  • DEFAULT_PIGEON_SCORE_RANGE,默认的鸽派指数区间
  • INIT_MONEY,每个人初始化时候的钱
  • PER_HUMAN_COST_BASE,每轮交锋的 金钱基数
  • MILESTORE_TIMES,里程碑总次数,相当于每个人一生中与他人交锋的次数
  • PIGEON_SCORE_PER_ROUND,每轮交锋后,产生的鸽派指数的变化量
  • DOUBLE_WIN_AWARD,如果达到双赢时候的奖励

整体的思路

注意: 不用尝试去吃透下面的每一个规则 (本就比较简陋),只要阅读的过程中,判断某个规则是否大体符合 鹰鸽博弈 就可以了。 本章的内容,我们的重点在于 读代码,并非是 重新实现,就像是读一个故事、一本小说,能读下来就可以,并不需要读完之后,能够复述出来。

基本的规则

  1. 每个人,有自己的鸽派指数 (0~1 之间) 以及财富 (money)。
  2. 1 减去 鸽派指数 就是他的 武力值 (鹰派指数),比如无合作精神的,鸽派指数为 0,那么它的 武力值 就是满分 1,当两只鹰碰到一起的时候,武力值高的,获得利益更高。
  3. 判断一个人是否是鸽派的,在于其 鸽派指数 是否大于 0.5。
  4. 如果一个人的财富已经归零,就认为是 死亡 的状态,不再参与跟别人的交锋。
  5. 在交锋后,每个人的财富会发生变化,并且会根据情况调整自己的 鸽派指数,比如 鸽派 遇到 鹰派,鸽 输,鹰 赢了,那么 鸽 和 鹰 都会降低自己的 鸽派指数,鸽 为了下次不会再输,而 鹰 为了下次赢更多。

财富再分派的规则

PER_HUMAN_COST_BASE 是每轮交锋的 金钱基数,比如是 10,那么每一轮交锋的开始,两个人都需要拿出 PER_HUMAN_COST_BASE * 0.5 / 2的押金,也就是每人需要拿出 2.5 (10 为例),两个人就是 2.5+2.5=5;同时,每一轮都会产生新的财富,结合每人拿出来的押金,最后重新分配。
新增的财富 的计算方式: 先算平均鸽派指数 再 PER_HUMAN_COST_BASE * (average_pigeon_score-0.5)。比如两只系数为 0 (毫无合作精神) 的鹰,每轮新的财富是 负5;两只 0.5 (佛系) 的,每轮新的财富是 0;而两只都是1(超高合作意识)的鸽,则产生最高值的新财富 5。这样设置有个巧合:两只 0 的鹰两败俱伤,两只 1 的鸽满载而归,全佛系的情况下不增不减。
最后分配财富后,如果一个人,获得高于最先拿出的押金,那么,这一局,他就赢了。
相信人类基因中有利他性的存在,所以,在双赢的情况下 (也就是两只鸽子遇到了),获得的财富会有个系数加成。

每一轮可分配的财富,是两个人的押金+新增的财富。再分配的过程, 谁的武力值高,谁能多分一些。
这里有很微妙的平衡,比如两个鸽,鸽派指数高的,其收益反而会低,但是这轮的结束,两人都是财富增加的状态,并且鸽派指数也会增加。如果是低鸽指数,但又全是鸽子的社会,随着时间的推演,每个人都会变成高指数的鸽子。

变异的概率

人类,是非常多变的。
一个全鸽的社会,一旦有暴力蔓延,它的文明不论多高,都会显得脆弱。人类历史上有太多野蛮铁蹄踏破文明的实例。
所以,我们增加了一个变异的情况,发生的概率为 5%。
原本明明双赢的两只鸽,因为对方突然鹰起来,那么,你就会连押金都亏掉,而对方拿走本轮所有的钱;原本明明是双输的两只鹰,突然良心发现或者天降甘露,每人会额外补贴一次押金,从而进入双赢的状态,两人的 鸽派指数 增加了……

源代码

class Human(object):
    def __init__(self, money=INIT_MONEY, pigeon_score=0.6):
        self.pigeon_score  = pigeon_score
        self.money = money

    @property
    def force_power(self):  # 武力值
        power = (1 - self.pigeon_score) or 0.00001
        if power < 0:
            power = 0.00001
        return float(power)  # 浮点类型 (带小数点)

    @property
    def is_pigeon(self):
        return self.pigeon_score > 0.5

    @property
    def is_dead(self):
        return self.money < 0

    @staticmethod
    def when_money_changed(human, paid, got):
        if paid > got: # 付出 大于 所得,输
            if human.is_pigeon:
                human.pigeon_score -= PIGEON_SCORE_PER_ROUND
            else:
                human.pigeon_score += PIGEON_SCORE_PER_ROUND
        elif got > paid: # 所得 大于 付出,赢
            if human.is_pigeon:
                human.pigeon_score += PIGEON_SCORE_PER_ROUND
            else:
                human.pigeon_score -= PIGEON_SCORE_PER_ROUND

    def meet(self, other_human):
        total_pigeon_score = self.pigeon_score + other_human.pigeon_score
        average_pigeon_score = total_pigeon_score/2.
        cost_per_human = PER_HUMAN_COST_BASE*0.5/2.
        self.money -= cost_per_human
        other_human.money -= cost_per_human
        new_money = PER_HUMAN_COST_BASE * (average_pigeon_score-0.5)
        round_money = cost_per_human * 2 + new_money
        # 进行财富的再分派
        total_force_power = self.force_power + other_human.force_power
        i_got = self.force_power/total_force_power * round_money
        other_got = other_human.force_power/total_force_power * round_money

        # 5% 的概率异变
        wow = random.random() < 0.05
        double_win = i_got>cost_per_human and other_got>cost_per_human
        double_loss = i_got<cost_per_human and other_got<cost_per_human
        if double_win:
            if wow:
                other_got += i_got
                i_got = 0
            else:
                i_got *= DOUBLE_WIN_AWARD
                other_got *= DOUBLE_WIN_AWARD
        if double_loss and wow:
            other_got += cost_per_human
            i_got += cost_per_human

        self.money += i_got
        other_human.money += other_got
        self.when_money_changed(self, paid=cost_per_human, got=i_got)
        self.when_money_changed(other_human, cost_per_human, other_got)

如上 Python 代码衍生的一些基础知识

类 (class) 相当于收纳、整理的空间,我们完成一个 class 的定义后,一般情况下,不会直接拿来用,而是有个实例 (instance) 化 的处理。
比如我们定义了 Human,那么直接 Human.money 是不行的,而是要如下:

human1 = Human()
human2 = Human()

在这个示例中,human1 与 human2 有相同的 class,但两者又是不同的对象 (object),可以查看 id(human1) 以及 id(human2),也就是说,一个 class 可以实例化出很多对象,对象之间又彼此各自独立。
但并不绝对,也是可以做到一个 class 不断实例化的过程中,只产生一个 id 唯一的 实例 (instance),但此处不做解释,估计多数人永远不会用到这种场景。

函数 与 属性,都是一个对象所拥有的,比如 human.money 是一个普通的属性 (面向对象的说法是 property),而human.when_money_changed 是一个函数 (面向对象的说法是 method)。
属性 可以直接拿来用,比如 human.moeny += 1 (相当于 human.money = human.money + 1),而 函数 则需要 调用,比如 human.when_money_changed(other_human, cost_per_human, other_got)。
其实,在 Python 里,对一个 instance (由某个 class 实例化后的对象) 而言,其子函数,也算是它的一个子属性。为了方便理解,就暂时把 函数 和 属性 当做两类事物。

我们看到上面代码中, 有单独一行的 @property,这是 Python 修饰器 的用法,本课程后续会有具体的介绍,在此处的用法,就是让一个 子函数 变为 子属性。如果没有 @property,则 human.is_dead 返回的是一个函数,而有了 @property,则返回 True(真) 或者 False(假) 这些具体的值。
@staticmethod 是另外一个针对 class 性质的 修饰器,class 实例化后,我们看到上面的子函数中的第一个变量是 self,也就是当前实例本身。 staticmethod 的第一个变量不是 self,相当于一个与当前实例无直接关系的函数,但为了方便代码的规整,而放于同一个 class 之内的。类似的还有 @classmethod,此处不再介绍,未来如果有看到这个用法 (比较少用),可以自己搜索引擎具体了解。