Python 如何在计算内核函数时使用numpy einsum和numexpr来提高性能?

Python 如何在计算内核函数时使用numpy einsum和numexpr来提高性能?,python,performance,numpy-einsum,numexpr,Python,Performance,Numpy Einsum,Numexpr,我试图为sklearn库中的svm.SVR方法定义一些著名的核函数,如RBF、双曲正切、Fourier等。我开始研究rbf(我知道svm中有一个rbf的默认内核,但我需要定义一个,以便以后能够自定义),在中找到一些有用的链接并选择了这个: def my_kernel(X,Y): K = np.zeros((X.shape[0],Y.shape[0])) for i,x in enumerate(X): for j,y in enumerate(Y):

我试图为
sklearn
库中的
svm.SVR
方法定义一些著名的核函数,如RBF、双曲正切、Fourier等。我开始研究
rbf
(我知道svm中有一个rbf的默认内核,但我需要定义一个,以便以后能够自定义),在中找到一些有用的链接并选择了这个:

def my_kernel(X,Y):
    K = np.zeros((X.shape[0],Y.shape[0]))
    for i,x in enumerate(X):
        for j,y in enumerate(Y):
            K[i,j] = np.exp(-1*np.linalg.norm(x-y)**2)
    return K

clf=SVR(kernel=my_kernel)
我使用这个是因为我可以将它用于我的火车(形状为[3850,4])和具有不同形状的测试数据(形状为[1200,4])。但问题是它太慢了,我不得不等这么长时间才有结果。我甚至在cython中使用了静态类型和MemoryView,但其性能不如默认的
svm
rbf内核。我还发现了关于相同问题的链接,但使用
numpy.einsum
numexpr.evaluate
对我来说有点困惑。事实证明,就速度性能而言,这是最好的代码:

从scipy.linalg.blas导入sgemm

def app2(X, gamma, var):
    X_norm = -gamma*np.einsum('ij,ij->i',X,X)
    return ne.evaluate('v * exp(A + B + C)', {\
        'A' : X_norm[:,None],\
        'B' : X_norm[None,:],\
        'C' : sgemm(alpha=2.0*gamma, a=X, b=X, trans_b=True),\
        'g' : gamma,\
        'v' : var\
    })
这段代码只适用于一个输入(X),我找不到一种方法对其进行修改(两个输入具有两种不同的大小-核函数获取形状(m,n)和(l,n)的矩阵,输出(m,l)根据)。我想我只需要替换第二个代码中第一个代码的
K[I,j]=np.exp(-1*np.linalg.norm(x-y)**2)
,以加快速度。任何帮助都将不胜感激。

三种可能的变体 变体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)
三种可能的变体 变体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)

您可以通过管道将原始内核传输到

kernel.py:

import numpy as np

#pythran export my_kernel(float64[:,:], float64[:,:])
def my_kernel(X,Y):
    K = np.zeros((X.shape[0],Y.shape[0]))
    for i,x in enumerate(X):
        for j,y in enumerate(Y):
            K[i,j] = np.exp(-1*np.linalg.norm(x-y)**2)
    return K
编译步骤:

> pythran kernel.py
没有重写步骤(不过你需要把内核放在一个单独的文件中),而且加速非常显著:在我的笔记本电脑上,使用

> python -m timeit -s 'from numpy.random import random; x = random((100,100)); y = random((100,100)); from svr_kernel import my_kernel as k' 'k(x,y)'

收集时间。

您可以通过管道传送原始内核

kernel.py:

import numpy as np

#pythran export my_kernel(float64[:,:], float64[:,:])
def my_kernel(X,Y):
    K = np.zeros((X.shape[0],Y.shape[0]))
    for i,x in enumerate(X):
        for j,y in enumerate(Y):
            K[i,j] = np.exp(-1*np.linalg.norm(x-y)**2)
    return K
编译步骤:

> pythran kernel.py
没有重写步骤(不过你需要把内核放在一个单独的文件中),而且加速非常显著:在我的笔记本电脑上,使用

> python -m timeit -s 'from numpy.random import random; x = random((100,100)); y = random((100,100)); from svr_kernel import my_kernel as k' 'k(x,y)'

收集计时。

此问题与欧几里得距离有关。或者你是对的。但是我不知道如何对两个不同形状的矩阵使用相同的解!这个问题与欧几里得距离有关。或者你是对的。但是我不知道如何对两个不同形状的矩阵使用相同的解!