[英]Filter a list of images by similarity relationship
我有一個圖像名稱列表和一個(閾值)相似度矩陣。 相似關系是自反和對稱的,但不一定是可傳遞的,即如果image_i
與image_j
和image_k
相似,則不一定意味着image_j
和image_k
相似。
例如:
images = ['image_0', 'image_1', 'image_2', 'image_3', 'image_4']
sm = np.array([[1, 1, 1, 0, 1],
[1, 1, 0, 0, 1],
[1, 0, 1, 0, 0],
[0, 0, 0, 1, 0],
[1, 1, 0, 0, 1]])
相似度矩陣sm
解釋如下:如果sm[i, j] == 1
則image_i
和image_j
相似,否則它們不相似。 這里我們看到image_0
類似於image_1
和image_2
,但image_1
和image_2
不相似(這只是非傳遞性的一個例子)。
我想保留最大數量的唯一圖像(根據給定的sm
矩陣,它們都是成對非相似的)。 對於此示例,它將是[image_2, image_3, image_4]
或[image_1, image_2, image_3]
(通常有多個這樣的子集,但我不介意保留哪個,只要它們是最大長度)。 我正在尋找一種有效的方法來做到這一點,因為我有成千上萬的圖像。
編輯:我原來的解決方案如下
np.array(images)[np.tril(sm).sum(0) == 1]
但是不能保證它會返回一個最大長度的子集。 考慮以下示例:
sm = np.array([[1, 1, 0, 0, 0],
[1, 1, 0, 0, 0],
[0, 0, 1, 1, 0],
[0, 0, 1, 1, 1],
[0, 0, 0, 1, 1]])
此解決方案將返回['image_1', 'image_4']
,而所需的結果是['image_0', 'image_2', 'image_4']
或['image_1', 'image_2', 'image_4']
。
更新:請參閱我的回答,它使用圖論更詳細地解釋了問題。 我仍然願意接受建議,因為我還沒有找到一種相當快速的方法來實現數千張圖像列表的結果。
稍微研究了一下,發現這就是圖論中所謂的最大獨立集問題,可惜是NP-hard問題。
圖 G 的獨立集合S 是 G 的頂點的子集,因此 S 中沒有頂點彼此相鄰。 在我們的例子中,我們正在尋找一個最大獨立集(MIS),即具有最大可能頂點數的獨立集。
有幾個用於處理圖形和網絡的庫,例如igraph或NetworkX ,它們具有查找最大獨立集的功能。 我最終使用了 igraph。
對於我的問題,我們可以將圖像視為圖 G 的頂點,將“相似度矩陣”視為鄰接矩陣:
images = ['image_0', 'image_1', 'image_2', 'image_3', 'image_4']
sm = np.array([[1, 1, 1, 0, 1],
[1, 1, 0, 0, 1],
[1, 0, 1, 0, 0],
[0, 0, 0, 1, 0],
[1, 1, 0, 0, 1]])
# Adjacency matrix
adj = sm.copy()
np.fill_diagonal(adj, 0)
# Create the graph
import igraph
g = igraph.Graph.Adjacency(adj.tolist(), mode='UNDIRECTED')
# Find the maximum independent sets
g.largest_independent_vertex_sets()
[(1, 2, 3), (2, 3, 4)]
不幸的是,這對於數千個圖像(頂點)來說太慢了。 所以我仍然願意接受關於更快的方法的建議(也許不是找到所有的 MIS,而是找到一個)。
注意:@Sergey (UPDATE#1) 和 @marke 提出的解決方案並不總是返回一個 MIS——它們是貪婪的近似算法,它們刪除最大度數的頂點,直到沒有邊緣為止。 為了證明這一點,請考慮以下示例:
sm = np.array([[1, 1, 0, 0, 0, 1],
[1, 1, 0, 1, 0, 0],
[0, 0, 1, 1, 1, 0],
[0, 1, 1, 1, 0, 0],
[0, 0, 1, 0, 1, 1],
[1, 0, 0, 0, 1, 1]])
兩種解決方案都返回[3, 5]
,但對於此示例,最大獨立集為兩個[(0, 3, 4), (1, 2, 5)]
,正如igraph
正確找到的那樣。 要了解為什么這些解決方案無法找到 MIS,下面是一個 gif,顯示了如何在每次迭代中刪除頂點和邊(這是np.argmax
的“副作用”,返回第一次出現多次出現的最大值):
Sergey 的解決方案(UPDATE#2)似乎有效,但它比 igraph 的largest_independent_vertex_sets()
慢得多。 對於速度比較,您可以使用以下隨機生成的長度為 100 的相似度矩陣:
a = np.random.randint(2, size=(100, 100))
# create a symmetric similarity matrix
sm = np.tril(a) + np.tril(a, -1).T
np.fill_diagonal(sm, 1)
# create adjacency matrix for igraph
adj = sm.copy()
np.fill_diagonal(adj, 0)
更新:事實證明,盡管我有數千個圖像 - 頂點,但邊的數量相對較少(即我有一個稀疏圖),因此就速度而言,使用 igraph 查找 MIS 是可以接受的。 或者,作為一種折衷方案,可以使用貪心近似算法來尋找一個大的獨立集(如果足夠幸運的話,也可以使用 MIS)。 下面是一個看起來相當快的算法:
def independent_set(adj):
'''
Given adjacency matrix, returns an independent set
of size >= np.sum(1/(1 + adj.sum(0)))
'''
adj = np.array(adj, dtype=bool).astype(np.uint8)
np.fill_diagonal(adj, 1) # for the purposes of algorithm
indep_set = set(range(len(adj)))
# Loop until no edges remain
while adj.sum(0).max() > 1:
degrees = adj.sum(0)
# Randomly pick a vertex v of max degree
v = random.choice(np.where(degrees == degrees.max())[0])
# "Remove" the vertex v and the edges to its neigbours
adj[v, :], adj[:, v] = 0, 0
# Update the maximal independent set
indep_set.difference_update({v})
return indep_set
或者更好的是,我們可以得到一個最大的獨立集:
def maximal_independent_set(adj):
adj = np.array(adj, dtype=bool).astype(np.uint8)
degrees = adj.sum(0)
V = set(range(len(adj))) # vertices of the graph
mis = set() # maximal independent set
while V:
# Randomly pick a vertex of min degree
v = random.choice(np.where(degrees == degrees.min())[0])
# Add it to the mis and remove it and its neighbours from V
mis.add(v)
Nv_c = set(np.nonzero(adj[v])[0]).union({v}) # closed neighbourhood of v
V.difference_update(Nv_c)
degrees[list(Nv_c)] = len(adj) + 1
return mis
據我了解,獨特的圖像是那些與其他圖像不同的圖像。 如果是這種情況,那么我們可以匯總行(或列)並選擇結果中等於 1 的元素。然后我們需要從圖像列表中獲取相同的元素。
目前我不知道如何在第二步刪除循環。
[images[i] for i in np.where(sm.sum(0) == 1)[0]]
更新#1
上面的討論使我們對這個問題有了新的認識。
一個新的想法是一次刪除一個圖像,選擇那些具有最大數量相似的圖像。
images = ['image_0', 'image_1', 'image_2', 'image_3', 'image_4']
sm = np.array([[1, 1, 1, 0, 1],
[1, 1, 0, 0, 1],
[1, 0, 1, 0, 0],
[0, 0, 0, 1, 0],
[1, 1, 0, 0, 1]])
ix = list(range(len(images)))
while sm[ix].T[ix].sum() != len(ix): # exit if we got the identity matrix
va = sm[ix].T[ix].sum(0) # count similar images
jx = np.argmax(va) # get the index of the worst image
del ix[jx] # delete index of the worst image
print([images[i] for i in ix])
輸出:
['image_2', 'image_3', 'image_4']
更新#2
相同,但檢查每個分支的相似度最差
res = []
def get_wres(sm, ix):
if sm[ix].T[ix].sum() == len(ix):
res.append(list(ix))
return
va = sm[ix].T[ix].sum(0) # count similar images
vx = np.max(va) # get the value of the worst
for i in range(len(ix)): # check every image
if va[i] == vx: # for the worst value
ixn = list(ix) # isolate one worst
del ixn[i] # image and
get_wres(sm, ixn) # try without it
get_wres(sm, ix)
print(res)
輸出:
[[2, 3, 4], [1, 2, 3]]
最終編輯:此解決方案是錯誤的,請參閱海報的答案。 我離開這篇文章是因為它被提到過幾次。
這是一個 foo 循環,不知道如何在沒有循環的情況下完成它:
results = [images[i] for i in range(len(images)) if sum(sm[i][i:]) == 1]
編輯:
這是一個更正的解決方案,它與@Sergey 的解決方案基本相同,但方式不同
def put_zeros_to_image_with_most_similarities(arr: np.array):
index = np.sum(arr, axis=1).argmax()
if np.sum(arr[index], axis=0) == 1:
return
arr[index] = 0
arr[:, index] = 0
for _ in sm:
put_zeros_to_image_with_most_similarities(sm)
results = [images[i] for i in range(len(images)) if sum(sm[i][i:]) == 1]
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.