做多目標(biāo)跟蹤的時候會碰到這個算法,每個人都有自己的說法講清楚這個算法是干什么的?我的老師就跟我說過是什么給工人分配活干(即理解為指派問題
),網(wǎng)上還看到有說紅娘盡可能匹配多的情侶等,透過這些感性理解,基本上就能理解大概是最大匹配的問題了。
然后加了限制:后來者優(yōu)先。即后匹配的能搶掉前人已匹配的對象,這個是有數(shù)學(xué)依據(jù)還是只是一種實(shí)現(xiàn)思路我就沒深究了。
我的理解不會比別人更高級,之所以能用一張圖說清楚,只不過是我作圖的時候發(fā)現(xiàn)可以把過程畫在一張圖里,只需要把圖示標(biāo)清楚就好了,這樣就不需要每一步畫一張圖了,一旦理解了,哪怕忘了,一瞅這張圖也能立刻回憶起來。
先上數(shù)據(jù):
import numpy as np
relationship_matrix = np.array([
[1,1,0,1,0,0,0],
[0,1,0,0,1,0,0],
[1,0,0,1,0,0,1],
[0,0,1,0,0,1,0],
[0,0,0,1,0,0,0],
[0,0,0,1,0,0,0]
], dtype=bool)
你可以理解為6個工人,7個工作,6個男孩,7個女孩等,當(dāng)然,6行7列,這么直觀理解也是一點(diǎn)問題都沒有的。
算法匹配過程如下:
hungarian_algorithm.png
- 灰藍(lán)線就是被搶掉的
- 綠線就是搶奪失敗的
-
紫線
是被搶了后找候選成功的 -
紅線
是一次性成功的
其中被搶的和搶奪失敗的還加了刪除線,這是為了強(qiáng)調(diào)。匹配成功的就是紅線
和紫線
,也就是說,我們匹配出來的是:
[0,1], [1,4], [2,0], [3,2], [4,3]
甚至可以這么表示這個過程:
x0,y0
x1,y1
x2,y0 -> x0,y1 -> x1->y4 (x2搶x0的,x0搶x1的)
x3,y2
x4,y3
x5,y3 -> x4匹配不到新的,搶奪失敗,-> x5,null
有沒有說清楚?就兩步:
- 根據(jù)關(guān)聯(lián)表直接建立關(guān)系
- 如果當(dāng)前
C
匹配的對象已經(jīng)被B
匹配過了,那么嘗試把它搶過來:
-
B
去找別的匹配- 找到了(
A
)就建立新的匹配- 如果新的匹配(
A
)也已經(jīng)被別人(D
)匹配了,那么那個“別人(D
)”也放棄當(dāng)前匹配去找別的(遞歸警告)
- 如果新的匹配(
- 如果找不到新的匹配,那么
C
搶奪失敗,遞歸中的D
也同理,失敗向上冒泡
- 找到了(
注意遞歸怎么寫代碼就能寫出來了:
nx, ny = relationship_matrix.shape # 6個x,7個y
# 如果x0與y0關(guān)聯(lián),x3也與y0關(guān)聯(lián),那么x0去找新的匹配時,需要把y0過濾掉
# 同理x0如果找到下一個y2,y2已被x2關(guān)聯(lián),那么x2找新的匹配時[y0, y2]都需要過濾掉
# 我們把這個數(shù)組存為y_used
y_used = np.zeros((ny,), dtype=bool) # 存y是否連接上
path = np.full((ny,), -1, dtype=int) # 存x連接的對象,沒有為-1
def find_other_path_and_used(x):
for y in range(ny):
if relationship_matrix[x, y] and not y_used[y]:
y_used[y] = True # 處于爭奪中的y,需要打標(biāo),在后續(xù)的遞歸時要過濾掉
if path[y] == -1 or find_other_path_and_used(path[y]):
path[y] = x # 直接連接 和 搶奪成功
return True
return False # 搶奪失敗 和 默認(rèn)失敗
for x in range(nx):
y_used[:] = False # empty
find_other_path_and_used(x)
for y, x in enumerate(path):
if x != -1:
print(x, y)
真的寫代碼實(shí)現(xiàn)的時候,難點(diǎn)反而是y_used
這個,第一遍代碼沒考慮這一點(diǎn),導(dǎo)致遞歸的時候每次都從開始而出現(xiàn)死循環(huán),意識到后把處于爭搶狀態(tài)中的
y
打個標(biāo)就好了。
scipy中有一個算法實(shí)現(xiàn)了Hungarian algorithm:
from scipy.optimize import linear_sum_assignment
# relationship_matrix是代價矩陣
# 所以我們要代價越小越好,就用1來減
rows, cols = linear_sum_assignment(1-relationship_matrix)
list(zip(rows, cols))
[(0, 0), (1, 1), (2, 6), (3, 2), (4, 3), (5, 4)]
為什么與上面不一樣呢?
- (0,0),(1,1)的匹配顯然不是我們實(shí)現(xiàn)的后來者優(yōu)先
- 他把行看成是工人,列看成是任務(wù),每個工人總要分配個任務(wù),所以(5,4)這種代價矩陣?yán)餂]有的關(guān)聯(lián)它也做出來了,目的只是讓“總代價”最小
(1-relationship_matrix)[rows, cols] # 總代價為1
array([0, 0, 0, 0, 0, 1])
從它的名字也能看出來,它是理解為指派問題
的(assignment
)