Отфильтруйте список изображений по сходству - PullRequest
1 голос
/ 25 января 2020

У меня есть список имен изображений и (пороговая) матрица сходства для них. Отношение подобия рефлексивно и симметрично c, но не обязательно транзитивно, т. Е. Если 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'].

Обновление : пожалуйста см. мой ответ, который объясняет проблему более подробно, используя теорию графов. Я все еще открыт для предложений, так как я не нашел достаточно быстрого способа достижения результата для списка из тысяч изображений.

Ответы [ 3 ]

4 голосов
/ 27 января 2020

После более подробного изучения я обнаружил, что это так называемая задача о максимальном независимом множестве в теории графов, которая, к сожалению, NP-трудная.

An независимое множество S графа G - это подмножество вершин 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')

enter image description here


# Find the maximum independent sets
g.largest_independent_vertex_sets()
[(1, 2, 3), (2, 3, 4)]

enter image description here


enter image description here


К сожалению, это слишком медленно для тысяч изображений (вершин). Так что я все еще открыт для предложений по более быстрому способу сделать это (возможно, вместо того, чтобы найти все MIS, просто найдите).

Примечание : предлагаемые решения от @Sergey (ОБНОВЛЕНИЕ # 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, ниже приведен рисунок, показывающий, как удаляются вершины и ребра на каждой итерации (что является «побочным эффектом» np.argmax, возвращая первое вхождение для нескольких вхождений максимального значения). ):

enter image description here

Решение Сергея (ОБНОВЛЕНИЕ № 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
3 голосов
/ 25 января 2020

Насколько я понимаю, уникальные изображения - это те, которые не похожи ни на какие другие. Если это так, то мы можем суммировать строки (или столбцы) и выбрать те элементы результата, которые равны 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]]
1 голос
/ 25 января 2020

окончательное редактирование: Это неверное решение, см. Ответ автора. Я оставляю этот пост, потому что он был упомянут пару раз.

Вот с fo l l 1014 *, не уверен, как это сделать без одного:

results = [images[i] for i in range(len(images)) if sum(sm[i][i:]) == 1]

edit:

Вот исправленное решение, оно делает то же самое, что и решение @ 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]
...