繁体   English   中英

加速cython代码

[英]Speed-up cython code

我有在 python 中工作的代码,并想使用 cython 来加速计算。 我复制的函数位于 .pyx 文件中,并从我的 python 代码中调用。 V、C、train、I_k 是二维 numpy 数组,而 lambda_u、user、hidden 是整数。 我没有任何使用 C 或 cython 的经验。 什么是使此代码更快的有效方法。 使用cython -a进行编译表明代码有缺陷,但我该如何改进它。 for i in prange (user_size, nogil=True):使用for i in prange (user_size, nogil=True):导致在Constructing Python slice object not allowed without gil

如何修改代码以收获cython的力量?

 @cython.boundscheck(False)
 @cython.wraparound(False)
 def u_update(V, C, train, I_k, lambda_u, user, hidden):
    cdef int user_size = user
    cdef int hidden_dim = hidden
    cdef np.ndarray U = np.empty((hidden_dim,user_size), float)
    cdef int m = C.shape[1]

    for i in range(user_size):
        C_i = np.zeros((m, m), dtype=float)
        for j in range(m):
            C_i[j,j]=C[i,j]

        U[:,i] = np.dot(np.linalg.inv(np.dot(V, np.dot(C_i,V.T)) + lambda_u*I_k), np.dot(V, np.dot(C_i,train[i,:].T)))

    return U

您正试图通过潜入游泳池的深处来使用cython 你应该从一些小东西开始,比如一些 numpy 例子。 或者甚至尝试改进np.diag

    i = 0
    C_i = np.zeros((m, m), dtype=float)
    for j in range(m):
        C_i[j,j]=C[i,j]

v.

    C_i = diag(C[i,:])

你能提高这个简单表达式的速度吗? diag没有被编译,但它确实执行了有效的索引分配。

 res[:n-k].flat[i::n+1] = v

但是cython的真正问题是这个表达式:

U[:,i] = np.dot(np.linalg.inv(np.dot(V, np.dot(C_i,V.T)) + lambda_u*I_k), np.dot(V, np.dot(C_i,train[i,:].T)))

np.dot被编译。 cython不会将其转换为c代码,也不会将所有 5 个dots合并为一个表达式。 它也不会触及inv 所以充其量cython会加速迭代包装器,但它仍然会调用这个 Python 表达式m次。

我的猜测是这个表达式可以被清理。 einsum替换内部dots可能可以消除对C_i的需要。 inv可能会使“矢量化”整个事情变得困难。 但我必须多研究一下。

但是,如果您想坚持使用cython路线,则需要将该U表达式转换为简单的迭代代码,而无需调用dotinv等 numpy 函数。

====================

我相信以下内容是等效的:

np.dot(C_i,V.T)
C[i,:,None]*V.T

在:

np.dot(C_i,train[i,:].T) 

如果train是 2d,则train[i,:]是 1d,而.T什么也不做。

In [289]: np.dot(np.diag([1,2,3]),np.arange(3))
Out[289]: array([0, 2, 6])
In [290]: np.array([1,2,3])*np.arange(3)
Out[290]: array([0, 2, 6])

如果我C_i对了,你就不需要C_i

======================

此外,这些计算可以移动到循环外,表达式如(未测试)

CV1 = C[:,:,None]*V.T   # a 3d array
CV2 = C * train.T  

for i in range(user_size):
    U[:,i] = np.dot(np.linalg.inv(np.dot(V, CV1[i,...]) + lambda_u*I_k), np.dot(V, CV2[i,...]))

进一步的步骤是将np.dot(V,CV...)移出循环。 这可能需要np.matmul (@) 或np.einsum 然后我们会有

for i...
    I = np.linalg.inv(VCV1[i,...])  
    U[:,i] = np.dot(I+ lambda_u), VCV2[i,])

甚至

for i...
     I[...i] = np.linalg.inv(...) # if inv can't be vectorized
U = np.einsum(..., I+lambda_u, VCV2)

这是一个粗略的草图,细节需要解决。

首先想到的是您没有输入函数参数并指定数据类型和维数,如下所示:

def u_update(np.ndarray[np.float64, ndim=2]V, np.ndarray[np.float64, ndim=2]\
C, np.ndarray[np.float64, ndim=2] train, np.ndarray[np.float64, ndim=2] \
I_k, int lambda_u, int user, int hidden) :

这将大大加快使用 2 个索引进行索引的速度,就像您在内部循环中所做的那样。

尽管您使用的是切片,但最好也对数组U执行此操作:

cdef np.ndarray[np.float64, ndim=2] U = np.empty((hidden_dim,user_size), np.float64)

接下来,您要重新定义C_i ,这是外循环每次迭代的大型二维数组。 此外,您还没有为其提供任何类型信息,如果 Cython 要提供任何加速,这是必须的。 要解决这个问题:

cdef np.ndarray[np.float64, ndim=2] C_i = np.zeros((m, m), dtype=np.float64)
    for i in range(user_size):
        C_i.fill(0)

在这里,我们已经定义了一次(带有类型信息),并通过填充零而不是每次都调用np.zeros()来创建一个新数组来重用内存。

此外,您可能只想完成调试关闭边界检查。

如果您需要在U[:,i]=...步骤中加速,您可以考虑使用 Cython 编写另一个函数来使用循环执行这些操作。

请阅读本教程,它应该让您了解在 Cython 中使用 Numpy 数组时应该做什么以及不应该做什么,并了解通过这些简单的更改可以获得多少加速。

暂无
暂无

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

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