Cython函数输出与Python函数输出略有不同

5 投票
2 回答
1406 浏览
提问于 2025-04-17 18:53

我把一个Python函数转换成了Cython版本,主要是给一些变量加上了类型。不过,Cython函数的输出和原来的Python函数稍微有点不同。

我在这篇文章中了解到了一些导致这种差异的原因 Cython: unsigned int indices for numpy arrays gives different result 但是即使知道了这些,我还是无法让Cython函数的结果和Python函数完全一致。

所以我整理了四个函数,展示我尝试过的内容。有人能帮我找出为什么每个函数的结果会有些不同吗?还有,怎样才能让Cython函数返回和function1完全相同的值呢?我在下面做了一些注释:

%%cython
import numpy as np
cimport numpy as np    

def function1(response, max_loc):    
    x, y = int(max_loc[0]), int(max_loc[1])

    tmp1 = (response[y,x+1] - response[y,x-1]) / 2*(response[y,x] - min(response[y,x-1], response[y,x+1]))
    tmp2 = (response[y,x+1] - response[y,x-1])
    tmp3 = 2*(response[y,x] - min(response[y,x-1], response[y,x+1]))

    print tmp1, tmp2, tmp3        
    return tmp1, tmp2, tmp3

cpdef function2(np.ndarray[np.float32_t, ndim=2] response, np.ndarray[np.float64_t, ndim=1] max_loc):
    cdef unsigned int x, y 
    x, y = int(max_loc[0]), int(max_loc[1])

    tmp1 = (response[y,x+1] - response[y,x-1]) / 2*(response[y,x] - min(response[y,x-1], response[y,x+1]))        
    tmp2 = (response[y,x+1] - response[y,x-1])
    tmp3 = 2*(response[y,x] - min(response[y,x-1], response[y,x+1]))     

    print tmp1, tmp2, tmp3        
    return tmp1, tmp2, tmp3


cpdef function3(np.ndarray[np.float32_t, ndim=2] response, np.ndarray[np.float64_t, ndim=1] max_loc):     
    cdef unsigned int x, y 
    x, y = int(max_loc[0]), int(max_loc[1])

    cdef np.float32_t tmp1, tmp2, tmp3
    cdef np.float32_t r1 =response[y,x+1]
    cdef np.float32_t r2 =response[y,x-1]
    cdef np.float32_t r3 =response[y,x]
    cdef np.float32_t r4 =response[y,x-1]
    cdef np.float32_t r5 =response[y,x+1]    

    tmp1 = (r1 - r2) / 2*(r3 - min(r4, r5))  
    tmp2 = (r1 - r2)
    tmp3 = 2*(r3 - min(r4, r5))

    print tmp1, tmp2, tmp3        
    return tmp1, tmp2, tmp3

def function4(response, max_loc):     
    x, y = int(max_loc[0]), int(max_loc[1])

    tmp1 = (float(response[y,x+1]) - response[y,x-1]) / 2*(float(response[y,x]) - min(response[y,x-1], response[y,x+1]))
    tmp2 = (float(response[y,x+1]) - response[y,x-1])
    tmp3 = 2*(float(response[y,x]) - min(response[y,x-1], response[y,x+1]))

    print tmp1, tmp2, tmp3        
    return tmp1, tmp2, tmp3

max_loc = np.asarray([ 15., 25.], dtype=np.float64) 
response = np.zeros((49,49), dtype=np.float32)     
x, y = int(max_loc[0]), int(max_loc[1])

response[y,x] = 0.959878861904  
response[y,x-1] = 0.438348740339
response[y,x+1] = 0.753262758255  

result1 = function1(response, max_loc)
result2 = function2(response, max_loc)
result3 = function3(response, max_loc)
result4 = function4(response, max_loc)
print result1
print result2
print result3
print result4

然后是结果:

0.0821185777156 0.314914 1.04306030273
0.082118573023 0.314914017916 1.04306024313
0.0821185708046 0.314914017916 1.04306030273
0.082118573023 0.314914017916 1.04306024313
(0.082118577715618812, 0.31491402, 1.043060302734375)
(0.08211857302303427, 0.3149140179157257, 1.0430602431297302)
(0.08211857080459595, 0.3149140179157257, 1.043060302734375)
(0.082118573023034269, 0.31491401791572571, 1.0430602431297302)

function1代表了我在原始Python函数中做的操作。tmp1是结果。

function2是我第一个Cython版本,结果稍微有点不同。显然,如果用带类型的变量(无符号整数或整数)来索引响应数组,结果会被强制转换为双精度浮点数(使用PyFloat_FromDouble),即使数组的类型是np.float32_t。但是如果用Python整数来索引数组,就会使用PyObject_GetItem函数,这样我得到的就是np.float32_t,这和function1的情况一样。因此,function1中的表达式是用np.float32_t类型的操作数计算的,而function2中的表达式是用双精度浮点数计算的。所以我在function1中打印的结果和function2稍微有点不同。

