Python / NumPy中的浮点数运算在不同机器上无法重现

12

比较在几台不同的计算机上进行的浮点运算结果,发现它们一直产生不同的结果。这里是一个简化的示例,可以复现这种行为:

import numpy as np
from numpy.random import randn as rand

M = 1024
N = 2048
np.random.seed(0)

a = rand(M,N).astype(dtype=np.float32)
w = rand(N,M).astype(dtype=np.float32)

b = np.dot(a, w)
for i in range(10):
    b = b + np.dot(b, a)[:, :1024]
    np.divide(b, 100., out=b)

print b[0,:3]

不同的机器会产生不同的结果,例如:

  • [ -2.85753540e-05 -5.94204867e-05 -2.62337649e-04]
  • [ -2.85751412e-05 -5.94208468e-05 -2.62336689e-04]
  • [ -2.85754559e-05 -5.94202756e-05 -2.62337562e-04]

但我也可以得到相同的结果,例如在两台同款的MacBook上运行。这种情况发生在安装有相同版本的Python和numpy,但不一定链接同样的BLAS库(例如Mac上的accelerate框架,Ubuntu上的OpenBLAS)的机器上。然而,不同的数值库都应该符合相同的IEEE浮点标准并给出完全相同的结果,是吗?


1
所有机器上的种子都一样吗? - Padraic Cunningham
是的,np.random.seed(0)生成相同的状态,因为所有机器都在使用相同版本的numpy。 - Urs
对于兴趣,您使用的是哪个版本的numpy? - Padraic Cunningham
使用不同的种子数字会产生相同的行为吗? - Padraic Cunningham
就我个人而言,b /= 100.b += np.dot(b, a[:, :1024]) 更快更简洁。 - Veedrac
1
BLAS库可能使用不同的算法,因此期望它们始终给出相同的输出似乎有些奇怪。 - Veedrac
1个回答

5
浮点数计算结果不总是可重现的。
如果您使用相同的可执行文件、输入、库和相同的编译器设置(开关),则可以在不同的机器上获得浮点运算的可重现结果。
但是,如果您使用动态链接库,则可能会因为众多原因而获得不同的结果。首先,正如Veedrac在评论中所指出的那样,它可能在不同的体系结构上使用不同的算法进行例程处理。其次,编译器可能会根据开关(各种优化、控制设置)生成不同的代码。即使a+b+c也会在不同的机器和编译器上产生非确定性结果,因为我们无法确定评估顺序和中间计算的精度。
阅读这里,了解为什么在不同的IEEE 754-1985实现中无法保证获得相同的结果。新标准(IEEE 754-2008)试图更进一步,但它仍然不能保证在不同的实现之间获得相同的结果,因为例如它允许实现者选择何时检测tinyness(下溢异常)。
可以在这篇文章中找到有关浮点确定性的更多信息。

2
在Python层面上,对于诸如a + b + c这样的表达式,我们可以确定其求值顺序和中间结果的精度:求值顺序是确定性的,并且中间结果被强制存储到内存中(因此,由于不可预测的寄存器溢出而导致的非确定性不是问题)。然而,在单个算术运算中仍存在双重舍入的可能性,尽管这个问题正在逐渐变得更加罕见... - Mark Dickinson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接