Вот два метода, один из которых использует einsum
, другой KDTree
:
einsum
делает то, что мы также можем достичь с помощью радиовещания, например, np.einsum('ik,jk', A, B)
примерно эквивалентно (A[:, None, :] * B[None, :, :]).sum(axis=2)
,Преимущество einsum заключается в том, что он выполняет суммирование сразу, поэтому он избегает создания промежуточного массива mxmxn.
KDTree
более сложный.Мы должны заранее инвестировать в создание дерева, но после этого очень эффективен запрос ближайших соседей.
import numpy as np
from scipy.spatial import cKDTree as KDTree
def f_einsum(A, B):
B2AB = np.einsum('ij,ij->i', B, B) / 2 - np.einsum('ik,jk', A, B)
idx = B2AB.argmin(axis=1)
D = A-B[idx]
return np.sqrt(np.einsum('ij,ij->i', D, D)), idx
def f_KDTree(A, B):
T = KDTree(B)
return T.query(A, 1)
m, n = 75, 4
A, B = np.random.randn(2, m, n)
de, ie = f_einsum(A, B)
dt, it = f_KDTree(A, B)
assert np.all(ie == it) and np.allclose(de, dt)
from timeit import timeit
for m, n in [(75, 4), (500, 4)]:
A, B = np.random.randn(2, m, n)
print(m, n)
print('einsum:', timeit("f_einsum(A, B)", globals=globals(), number=1000))
print('KDTree:', timeit("f_KDTree(A, B)", globals=globals(), number=1000))
Пример выполнения:
75 4
einsum: 0.067826496087946
KDTree: 0.12196151306852698
500 4
einsum: 3.1056990439537913
KDTree: 0.85108971898444
Мы видим, что при небольшом размере задачи прямой метод(einsum) быстрее, а при больших проблемах KDTree побеждает.