我的代码在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
答案 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]
诉P>
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
次。
我的猜测是这个表达式可以清理掉。用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数组时应该做些什么,以及不应该做什么,以及了解使用这些数据可以获得多少加速简单的改变。