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

时间:2017-11-13 19:03:07

标签: python numpy

我想为具有X行和n列的数据矩阵d计算RBF或“高斯”内核。得到的平方核矩阵由下式给出:

K[i,j] = var * exp(-gamma * ||X[i] - X[j]||^2)

vargamma是标量。

在python中执行此操作的最快方法是什么?

3 个答案:

答案 0 :(得分:7)

我将提出四种不同的方法来计算这样的内核,然后比较它们的运行时。

使用纯numpy

在这里,我使用了||x-y||^2 = ||x||^2 + ||y||^2 - 2 * x^T * y

这一事实
import numpy as np

X_norm = np.sum(X ** 2, axis = -1)
K = var * np.exp(-gamma * (X_norm[:,None] + X_norm[None,:] - 2 * np.dot(X, X.T)))

使用numexpr

numexpr是一个python包,允许在numpy数组上进行高效和并行化的数组操作。我们可以按如下方式使用它来执行与上面相同的计算:

import numpy as np
import numexpr as ne

X_norm = np.sum(X ** 2, axis = -1)
K = ne.evaluate('v * exp(-g * (A + B - 2 * C))', {
        'A' : X_norm[:,None],
        'B' : X_norm[None,:],
        'C' : np.dot(X, X.T),
        'g' : gamma,
        'v' : var
})

使用scipy.spatial.distance.pdist

我们还可以使用scipy.spatial.distance.pdist计算成对平方欧氏距离的非冗余数组,计算该数组上的内核,然后将其转换为方阵:

import numpy as np
from scipy.spatial.distance import pdist, squareform

K = squareform(var * np.exp(-gamma * pdist(X, 'sqeuclidean')))
K[np.arange(K.shape[0]), np.arange(K.shape[1])] = var

使用sklearn.metrics.pairwise.rbf_kernel

sklearn为直接计算RBF内核提供built-in method

import numpy as np
from sklearn.metrics.pairwise import rbf_kernel

K = var * rbf_kernel(X, gamma = gamma)

运行时比较

我使用了25,000个512维随机样本进行测试,并在英特尔酷睿i7-7700HQ(4核@ 2.8 GHz)上进行实验。更确切地说:

X = np.random.randn(25000, 512)
gamma = 0.01
var = 5.0

每个方法运行7次,并报告每次执行时间的平均值和标准差。

|               Method                |       Time        |
|-------------------------------------|-------------------|
| numpy                               | 24.2 s ± 1.06 s   |
| numexpr                             | 8.89 s ± 314 ms   |
| scipy.spatial.distance.pdist        | 2min 59s ± 312 ms |
| sklearn.metrics.pairwise.rbf_kernel | 13.9 s ± 757 ms   |

首先,scipy.spatial.distance.pdist的速度非常慢。

numexpr几乎是纯numpy方法的3倍,但这个加速因子会随着可用CPU的数量而变化。

sklearn.metrics.pairwise.rbf_kernel不是最快的方式,但只比numexpr慢一点。

答案 1 :(得分:5)

您正在answer post进行大量优化。我想补充几点(主要是调整)。我会在答案帖子的基础上建立胜利者,这似乎是numexpr基于。{/ p>

调整#1

首先,np.sum(X ** 2, axis = -1)可以使用np.einsum进行优化。虽然这部分不是最大的开销,但任何类型的优化都不会受到伤害。因此,该总和可以表示为 -

X_norm = np.einsum('ij,ij->i',X,X)

调整#2

其次,我们可以利用Scipy支持的blas函数,如果允许使用单精度dtype,则可以使用单精度dtype,而不是双精度dtype。因此,np.dot(X, X.T)可以使用SciPy's sgemm计算,如此 -

sgemm(alpha=1.0, a=X, b=X, trans_b=True)

使用gamma重新排列负号后,可以进行更多调整,这样我们就可以向sgemm提供更多信息。此外,我们会将gamma推入alpha字词。

调整实施

因此,通过这两个优化,我们将有两个变体(如果我可以这样说),numexpr方法,如下所示 -

from scipy.linalg.blas import sgemm

def app1(X, gamma, var):
    X_norm = -np.einsum('ij,ij->i',X,X)
    return ne.evaluate('v * exp(g * (A + B + 2 * C))', {\
        'A' : X_norm[:,None],\
        'B' : X_norm[None,:],\
        'C' : np.dot(X, X.T),\
        'g' : gamma,\
        'v' : var\
    })

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\
    })

运行时测试

Numexpr基于你的答案帖子 -

def app0(X, gamma, var):
    X_norm = np.sum(X ** 2, axis = -1)
    return ne.evaluate('v * exp(-g * (A + B - 2 * C))', {
            'A' : X_norm[:,None],
            'B' : X_norm[None,:],
            'C' : np.dot(X, X.T),
            'g' : gamma,
            'v' : var
    })

计时和验证 -

In [165]: # Setup
     ...: X = np.random.randn(10000, 512)
     ...: gamma = 0.01
     ...: var = 5.0

In [166]: %timeit app0(X, gamma, var)
     ...: %timeit app1(X, gamma, var)
     ...: %timeit app2(X, gamma, var)
1 loop, best of 3: 1.25 s per loop
1 loop, best of 3: 1.24 s per loop
1 loop, best of 3: 973 ms per loop

In [167]: np.allclose(app0(X, gamma, var), app1(X, gamma, var))
Out[167]: True

In [168]: np.allclose(app0(X, gamma, var), app2(X, gamma, var))
Out[168]: True

答案 2 :(得分:0)

如果您针对大量伽马评估 X,使用@Callidior 和@Divakar 完成的技巧保存负成对距离矩阵很有用。

from numpy import exp, matmul, power, einsum, dot
from scipy.linalg.blas import sgemm
from numexpr import evaluate

def pdist2(X):
    X_norm = - einsum('ij,ij->i', X, X)
    return evaluate('A + B + C', {
        'A' : X_norm[:,None],
        'B' : X_norm[None,:],
        'C' : sgemm(alpha=2.0, a=X, b=X, trans_b=True),
    })

pairwise_distance_matrix = pdist2(X)

那么,最好的解决方案是使用 numexpr 来计算指数。

def rbf_kernel2(gamma, p_matrix):
    return evaluate('exp(g * m)', {
        'm' : p_matrix,
        'g' : gamma,
    })

示例:

import numpy as np
np.random.seed(1001)
X= np.random.rand(1001, 5).astype('float32')
p_matrix_test = pdist2(X)
gamma_test_list = (10 ** np.linspace(-2, 1, 11)).astype('float32')

def app2(gamma, X):
    X_norm = - gamma * einsum('ij,ij->i', X, X)
    return evaluate('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,
    })

我有结果:

%timeit y = [app2(gamma_test, x_test) for gamma_test in gamma_test_list]
<块引用>

每个循环 70.8 ms ± 5.06 ms(平均值 ± 标准偏差,7 次运行,每次 10 次循环)

%timeit y = [rbf_kernel2(gamma_test, p_matrix_test) for gamma_test in gamma_test_list]
<块引用>

每个循环 33.6 ms ± 2.33 ms(平均值 ± 标准偏差,7 次运行,每次 10 次循环)

请注意,您之前需要添加开销来计算成对距离矩阵,但如果您针对大量伽马进行评估,则开销应该不会太多。