计算(平方)欧几里得距离的替代方法的数值问题



我想快速计算平方欧几里得,如下所述:

在 python 中计算 RBF 内核的最快方法是什么?

注意1:我只对距离感兴趣,对RBF内核感兴趣。

注意2:我在这里忽略了numexpr,只直接使用numpy。

简而言之,我计算:

|| x - y ||^2 = ||x||^2 + ||y||^2 - 2. * (x @ y.T)

scipy.pdist相比,我能够更快地计算~10倍的距离矩阵。但是,我观察到数值问题,如果我取平方根来获得欧几里得距离,情况会变得更糟。我的值按1E-8 - 1E-7的顺序排列,应该正好为零(即重复点或到自身点的距离(。

问题:

是否有方法或想法可以克服这些数值问题(在不牺牲太多评估速度的情况下可以理解(?还是数字问题是为什么没有采取这条道路(例如scipy.pdist(的原因?

例:

这是一个显示数字问题的小代码示例(不是加速,请查看上面链接的 SO 线程的答案(。

import numpy as np
M = np.random.rand(1000, 10)
M_norm = np.sum(M**2, axis=1)
res = M_norm[:, np.newaxis] + M_norm[np.newaxis, :] - 2. * M @ M.T
unique = np.unique(np.diag(res))  # analytically all diag values are exactly zero 
sqrt_unique = np.sqrt(unique)

print(unique)
print(sqrt_unique)

示例输出:

[-2.66453526e-15 -1.77635684e-15 -8.88178420e-16 -4.44089210e-16
0.00000000e+00  4.44089210e-16  8.88178420e-16  1.77635684e-15
3.55271368e-15]
[           nan            nan            nan            nan
0.00000000e+00 2.10734243e-08 2.98023224e-08 4.21468485e-08
5.96046448e-08]

如您所见,有些值也是负数(这会导致在获取 sqrt 后出现nan(。当然,这些很容易被抓住——但小正数对于欧几里得情况来说有很大的误差(例如abs_error=5.96046448e-08(

根据我的评论,使用abs可能是清理该算法固有的数值稳定性的最佳选择。 由于您担心性能,因此您可能应该使用突变赋值运算符,因为它们会导致创建更少的垃圾,因此可以更快。 此外,当使用许多功能(例如 10k(运行它时,我看到pdist比这个实现慢。

综上所述,我们得到:

import numpy as np
def edist0(M):
"calculate pairwise euclidean distance"
M_norm = np.sum(M**2, axis=1)
res = M_norm[:, np.newaxis] + M_norm[np.newaxis, :] - 2. * M @ M.T    
return np.sqrt(np.abs(res))
def edist1(M):
"optimised calculation of pairwise euclidean distance"
M_norm = np.einsum('ij,ij->i', M, M)
res = M @ M.T
res *= -2.
res += M_norm[:, np.newaxis]
res += M_norm[np.newaxis, :]
return np.sqrt(np.abs(res, out=res), out=res)

在IPython中计时:

from scipy.spatial import distance
M = np.random.rand(1000, 10000)
%timeit distance.squareform(distance.pdist(M))
%timeit edist0(M)
%timeit edist1(M)

我得到:

2.82 s ± 60.1 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
296 ms ± 6.07 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
153 ms ± 1.58 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

并且没有来自sqrt的错误/警告

链接的问题还指出scikit-learn具有良好的距离内核良好实现,欧几里得的pairwise_distances基准为:

from sklearn.metrics import pairwise_distances
%timeit pairwise_distances(M)
170 ms ± 5.51 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

如果您已经在使用该软件包,这可能很好用

最新更新