Существует eucl_dist
пакет (отказ от ответственности: я его автор), который в основном содержит два метода для решения проблемы вычисления квадратов евклидовых расстояний, которые более эффективны, чем SciPy's cdist
, особенно для больших массивов (с большим количеством столбцов).
Мы будем использовать некоторые коды из его source code
, чтобы приспособиться к нашей проблеме, чтобы дать нам два подхода.
Подход № 1
Следуя wiki contents
, мы могли бы использовать matrix-multiplication
и некоторые NumPy specific implementations
для нашего первого подхода,вот так -
def pdist_squareformed_numpy(a):
a_sumrows = np.einsum('ij,ij->i',a,a)
dist = a_sumrows[:,None] + a_sumrows -2*np.dot(a,a.T)
np.fill_diagonal(dist,0)
return dist
Подход № 2
Еще один способ - создать «расширенные» версии входных массивов, которые снова подробно обсуждаются в этом исходном коде github.используйте кодовую ссылку для нашего второго подхода, который лучше для меньших столбцов, как здесь, например, так:
def ext_arrs(A,B, precision="float64"):
nA,dim = A.shape
A_ext = np.ones((nA,dim*3),dtype=precision)
A_ext[:,dim:2*dim] = A
A_ext[:,2*dim:] = A**2
nB = B.shape[0]
B_ext = np.ones((dim*3,nB),dtype=precision)
B_ext[:dim] = (B**2).T
B_ext[dim:2*dim] = -2.0*B.T
return A_ext, B_ext
def pdist_squareformed_numpy_v2(a):
A_ext, B_ext = ext_arrs(a,a)
dist = A_ext.dot(B_ext)
np.fill_diagonal(dist,0)
return dist
Обратите внимание, что они дают нам евклидовы расстояния в квадрате.Таким образом, для фактических расстояний мы хотим использовать np.sqrt()
, если это конечный результат.
Примерные прогоны -
In [380]: np.random.seed(0)
...: a = np.random.rand(5,3)
In [381]: from scipy.spatial.distance import cdist
In [382]: cdist(a,a)
Out[382]:
array([[0. , 0.29, 0.42, 0.2 , 0.57],
[0.29, 0. , 0.58, 0.42, 0.76],
[0.42, 0.58, 0. , 0.45, 0.9 ],
[0.2 , 0.42, 0.45, 0. , 0.51],
[0.57, 0.76, 0.9 , 0.51, 0. ]])
In [383]: np.sqrt(pdist_squareformed_numpy(a))
Out[383]:
array([[0. , 0.29, 0.42, 0.2 , 0.57],
[0.29, 0. , 0.58, 0.42, 0.76],
[0.42, 0.58, 0. , 0.45, 0.9 ],
[0.2 , 0.42, 0.45, 0. , 0.51],
[0.57, 0.76, 0.9 , 0.51, 0. ]])
In [384]: np.sqrt(pdist_squareformed_numpy_v2(a))
Out[384]:
array([[0. , 0.29, 0.42, 0.2 , 0.57],
[0.29, 0. , 0.58, 0.42, 0.76],
[0.42, 0.58, 0. , 0.45, 0.9 ],
[0.2 , 0.42, 0.45, 0. , 0.51],
[0.57, 0.76, 0.9 , 0.51, 0. ]])
Время на 10k
точках -
In [385]: a = np.random.rand(10000,3)
In [386]: %timeit cdist(a,a)
1 loop, best of 3: 309 ms per loop
# Approach #1
In [388]: %timeit pdist_squareformed_numpy(a) # squared eucl distances
1 loop, best of 3: 668 ms per loop
In [389]: %timeit np.sqrt(pdist_squareformed_numpy(a)) # actual eucl distances
1 loop, best of 3: 812 ms per loop
# Approach #2
In [390]: %timeit pdist_squareformed_numpy_v2(a) # squared eucl distances
1 loop, best of 3: 237 ms per loop
In [391]: %timeit np.sqrt(pdist_squareformed_numpy_v2(a)) # actual eucl distances
1 loop, best of 3: 395 ms per loop
Второй подход кажется близким к cdist
по производительности!