python #algorithm #numpy #geometry
#python #алгоритм #numpy #геометрия
Вопрос:
У меня есть два набора из n точек, как массивы Numpy, в случайном порядке. Я должен связать точки между двумя списками на основе расстояния (L2), чтобы каждая точка в list1 получала одну и только соответствующую точку, ближайшую к list2.
Мой вопрос: каков самый быстрый способ сделать это с точки зрения времени вычислений?
На данный момент я вычисляю симметричную матрицу кросс-норм (с помощью scipy.spatial.distance_matrix) и сортирую точки оттуда, выполняя цикл для нахождения наименьшей нормы во всей матрице. Затем удалите соответствующие строки и столбцы и выполняйте итерацию до тех пор, пока матрица не станет пустой. Интересно, есть ли известный более быстрый способ сделать это?
[РЕДАКТИРОВАТЬ]: Вот код и пример, который я получаю
import numpy as np
import numpy.ma as ma
import matplotlib.pyplot as plt
from scipy.spatial import distance_matrix
rng = np.random.default_rng()
lst1 = rng.random((10, 2))
lst2 = lst1 0.1 * rng.standard_normal(lst1.shape) # rng.random((10, 2))
mask = np.zeros((len(lst1), len(lst2)), dtype=bool)
dst = ma.array(distance_matrix(lst1, lst2), mask=mask)
ord_lst1 = []
ord_lst2 = []
for i in range(min(len(lst1), len(lst2))):
index = np.unravel_index(np.argmin(dst), shape=dst.shape)
ord_lst1.append(lst1[index[0], :])
ord_lst2.append(lst2[index[1], :])
dst[index[0], :] = ma.masked
dst[:, index[1]] = ma.masked
fig = plt.figure()
plt.grid(True)
plt.scatter(x=lst1[:, 0], y=lst1[:, 1], label="list1")
plt.scatter(x=lst2[:, 0], y=lst2[:, 1], label="list2")
for p1, p2 in zip(ord_lst1, ord_lst2):
plt.plot((p1[0], p2[0]), (p1[1], p2[1]), "--", color="black")
plt.legend()
Как вы можете видеть, огромная связь посередине между двумя очень удаленными точками может вызывать беспокойство. Однако точка списка 1 в (0.4, 0.6) имеет наиболее близкое совпадение с верхним правым списком 2, поэтому создается ассоциация и исключаются эти две точки из дальнейшего объединения.
Спасибо 🙂
Комментарии:
1. Пожалуйста, добавьте некоторые данные
2. Что делать, если какая-то точка из списка 2 является ближайшей для некоторых точек списка 1? (На вашем рисунке не показаны такие неоднозначные случаи). Кажется, в этом случае вам нужно какое-то взвешенное сопоставление.
3. @DaniMesejo: данные здесь были сгенерированы с использованием: rng = np.random.default_rng() lst1 = rng.random((10, 2)) lst2 = lst1 0.03 * rng.standard_normal(lst1.shape)
4. @MBo: Поскольку я ищу таблицу ассоциаций 1 к 1, для данной точки в list1 с ней должна быть связана ближайшая точка в list2, что делает невозможным объединение этих точек с другими. Это как если бы мы сначала связали самые близкие, а они вытащили их из процесса ассоциации.
5. список 1: [1,0], [0,0] список 2: [0,1], [1,2]. Если мы пройдем list1 слева направо, у нас будет соответствие индексов 0-1, 1-0, если мы пройдем справа налево, у нас будет соответствие 0-0, 1-1 (выглядит более интуитивно понятным)
Ответ №1:
Загляните в scipy.spatial.KDTree https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial .KDTree.html
Создайте KDTree из списка 2 и запрашивайте его в каждой точке списка 1
Следующий фрагмент не тестировался, поэтому может потребоваться отладка. Это должно стать началом вашего собственного дизайна
#L1 is numpy array with shape (N,2)
#L2 is numpy array with shape (N,2)
import scipy.spatial
tree=scipy.spatial.KDTree(L2)
assoc=[]
for I1,point in enumerate(L1):
_,I2 = tree.query(point,k=1)
assoc.append((I1,I2))
assoc
Переменная содержит конечные ассоциации в виде списка кортежей индексов
РЕДАКТИРОВАТЬ: чтобы помочь с проблемой неуникальных ассоциаций, одним из первых шагов может быть запуск алгоритма KDTree дважды, один раз с «основным списком» L1 и один раз с «основным списком» L2, затем сохранить только общие ассоциации между ними. затем вы можете обрабатывать оставшиеся точки как специальныйслучай.
Комментарии:
1. Что произойдет, если две точки из list1 получат одинаковый результат в list2?
2. Этот ответ действительно хорош. Я не знал о KDTrees. Однако, как отметил @Stef, это не гарантирует эксклюзивности. Это также не гарантирует, что это самое близкое совпадение. Когда мы перебираем точки в L1, чтобы найти ближайшую точку в L2, это даст связь между этой точкой L1 и ближайшими к ней точками L2, но нет никакой гарантии, что нет другой точки L1 ближе к этой точке L2, которая аннулировала бы эту связь.
3. Это может оказаться медленнее, но вы можете записать каждую точку, которая была ранее сопоставлена, а затем запросить больше точек, когда ближайшая точка уже занята. Чтобы запросить более одного соседа, вы можете изменить значение
k
вquery
вызове функции. Это вернет списокk
ближайших соседей. Вам нужно перебрать эти результаты, чтобы исключить уже «взятых» соседей. Это решает случай, о котором упоминает @Stef, но является предвзятым в зависимости от порядка прохождения L1. Если вы хотите минимизировать смещение в зависимости от порядка, вы можете рандомизировать свой обход L1.4. Если вы опубликуете свою оригинальную реализацию на python, возможно, удастся найти способы ускорить ваш код без изменения вашего алгоритма. Известно, что некоторые методы python и NumPy работают медленнее, чем другие
5. @MichaelSohnen: Я бы посоветовал вам обновить свой ответ своим последним комментарием, просто подтвердив его. Спасибо за руку!