简体   繁体   English

将函数应用于ndarray的每一行

[英]Apply a function to each row of a ndarray

I have this function to calculate squared Mahalanobis distance of vector x to mean: 我有这个函数来计算向量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

I have an numpy array that has a shape of (25, 4) . 我有一个numpy数组,形状为(25, 4) 25,4 (25, 4) So, I want to apply that function to all 25 rows of my array without a for loop. 所以,我想在没有for循环的情况下将该函数应用于我的数组的所有25行。 So, basically, how can I write the vectorized form of this loop: 那么,基本上,我该如何编写这个循环的矢量化形式:

for r in d1:
    mahalanobis_sqdist(r[0:4], mean1, Sig1)

where mean1 and Sig1 are : 其中mean1Sig1是:

>>> 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]])

I have tried the following but it didn't work: 我尝试过以下但是没有用:

>>> 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

To apply a function to each row of an array, you could use: 要将函数应用于数组的每一行,您可以使用:

np.apply_along_axis(mahalanobis_sqdist, 1, d1, mean1, Sig1)    

In this case, however, there is a better way. 然而,在这种情况下,有一种更好的方法。 You don't have to apply a function to each row. 您不必将函数应用于每一行。 Instead, you can apply NumPy operations to the entire d1 array to calculate the same result. 相反,您可以将NumPy操作应用于整个d1数组以计算相同的结果。 np.einsum can replace the for-loop and the two calls to np.dot : 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)

Here are some benchmarks: 以下是一些基准测试:

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)

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

Thus mahalanobis_sqdist2 is about 18x faster than a for-loop , and 26x faster than using np.apply_along_axis . 因此, mahalanobis_sqdist2for-loop快约18倍,比使用np.apply_along_axis快26倍。


Note that np.apply_along_axis , np.vectorize , np.frompyfunc are Python utility functions. 请注意, np.apply_along_axisnp.vectorizenp.frompyfunc是Python实用程序函数。 Under the hood they use for- or while-loop s. 在引擎盖下,它们使用for-while-loop s。 There is no real "vectorization" going on here. 这里没有真正的“矢量化”。 They can provide syntactic assistance, but don't expect them to make your code perform any better than a for-loop you write yourself. 他们可以提供语法帮助,但是不要指望它们使你的代码比你自己编写的for-loop更好。

The answer by @unutbu works very nicely for applying any function to the rows of an array. @unutbu的答案非常适合将任何函数应用于数组的行。 In this particular case, there are some mathematical symmetries you can use that will speed things up considerably if you are working with large arrays. 在这种特殊情况下,您可以使用一些数学对称性,如果您使用大型数组,这将大大加快速度。

Here is a modified version of your function: 以下是您的功能的修改版本:

def mahalanobis_sqdist3(x, mean, Sigma):
    Sigma_inv = np.linalg.inv(Sigma)
    xdiff = x - mean
    return (xdiff.dot(Sigma_inv)*xdiff).sum(axis=-1)

If you end up using any sort of large Sigma , I would recommend that you cache Sigma_inv and pass that in as an argument to your function instead. 如果您最终使用任何类型的大型Sigma ,我建议您缓存Sigma_inv并将其作为参数传递给您的函数。 Since it is 4x4 in this example, this doesn't matter. 由于在这个例子中它是4x4,这没关系。 I'll show how to deal with large Sigma anyway for anyone else who comes across this. 无论如何,我将展示如何处理大型Sigma ,对于遇到这种情况的任何其他人。

If you aren't going to be using the same Sigma repeatedly, you won't be able to cache it, so, instead of inverting the matrix, you could use a different method to solve the linear system. 如果您不打算重复使用相同的Sigma ,则无法对其进行缓存,因此,您可以使用不同的方法来解决线性系统,而不是反转矩阵。 Here I'll use the LU decomposition built in to SciPy. 在这里,我将使用SciPy内置的LU分解。 This only improves the time if the number of columns of x is large relative to its number of rows. 如果x的列数相对于其行数较大,则这仅改善了时间。

Here is a function that shows that approach: 这是一个显示该方法的函数:

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)

Here are some timings. 这是一些时间安排。 I'll include the version with einsum as mentioned in the other answer. 我会在其他答案中提到包含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)

giving: 赠送:

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

However, changing the sizes of the arrays involved changes the timing results. 但是,更改所涉及的阵列的大小会更改计时结果。 For example, letting x = np.random.rand(2500, 4) , the timings are: 例如,让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

And letting x = np.random.rand(1000, 1000) , Sigma1 = np.random.rand(1000, 1000) , and mean1 = np.random.rand(1000) , the timings are: 并且让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

Edit : I noticed that one of the other answers used the Cholesky decomposition. 编辑 :我注意到其他一个答案使用了Cholesky分解。 Given that Sigma is symmetric and positive definite, we can actually do better than my above results. 鉴于Sigma是对称且肯定的,我们实际上可以比上面的结果做得更好。 There are some good routines from BLAS and LAPACK available through SciPy that can work with symmetric positive-definite matrices. 通过SciPy可以获得BLAS和LAPACK的一些很好的例程,它们可以使用对称正定矩阵。 Here are two faster versions. 这是两个更快的版本。

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)

The first one still inverts Sigma. 第一个仍然反转Sigma。 If you pre-compute the inverse and reuse it, it is much faster (the 1000x1000 case takes 35.6ms on my machine with the pre-computed inverse). 如果你预先计算逆并重复使用它,它会快得多(1000x1000的情况在我的机器上需要35.6ms,并带有预先计算的逆)。 I also used einsum to take the product then sum along the last axis. 我还使用einsum取出产品然后沿最后一个轴求和。 This ended up being marginally faster than doing something like (A * B).sum(axis=-1) . 这最终比做(A * B).sum(axis=-1)更快。 These two functions give the following timings: 这两个函数给出以下时间:

First test case: 第一个测试用例:

10000 loops, best of 3: 55.3 µs per loop
100000 loops, best of 3: 14.2 µs per loop

Second test case: 第二个测试案例:

10000 loops, best of 3: 121 µs per loop
10000 loops, best of 3: 79 µs per loop

Third test case: 第三个测试用例:

10 loops, best of 3: 92.5 ms per loop
10 loops, best of 3: 48.2 ms per loop

Just saw a really nice comment on reddit that might speed things up even a little more: 刚看到reddit上的一个非常好的评论,可能会加快速度:

This is not surprising to anyone who uses numpy regularly. 对于经常使用numpy的人来说,这并不奇怪。 For loops in python are horribly slow. 因为python中的循环非常慢。 Actually, einsum is pretty slow too. 实际上,einsum也很慢。 Here's a version that is faster if you have lots of vectors (500 vectors in 4 dimensions is enough to make this version faster than einsum on my machine): 如果你有很多向量,那么这个版本会更快(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)

If your points are also high dimensional then computing the inverse is slow (and generally a bad idea anyway) and you can save time by solving the system directly (500 vectors in 250 dimensions is enough to make this version the fastest on my machine): 如果你的点也是高维的,那么计算逆是很慢(并且通常是一个坏主意)并且你可以通过直接求解系统来节省时间(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)

The problem is that np.vectorize vectorizes over all arguments, but you need to vectorize only over the first one. 问题是np.vectorize所有参数进行矢量化,但是您需要仅在第一个参数上进行矢量化。 You need to use excluded keyword argument to vectorize : 您需要使用excluded关键字参数进行vectorize

np.vectorize(mahalanobis_sqdist, excluded=[1, 2])

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM