基於 Agent 的模型入門:Python 實現隔離模擬
(點擊
上方藍字
,快速關注我們)
編譯:伯樂在線 - Ree Ray
如有好文章投稿,請點擊 → 這裡了解詳情
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 默認安裝的。
import
matplotlib
.
pyplot
as
plt
import
itertools
import
random
import
copy
接下來,定義名為 Schelling 的類,涉及到 6 個參數:城市的寬和高,空房子的比例,相似性閾值,迭代數和種族數。我們在這個類中定義了 4 個方法:populate,is_unsatisfied,update,move_to_empty, 還有 plot)。
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 方法被用在模擬的開頭,這個方法將居民隨機分配在網格上。
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。
def
is_unsatisfied
(
self
,
x
,
y
)
:
race
=
self
.
agents
[(
x
,
y
)]
count_similar
=
0
count_different
=
0
if
x
>
0
and
y
>
0
and
(
x
-
1
,
y
-
1
)
not
in
self
.
empty_houses
:
if
self
.
agents
[(
x
-
1
,
y
-
1
)]
==
race
:
count_similar
+=
1
else
:
count_different
+=
1
if
y
>
0
and
(
x
,
y
-
1
)
not
in
self
.
empty_houses
:
if
self
.
agents
[(
x
,
y
-
1
)]
==
race
:
count_similar
+=
1
else
:
count_different
+=
1
if
x
<
(
self
.
width
-
1
)
and
y
>
0
and
(
x
+
1
,
y
-
1
)
not
in
self
.
empty_houses
:
if
self
.
agents
[(
x
+
1
,
y
-
1
)]
==
race
:
count_similar
+=
1
else
:
count_different
+=
1
if
x
>
0
and
(
x
-
1
,
y
)
not
in
self
.
empty_houses
:
if
self
.
agents
[(
x
-
1
,
y
)]
==
race
:
count_similar
+=
1
else
:
count_different
+=
1
if
x
<
(
self
.
width
-
1
)
and
(
x
+
1
,
y
)
not
in
self
.
empty_houses
:
if
self
.
agents
[(
x
+
1
,
y
)]
==
race
:
count_similar
+=
1
else
:
count_different
+=
1
if
x
>
0
and
y
<
(
self
.
height
-
1
)
and
(
x
-
1
,
y
+
1
)
not
in
self
.
empty_houses
:
if
self
.
agents
[(
x
-
1
,
y
+
1
)]
==
race
:
count_similar
+=
1
else
:
count_different
+=
1
if
x
>
0
and
y
<
(
self
.
height
-
1
)
and
(
x
,
y
+
1
)
not
in
self
.
empty_houses
:
if
self
.
agents
[(
x
,
y
+
1
)]
==
race
:
count_similar
+=
1
else
:
count_different
+=
1
if
x
<
(
self
.
width
-
1
)
and
y
<
(
self
.
height
-
1
)
and
(
x
+
1
,
y
+
1
)
not
in
self
.
empty_houses
:
if
self
.
agents
[(
x
+
1
,
y
+
1
)]
==
race
:
count_similar
+=
1
else
:
count_different
+=
1
if
(
count_similar
+
count_different
)
==
0
:
return
False
else
:
return
float
(
count_similar
)
/
(
count_similar
+
count_different
)
<
self
.
happy
_
thresholdupdate 方法將查看網格上的居民是否尚未滿意,如果尚未滿意,將隨機把此人分配到空房子中。並模擬 n_iterations 次。
def
update
(
self
)
:
for
i
in
range
(
self
.
n_iterations
)
:
self
.
old_agents
=
copy
.
deepcopy
(
self
.
agents
)
n_changes
=
0
for
agent
in
self
.
old_agents
:
if
self
.
is_unhappy
(
agent
[
0
],
agent
[
1
])
:
agent_race
=
self
.
agents
[
agent
]
empty_house
=
random
.
choice
(
self
.
empty_houses
)
self
.
agents
[
empty_house
]
=
agent_race
del
self
.
agents
[
agent
]
self
.
empty_houses
.
remove
(
empty_house
)
self
.
empty_houses
.
append
(
agent
)
n_changes
+=
1
n_changes
if
n_changes
==
0
:
break
move_to_empty 方法把房子坐標(x, y)作為傳入參數,並將 (x, y) 房間內的居民遷入空房子。這個方法被 update 方法調用,會把尚不滿意的人遷入空房子。
def
move_to_empty
(
self
,
x
,
y
)
:
race
=
self
.
agents
[(
x
,
y
)]
empty_house
=
random
.
choice
(
self
.
empty_houses
)
self
.
updated_agents
[
empty_house
]
=
race
del
self
.
updated_agents
[(
x
,
y
)]
self
.
empty_houses
.
remove
(
empty_house
)
self
.
empty_houses
.
append
((
x
,
y
))
plot 方法用來繪製整個城市和居民。我們隨時可以調用這個方法來了解城市的居民分布。這個方法有兩個傳入參數:title 和 file_name。
def
plot
(
self
,
title
,
file_name
)
:
fig
,
ax
=
plt
.
subplots
()
# 如果要進行超過 7 種顏色的模擬,你應該相應地進行設置
agent_colors
=
{
1
:
"b"
,
2
:
"r"
,
3
:
"g"
,
4
:
"c"
,
5
:
"m"
,
6
:
"y"
,
7
:
"k"
}
for
agent
in
self
.
agents
:
ax
.
scatter
(
agent
[
0
]
+
0.5
,
agent
[
1
]
+
0.5
,
color
=
agent_colors
[
self
.
agents
[
agent
]])
ax
.
set_title
(
title
,
fontsize
=
10
,
fontweight
=
"bold"
)
ax
.
set_xlim
([
0
,
self
.
width
])
ax
.
set_ylim
([
0
,
self
.
height
])
ax
.
set_xticks
([])
ax
.
set_yticks
([])
plt
.
savefig
(
file_name
)
4. 模擬
現在我們實現了 Schelling 類,可以模擬模擬並繪製結果。我們會按照下面的需求(characteristics)進行三次模擬:
寬 = 50,而高 = 50(包含 2500 間房子)
30% 的空房子
相似性閾值 = 30%(針對模擬 1),相似性閾值 = 50%(針對模擬 2),相似性閾值 = 80%(針對模擬 3)
最大迭代數 = 500
種族數量 = 2
創建並「填充」城市。
schelling_1
=
Schelling
(
50
,
50
,
0.3
,
0.3
,
500
,
2
)
schelling_1
.
populate
()
schelling_2
=
Schelling
(
50
,
50
,
0.3
,
0.5
,
500
,
2
)
schelling_2
.
populate
()
schelling_3
=
Schelling
(
50
,
50
,
0.3
,
0.8
,
500
,
2
)
schelling_3
.
populate
()
接下來,我們繪製初始階段的城市。注意,相似性閾值在城市的初始狀態不起作用。
schelling_1_1
.
plot
(
"Schelling Model with 2 colors: Initial State"
,
"schelling_2_initial.png"
)
下面我們運行 update 方法,繪製每個相似性閾值的最終分布。
schelling_1
.
update
()
schelling_2
.
update
()
schelling_3
.
update
()
schelling_1
.
plot
(
"Schelling Model with 2 colors: Final State with Similarity Threshold 30%"
,
"schelling_2_30_final.png"
)
schelling_2
.
plot
(
"Schelling Model with 2 colors: Final State with Similarity Threshold 50%"
,
"schelling_2_50_final.png"
)
schelling_3
.
plot
(
"Schelling Model with 2 colors: Final State with Similarity Threshold 80%"
,
"schelling_2_80_final.png"
)
觀察以上的繪圖,我們可以發現相似性閾值越高,城市的隔離度就越高。此外,我們還會發現即便相似性閾值很小,城市依舊會產生隔離。換言之,即使居民非常包容(tolerant)(相當於相似性閾值很小),還是會以隔離告終。我們可以總結出:宏觀所見並非微觀所為。
5. 測量隔離
以上模擬,我們只通過可視化來確認隔離發生。然而,我們卻沒有對隔離的計算進行定量評估。本節我們會定義這個評估標準,我們也會模擬一些模擬來確定理想閾值和隔離程度的關係。
首先在 Schelling 類中添加 calculate_similarity 方法。這個方法會計算每個 Agent 的相似性並得出均值。我們會用平均相似比評估隔離程度。
def
calculate_similarity
(
self
)
:
similarity
=
[]
for
agent
in
self
.
agents
:
count_similar
=
0
count_different
=
0
x
=
agent
[
0
]
y
=
agent
[
1
]
race
=
self
.
agents
[(
x
,
y
)]
if
x
>
0
and
y
>
0
and
(
x
-
1
,
y
-
1
)
not
in
self
.
empty_houses
:
if
self
.
agents
[(
x
-
1
,
y
-
1
)]
==
race
:
count_similar
+=
1
else
:
count_different
+=
1
if
y
>
0
and
(
x
,
y
-
1
)
not
in
self
.
empty_houses
:
if
self
.
agents
[(
x
,
y
-
1
)]
==
race
:
count_similar
+=
1
else
:
count_different
+=
1
if
x
<
(
self
.
width
-
1
)
and
y
>
0
and
(
x
+
1
,
y
-
1
)
not
in
self
.
empty_houses
:
if
self
.
agents
[(
x
+
1
,
y
-
1
)]
==
race
:
count_similar
+=
1
else
:
count_different
+=
1
if
x
>
0
and
(
x
-
1
,
y
)
not
in
self
.
empty_houses
:
if
self
.
agents
[(
x
-
1
,
y
)]
==
race
:
count_similar
+=
1
else
:
count_different
+=
1
if
x
<
(
self
.
width
-
1
)
and
(
x
+
1
,
y
)
not
in
self
.
empty_houses
:
if
self
.
agents
[(
x
+
1
,
y
)]
==
race
:
count_similar
+=
1
else
:
count_different
+=
1
if
x
>
0
and
y
<
(
self
.
height
-
1
)
and
(
x
-
1
,
y
+
1
)
not
in
self
.
empty_houses
:
if
self
.
agents
[(
x
-
1
,
y
+
1
)]
==
race
:
count_similar
+=
1
else
:
count_different
+=
1
if
x
>
0
and
y
<
(
self
.
height
-
1
)
and
(
x
,
y
+
1
)
not
in
self
.
empty_houses
:
if
self
.
agents
[(
x
,
y
+
1
)]
==
race
:
count_similar
+=
1
else
:
count_different
+=
1
if
x
<
(
self
.
width
-
1
)
and
y
<
(
self
.
height
-
1
)
and
(
x
+
1
,
y
+
1
)
not
in
self
.
empty_houses
:
if
self
.
agents
[(
x
+
1
,
y
+
1
)]
==
race
:
count_similar
+=
1
else
:
count_different
+=
1
try
:
similarity
.
append
(
float
(
count_similar
)
/
(
count_similar
+
count_different
))
except
:
similarity
.
append
(
1
)
return
sum
(
similarity
)
/
len
(
similarity
)
接下去,我們算出每個相似性閾值的平均相似比,並繪製出相似性閾值和相似比之間的關係。
similarity_threshold_ratio
=
{}
for
i
in
[
0
,
0.1
,
0.2
,
0.3
,
0.4
,
0.5
,
0.6
,
0.7
]
:
schelling
=
Schelling
(
50
,
50
,
0.3
,
i
,
500
,
2
)
schelling
.
populate
()
schelling
.
update
()
similarity_threshold_ratio
[
i
]
=
schelling
.
calculate_similarity
()
fig
,
ax
=
plt
.
subplots
()
plt
.
plot
(
similarity_threshold_ratio
.
keys
(),
similarity_threshold_ratio
.
values
(),
"ro"
)
ax
.
set_title
(
"Similarity Threshold vs. Mean Similarity Ratio"
,
fontsize
=
15
,
fontweight
=
"bold"
)
ax
.
set_xlim
([
0
,
1
])
ax
.
set_ylim
([
0
,
1.1
])
ax
.
set_xlabel
(
"Similarity Threshold"
)
ax
.
set_ylabel
(
"Mean Similarity Ratio"
)
plt
.
savefig
(
"schelling_segregation_measure.png"
)
通過上圖,可以發現即便相似性閾值非常小,依然會得到很高的隔離度(由平均相似性評估)。舉個例子,相似閾值為 0.3,會得到 0.75 的平均相似性。我們可以通過量化再次確定宏觀所見並非微觀所為。
6. 總結
在本文中,我們介紹了一個基於 Agent 的模型——「Schelling 隔離模型」,並用 Python 實現。這個十分簡單的模型幫助我們理解了非常複雜的現象:多民族城市中的種族隔離。我們可以發現城市種族的高度隔離不必解讀成個體層面的偏隘。
參考
https://www.coursera.org/course/modelthinking
看完本文有收穫?請轉
發分享給更多人
關注「P
ython開發者」,提升Python技能
※Python超級程序員使用的開發工具
※編程語言中的超級英雄們:Python如同蝙蝠戰車,Ruby可比蝙蝠俠……
※用Python攻破12306的最後一道防線,我看一遍就會了!
※一件有趣的事:我用 Python 爬了爬自己的微信朋友
※Python如何解析fasta格式,並儲存為字典?
TAG:Python |
※Python yield與實現
※基於Tensorflow實現DeepFM
※Occipital推出MR創作工具Bridge Engine,實現密實3D映射
※基於google protobuf的gRPC實現
※用Pytorch 實現的 Capsule Network
※用 greenlet 實現 Python 中的並發
※AspectJ 框架 spring 實現 AOP?
※Wormhole:一種基於Bitcoin Cash的智能合約實現方案
※Machine Learning(一):基於 TensorFlow 實現寵物血統智能識別
※CapsNet入門系列番外:基於TensorFlow實現膠囊網路
※SAP Cloud for Customer Extensibility的設計與實現
※深入 Spring Boot :實現對 Fat Jar jsp 的支持
※SpringCloud如何實現Eureka集群、HA機制?
※用Python 實現的大規模線性回歸、分類和排名庫——lightning
※用TensorFlow 實現的模型集合
※實現高速點擊的 Android Monkey 自動化工具 fastmonkey-代號 Maxim
※ArrayList,LinkedList,Vector基本原理與實現
※使用Tensorflow Object Detection API實現對象檢測
※CentOS+OpenVZ+Vtonf實現Linux虛擬化
※Andrew Ng經典機器學習課程的Python實現2