加速cython代码

时间:2016-09-28 11:15:26

标签: python numpy cython

我的代码在python中工作,并希望使用cython来加速计算。我复制的函数位于.pyx文件中,并从我的python代码中调用。 V,C,train,I_k是2-d numpy数组,lambda_u,user,hidden是int。 我没有使用C或cython的经验。什么是有效的 使代码更快的方法。 使用cython -a进行编译会向我显示代码存在缺陷但我该如何改进它。使用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

2 个答案:

答案 0 :(得分:3)

您正试图通过潜入池的深层来使用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]

    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.dotcython无法将其转换为c代码,也不会将所有5 dots合并为一个表达式。它也不会触及inv。因此,最好cython将加速迭代包装器,但它仍将调用此Python表达式m次。

我的猜测是这个表达式可以清理掉。用dots替换内部einsum可能会消除对C_i的需求。 inv可能会进行“矢量化”。整件事情很难。但我必须更多地研究它。

但是如果你想坚持使用cython路由,你需要将U表达式转换为简单的迭代代码,而不需要调用像dot和{{1}这样的numpy函数}。

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

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

inv

在:

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

如果np.dot(C_i,train[i,:].T) 为2d,则train为1d,train[i,:]不执行任何操作。

.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

进一步的步骤是将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,])

这是一个草图,需要详细说明。

答案 1 :(得分:2)

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

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编写另一个函数来使用循环执行这些操作。

请阅读此tutorial,它可以让您了解在Cython中使用Numpy数组时应该做些什么,以及不应该做什么,以及了解使用这些数据可以获得多少加速简单的改变。