【问题标题】:Slow Euclidean Distance慢欧几里得距离
【发布时间】:2015-07-15 16:44:30
【问题描述】:

我正在用下面的python代码计算欧几里得距离:

def getNeighbors(trainingSet, testInstance, k, labels):
    distances = []
    for x in range(len(trainingSet)):
        dist = math.sqrt(((testInstance[0] - trainingSet[x][0]) ** 2) +    ((testInstance[1] - trainingSet[x][1]) ** 2))
        distances.append([dist, labels[x]])
    distances = np.array(distances)   
    return distances

用于计算给定点与其他 10 个点的距离,这很好。但是当我计算一个点与其他 18563 个点的距离时,计算机会挂起,大约 3 小时没有响应。

我怎样才能更快地计算 18563 点?

【问题讨论】:

  • 您可以使用math.hypot(x, y) 代替dist = math.sqrt(((testInstance[0] - trainingSet[x][0]) ** 2) + ((testInstance[1] - trainingSet[x][1]) ** 2)),以及xrange 代替range
  • 您是否正在做任何可能需要成对距离的事情,或者您是否只计算到一个显着点的距离? 18563 个点的成对距离将需要更长的时间,这仅仅是因为要计算大约 1.72 亿个点。
  • 我举一个例子,计算它与其他点的距离。
  • 不要使用 for 循环来做繁重的工作(在 python 中)。要么矢量化,所以你可以 numpy 或使用不同的东西,如 cython 或 numba。
  • 你能提供一个complete minimal example 而不仅仅是这个函数吗?因为如果你只调用一次,即使有 18563 个点的列表,它真的不应该花很长时间。目前尚不清楚您在做什么需要 3 小时,但是人们可以单独对这段代码进行 5% 的小优化并没有帮助(除了将其减少到 2 小时和51 分钟)。

标签: python numpy knn


【解决方案1】:

您可以通过先转换为 NumPy,然后使用向量运算来加快此过程,而不是在循环中进行工作,然后再转换为 NumPy。像这样的:

trainingArray = np.array(trainingSet)
distances = ((testInstance[0] - trainingArray[:, 0]) ** 2 +
             (testInstance[1] - trainingArray[:, 1]) ** 2).sqrt()

(这显然未经测试,因为没有足够的上下文来知道我不得不猜测的那些变量中的实际内容,但它会接近那个。)

您可以采取其他措施来挤出一些额外的 % — 尝试将 ** 2 替换为自乘法或将 sqrt 替换为 ** .5,或者(可能最好)将整个内容替换为 @987654326 @。 (如果你不知道如何使用 timeit——或者更好的是,IPython 和它的 %timeit 魔法——现在是学习的好时机。)

但最终,这只会为您提供大约一个数量级的恒定乘数加速。也许需要 15 分钟而不是 3 小时。这很好,但是……为什么首先要花 3 个小时?你在这里所做的应该是几秒钟,甚至更少。这里显然有更大的错误,比如当你认为你只调用一次时,你可能调用了这个函数 N**2 次。你真的需要修复那部分。

当然,这样做仍然值得。首先,逐元素操作比循环更简单、更易读,而且更难出错。其次,即使你将整个程序缩短到 3.8 秒,你也会为 0.38 秒的数量级加速感到高兴,对吧?

【讨论】:

  • trainingArray 是什么?
  • @user2831683:这是一个 NumPy array,其元素与 listsettrainingSet 中的任何内容相同。通过将其设为 NumPy array 而不是原生 Python 集合,我们可以进行 NumPy 切片,更重要的是,可以进行 NumPy 矢量化操作。
  • 我的意思是,你没有使用它。
  • 这比使用 np.hypot() 快吗?
  • @abarnert 回答我自己的问题,获得 18563 分:** 0.5np.sqrt() 需要 0.77 毫秒 np.hypot() 和自乘(而不是 ** 2)需要 1.14 毫秒。显然,您的其他点是这里的关键步骤(这两个值实际上都是瞬时的 c.f. 3h),但在挤出最后一点速度时知道这一点很有趣。
【解决方案2】:

自己写sqrt计算器有风险,hypot抗上溢和下溢非常安全

x_y = np.array(trainingSet)
x = x_y[0]
y = x_y[1]
distances = np.hypot(
    np.subtract.outer(x, x),
    np.subtract.outer(y, y)
)

在速度方面它们是相同的

%%timeit
np.hypot(i, j)
# 1.29 µs ± 13.2 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
%%timeit
np.sqrt(i**2+j**2)
# 1.3 µs ± 9.87 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

下溢

i, j = 1e-200, 1e-200
np.sqrt(i**2+j**2)
# 0.0

溢出

i, j = 1e+200, 1e+200
np.sqrt(i**2+j**2)
# inf

没有下溢

i, j = 1e-200, 1e-200
np.hypot(i, j)
# 1.414213562373095e-200

无溢出

i, j = 1e+200, 1e+200
np.hypot(i, j)
# 1.414213562373095e+200

Refer

【讨论】:

    猜你喜欢
    • 2013-03-02
    • 2014-02-04
    • 1970-01-01
    • 2021-10-01
    • 2012-12-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多