Jea*_*cut 5 python rounding-error contiguous
问题 :
我偶然发现了一个对行业产生深远(且非常有影响力)影响的问题,并且似乎没有在任何地方记录:
似乎在 python 中,矩阵乘法(使用@或np.matmul)在 C 连续数组和 F 连续数组之间给出了不同的答案:
import platform
print(platform.platform()) # Linux-5.10.0-23-cloud-amd64-x86_64-with-glibc2.31
import numpy as np
print(np.__version__) # 1.23.5
np.random.seed(0) # Will work with most of seed, I believe
M, N = 10, 5
X_c = np.random.normal(size=M*N).reshape(M,N)
X_f = np.asfortranarray(X_c)
assert (X_c == X_f).all()
p = np.random.normal(size=N)
assert (X_c @ p == X_f @ p).all() # FAIL
Run Code Online (Sandbox Code Playgroud)
据您所知,此问题是否记录在任何地方?
后果示例:
在某些情况下,这些错误可能会变得很大:
示例:在sklearn.linear_model.LinearRegression类中,fit在 F 连续的近奇异矩阵 X(通常来自 pandas DataFrame)的情况下,该方法将导致非常错误的参数。这可能会导致预测到处都是并且 R2 为负值。
在我看来,您注意到的问题是浮点运算的基本限制造成的;尤其
\n考虑下面代码中的示例,其中我们想要对数组的所有元素求和a。我们将以两种不同的顺序执行此操作:sum_1从左到右对所有值求和,sum_2逐步对所有相邻的值对求和,直到只剩下一个值。从数学上讲,这应该没有什么区别,因为求和是实数的关联运算。然而,正如我们从最后一行的输出中看到的那样,它与计算机上的浮点数不关联:
import numpy as np\n\neps_half = np.finfo(float).eps / 2\na = np.asarray([0.5, 0.5, eps_half, eps_half], dtype=float)\n\nsum_1 = ((a[0] + a[1]) + a[2]) + a[3]\nsum_2 = (a[0] + a[1]) + (a[2] + a[3])\nprint(sum_1, "versus", sum_2)\n# >>> 1.0 versus 1.0000000000000002\nRun Code Online (Sandbox Code Playgroud)\n这里发生了什么?请注意 的使用eps,它被定义为1.0 和下一个大于 1.0 的最小可表示浮点数之间的差。对于该float类型,至少在我的系统上,该值是 ca。2e-16,即 0.0000000000000002。该定义意味着,如果我们添加一个小于eps1.0 的值,结果仍然是 1.0!
在上面构建的示例中,后者发生在 for 的计算中,sum_1但不发生在 的计算中sum_2,从它们的计算步骤可以看出:
sum_1:\n0.5 + 0.5 \xe2\x86\x92 1.0 添加 a[0] 和 a[1]:按预期工作1.0 + eps/2 \xe2\x86\x92 1.0 将 a[2] 添加到先前的总和中:eps/2 太小,无法将结果表示为大于 1.0 的自身浮点值,因此总和仍为 1.01.0 + eps/2 \xe2\x86\x92 1.0 将 a[3] 添加到先前的总和中:与步骤 2 中的问题相同sum_2:\n0.5 + 0.5 \xe2\x86\x92 1.0 添加 a[0] 和 a[1]:按预期工作eps/2 + eps/2 \xe2\x86\x92 eps 添加 a[2] 和 a[3]:按预期工作1.0 + eps \xe2\x86\x92 1.0000000000000002 添加步骤 1 和 2 的结果:按预期工作,因为 eps 足够大,结果可以表示为其自身大于 1.0 的浮点值你可能会问自己,你的观察结果与我上面的例子有什么关系。最有可能发生的情况如下:
\n对于矩阵向量乘积的结果向量中的每个元素,如代码中计算的那样,本质上我们计算两个向量的点积:(1) 给定矩阵的相应行和 (2) 给定向量。根据定义,点积将给定向量的相应元素相乘,然后将这些中间结果相加。现在,在根据相同的输入值计算矩阵向量乘积时,唯一可能给我们带来不同结果的选择可能在于这个求和;或者更确切地说,它的顺序,作为每个点积的最后一步。事情是这样的:
\neps,而是对大浮点值和小浮点值求和的一般问题。我尝试总结一些应该作为关键要点的要点:
\nprint(np.abs(X_c @ p - X_f @ p).max())\n# >>> 2.220446049250313e-16\nRun Code Online (Sandbox Code Playgroud)\n\xe2\x80\xa6 非常小eps(顺便说一句,它是上面的精确值)。这并不意味着应该忽略这种不精确性,例如,它们可能会因渐进计算而放大。np.allclose()精确相等比较之类的函数。我承认,我有点怀疑你的最终结论;也就是说,你看到的巨大错误sklearn.linear_model.LinearRegression有相同的原因,但也许是上述放大效应的结果。也许您应该提供一个最小的可重复示例以供进一步调查。