Три возможных варианта
Варианты 1 и 3 используют
(a-b) ^ 2 = a ^ 2 + b ^ 2 - 2ab
как описано здесь или здесь . Но для особых случаев, таких как небольшое второе измерение, вариант 2 также подходит.
import numpy as np
import numba as nb
import numexpr as ne
from scipy.linalg.blas import sgemm
def vers_1(X,Y, gamma, var):
X_norm = -gamma*np.einsum('ij,ij->i',X,X)
Y_norm = -gamma*np.einsum('ij,ij->i',Y,Y)
return ne.evaluate('v * exp(A + B + C)', {\
'A' : X_norm[:,None],\
'B' : Y_norm[None,:],\
'C' : sgemm(alpha=2.0*gamma, a=X, b=Y, trans_b=True),\
'g' : gamma,\
'v' : var\
})
#Maybe easier to read but slow, if X.shape[1] gets bigger
@nb.njit(fastmath=True,parallel=True)
def vers_2(X,Y):
K = np.empty((X.shape[0],Y.shape[0]),dtype=X.dtype)
for i in nb.prange(X.shape[0]):
for j in range(Y.shape[0]):
sum=0.
for k in range(X.shape[1]):
sum+=(X[i,k]-Y[j,k])**2
K[i,j] = np.exp(-1*sum)
return K
@nb.njit(fastmath=True,parallel=True)
def vers_3(A,B):
dist=np.dot(A,B.T)
TMP_A=np.empty(A.shape[0],dtype=A.dtype)
for i in nb.prange(A.shape[0]):
sum=0.
for j in range(A.shape[1]):
sum+=A[i,j]**2
TMP_A[i]=sum
TMP_B=np.empty(B.shape[0],dtype=A.dtype)
for i in nb.prange(B.shape[0]):
sum=0.
for j in range(B.shape[1]):
sum+=B[i,j]**2
TMP_B[i]=sum
for i in nb.prange(A.shape[0]):
for j in range(B.shape[0]):
dist[i,j]=np.exp((-2.*dist[i,j]+TMP_A[i]+TMP_B[j])*-1)
return dist
Задержка
gamma = 1.
var = 1.
X=np.random.rand(3850,4)
Y=np.random.rand(1200,4)
res_1=vers_1(X,Y, gamma, var)
res_2=vers_2(X,Y)
res_3=vers_3(X,Y)
np.allclose(res_1,res_2)
np.allclose(res_1,res_3)
%timeit res_1=vers_1(X,Y, gamma, var)
19.8 ms ± 615 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
%timeit res_2=vers_2(X,Y)
16.1 ms ± 938 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit res_3=vers_3(X,Y)
13.5 ms ± 162 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)