《自私的基因》书评

对于流行的东西我总有意无意保持一定的距离,《自私的基因》也属于此类。这本书在我上大学时就已十分流行,我在许多场合、听许多人谈起过这本书,但一直没有下定决心去读。大概因为这个名字过于标新立异,透漏着一种想要以奇特的理论来震撼读者的刻意。最近重点在读人类学的书,于是终于决心翻出来认真读一读。

这本书主要从基因的自私性出来解释个体和群体中的行为,尤其是最受传统进化论困扰的利他的行为

简单的说,这本书的理论框架是这样的:

  1. 基因是进化的基本单位,所谓基因是控制某个生物形状或特性的染色体片段。
    个体或者群体都不是进化的基本单位。
  2. 基因进化的竞争对手是控制同一个性状或特性的等位基因。
  3. 基因的进化的目标是为了提高自己在整个等位基因库中的比例。
  4. 基因进化的一切目标都是以此为出发点,是完全利己的。
  5. 基因进化主要通过控制个体的形状或特性来实现。
  6. 不同的形状或者遗传特性最终会为个体带来不同的繁殖机会,从而影响控制这一特性的基因在后代中的比例。
  7. 基因和等位基因之间的竞争会最终达到一个稳定状态,各个等位基因在基因库中所占的比例保持稳定。
  8. 带来这个稳定状态的策略是进化稳定策略(evolution stable strategy),稳定策略是不同策略博弈的产物。
  9. 基因的竞争并不是单一的,而是相互影响的,最后可能造成不同基因相互配合的效果。
  10. 生物的行为包括利他的行为都可以用基因的自私性来解释。

这本书最核心的概念是ESS,也就是进化稳定策略。ESS的特别之处在于,它从博弈论的角度来研究带来不同竞争策略的基因是如何相互影响并最终达成平衡的。作者几乎花了一半的篇幅在讲解如何使用ESS这个概念解释生物的行为,其中着墨最多也最有趣的是生物的性选择策略,这里可以简要介绍一下。
 

生物性选择的基础是两性生育和抚养后代的成本的不对等。这种不对等主要体现在两性的配子的不对等(出现这种不对等的原因作者在书中有介绍)以及孕育后代的不对等上。雌性和雄性在博弈过程中的优化目标都是尽可能多地繁殖带有自己基因的后代,然而不对等的生殖成本带来了雄性和雌性在性选择策略上的差异。作者归纳一般有两种性选择的策略,幸福家庭类以及大丈夫类的。

幸福家庭类的策略中,雌性选择延长决定时间来考验男性的忠诚程度,进而推断出其照料后代的机会和能力,雄性选择通过大量前期投入来证明自己付出的意愿和能力。这两种类型之外还有两个变动因素是浪荡的雌性和薄情雄性,前者会和任何后代交配但并不会养育后代,后者会在交配后抛弃雌性和幼子。浪荡的雌性会有更多的机会和诚实的雄性集合,但是随着浪荡雌性比例在后代中的提高,薄情雄性的机会也随着增大了, 最后养育成本还是会到浪荡雌性一方,而对着薄情雄性的增多,忠诚雌性的优势就会增加,比起被抛弃,不产生交配的净收益更大,因此忠诚雌性对浪荡雌性更具有优势。而对着忠诚雌性比例的增加,薄情雄性的数量就会相应下降了,因为无法找到足够进行交配的雌性。这四种性选择的个体的比例就会在这样的动态博弈之中到达平衡。
 

当然作者还用自私基因的理论介绍了更多生物的行为,比如在有些鱼类当中,主要依靠雄性照顾后代。这主要是因为雌性会先排出卵子并逃走,从而将照料后代的任务转嫁到雄性一方。作者通过这些例子有效的证明了自私的基因这个理论所具有的强大说服力。用一个简洁有力的理论对诸多现象进行剖析本身是十分有趣的,更多的例子以后有机会再单独拿出来介绍。 

