`numpy.random.normal` 在不同系统上生成的数字不同。

3

我正在比较在两个不同系统上使用np.random.normal生成的数字(详见下文)并使用以下代码进行比较(我使用旧版np.random.seed,因为另一个程序使用它来产生输出,我最终想要验证这个输出)(1)

import numpy as np

np.random.seed(0)
x = np.random.normal(scale=1e-3, size=10**5)
np.save('test.npy', x)

然后我将test.npy从一个系统复制到另一个系统,然后比较这两个版本:

>>> other = np.load('test.npy')
>>> (x != other).sum(), len(x)
(29, 100000)
>>> mask = x != other
>>> np.abs(x[mask] - other[mask])
array([5.42101086e-20, 1.35525272e-20, 2.71050543e-20, 5.42101086e-20,
       1.08420217e-19, 1.08420217e-19, 2.16840434e-19, 2.16840434e-19,
       1.35525272e-20, 1.08420217e-19, 1.08420217e-19, 5.42101086e-20,
       2.71050543e-20, 1.08420217e-19, 2.16840434e-19, 5.42101086e-20,
       2.71050543e-20, 2.16840434e-19, 2.16840434e-19, 2.71050543e-20,
       2.71050543e-20, 1.08420217e-19, 1.08420217e-19, 1.08420217e-19,
       5.42101086e-20, 1.08420217e-19, 1.08420217e-19, 5.42101086e-20,
       2.71050543e-20])
>>> x[mask]
array([ 4.52489093e-04,  9.78961454e-05, -1.47113076e-04, -3.67859222e-04,
       -5.33279620e-04,  8.40794952e-04, -7.75987295e-04,  1.34205479e-03,
        6.34459482e-05,  5.07109360e-04, -7.68363366e-04,  3.33350262e-04,
       -2.19367067e-04,  6.11402140e-04, -1.30486526e-03, -4.42699624e-04,
        1.45463287e-04, -1.22491651e-03,  1.05226781e-03, -2.43032730e-04,
       -2.40551279e-04,  4.95396595e-04, -7.25454745e-04, -8.50779215e-04,
       -2.66274662e-04,  7.28854386e-04,  8.38515107e-04,  3.36152654e-04,
       -1.26550328e-04])

所以有29个元素在100,000个元素中稍微有所不同。但是,我不明白这种差异来自何处。我确认我在两个系统上都安装了相同版本的Python和NumPy:python==3.9.4numpy==1.20.2(通过python -m pip install numpy==1.20.2获取;但我也检查了最新版本的numpy==1.23.0,结果完全相同)。我验证了RNG状态(通过np.random.get_state())在调用np.random.normal之前和之后在两个系统上是相同的。我多次保存和复制了test.npy文件,并且还通过MD5校验进行了验证,因此差异必须源于随机数生成本身(1)。然而,我看不出这是可能的,因为两者都使用相同的随机状态进行初始化。

关于系统的信息

A系统(保存test.npy的系统):

$ uname -a
Linux SystemA 3.10.0-1160.31.1.el7.x86_64 #1 SMP Thu Jun 10 13:32:12 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux

我也在系统A2上进行了测试,该系统的操作系统版本与系统A相同,但配备了不同的CPU,但结果从A到A2并未发生变化,因此我怀疑是操作系统版本问题。

系统B(加载test.npy文件的系统):

$ uname -a
Linux SystemB 5.4.0-113-generic #127-Ubuntu SMP Wed May 18 14:30:56 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux

注释 (1): 当我按照文档中建议的方式使用 np.random.seed,即 rs = RandomState(MT19937(SeedSequence(0))) 时,我发现两个系统之间的差异仍然存在。但是,当我使用 np.random.default_rng(seed=0),也就是新的 PCG64 时,我发现这种差异消失了。


如果其他人也对uname -a输出感到困惑。 - Michael Szczesny
也许是由于浮点数的属性所致?请参见https://dev59.com/i10a5IYBdhLWcg3wqaRS - Gediminas
相关(不是回答这个问题):https://dev59.com/usLra4cB1Zd3GeqPMpNI - Peter O.
1个回答

3
鉴于差异都很小,这表明底层的位生成器正在执行相同的操作。这只是与底层数学库之间的差异有关。
NumPy的传统生成器使用libm中的sqrtlog函数,您可以看到它通过首先查找提供生成器的共享对象来引入这些符号:
import numpy as np

print(np.random.mtrand.__file__)

然后使用以下方式转储符号:

nm -C -gD mtrand.*.so | grep GLIBC

以上输出中的mtrand文件名是从哪里来的。

我得到了很多其他符号输出,但这可能解释了差异。

猜测与log实现有关,因此您可以尝试使用以下命令进行测试:

import numpy as np

np.random.seed(0)

x = 2 * np.random.rand(2, 10**5) - 1
r2 = np.sum(x * x, axis=0)

np.save('test-log.npy', np.log(r2))

并比较这两个系统之间的区别。


这似乎是原因。我比较了r2(确保满足0 < r2 < 1),两个系统的数字是一致的。然后我比较了np.log(r2),它们不一致。有趣的是,对于结果正常值,只有这么小一部分受到影响;有没有办法查看libm函数的实现?我该如何找到libm版本?我检查了ldconfig -p | grep libm\\.,它给出了libm.so.6 (libc6,x86-64, OS ABI: Linux 2.6.32)libm.so.6 (libc6,x86-64, OS ABI: Linux 3.2.0);那些是库版本号吗? - a_guest
然而,这如何解释对于PCG64没有差异呢?我创建的10**5个样本只是“幸运”吗?我看到random_normal的新版本使用npy_log1p,但那也可能在底层使用log?此外,它使用exp,这也可能有不同的实现方式(?)。 - a_guest
这可能是因为PCG64是NumPy中伪随机生成的一个新系统,自1.17版本以来。还可以参考此问题。另外,您提出的差异可能是由于浮点数实现差异引起的;还可以参考涉及R语言的此问题 - Peter O.
@a_guest 确定代码来自哪里是非常系统特定的。我建议询问您的发行版软件包管理器(例如,在Debian / Ubuntu下,您将使用[dpkg](https://unix.stackexchange.com/q/224186/90376)),然后搜索与该版本软件包相关联的源代码。 - Sam Mason

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