我一直在尝试升级一个包含大量标量几何运算的库,这样它们也可以与 numpy 数组一起使用。在执行此操作时,我注意到 numpy divide 有一些奇怪的行为。
在原始代码中,如果两个变量都不为零,则检查变量之间的归一化差异,然后切换到 numpy,最终看起来像这样:
import numpy as np
a = np.array([0, 1, 2, 3, 4])
b = np.array([1, 2, 3, 0, 4])
o = np.zeros(len(a))
o = np.divide(np.subtract(a, b), b, out=o, where=np.logical_and(a != 0, b != 0))
print(f'First implementation: {o}')
对于无法计算的实例,我传入了一个初始化为零的输出缓冲区;这返回:
First implementation: [ 0. -0.5 -0.33333333 0. 0. ]
我不得不为标量稍微修改一下,因为 out
需要一个数组,但它看起来没问题。
a = 0
b = 4
o = None if np.isscalar(a) else np.zeros(len(a))
o = np.divide(np.subtract(a, b), b, out=o, where=np.logical_and(b != 0, a != 0))
print(f'Modified out for scalar: {o}')
返回
Modified out for scalar: 0.0.
然后通过一些测试功能运行它,发现其中很多都失败了。深入研究,我发现第一次使用 where
设置为 False
的标量调用除法时,函数返回零,但如果我再次调用它,第二个时间它返回一些不可预测的东西。
a = 0
b = 4
print(f'First divide: {np.divide(b, a, where=False)}')
print(f'Second divide: {np.divide(b, a, where=False)}')
返回
First divide: 0.0
Second divide: 4.0
查看文档,它说“其中条件为 False 的位置将保持未初始化状态”,所以我猜 numpy 是一些内部缓冲区,它最初设置为零,然后它最终会保留一个较早的中间值。
我正在努力了解如何在有或没有 where
子句的情况下使用 divide
;如果我使用 where
我会得到不可预知的输出,如果我不使用,我将无法防止被零除。在这些情况下,我是不是遗漏了什么,或者我只需要有不同的代码路径?我意识到我已经使用 out
变量进入了不同的代码路径。
如有任何建议,我将不胜感激。
最佳答案
在我看来这像是一个错误。但是我认为无论如何出于性能原因,您都希望在标量的情况下将对 ufuncs 的调用短路,因此这是一个试图防止它过于困惑的问题。由于 a
或 b
可能是标量,因此您需要同时检查它们。将该检查放入有条件地返回输出数组或 None
的函数中,您可以这样做
def scalar_test_np_zeros(a, b):
"""Return np.zeros for the length of arguments unless both
arguments are scalar, then None."""
if a_is:=np.isscalar(a) and np.isscalar(b):
return None
else:
return np.zeros(len(a) if a_is else len(b))
a = 0
b = 4
if o := scalar_test_np_zeros(a, b) is None:
o = (a-b)/b if a and b else 0.0
else:
np.divide(np.subtract(a, b), b, out=o,
where=np.logical_and(b != 0, a != 0))
标量测试在其他有类似问题的代码中很有用。
关于python - 标量的奇怪 numpy 划分行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/74187097/