作者还花费了很多篇幅批判群体进化论。所谓群体进化论是认为生物的利他行为是出于对整个群体或者物种的利益的考虑,也即牺牲自己造福种群的理论。作者认为,这一概念的根源其一是人们将进化的单位上升到了群体,其二是因为这种理论契合了人类的道德感召的需求,即对牺牲自我的利益以换取更多人利益的这种利他主义的感召(这在许多宗教中非常常见)。但这个理论的根源问题在于,让生物在长期物种的利益和短期个体的利益之间选择前者无疑是不现实的,另一方面,种群进化的理论在很多例子当中是有解释力不足的问题的。

在最后一章中,作者还提出了一个文化觅母(meme)的概念。meme指人类的文化观念。meme的发展目标与基因类似,就是最大程度上的复制自己。但是meme的利益并不总是和基因的利益相同。比如有越来越多的家庭选择不生育孩子,这种观念就与基因传播自己到下一代的利益相悖。但平衡指出在于,如果二者相悖太远,meme传播的基础人类载体本身就会减少,从而降低自己被传播的机会。 

人类行为的特殊性在于,除了受到遗传的影响,还会受到文化的影响。在本书中,作者一直强调,从进化的观点看,生物只是运载和复制基因的载体。同样的,人也可以看作是文化观念的载体,文化不断发生,发展,传播,演变,和基因的演化和气相似。而这种相似性正是meme这个概念所想要揭示的规律。

 
当然,自私基因的理论如此强大,以至于有人会产生人类的高尚行为背后不过是基因的意志这种感觉。实际上,在很多年前初次知道这种理论的时候我也感到有些困惑:这和历史上的种种还原论何其相似,人类的行为被还原到了化学物理层面,现在又被还原到了生物层面,那么人类的这些高尚的可贵的行为真的包含了值得赞颂的东西吗?

我的回答是:人具有自由意志。尽管人类的行为强烈的受到基因的影响甚至控制,但是人类有足够的自由意志可以克服甚至消除这种影响,而这才是人类最可贵的地方。父母对子女的爱固然包含着基因的自私性这一根源,但是我们无法否认他们事实上的付出、奉献和关爱,理解了这些才会更加感到人的可贵。 

Python Notes: Global Interpreter Lock

Why Python use GIL

Python uses reference count for memory management. Each objects in python has a reference count variables that keep track of the number of reference to the object. When the reference count goes back to 0, Python would release this object from memory.

However, in multi threading scenario, multiple thread might access the same object, and object reference count could be changed incorrectly in race conditions. Then objects that should be released could still stay in memory and worst case, objects that should not be release are incorrectly released.

To solve this problem, python introduced GIL, which is a global lock in python interpreter level. The rules is that, any python code has to acquire this lock to be executed. You might ask why not add one lock to each objects? This could result in deadlock.

In this way, python code guarantees that only one thread would be able to change the object reference count.

Problem of GIL

The GIL solution, however has problems in that Python code would not be able to utilize multi CPUs. If your code is CPU intensive, python multi-thread would not help you at all. However, if you program is not CPU intensive, but I/O intensive, for example, network application, Python thread is still a good choice.

Solution to this problem?

Are there solutions to the problem? Yes, there are. Python community has tries many times to solve this problem. Python GIL is not really tie to python language it self, it ties to Python interpreter it self. So, as long as we change the underlying python interpreter, python could support multithread. For example, Jython, is implemented in Java.

However, another important reason why Python GIL is not removed is that python has many extended libraries that are writing in C. Those libraries works well with Python in that they don’t need to worry about multi-thread models, the GIL model is really easy to integrate. Moving those libraries to other interpreters are hard works.

Another solution is to use multiprocess instead of multithread. Python has good designed libraries that supports multiprocess. However, process management would have more overhead than thread management for operating system, which means the performance of multiprocess programs are worse than multithreads.

Python Notes: Decorator

By definition, a decorator is a function that takes another function and extends the behavior of the latter function without explicitly modifying it. Built-in decorators such as @staticmethod, @classmethod, and @property work in the same way.

