高效精确计算欧氏距离

时间:2016-06-13 16:30:40

标签: python python-3.x euclidean-distance

经过一些在线调查(12numpyscipyscikitmath),我找到了几种方法用于计算Python中的欧几里德距离

# 1
numpy.linalg.norm(a-b)

# 2
distance.euclidean(vector1, vector2)

# 3
sklearn.metrics.pairwise.euclidean_distances  

# 4
sqrt((xa-xb)^2 + (ya-yb)^2 + (za-zb)^2)

# 5
dist = [(a - b)**2 for a, b in zip(vector1, vector2)]
dist = math.sqrt(sum(dist))

# 6
math.hypot(x, y)

我想知道是否有人可以提供有关上述哪一项(或我未找到的任何其他)在效率方面被认为是最佳的洞察力的精度即可。如果有人知道任何资源,它会讨论那个也很棒的主题。

context 我感兴趣的是计算数字元组对之间的欧几里德距离,例如: (52, 106, 35, 12)(33, 153, 75, 10)之间的距离。

5 个答案:

答案 0 :(得分:12)

结论第一:

从使用timeit进行效率测试的测试结果中,我们可以得出结论关于效率

Method5 (zip, math.sqrt)> Method1 (numpy.linalg.norm)> Method2 (scipy.spatial.distance)> Method3 (sklearn.metrics.pairwise.euclidean_distances )

虽然我没有真正测试你的Method4,因为它不适用于一般情况,而且通常等同于Method5

对于其他人,非常令人惊讶的是,Method5是最快的。对于使用Method1的{​​{1}},正如我们所期望的那样,在C中进行了大量优化,是第二快的。

对于numpy,如果直接进入函数定义,您将看到它实际上正在使用scipy.spatial.distance,除了它将在实际{{1}之前对两个输入向量执行验证}}。这就是为什么它比numpy.linalg.norm略慢一些。

最后针对numpy.linalg.norm,根据文档:

  

与其他计算距离的方法相比,此公式有两个优点。首先,它在处理稀疏数据时具有计算效率。其次,如果一个参数变化但另一个参数保持不变,则可以预先计算点(x,x)和/或点(y,y)。   但是,这不是进行此计算的最精确方法,此函数返回的距离矩阵可能并不是完全对称的

由于在您的问题中您希望使用一组固定的数据,因此不会反映此实现的优势。由于性能和精度之间的权衡,它在所有方法中也提供了最差的精度。

关于精确度 numpy.linalg.norm = sklearn = Method5> Metho1

效率测试脚本:

Method2

效率测试输出:

Method3

精密测试脚本&结果

import numpy as np
from scipy.spatial import distance
from sklearn.metrics.pairwise import euclidean_distances
import math

# 1
def eudis1(v1, v2):
    return np.linalg.norm(v1-v2)

# 2
def eudis2(v1, v2):
    return distance.euclidean(v1, v2)

# 3
def eudis3(v1, v2):
    return euclidean_distances(v1, v2)

# 5
def eudis5(v1, v2):
    dist = [(a - b)**2 for a, b in zip(v1, v2)]
    dist = math.sqrt(sum(dist))
    return dist

dis1 = (52, 106, 35, 12)
dis2 = (33, 153, 75, 10)
v1, v2 = np.array(dis1), np.array(dis2)

import timeit

def wrapper(func, *args, **kwargs):
    def wrapped():
        return func(*args, **kwargs)
    return wrapped

wrappered1 = wrapper(eudis1, v1, v2)
wrappered2 = wrapper(eudis2, v1, v2)
wrappered3 = wrapper(eudis3, v1, v2)
wrappered5 = wrapper(eudis5, v1, v2)
t1 = timeit.repeat(wrappered1, repeat=3, number=100000)
t2 = timeit.repeat(wrappered2, repeat=3, number=100000)
t3 = timeit.repeat(wrappered3, repeat=3, number=100000)
t5 = timeit.repeat(wrappered5, repeat=3, number=100000)

print('\n')
print('t1: ', sum(t1)/len(t1))
print('t2: ', sum(t2)/len(t2))
print('t3: ', sum(t3)/len(t3))
print('t5: ', sum(t5)/len(t5))

答案 1 :(得分:2)

这并不能完全回答问题,但是值得一提的是,如果您对实际的欧几里德距离不感兴趣,而只是想将欧几里德距离相互比较,则平方根就是单调函数,即x * *(1/2)

因此,如果您不希望显式距离,例如,只想知道vector1的欧几里德距离是否更接近向量列表,称为ve​​ctorlist,则可以避免使用昂贵的方法(就精度和精度而言,时间)平方根,但可以解决类似问题

