
本文深入探讨了NumPy中np.linalg.norm与手动计算平方范数在数值精度上的差异。尽管print()输出可能显示一致,但np.array_equal可能揭示细微的浮点数不相等。这源于np.linalg.norm内部的开方操作及其后续的平方运算,以及NumPy默认的打印精度设置如何掩盖这些微小差异。文章提供了详细的代码示例和原理分析,并给出处理浮点数比较的建议。
理解数值计算中的微妙差异
在进行科学计算时,尤其是在处理浮点数时,看似等价的操作有时会产生极其微小的数值差异,这些差异在默认的输出显示中可能被隐藏。本节将通过一个具体的numpy示例来揭示这种现象,并深入分析其背后的原因。
假设我们有以下两个NumPy数组:
import numpy as npa = np.array([[ 0, 1, 10, 2, 5]])b = np.array([[ 0, 1, 18, 15, 5], [13, 9, 23, 3, 22], [ 2, 10, 17, 4, 8]])
我们希望计算 a 中每个向量与 b 中每个向量之间的欧氏距离的平方,并取负号后除以2。我们尝试两种不同的方法。
方法一:使用 np.linalg.norm这种方法首先计算向量差的L2范数(即欧氏距离),然后将其平方。
m1 = -np.linalg.norm(a[:, np.newaxis, :] - b[np.newaxis, :, :], axis=-1) ** 2 / 2print("m1:", m1)
方法二:手动计算平方和这种方法直接计算向量差的平方和,这正是欧氏距离平方的定义。
m2 = -np.sum(np.square(a[:, np.newaxis, :] - b[np.newaxis, :, :]), axis=-1) / 2print("m2:", m2)
当我们打印 m1 和 m2 的结果时,它们看起来是完全相同的:
m1: [[-116.5 -346. -73.5]]m2: [[-116.5 -346. -73.5]]
然而,当我们使用 np.array_equal 来检查这两个数组是否完全相等时,结果却出人意料:
print(f"np.array_equal(m1, m2): {np.array_equal(m1, m2)}")# 输出: np.array_equal(m1, m2): False
这表明 m1 和 m2 之间存在差异。更令人困惑的是,如果我们创建一个字面量数组 sanity_check,并与 m1 和 m2 进行比较,会发现:
sanity_check = np.array([[-116.5, -346. , -73.5]])print(f"np.array_equal(sanity_check, m1): {np.array_equal(sanity_check, m1)}")print(f"np.array_equal(sanity_check, m2): {np.array_equal(sanity_check, m2)}")# 输出:# np.array_equal(sanity_check, m1): False# np.array_equal(sanity_check, m2): True
这表明 m1 是“异常”的一个,它与我们期望的精确值不符,而 m2 却与精确值匹配。
揭示数值差异的真相:浮点数运算的本质
要理解 m1 产生差异的原因,我们需要深入了解浮点数运算的精度问题。np.linalg.norm 函数在计算向量的L2范数时,其内部逻辑是计算 sqrt(sum(v_i^2))。当我们将 np.linalg.norm 的结果再次平方时,实际上执行了 (sqrt(sum(v_i^2)))^2。
在浮点数算术中,sqrt(X)**2 并不总是严格等于 X。由于计算机内部表示浮点数的限制,开方和平方这两个逆操作可能会引入微小的精度损失。
我们可以通过一个简单的例子来验证这一点:
val_squared = 8**2 + 13**2print(f"8**2 + 13**2: {val_squared}")print(f"np.sqrt(8**2 + 13**2)**2: {np.sqrt(val_squared)**2}")# 输出:# 8**2 + 13**2: 233# np.sqrt(8**2 + 13**2)**2: 232.99999999999997
可以看到,np.sqrt(233)**2 的结果略小于 233。正是这种微小的精度损失,导致了 m1 与 m2 之间的差异。m2 直接计算了平方和,避免了中间的开方操作,因此保留了更高的精度。
为了直观地看到 m1 和 m2 之间实际的数值差异,我们可以将它们转换为列表,以显示完整的浮点数精度:
print(f"m1.tolist(): {m1.tolist()}")print(f"m2.tolist(): {m2.tolist()}")# 输出:# m1.tolist(): [[-116.49999999999999, -346.0, -73.5]]# m2.tolist(): [[-116.5, -346.0, -73.5]]
现在,m1 在第一个元素上的微小差异清晰可见,而 m2 则精确地保持了期望值。
理解打印输出的“假象”:NumPy的打印选项
为什么 print(m1) 和 print(m2) 的输出看起来完全相同,却在 np.array_equal 中表现出不同呢?这与NumPy的打印选项有关。NumPy通过 np.set_printoptions 函数控制数组的打印格式,包括浮点数的显示精度。
默认情况下,NumPy的打印选项可能设置了较低的显示精度(例如,precision=3),这意味着它只会显示小数点后几位,从而隐藏了那些超出显示精度的微小差异。
我们可以通过 np.get_printoptions() 查看当前的打印设置:
print(np.get_printoptions())# 默认输出可能类似:# {'edgeitems': 3, 'threshold': 1000, 'floatmode': 'maxprec', 'precision': 3, 'suppress': False, 'linewidth': 75, 'nanstr': 'nan', 'infstr': 'inf', 'sign': '-', 'formatter': None, 'legacy': False}
其中 precision 参数控制了浮点数的显示精度。当 precision 设置为较小的值时,例如 3,像 232.99999999999997 这样的数字在打印时就会被四舍五入显示为 233.0。
如果我们将打印精度调高,例如设置为 17 位小数,这些隐藏的差异就会显现出来:
np.set_printoptions(precision=17)print("m1 (高精度):", m1)print("m2 (高精度):", m2)# 输出:# m1 (高精度): [[-116.4999999999999858 -346.0000000000000000 -73.5000000000000000]]# m2 (高精度): [[-116.5000000000000000 -346.0000000000000000 -73.5000000000000000]]
此时,m1 和 m2 之间的差异在打印输出中也变得可见。
总结与最佳实践
通过上述分析,我们可以得出以下结论和最佳实践:
np.linalg.norm 与精度:当计算欧氏距离的平方(或其他范数的平方)时,如果使用 np.linalg.norm 后再进行平方操作,可能会因为内部的开方和平方过程引入浮点数精度误差。避免不必要的开方:对于计算平方欧氏距离等场景,直接使用 np.sum(np.square(diff), axis=-1) 的方式通常比 np.linalg.norm(diff, axis=-1)**2 更具数值稳定性,因为它避免了中间的开方操作。理解打印输出的局限性:NumPy的默认打印选项会限制浮点数的显示精度,这可能掩盖实际存在的微小数值差异。在调试数值问题时,应注意调整 np.set_printoptions(precision=…) 或使用 tolist() 等方法查看完整精度。浮点数比较的注意事项:由于浮点数的本质,直接使用 == 或 np.array_equal 来比较两个浮点数数组是否相等是危险的,因为即使是理论上应该相等的数值也可能因精度问题而略有不同。推荐使用带容差的比较方法,例如 np.isclose() 或 np.allclose(),它们允许在一定误差范围内判断数值是否“足够接近”。
# 示例:使用 np.allclose 进行浮点数比较print(f"np.allclose(m1, m2): {np.allclose(m1, m2)}")# 输出: np.allclose(m1, m2): True (默认容差下认为相等)# 我们可以通过调整 rtol 和 atol 参数来控制容差# np.allclose(m1, m2, rtol=1e-05, atol=1e-08)
通过理解这些浮点数计算的细微之处和NumPy的工具特性,我们可以更准确地进行数值分析和编程,避免潜在的精度陷阱。
以上就是NumPy中np.linalg.norm的数值精度与浮点数打印陷阱解析的详细内容,更多请关注创想鸟其它相关文章!
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1375245.html
微信扫一扫
支付宝扫一扫