How decorator works? Let's take a look at the following example:

def my_decorator(some_func):
    def wrapper():
        some_func()
        print("Some function is being called.")

    return wrapper

def another_func():
    print("Another function is being called.")

foo = my_decorator(another_func)

foo()

You would see the following print on the screen:

"Another function is being called."
"Some function is being called."

As you can see, we pass some_func into a closure, and do something before or after calling this function without modifying its original behavior, and we return the function. As we already learned, python functions are just like other python objects, they are first class objects. The returned function could be called just as any other functions.

@decorator

The above example is already very similar to decorator. The difference is that decorator often comes with a @ symbol. This is an example of python syntax sugar, which often refers to syntax in a programming language that aims to make the things easy to read or to express. For example, the following is an example of a decorator:

@my_decorator
def another_func():
    print("Another function is being called.")

Decorator that takes any argument

In python, we can use *args and **kargs to represent arbitrary arguments. The following example shows how to take arbitrary arguments in a decorator:

def proxy(func):
    def wrapper(*args, **kargs):
        return func(*args, **kargs)
    return wrapper

Decorator with parameters

Sometimes we want the decorator to take parameters, for example, we should implement it in this way:

def decorator(argument):
    def real_decorator(function):
        def wrapper(*args, **kwargs):
            do_something_with_argument(argument)
            result = function(*args, **kwargs)
        return wrapper
    return real_decorator

Decorator tips

One practical tips when defining decorator is to use the functoolss.wraps, this function would keep all the meta data information of the original functions, including the function signature and docstring information.

import functools

def uppercase(func):
    @functools.wraps(func)
    def warpper():
        return func()
    return wrapper

Python Notes: function as first class object

Per history of python blog, everything in python are first class objects, that means all objects that could be named in the language (e.g., integers, strings, functions, classes, modules, methods, etc.) to have equal status. Th:at is, they can be assigned to variables, placed in lists, stored in dictionaries, passed as arguments, and so forth..

Essentially, functions return a value based on the given arguments. In Python, functions are first-class objects as well. This means that functions can be passed around, and used as arguments, just like any other value (e.g, string, int, float).

Internally, python use a common C data structure that are used everywhere in the interpreter to represent all objects, either it is a python function or a integer.

However, when it comes to python function as first class, there are subtle things to think about when doing design.

Think about the following function definition:

class A:
    def __init__(self, x):
        self.x = x

    def foo(self, bar):
        print self.x, bar

What would happen if you assign A.foo to a variable: b = A.foo. The first argument of the function would have to be the instance itself. To handle this problem, python 2 returns a unbound method, which is a warper around the original function, but it restrict that the first argument of the function has to be the object instance: a = A(), b(a). In python 3, however, this restriction is removed as the author found this is not very useful.

Let’s think about the second condition, when you have a instance of a class: a = A(1), b = a.foo. In this case, python would return a bound method which is a thin wrapper around the original function. Bound method stores the instance as a internal object and this object would be the default first argument when calling this function.

马尔萨斯和《人口原理》

马尔萨斯是十八世纪英国的人口学家和政治经济学家。他最被广为人知的作品是《人口论》,而其中又以马尔萨斯人口陷阱这一概念最为人所熟知。

马尔萨斯陷阱

马尔萨斯人口陷阱(Malthusian Trap)是说:

  • 在没有限制的情况下,人口会呈现指数型增长
  • 食物只会呈现现行增长
  • 人口的增长一定会超过食物增长,从而导致食物不足

马尔萨斯认为,人类有两种方式会避免或者延缓这一情况的出现:

  • 有意识的晚婚晚育。
  • 缩减人类寿命的时间,比如战争,瘟疫,饥荒等。

需要进一步阐明的事,马尔萨斯所说的极限,是说在系统达到均衡的条件下,处在社会最底层的那部分人永远只能生活在勉强温饱的生存线上。至于处于社会底层的人口数量有多大,这是随着社会制度不同而不同的。

