双python for循环的numpy矢量化

双python for循环的numpy矢量化,python,numpy,linear-algebra,Python,Numpy,Linear Algebra,V是(n,p)numpy数组,通常维数为n~10,p~20000 我现在的代码看起来像 A = np.zeros(p) for i in xrange(n): for j in xrange(i+1): A += F[i,j] * V[i,:] * V[j,:] 我该如何重写它以避免双重python for循环?这方面的困难之处在于,您只想使用j获取元素的总和,而Isaac的答案似乎很有希望,因为它删除了这两个嵌套的for循环,您必须创建一个中间数组M,它是原始V数组大

V是(n,p)numpy数组,通常维数为n~10,p~20000

我现在的代码看起来像

A = np.zeros(p)
for i in xrange(n):
    for j in xrange(i+1):
        A += F[i,j] * V[i,:] * V[j,:]

我该如何重写它以避免双重python for循环?

这方面的困难之处在于,您只想使用
j获取元素的总和,而Isaac的答案似乎很有希望,因为它删除了这两个嵌套的for循环,您必须创建一个中间数组
M
,它是原始
V
数组大小的
n
倍。Python for循环并不便宜,但内存访问也不是免费的:

n = 10
p = 20000
V = np.random.rand(n, p)
F = np.random.rand(n, n)

def op_code(V, F):
    n, p = V.shape
    A = np.zeros(p)
    for i in xrange(n):
        for j in xrange(i+1):
            A += F[i,j] * V[i,:] * V[j,:]
    return A

def isaac_code(V, F):
    n, p = V.shape
    F = F.copy()
    F[np.triu_indices(n, 1)] = 0
    M = (V.reshape(n, 1, p) * V.reshape(1, n, p)) * F.reshape(n, n, 1)
    return M.sum((0, 1))
如果您现在两个都参加试驾:

In [20]: np.allclose(isaac_code(V, F), op_code(V, F))
Out[20]: True

In [21]: %timeit op_code(V, F)
100 loops, best of 3: 3.18 ms per loop

In [22]: %timeit isaac_code(V, F)
10 loops, best of 3: 24.3 ms per loop
因此,删除for循环会使您的速度降低8倍。不是一件很好的事情。。。在这一点上,您甚至可能要考虑一个大约3ms来评估的函数是否需要进一步优化。如果您这样做了,使用
np.einsum
,可以有一个小小的改进:

def einsum_code(V, F):
    n, p = V.shape
    F = F.copy()
    F[np.triu_indices(n, 1)] = 0
    return np.einsum('ij,ik,jk->k', F, V, V)
现在:

In [23]: np.allclose(einsum_code(V, F), op_code(V, F))
Out[23]: True

In [24]: %timeit einsum_code(V, F)
100 loops, best of 3: 2.53 ms per loop

所以这大约是20%的速度,引入的代码可能不像for循环那样可读。我想说不值得…

这个表达式可以写成

因此,您可以使用
np.newaxis
-构造如下求和:

na = np.newaxis
X = (np.tri(n)*F)[:,:,na]*V[:,na,:]*V[na,:,:]
X.sum(axis=1).sum(axis=0)

这里构建了一个3D数组
X[i,j,p]
,然后将前两个轴相加,形成一个1D数组
a[p]
。此外,
F
与三角形矩阵相乘,以根据问题限制求和

F
的形状是什么?是代码< >(n,n)<代码>还是<代码>(n,n,p)< /代码>?您是否考虑编写C中的循环(因为它们很简单),并且使用Spypy.WeaveBLITZ?我宁愿避免Cython或Weav.Belz暂时,如果一些Python代码将“足够好”,那么<代码> F[NP.TruuthCalpand(n,1)]=0 < /Cord>?这样的话,
F
(偏移1)的上半部分是零,不会对求和产生影响。而且,
a.sum(0)。sum(0)
可以写成
np.sum(a,(0,1))
a.sum((0,1))
,虽然速度不快,但我觉得它更可读。正如我所说,此代码确实不是矢量化的好候选。不知道
einsum
;非常整洁!是的。当整个过程有点麻烦时,矢量化代码的一个常见的启发式方法是在长轴上矢量化,在短轴上循环,这几乎可以让您从头痛的一小部分中获得所有好处。但是在这里,这就是OP已经做过的!谢谢,在使用一系列输入(n=5-30&p=100-20000)对每个输入进行测试之后,我最终使用了einsum方法,我不知道它在那里。
In [23]: np.allclose(einsum_code(V, F), op_code(V, F))
Out[23]: True

In [24]: %timeit einsum_code(V, F)
100 loops, best of 3: 2.53 ms per loop
na = np.newaxis
X = (np.tri(n)*F)[:,:,na]*V[:,na,:]*V[na,:,:]
X.sum(axis=1).sum(axis=0)