我有一个给定memoryview向量的函数,我想计算该向量的范数。到目前为止,我通过将memoryview转换为Numpy数组并通过np.sqrt(V.dot(V))
计算范数来实现这一点。现在,出于速度原因,我想放弃该步骤,但是该程序在使用以下实现时有时会失败。
cdef do_something(np.double_t[::1] M_mem):
cdef:
int i
np.double_t norm_mv = 0
np.double_t norm_np = 0
np.ndarray[np.double_t, ndim=1] V = np.copy(np.asarray(M_mem))
# Original implementation -- working
norm_np = np.sqrt(V.dot(V))
# My failed try with memoryview -- not working
for i in range(M_mem.shape[0]):
norm_mv += M_mem[i]**2
norm_mv = np.sqrt(norm_mv)
# norm_mv != norm_np
我怀疑原因是Floating Point Arithmetic妨碍了足够大的向量。是否存在一种数值稳定的方法来计算Cython memoryview的范数?
更新
检查后,发现舍入错误可能毫无意义。相反,发生了一件非常奇怪的事情。我的实际功能如下:
@cython.boundscheck(False)
@cython.cdivision(True)
@cython.wraparound(False)
cdef np.double_t[:,::1] GS_coefficients(np.double_t[:,::1] M_mem):
cdef:
int n, i, k
int N_E = M_mem.shape[1]
np.ndarray[np.double_t, ndim=2] W = np.asarray(M_mem)
np.ndarray[np.double_t, ndim=2] V = np.copy(W)
np.double_t[:,::1] G = np.eye(N_E, dtype=np.float64)
np.longdouble_t norm = 0 # np.sqrt(V[:,0].dot(V[:,0]))
for i in range(M_mem.shape[0]):
norm += M_mem[i,0]**2
norm = sqrt(norm)
print("npx: ", np.sqrt(V[:,0].dot(V[:,0]))) # line 1
print("cp: ", norm) # line 2
V[:,0] /= norm
G[0,0] /= norm
for n in range(1, N_E):
for i in range(0, n):
G[n,i] = - (V[:,i].dot(W[:,n]))
V[:,n] += G[n,i] * V[:,i]
norm = np.sqrt(V[:,n].dot(V[:,n]))
V[:,n] /= norm
for i in range(n+1):
G[n,i] /= norm
return G
我插入了print
语句来检查与norm
的结果“相等”。现在的问题是,上面的代码可以正常工作。但是,当我注释掉第一个打印语句(第1行)时,代码可以正常运行该函数,但在程序执行后不久就会失败。那里发生了什么事?这不只是一个print
语句,甚至在操作上也不会影响其他任何内容吗?
更新2
以下是我尝试的一个最小,完整和可验证的示例:
DEF N_E_cpt = 4
cimport cython
cimport numpy as np
import numpy as np
from libc.math cimport sqrt
@cython.boundscheck(False)
@cython.cdivision(True)
@cython.wraparound(False)
cdef np.double_t[:,::1] GS_coefficients(np.double_t[:,::1] M_mem):
"""Writes the coefficients, that the Gram-Schmidt procedure
provides in a Matrix and retruns it."""
cdef:
int n, i, k
int N_E = M_mem.shape[1]
np.ndarray[np.double_t, ndim=2] W = np.asarray(M_mem)
np.ndarray[np.double_t, ndim=2] V = np.copy(W)
np.double_t[:,::1] G = np.eye(N_E, dtype=np.float64)
np.longdouble_t norm = 0 # np.sqrt(V[:,0].dot(V[:,0]))
for i in range(M_mem.shape[0]):
norm += M_mem[i,0]**2
norm = sqrt(norm)
print("npx: ", np.sqrt(V[:,0].dot(V[:,0]))) # line 1
print("cp: ", norm) # line 2
V[:,0] /= norm
G[0,0] /= norm
for n in range(1, N_E):
for i in range(0, n):
G[n,i] = - (V[:,i].dot(W[:,n]))
V[:,n] += G[n,i] * V[:,i]
norm = np.sqrt(V[:,n].dot(V[:,n]))
V[:,n] /= norm
for i in range(n+1):
G[n,i] /= norm
return G
@cython.boundscheck(False)
@cython.cdivision(True)
@cython.wraparound(False)
cdef np.double_t[:,::1] G_mat(np.double_t[:,::1] M_mem):
"""Calls GS_coefficients and uses the coefficients to calculate
the entries of the transformation matrix G_ij"""
cdef:
np.double_t[:,::1] G_mem = GS_coefficients(M_mem)
int N_E = G_mem.shape[1]
np.double_t carr[N_E_cpt][N_E_cpt]
np.double_t[:,::1] G = carr
int n, i, j
# delete lower triangle in G
G[...] = G_mem
for i in range(N_E_cpt):
for j in range(0, i):
G[i,j] = 0.
for n in range(1, N_E):
for i in range(0, n):
for j in range(0, i+1):
G[n,j] += G_mem[n,i] * G[i,j]
return G
def run_test():
cdef:
np.double_t[:,::1] A_mem
np.double_t[:,::1] G
np.ndarray[np.double_t, ndim=2] A = np.random.rand(400**2, N)
int N = 4
A_mem = A
G = G_mat(A_mem)
X = np.zeros((400**2, N))
for i in range(0, N):
for j in range(0,i+1):
X[:,i] += G[i,j] * A[:,j]
print(X)
print("\n", X.T.dot(X))
run_test()
我认为没有必要了解该代码的功能。对我来说,真正的奥秘在于,为什么print
声明会有所作为。
因此,此代码应该采用的是将一组非正交向量作为矩阵向量写成列向量,然后返回一个正交化矩阵,对矩阵向量进行正交化,如下所示:
因此A_ {orthonormal}等效于代码中的X矩阵。当您将正交矩阵的转置矩阵与正交矩阵本身相乘时,您将获得单位矩阵,只要print
语句#line1在其中,就可以得到单位矩阵。删除后,还会出现非对角线的条目,这意味着矩阵甚至不是正交的。为什么?
答案 0 :(得分:1)
至少有一个错字
for i in range(M_mem.shape[0]):
norm += M_mem[i]**2
->
for i in range(M_mem.shape[0]):
norm_mv += M_mem[i]**2
否则,我推荐以下更惯用的版本:
import numpy as np
cimport numpy as np
from libc.math cimport sqrt
def do_something(double[::1] M_mem):
cdef:
int i
double norm_mv = 0
double norm_np = 0
double[::1] V = np.copy(np.asarray(M_mem))
# Original implementation -- working
norm_np = np.sqrt(np.dot(V, V))
# My failed try with memoryview -- not working
for i in range(M_mem.shape[0]):
norm_mv += M_mem[i]**2
norm_mv = sqrt(norm_mv)
# norm_mv != norm_np
return norm_np, norm_mv
import 和 cimport numpy并使用libc.math
中的标量数学函数而不是NumPy版本。您仍然可以通过用@cython.boundscheck(False)
装饰例程来加快代码速度(然后需要cimport cython
)。