[英]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
表达式转换为简单的迭代代码,而无需调用dot
和inv
等 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.