添加数字时,在python / numpy中浮动精度细分

时间:2013-01-11 13:33:03

标签: python numpy precision

由于numpy使用的数字非常少,我遇到了一些问题。我花了几个星期的时间来追溯数值积分的常数问题,即当我在函数中添加浮点数时,float64精度会丢失。使用产品而不是总和执行数学上相同的计算会产生正常的值。

以下是代码示例和结果图:

from matplotlib.pyplot import *
from numpy import vectorize, arange
import math

def func_product(x):
    return math.exp(-x)/(1+math.exp(x))

def func_sum(x):
    return math.exp(-x)-1/(1+math.exp(x))

#mathematically, both functions are the same

vecfunc_sum = vectorize(func_sum)
vecfunc_product = vectorize(func_product)

x = arange(0.,300.,1.)
y_sum = vecfunc_sum(x)
y_product = vecfunc_product(x)

plot(x,y_sum,    'k.-', label='sum')
plot(x,y_product,'r--',label='product')

yscale('symlog', linthreshy=1E-256)
legend(loc='lower right')
show()

enter image description here

正如你所看到的,相当低的总和值散布在零附近或正好为零,而多重值很好......

拜托,有人可以帮忙/解释一下吗?非常感谢!

3 个答案:

答案 0 :(得分:5)

由于舍入误差,浮点精度对加法/减法非常敏感。最终,1+exp(x)变得如此之大,以至于向exp(x)添加1会产生与exp(x)相同的内容。在双精度范围内exp(x) == 1e16

>>> (1e16 + 1) == (1e16)
True
>>> (1e15 + 1) == (1e15)
False

请注意math.log(1e16)大约是37 - 这大致是你的情节中疯狂的事情。

你可以遇到同样的问题,但是在不同的尺度上:

>>> (1e-16 + 1.) == (1.)
True
>>> (1e-15 + 1.) == (1.)
False

对于你所在政权中的绝大多数要点,你的func_product实际上在计算:

exp(-x)/exp(x) == exp(-2*x)

这就是为什么你的图形具有-2的良好斜率。

把它带到另一个极端,你的其他版本正在计算(至少近似):

exp(-x) - 1./exp(x) 

大约是

exp(-x) - exp(-x)

答案 1 :(得分:4)

这是catastrophic cancellation的一个例子。

让我们看一下计算失误的第一点,x = 36.0

In [42]: np.exp(-x)
Out[42]: 2.3195228302435691e-16

In [43]: - 1/(1+np.exp(x))
Out[43]: -2.3195228302435691e-16

In [44]: np.exp(-x) - 1/(1+np.exp(x))
Out[44]: 0.0

使用func_product的计算不会减去几乎相等的数字,因此可以避免灾难性的取消。


顺便说一句,如果您将math.exp更改为np.exp,则可以摆脱np.vectorize(这很慢):

def func_product(x):
    return np.exp(-x)/(1+np.exp(x))

def func_sum(x):
    return np.exp(-x)-1/(1+np.exp(x))

y_sum = func_sum_sum(x)
y_product = func_product_product(x)

答案 2 :(得分:2)

问题是您的func_sumnumerically unstable,因为它涉及两个非常接近的值之间的减法。

例如,在func_sum(200)的计算中,math.exp(-200)1/(1+math.exp(200))具有相同的值,因为将1添加到math.exp(200)无效,因为它超出了64位浮点的精度:

math.exp(200).hex()
0x1.73f60ea79f5b9p+288

(math.exp(200) + 1).hex()
0x1.73f60ea79f5b9p+288

(1/(math.exp(200) + 1)).hex()
0x1.6061812054cfap-289

math.exp(-200).hex()
0x1.6061812054cfap-289

这解释了为什么func_sum(200)给出零,但x轴上的点怎么样?这些也是由浮点不精确引起的;偶尔会发生math.exp(-x)不等于1/math.exp(x);理想情况下,math.exp(x)是与e^x最接近的浮点值,1/math.exp(x)是与math.exp(x)计算的浮点数的倒数最接近的浮点值,不一定是e^-x。实际上,math.exp(-100)1/(1+math.exp(100))非常接近,实际上只是在最后一个单元中有所不同:

math.exp(-100).hex()
0x1.a8c1f14e2af5dp-145

(1/math.exp(100)).hex()
0x1.a8c1f14e2af5cp-145

(1/(1+math.exp(100))).hex()
0x1.a8c1f14e2af5cp-145

func_sum(100).hex()
0x1.0000000000000p-197

因此,您实际计算的是math.exp(-x)1/math.exp(x)之间的差异(如果有)。您可以跟踪函数math.pow(2, -52) * math.exp(-x)的行,看它是否通过func_sum的正值(回想一下,52是64位浮点中有效数字的大小)。