【问题标题】:Filter a list of images by similarity relationship按相似关系过滤图像列表
【发布时间】:2020-01-25 08:41:11
【问题描述】:

我有一个图像名称列表和一个(阈值)相似度矩阵。相似关系是自反的和对称的,但不一定是传递的,即如果image_iimage_jimage_k相似,那么并不一定意味着image_jimage_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_iimage_j相似,否则不相似。这里我们看到image_0image_1image_2 相似,但image_1image_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']

更新:请参阅我的回答,它使用图论更详细地解释了该问题。我仍然愿意接受建议,因为我还没有找到一种相当快速的方法来获得数千张图像列表的结果。

【问题讨论】:

  • 为什么在你的例子中你没有“保留”image_0?
  • @Gulzar,因为 image_0 类似于 image_2。
  • 但它与图 3 不同,并且相似性不具有传递性。如果您对要求进行更详细的解释,我会更愿意。
  • @Gulzar 由于 image_0 与 image_2 相似,我不需要它们都出现在最终列表中。假设它们是完全重复的,因此没有理由同时保留它们,即使 image_0 与 image_3 不相似。
  • 最初,您只有一个独特的图像#3。但是从列表中删除图像#0 后,图像#2 也变得唯一。因此,删除图像,我们可以得到一个列表,其中所有剩余的图像都是唯一的,对吧?

标签: python numpy igraph graph-theory independent-set


【解决方案1】:

稍微研究了一下,发现这就是图论中所谓的最大独立集问题,可惜是NP-hard。

图 G 的 independent set S 是 G 的顶点的子集,因此 S 中没有顶点彼此相邻。在我们的例子中,我们正在寻找一个最大独立集 (MIS),即具有最大可能顶点数的独立集。

有几个库用于处理图和网络,例如 igraphNetworkX,它们具有查找最大独立集的功能。我最终使用了 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

【讨论】:

  • 恭喜!!!您找到了数学解决方案并找到了 Python 模块。这真的很酷。 Python 不是解决 NP 难题的最佳工具。 Python 模块主要使用 C 代码来加速执行。如果您找到使用 GPU 执行此任务的模块,它会快得多。例如,使用 ffmpeg 的视频编码在我的计算机上使用 GPU 时运行速度大约快 12 倍。
【解决方案2】:

据我了解,独特的图像与其他图像不同。如果是这种情况,那么我们可以汇总行(或列)并选择结果中等于 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]]

【讨论】:

  • 谢尔盖感谢您的回答!但是事实证明,您更新的解决方案对我来说太慢了(请参阅我的答案)。
  • @AndreasK。谢谢你给的信息。我已经评论了你的答案。
【解决方案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]

【讨论】:

  • 正如@Sergey 所指出的,如果你交换例如image_0image_4,使用您的解决方案的结果将是[image_0, image_3],但我们仍然应该得到最大长度子集,即[image_2, image_3, image_4][image_1, image_2, image_3]
  • 是的,我更新了答案以使其正确(我希望,考虑所有特殊情况和可能性非常棘手)
  • 恐怕这不是正确的解决方案,看我的回答。还是谢谢!
猜你喜欢
  • 2016-10-15
  • 2014-01-15
  • 1970-01-01
  • 2021-03-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-17
相关资源
最近更新 更多