function3是我第二次尝试Cython,想要得到和function1相同的输出。在这里,我使用无符号整数索引来访问响应数组,但结果保留在np.float32_t的中间变量中,然后再用这些变量进行计算。结果还是稍微有点不同。显然,打印语句会使用PyFloat_FromDouble,所以它无法打印np.float32_t。

接着,我尝试把Python函数改成和Cython的版本一致。function4试图通过在每个表达式中至少将一个操作数转换为浮点数来实现,这样其他操作数也会被强制转换为Python浮点数,而在Cython中这就是双精度浮点数,表达式也就用双精度浮点数计算,就像在function2中一样。函数内部的打印结果和function2完全相同,但返回的值却稍微有点不同?!

2 个回答

2

我们来比较一下:

  • function1 一直使用 float32_t 类型。
  • function2 在索引时转换为 float,中间步骤使用 float,最后再转换回 float32_t 得到最终结果。
  • function3 先转换为 float,然后立刻又转换回 float32_t,中间步骤用的是 float32_t
  • function4 转换为 float,进行中间步骤,然后最终结果以 float 返回。

至于为什么 function4 打印的结果和 function2 一样,但返回的结果却不同:如果你看看这些类型,就会明白。它们的值看起来足够接近,所以打印出来的结果相同,但在表示上却不够接近,因此返回的结果不同。这并不奇怪,因为它们不是同一种类型。

2

如果你使用的是单精度浮点数,这种浮点数的精度大约只有7.225位小数,我觉得从单精度转换到双精度时,产生的小差异应该不会太大。

为了更清楚地说明一下function2的描述,如果你用一个对象来索引,Cython会使用PyObject_GetItem来获取一个np.float32的标量对象(而不是np.float32_t,后者只是C语言中的float的别名)。如果你直接在缓冲区中索引,而Cython需要一个对象,它会调用PyFloat_FromDouble。因为tmp1tmp2tmp3没有指定类型,所以它需要对象来进行赋值。

而在function3中,你已经给tmp变量指定了类型,但它仍然需要创建float对象来打印和返回结果。如果你使用NumPy的ndarray(见下文),就不会有这个问题:

顺便提一下,在function1中,当你把结果除以2时,你把结果提升到了np.float64。例如:

>>> type(np.float32(1) / 2)
<type 'numpy.float64'>

与之相比:

>>> type(np.float32(1) / np.float32(2))
<type 'numpy.float32'>

即使你确保在defcpdef函数中所有操作都是float32,最终的结果在编译后的扩展模块中可能仍然会有所不同。在下面的例子中,我检查了function1中的中间结果都是np.float32对象。在生成的function2的C代码中,我确认没有转换为double(或等效的别名)。然而这两个函数的结果仍然略有不同。我可能需要深入查看编译后的汇编代码才能找出原因,但也许我忽略了一些简单的东西。

def function1(response, max_loc):    
    tmp = np.zeros(3, dtype=np.float32)
    x, y = int(max_loc[0]), int(max_loc[1])
    tmp[0] = (((response[y,x+1] - response[y,x-1]) / np.float32(2)) *
             (response[y,x] - min(response[y,x-1], response[y,x+1])))
    tmp[1] = response[y,x+1] - response[y,x-1]
    tmp[2] = 2*(response[y,x] - min(response[y,x-1], response[y,x+1]))

    print tmp[0], tmp[1], tmp[2]
    return tmp

cpdef function2(np.ndarray[np.float32_t, ndim=2] response, max_loc):
    cdef np.ndarray[np.float32_t, ndim=1] tmp = np.zeros(3, dtype=np.float32)
    cdef unsigned int x, y
    x, y = int(max_loc[0]), int(max_loc[1])
    tmp[0] = (((response[y,x+1] - response[y,x-1]) / <np.float32_t>2) *
             (response[y,x] - min(response[y,x-1], response[y,x+1])))
    tmp[1] = response[y,x+1] - response[y,x-1]
    tmp[2] = 2*(response[y,x] - min(response[y,x-1], response[y,x+1]))

    print tmp[int(0)], tmp[int(1)], tmp[int(2)]
    return tmp

比较:

>>> function1(response, max_loc)
0.0821186 0.314914 1.04306
array([ 0.08211858,  0.31491402,  1.0430603 ], dtype=float32)

>>> function2(response, max_loc)
0.0821186 0.314914 1.04306
array([ 0.08211857,  0.31491402,  1.0430603 ], dtype=float32)

撰写回答