马尔萨斯为什么会认为人口数量会有极限呢?因为存在着生产的极限,在极限附近,生产投入的回报是递减的,也就是说,随着投入的增加,单位产出实际上是减少的。为什么我们没有陷入马尔萨斯所说的陷阱当中呢?因为技术的进步,我们不断发现更新更有效的生产方法,使得生产的边际回报是递增的。另一方面,主要是因为现代发达国家的生育水平逐渐下降,有些国家甚至到了低于代际更替水平一下,带来实际人口的减少。这主要是城市化造成的,城市化带来和生育孩子机会成本的提高:妇女的劳动价值越来越高,为了生育小孩儿放弃的成本也就会越来越高。另一方面是,多生孩子意味着需要稀释在每个孩子身上的投入,从而可能降低生育质量。

工资铁律

从马尔萨斯定律推论出来的一个理论是工资铁律。工资铁律是说,对于非技术工人,实物工资在长期将永远倾向于接近仅足够维持工人最低生活所需的水平。工资铁律描述的启示是马尔萨斯的模型出于边界时的情形,如果工资继续降低,工人将无法维持生活,工人数量将停止增加,如果工资升高,工人数量将会上升,压低劳动成本,从而使得工资回到最低水平。但是我们并未看到工资铁律的实际出现,这是因为,新的技术,投资导致市场对于劳动力需求的增大,避免了因为劳工过剩而带来的工资降低。

马尔萨斯弹簧

马尔萨斯理论的另一个衍生是马尔萨斯弹簧。马尔萨斯弹簧模型是用于解释文明诞生所需要的剩余如何发生。我们都知道,因为有这些剩余,一个社会才会有能力供养不进行实际劳作的人口,从而使他们有机会发展科技,文学,艺术等等。但是为什么会产生这些剩余呢?常见的误区是认为这是由于技术带来的生产力的提高,导致实际产出多于供养人口所需要的实物数量,于是便产生了剩余。但是根据马尔萨斯的理论,人口数量总是会趋近于极限水平,也就是剩余为零的水平,这样的人口增长只需要几代人便可以实现。但是为何这种情况没有发生呢?一个重要原因就是税赋,因为税赋的存在,劳动人群的产出被拿走一部分,使得实际的粮食产出降低,人口也就不会增加到极限水平。这种因为税赋带来的对于人口的抑制作用便称为马尔萨斯弹簧。

不得不说这种解释是包含着很多洞见的,如果从税赋的最终效果来看,它们确实被用来供养一个广大的上层阶级,从而促进了社会的发展。但是另一方面,实际非劳动人口之所以可以生存下来并将它们的技艺发展提高,也是因为他们的产品实际上是被需要的。比如手工艺人制作的农具,比如喜剧演员的表演,都是劳动人群愿意购买的产品和服务,是市场价值,而不是强制的税赋供养了这些非劳动人口。单着并不意味着马尔萨斯弹簧的实效,实际上,马尔萨斯的理论强调的长期的趋势。马尔萨斯认为,从长期看,人们的生活水平总是因为实际人口的增加而降低的,而生活水平的降低意味着购买能力的降低,供养非劳动人口的市场缩小,文明的发展因此会被抑制。因此可以说税赋确实是长期一直人口增长的有效弹簧。

马尔萨斯与进化论

马尔萨斯的理论也启发了达尔文发明生物进化理论。生物进化理论要论证的是生物是通过不断在适应环境的过程中进化而来,而这是结果,生物进化的动因在于生存压力,其中最重要的压力之一是食物资源的压力,因为没有足够的食物资源,生物群体内部同样要发生激烈竞争。

马尔萨斯在起人口论的第十八章中阐明了人口压力对于文明发展的推动作用。马尔萨斯认为,正因为人口永远会朝向极限的状态发展,资源永远都是不足的,因此富有才智的人们才会愿意去努力发展自己。如果谁也不想在社会的阶梯上往上爬,谁也不担心会从社会的阶梯上摔落下来,如果勤劳得不到奖励,懒惰得不到惩罚,中产阶级就肯定不是现在这种样子了。 了解到奋斗这个过程的无可避免,或许是我们在奋斗路上的一个小小安慰。