min(vectorlist, key = lambda compare: sum([(a - b)**2 for a, b in zip(vector1, compare)])

答案 2 :(得分:1)

我不知道精度和速度与您提到的其他库相比如何,但您可以使用内置的math.hypot()函数为2D矢量执行此操作:

from math import hypot

def pairwise(iterable):
    "s -> (s0, s1), (s1, s2), (s2, s3), ..."
    a, b = iter(iterable), iter(iterable)
    next(b, None)
    return zip(a, b)

a = (52, 106, 35, 12)
b = (33, 153, 75, 10)

dist = [hypot(p2[0]-p1[0], p2[1]-p1[1]) for p1, p2 in pairwise(tuple(zip(a, b)))]
print(dist)  # -> [131.59027319676787, 105.47511554864494, 68.94925670375281]

答案 3 :(得分:0)

作为一般经验法则,请尽可能坚持scipynumpy实现,因为它们会进行矢量化并且比本机Python代码快得多。 (主要原因是:在C中实现,向量化消除了循环所做的类型检查开销。)

(旁白:我的回答并不包括这里的精确度,但我认为同样的原则适用于效率的精确度。)

作为一个额外的奖励,我将提供一些关于如何分析代码,衡量效率的信息。如果您正在使用IPython解释器,那么秘诀就是使用%prun行魔法。

In [1]: import numpy

In [2]: from scipy.spatial import distance

In [3]: c1 = numpy.array((52, 106, 35, 12))

In [4]: c2 = numpy.array((33, 153, 75, 10))

In [5]: %prun distance.euclidean(c1, c2)
         35 function calls in 0.000 seconds

   Ordered by: internal time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.000    0.000 {built-in method builtins.exec}
        1    0.000    0.000    0.000    0.000 linalg.py:1976(norm)
        1    0.000    0.000    0.000    0.000 {built-in method numpy.core.multiarray.dot}
        6    0.000    0.000    0.000    0.000 {built-in method numpy.core.multiarray.array}
        4    0.000    0.000    0.000    0.000 numeric.py:406(asarray)
        1    0.000    0.000    0.000    0.000 distance.py:232(euclidean)
        2    0.000    0.000    0.000    0.000 distance.py:152(_validate_vector)
        2    0.000    0.000    0.000    0.000 shape_base.py:9(atleast_1d)
        1    0.000    0.000    0.000    0.000 misc.py:11(norm)
        1    0.000    0.000    0.000    0.000 function_base.py:605(asarray_chkfinite)
        2    0.000    0.000    0.000    0.000 numeric.py:476(asanyarray)
        1    0.000    0.000    0.000    0.000 {method 'ravel' of 'numpy.ndarray' objects}
        1    0.000    0.000    0.000    0.000 linalg.py:111(isComplexType)
        1    0.000    0.000    0.000    0.000 <string>:1(<module>)
        2    0.000    0.000    0.000    0.000 {method 'append' of 'list' objects}
        1    0.000    0.000    0.000    0.000 {built-in method builtins.issubclass}
        4    0.000    0.000    0.000    0.000 {built-in method builtins.len}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
        2    0.000    0.000    0.000    0.000 {method 'squeeze' of 'numpy.ndarray' objects}


In [6]: %prun numpy.linalg.norm(c1 - c2)
         10 function calls in 0.000 seconds

   Ordered by: internal time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.000    0.000 {built-in method builtins.exec}
        1    0.000    0.000    0.000    0.000 linalg.py:1976(norm)
        1    0.000    0.000    0.000    0.000 {built-in method numpy.core.multiarray.dot}
        1    0.000    0.000    0.000    0.000 <string>:1(<module>)
        1    0.000    0.000    0.000    0.000 numeric.py:406(asarray)
        1    0.000    0.000    0.000    0.000 {method 'ravel' of 'numpy.ndarray' objects}
        1    0.000    0.000    0.000    0.000 linalg.py:111(isComplexType)
        1    0.000    0.000    0.000    0.000 {built-in method builtins.issubclass}
        1    0.000    0.000    0.000    0.000 {built-in method numpy.core.multiarray.array}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}

%prun的作用是告诉你函数调用运行了多长时间,包括一些跟踪以找出瓶颈可能的位置。在这种情况下,scipy.spatial.distance.euclideannumpy.linalg.norm实现都非常快。假设您定义了一个函数dist(vect1, vect2),您可以使用相同的IPython魔术调用进行概要分析。作为另一个额外的奖励,%prun也适用于Jupyter笔记本,你可以%%prun来分析整个代码单元,而不仅仅是一个函数,只需将%%prun作为第一个那个细胞的一行。

答案 4 :(得分:0)

这里是有关如何仅使用numpy的示例。

import numpy as np

a = np.array([3, 0])
b = np.array([0, 4])

c = np.sqrt(np.sum(((a - b) ** 2)))
# c == 5.0