Нахождение суммы минимального расстояния от точек в одном списке до точек в другом списке? - PullRequest
1 голос
/ 29 мая 2019

У меня есть два списка, содержащие x и y количество n-мерных точек соответственно. Я должен был вычислить сумму минимальных расстояний каждой точки в списке один (содержащий х точек) от каждой точки во втором списке (содержащий у точек). Расстояние, которое я вычисляю, является евклидовым расстоянием. Требуется оптимизированное решение.

Я уже реализовал его наивное решение в Python. Но его временная сложность слишком велика, чтобы использовать ее где угодно. Будет возможна оптимизация. Можно ли уменьшить эту сложность времени по сравнению с тем, что я реализовал?

Я читал эту статью , которую я пытался реализовать. В этом у них была та же самая проблема, с которой они заявили, что это особое условие Движение Земли . Поскольку не было дано никакого кода, следовательно, невозможно узнать, как он реализован Таким образом, моя наивная реализация, приведенный выше код был слишком медленным, чтобы работать с набором данных из 11k документов. Я использовал Google Colab для выполнения моего кода.

# Calculating Euclidean distance between two points
def euclidean_dist(x,y):
  dd = 0.0
  #len(x) is number of dimensions. Basically x and y is a 
  #list which contains coordinates of a point
  for i in range(len(x)):
    dd = dd+(x[i]-y[i])**2
  return dd**(1/2)

# Calculating the desired solution to our problem
def dist(l1,l2):
  min_dd = 0.0
  dd = euclidean_dist(l1[0],l2[0])
  for j in range(len(l1)):
    for k in range(len(l2)):
      temp = euclidean_dist(l1[j],l2[k])
      if dd > temp:
        dd = temp
    min_dd = min_dd+dd
    dd = euclidean_dist(l1[j],l2[0])
  return min_dd  

Ответы [ 4 ]

0 голосов
/ 29 мая 2019

Маленькие массивы

Для двух массивов x и y формы (n,) и (m,), соответственно, вы можете векторизовать вычисления расстояния и затем получить минимальное расстояние:

import numpy as np

n = 10
m = 20

x = np.random.random(n)
y = np.random.random(m)

# Using squared distance matrix and taking the
# square root at the minimum value
distance_matrix = (x[:,None]-y[None,:])**2
minimum_distance_sum = np.sum(np.sqrt(np.min(distance_matrix, axis=1)))

Для массивов формы (n,l) и (m,l) вам просто нужно вычислить distance_matrix как:

distance_matrix = np.sum((x[:,None]-y[None,:])**2, axis=2)

В качестве альтернативы вы можете использовать np.linalg.norm, scipy.spatial.distance.cdist, np.einsum и т. Д., Но во многих случаях они не быстрее.

Большие массивы

Если значения l, n и m выше слишком велики для того, чтобы вы могли хранить в памяти distance_matrix, вы можете использовать математическую нижнюю и верхнюю границу евклидова расстояния, чтобы увеличить скорость (см. этот документ . Так как он основан на циклах, он будет очень медленным, но можно обернуть функции с помощью numba, чтобы противостоять этому:

import numpy as np
import numba

@numba.jit(nopython=True, fastmath=True)
def get_squared_distance(a,b):
    return np.sum((a-b)**2)

def get_minimum_distance_sum(x,y):
    n = x.shape[0]
    m = y.shape[0]
    l = x.shape[1]

    # Calculate mean and standard deviation of both arrays
    mx = np.mean(x, axis=1)
    my = np.mean(y, axis=1)
    sx = np.std(x, axis=1)
    sy = np.std(y, axis=1)
    return _get_minimum_distance_sum(x,y,n,m,l,mx,my,sx,sy)

@numba.jit(nopython=True, fastmath=True)
def _get_minimum_distance_sum(x,y,n,m,l,mx,my,sx,sy):
    min_distance_sum = 0
    for i in range(n):
        min_distance = get_squared_distance(x[i], y[0])
        for j in range(1,m):
            if i == 0 and j == 0:
                continue
            lower_bound = l * ((mx[i] - my[j])**2 + (sx[i] - sy[j])**2)
            if lower_bound >= min_distance:
                continue
            distance = get_squared_distance(x[i], y[j])
            if distance < min_distance:
                min_distance = distance
        min_distance_sum += np.sqrt(min_distance)

    return min_distance_sum

def test_minimum_distance_sum():
    # Will likely be much larger for this to be faster than the other method
    n = 10
    m = 20
    l = 100

    x = np.random.random((n,l))
    y = np.random.random((m,l))

    return get_minimum_distance_sum(x,y)

Этот подход должен быть быстрее, чем прежний подход с увеличенным размером массива. Алгоритм может быть немного улучшен, как описано в статье, но любое ускорение будет сильно зависеть от формы массивов.

Задержка

На моем ноутбуке на двух массивах формы (1000,100) ваш подход занимает ~ 1 мин, подход "маленьких массивов" занимает 690 мс, а подход "больших массивов" занимает 288 мс. Для двух массивов формы (100, 3) ваш подход занимает 28 мс, подход "маленьких массивов" - 429 мкс, а подход "больших массивов" - 578 мкс.

0 голосов
/ 29 мая 2019

Чтобы сократить время выполнения, я бы предложил найти манхэттенские расстояния (дельта х + дельта у), отсортировать результирующий массив для каждой точки и затем создать буфер с + 20% наименьшего манхэттенского расстояния, если значения в отсортированном списке находятся вВ этом диапазоне + 20% вы можете вычислить евклидово расстояние и найти правильный / минимальный евклидов ответ.

Это сократит время, но цифра в 20% может не уменьшить время, если все точки расположены близко друг к другу, какбольшинство из них поместятся в буферной области, попробуйте настроить параметр 20%, чтобы увидеть, что лучше всего подходит для вашего набора данных.Имейте в виду, что его слишком большое уменьшение может привести к неточным ответам из-за характера евклидовых и манхэттенских расстояний.

0 голосов
/ 29 мая 2019

Это похоже на проблему k-ближайшего соседа, поэтому поиск каждой ближайшей точки к данной точке стоит O (N) и для вашей задачи должно быть O (N ^ 2).

Иногда с использованием kd-tree МОЖЕТ улучшить производительность, если ваши данные низкоразмерны.

0 голосов
/ 29 мая 2019

Чтобы вычислить расстояние между двумя точками, вы можете использовать формулу расстояния:

enter image description here

, которую вы можете реализовать таким образом в python:

import math

def dist(x1, y1, x2, y2):
    return math.sqrt(pow(x1 - x2, 2) + pow(y1 - y2, 2))

Тогда все, что вам нужно сделать, это перебрать список X или Y, проверить расстояние между двумя точками и сохранить его, если оно находится под текущим сохраненным минимальным расстоянием.Вы должны получить алгоритм сложности O (n²), который вам нужен.Вот рабочий пример:

min_dd = None
for i in range(len(l1)):
    for j in range(i + 1, len(l1)):
        dd = dist(l1[i], l2[i], l1[j], l2[j])
        if min_dd is None or dd < min_dd:
            min_dd = dd

С этим вы можете получить довольно хорошие показатели даже с большим списком очков.

...