Python Notes: Iterator, Generator and Co-routine

Iteration

Python support iteration, for example, iterating over a list:

for elem in [1, 2, 3]:
    print elem

Iterating over a dict:

for key in {'Google': 'G',
              'Yahoo': 'Y',
              'Microsoft': 'M'}:
    print key

Iterating over a file:

with open('path/to/file.csv', 'r') as file:
    for line in file:
        print line

We use iterable objects in many ways, for example, reductions: sum(s), min(s), constructors: list(s), in operators: item in s.

The reason why we can iterate over iterable is because of iterable protocols: any objects that supports iter() and next() is an itterable. For example, we can define one itterable object in the following way:

class count:

def __init__(self, start):
    self.count = start

def __iter__(self):
    return self

Def next(self):
    if self.count < 0:
        raise StopIteration
    r = self.count
    self.count -=1
    return r

We can use the above example in this way:

c = count(5)
for i in c:
    print I
# 5, 4, 3, 2, 1

Generator

So what is a generator? By definition: a generator is a function that produces a sequence of results instead of a single value.

So generator is a function, it is different from other functions that it generates a sequence of results instead of a single value. Generator function is very different from normal function, calling the generator function will create one generator, but would not execute it, until next() is called. The following is an example of generator:

def count(n):
    while n > 0:
        yield n
        n -= 1

c = count(5)

Note that when we first initiate count, it won't execute. Until the first time we call, c.next() the generator would start to execute. But it will suspend on the yield command, until next time it executes.

So to speak, a generator is a convenient way of writing an iterator, and you don't have to worry about iterator protocols.

Except for yield based generator function, python also supports generator expression:

a = [1, 2, 3, 4]
b = [x*2 for x in a]
c = (x*2 for x in a)

b is still a regular list, while c is a generator.

Co-routine

Python coroutine is very similar to generator. Think about the following pattern:

def receive_count():
    try:
        while True:
            n = (yield) # Yield expression
            print "T-minues ", n
    except GeneratorExit:
        print "Exit from generator."

The above form of generator is called coroutine. Coroutine is different from generator in that it receives data instead of generates data. Think of it as a consumer or receiver.

To use python co-routine, you need to call next() first so that the function executes to the yield field part, then you can use send to send the value to the function. For example:

    c = receive_count()
    c.next() # trigger to yield function
    c.send(1) # sending 1 to the co-routine.
    # prints "T-minus 1"

Python provided a decorator called @consumer to execute the next() function part. With the consumer decorator, the co-routine can be used directly.

Then the question is: why don't we just declare co-routine as a regular function where you can send the value to it directly instead of relying on the yield expression? Using coroutine in the given examples doesn't fully justify it's value. More often, people use co-routine to implement a application level multiple threading. I will introduce more about this later.

Python Notes: Context management

Python supports context management. Which often used when handling resources, for example, file, network connection. With statement helps make sure the resources are cleaned up or released. There are two major functions for context management: __enter__ and __exit__.

__enter__ is triggered when the with statement is first triggered, while __exit__ statement is triggered when the statement finishes execution.

One very common usage of with statement is when we open up files:

    with open('/path/to/file', 'r') as file:
        for line in file():
            print(line)

The with statement on this example will automatically close the file descriptor no matter how this with block exits.

Python with statement also supports nesting, for example:

    with open('/open/to/file', 'r') as infile:
        with open('write/to/file', 'w') as outfile:
            for line in infile:
                if line.startswith('Test'):
                    outfile.write(line)

If you want your code to support with statement based context management, just override the two functions on your code, for example:

class Resource:
    def __init__(self, res):
        self.res = res

    def __enter__(self):
        #define your code here

    def __exit__(self, exc_type, exc_value, traceback):
        #define your code here

There is another way to support context management, which is to use the contextlib.contextmanager, we will introduce this later.