Vah*_*ili 12 python arrays numpy vectorization
我有这个函数来计算向量x的平方Mahalanobis距离意味着:
def mahalanobis_sqdist(x, mean, Sigma):
'''
Calculates squared Mahalanobis Distance of vector x
to distibutions' mean
'''
Sigma_inv = np.linalg.inv(Sigma)
xdiff = x - mean
sqmdist = np.dot(np.dot(xdiff, Sigma_inv), xdiff)
return sqmdist
Run Code Online (Sandbox Code Playgroud)
我有一个形状为的numpy数组(25, 4).所以,我想在没有for循环的情况下将该函数应用于我的数组的所有25行.那么,基本上,我该如何编写这个循环的矢量化形式:
for r in d1:
mahalanobis_sqdist(r[0:4], mean1, Sig1)
Run Code Online (Sandbox Code Playgroud)
在哪里mean1和Sig1是:
>>> mean1
array([ 5.028, 3.48 , 1.46 , 0.248])
>>> Sig1 = np.cov(d1[0:25, 0:4].T)
>>> Sig1
array([[ 0.16043333, 0.11808333, 0.02408333, 0.01943333],
[ 0.11808333, 0.13583333, 0.00625 , 0.02225 ],
[ 0.02408333, 0.00625 , 0.03916667, 0.00658333],
[ 0.01943333, 0.02225 , 0.00658333, 0.01093333]])
Run Code Online (Sandbox Code Playgroud)
我尝试过以下但是没有用:
>>> vecdist = np.vectorize(mahalanobis_sqdist)
>>> vecdist(d1, mean1, Sig1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python2.7/dist-packages/numpy/lib/function_base.py", line 1862, in __call__
theout = self.thefunc(*newargs)
File "<stdin>", line 6, in mahalanobis_sqdist
File "/usr/lib/python2.7/dist-packages/numpy/linalg/linalg.py", line 445, in inv
return wrap(solve(a, identity(a.shape[0], dtype=a.dtype)))
IndexError: tuple index out of range
Run Code Online (Sandbox Code Playgroud)
unu*_*tbu 19
要将函数应用于数组的每一行,您可以使用:
np.apply_along_axis(mahalanobis_sqdist, 1, d1, mean1, Sig1)
Run Code Online (Sandbox Code Playgroud)
然而,在这种情况下,有一种更好的方法.您不必将函数应用于每一行.相反,您可以将NumPy操作应用于整个d1数组以计算相同的结果.np.einsum可以替换for-loop和两个调用np.dot:
def mahalanobis_sqdist2(d, mean, Sigma):
Sigma_inv = np.linalg.inv(Sigma)
xdiff = d - mean
return np.einsum('ij,im,mj->i', xdiff, xdiff, Sigma_inv)
Run Code Online (Sandbox Code Playgroud)
以下是一些基准测试:
import numpy as np
np.random.seed(1)
def mahalanobis_sqdist(x, mean, Sigma):
'''
Calculates squared Mahalanobis Distance of vector x
to distibutions mean
'''
Sigma_inv = np.linalg.inv(Sigma)
xdiff = x - mean
sqmdist = np.dot(np.dot(xdiff, Sigma_inv), xdiff)
return sqmdist
def mahalanobis_sqdist2(d, mean, Sigma):
Sigma_inv = np.linalg.inv(Sigma)
xdiff = d - mean
return np.einsum('ij,im,mj->i', xdiff, xdiff, Sigma_inv)
def using_loop(d1, mean, Sigma):
expected = []
for r in d1:
expected.append(mahalanobis_sqdist(r[0:4], mean1, Sig1))
return np.array(expected)
d1 = np.random.random((25,4))
mean1 = np.array([ 5.028, 3.48 , 1.46 , 0.248])
Sig1 = np.cov(d1[0:25, 0:4].T)
expected = using_loop(d1, mean1, Sig1)
result = np.apply_along_axis(mahalanobis_sqdist, 1, d1, mean1, Sig1)
result2 = mahalanobis_sqdist2(d1, mean1, Sig1)
assert np.allclose(expected, result)
assert np.allclose(expected, result2)
Run Code Online (Sandbox Code Playgroud)
In [92]: %timeit mahalanobis_sqdist2(d1, mean1, Sig1)
10000 loops, best of 3: 31.1 µs per loop
In [94]: %timeit using_loop(d1, mean1, Sig1)
1000 loops, best of 3: 569 µs per loop
In [91]: %timeit np.apply_along_axis(mahalanobis_sqdist, 1, d1, mean1, Sig1)
1000 loops, best of 3: 806 µs per loop
Run Code Online (Sandbox Code Playgroud)
因此mahalanobis_sqdist2比a快约18 for-loop倍,比使用快26倍np.apply_along_axis.
需要注意的是np.apply_along_axis,np.vectorize,np.frompyfunc是Python的实用功能.在引擎盖下他们使用for-或while-loops.这里没有真正的"矢量化".他们可以提供语法帮助,但不要指望他们让你的代码比for-loop你自己写的更好.
@unutbu的答案非常适合将任何函数应用于数组的行.在这种特殊情况下,您可以使用一些数学对称性,如果您使用大型数组,这将大大加快速度.
以下是您的功能的修改版本:
def mahalanobis_sqdist3(x, mean, Sigma):
Sigma_inv = np.linalg.inv(Sigma)
xdiff = x - mean
return (xdiff.dot(Sigma_inv)*xdiff).sum(axis=-1)
Run Code Online (Sandbox Code Playgroud)
如果您最终使用任何类型的大型Sigma,我建议您缓存Sigma_inv并将其作为参数传递给您的函数.由于在这个例子中它是4x4,这没关系.Sigma无论如何,我会向其他遇到此问题的人展示如何处理大型广告.
如果您不打算Sigma重复使用相同的,则无法对其进行缓存,因此,您可以使用不同的方法来解决线性系统,而不是反转矩阵.在这里,我将使用SciPy内置的LU分解.如果列数x相对于其行数较大,则这仅改善了时间.
这是一个显示该方法的函数:
from scipy.linalg import lu_factor, lu_solve
def mahalanobis_sqdist4(x, mean, Sigma):
xdiff = x - mean
Sigma_inv = lu_factor(Sigma)
return (xdiff.T*lu_solve(Sigma_inv, xdiff.T)).sum(axis=0)
Run Code Online (Sandbox Code Playgroud)
这是一些时间安排.我将包含einsum其他答案中提到的版本.
import numpy as np
Sig1 = np.array([[ 0.16043333, 0.11808333, 0.02408333, 0.01943333],
[ 0.11808333, 0.13583333, 0.00625 , 0.02225 ],
[ 0.02408333, 0.00625 , 0.03916667, 0.00658333],
[ 0.01943333, 0.02225 , 0.00658333, 0.01093333]])
mean1 = np.array([ 5.028, 3.48 , 1.46 , 0.248])
x = np.random.rand(25, 4)
%timeit np.apply_along_axis(mahalanobis_sqdist, 1, x, mean1, Sig1)
%timeit mahalanobis_sqdist2(x, mean1, Sig1)
%timeit mahalanobis_sqdist3(x, mean1, Sig1)
%timeit mahalanobis_sqdist4(x, mean1, Sig1)
Run Code Online (Sandbox Code Playgroud)
赠送:
1000 loops, best of 3: 973 µs per loop
10000 loops, best of 3: 36.2 µs per loop
10000 loops, best of 3: 40.8 µs per loop
10000 loops, best of 3: 83.2 µs per loop
Run Code Online (Sandbox Code Playgroud)
但是,更改所涉及的阵列的大小会更改计时结果.例如,让x = np.random.rand(2500, 4)时间,时间是:
10 loops, best of 3: 95 ms per loop
1000 loops, best of 3: 355 µs per loop
10000 loops, best of 3: 131 µs per loop
1000 loops, best of 3: 337 µs per loop
Run Code Online (Sandbox Code Playgroud)
让x = np.random.rand(1000, 1000),Sigma1 = np.random.rand(1000, 1000)和mean1 = np.random.rand(1000),时间是:
1 loops, best of 3: 1min 24s per loop
1 loops, best of 3: 2.39 s per loop
10 loops, best of 3: 155 ms per loop
10 loops, best of 3: 99.9 ms per loop
Run Code Online (Sandbox Code Playgroud)
编辑:我注意到其他一个答案使用了Cholesky分解.鉴于这Sigma是对称和肯定的,我们实际上可以比上面的结果做得更好.通过SciPy可以获得BLAS和LAPACK的一些很好的例程,可以使用对称正定矩阵.这是两个更快的版本.
from scipy.linalg.fblas import dsymm
def mahalanobis_sqdist5(x, mean, Sigma_inv):
xdiff = x - mean
Sigma_inv = la.inv(Sigma)
return np.einsum('...i,...i->...',dsymm(1., Sigma_inv, xdiff.T).T, xdiff)
from scipy.linalg.flapack import dposv
def mahalanobis_sqdist6(x, mean, Sigma):
xdiff = x - mean
return np.einsum('...i,...i->...', xdiff, dposv(Sigma, xdiff.T)[1].T)
Run Code Online (Sandbox Code Playgroud)
第一个仍然反转Sigma.如果你预先计算逆并重复使用它,它会快得多(1000x1000的情况在我的机器上需要35.6ms,并带有预先计算的逆).我还使用einsum取出产品然后沿最后一个轴求和.这最终比做类似的事情要快一些(A * B).sum(axis=-1).这两个函数给出以下时间:
第一个测试用例:
10000 loops, best of 3: 55.3 µs per loop
100000 loops, best of 3: 14.2 µs per loop
Run Code Online (Sandbox Code Playgroud)
第二个测试案例:
10000 loops, best of 3: 121 µs per loop
10000 loops, best of 3: 79 µs per loop
Run Code Online (Sandbox Code Playgroud)
第三个测试用例:
10 loops, best of 3: 92.5 ms per loop
10 loops, best of 3: 48.2 ms per loop
Run Code Online (Sandbox Code Playgroud)
小智 5
刚看到reddit上的一个非常好的评论,可能会加快速度:
对于经常使用numpy的人来说,这并不奇怪.因为python中的循环非常慢.实际上,einsum也很慢.如果你有很多向量,那么这个版本会更快(4个维度中的500个向量足以使这个版本比我机器上的einsum更快):
def no_einsum(d, mean, Sigma):
L_inv = np.linalg.inv(numpy.linalg.cholesky(Sigma))
xdiff = d - mean
return np.sum(np.dot(xdiff, L_inv.T)**2, axis=1)
Run Code Online (Sandbox Code Playgroud)
如果你的点也是高维的,那么计算逆是很慢(并且通常是一个坏主意)并且你可以通过直接求解系统来节省时间(250个维度中的500个向量足以使这个版本在我的机器上最快):
def no_einsum_solve(d, mean, Sigma):
L = numpy.linalg.cholesky(Sigma)
xdiff = d - mean
return np.sum(np.linalg.solve(L, xdiff.T)**2, axis=0)
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
20107 次 |
| 最近记录: |