2005 年诺贝尔经济学奖得主托马斯·谢林(Thomas Schelling)在上世纪 70 年代就纽约的人种居住分布得出了著名的 Schelling segregation model,这是一个 ABM(agent-based model),当年谢林只能通过铅笔在纸上进行模拟,而这次则利用 Python 进行仿真实现这个模型。
在计算机科学中,基于 Agent 的模型(agent-based models)被用来评估独立(autonomous)agent(诸如个体、群组或物体)在整个系统中的影响。这个强大的分析工具常被用在实验难以开展或者费用太高的时候。在社会科学,计算机科学,经济学和商务各领域,这个模型都有着相当广泛的应用。
在本文中,我会向你介绍用基于 Agent 的模型理解复杂现象的威力。为此,我们会用到一些 Python,社会学的案例分析和 Schelling 模型。
1. 案例分析
如果你观察多民族(multi-ethnic)混居城市的种族(racial)分布,你会对不可思议的种族隔离感到惊讶。举个例子,下面是美国人口普查局(US Census)用种族和颜色对应标记的纽约市地图。种族隔离清晰可见。
许多人会从这个现象中认定人是偏隘的(intolerant),不愿与其它种族比邻而居。然而进一步看,会发现细微的差别。2005 年的诺贝尔经济学奖得主托马斯·谢林(Thomas Schelling)在上世纪七十年代,就对这方面非常感兴趣,并建立了一个基于 Agent 的模型——“Schelling 隔离模型”的来解释这种现象。借助这个极其简单的模型,Schelling 会告诉我们,宏观所见并非微观所为(What’s going down)。
我们会借助 Schelling 模型模拟一些仿真来深刻理解隔离现象。
2. Schelling 隔离模型:设置和定义
基于 Agent 的模型需要三个参数:1)Agents,2)行为(规则)和 3)总体层面(aggregate level)的评估。在 Schelling 模型中,Agents 是市民,行为则是基于相似比(similarity ratio )的搬迁,而总体评估评估就是相似比。
假设城市有 n 个种族。我们用唯一的颜色来标识他们,并用网格来代表城市,每个单元格则是一间房。要么是空房子,要么有居民,且数量为 1。如果房子是空的,我们用白色标识。如果有居民,我们用此人的种群颜色来标识。我们把每个人周边房屋(上下左右、左上右上、左下右下)定义为邻居。
Schelling 的目的是想测试当居民更倾向于选择同种族的邻居(甚至多种族)时会如果发生什么。如果同种族邻居的比例上升到确定阈值(称之为相似性阈值(Similarity Threshold)),那么我们认为这个人已经满意(satisfied)了。如果还没有,就是不满意。
Schelling 的仿真如下。首先我们将人随机分配到城里并留空一些房子。对于每个居民,我们都会检查他(她)是否满意。如果满意,我们什么也不做。但如果不满意,我们把他分配到空房子。仿真经过几次迭代后,我们会观察最终的种族分布。
3. Schelling 模型的 Python 实现
早在上世纪 70 年代,Schelling 就用铅笔和硬币在纸上完成了他的仿真。我们现在则用 Python 来完成相同的仿真。
为了模拟仿真,我们首先导入一些必要的库。除了 Matplotlib
以外,其它库都是 Python 默认安装的。
1
2
3
4
5
|
import matplotlib.pyplot as plt
import itertools
import random
import copy
|
接下来,定义名为 Schelling
的类,涉及到 6 个参数:城市的宽和高,空房子的比例,相似性阈值,迭代数和种族数。我们在这个类中定义了 4 个方法:populate
,is_unsatisfied
,update
,move_to_empty
, 还有 plot
)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
class Schelling:
def __init__(self, width, height, empty_ratio, similarity_threshold, n_iterations, races = 2):
self.width = width
self.height = height
self.races = races
self.empty_ratio = empty_ratio
self.similarity_threshold = similarity_threshold
self.n_iterations = n_iterations
self.empty_houses = []
self.agents = {
}
def populate(self):
....
def is_unsatisfied(self, x, y):
....
def update(self):
....
def move_to_empty(self, x, y):
....
def plot(self):
....
|
poplate
方法被用在仿真的开头,这个方法将居民随机分配在网格上。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
def populate(self):
self.all_houses = list(itertools.product(range(self.width),range(self.height)))
random.shuffle(self.all_houses)
self.n_empty = int( self.empty_ratio * len(self.all_houses) )
self.empty_houses = self.all_houses[:self.n_empty]
self.remaining_houses = self.all_houses[self.n_empty:]
houses_by_race = [self.remaining_houses[i::self.races] for i in range(self.races)]
for i in range(self.races):
# 为每个种族创建 agent
self.agents = dict(
self.agents.items() +
dict(zip(houses_by_race[i], [i+1]*len(houses_by_race[i]))).items()
|
is_unsatisfied
方法把房屋的 (x, y)
坐标作为传入参数,查看同种群邻居的比例,如果比理想阈值(happiness threshold)高则返回 True
,否则返回 False
。
|