【问题标题】:Most memory-efficient way to compute abs()**2 of complex numpy ndarray计算复杂 numpy ndarray 的 abs()**2 的最节省内存的方法
【发布时间】:2015-08-06 21:52:18
【问题描述】:

我正在寻找最节省内存的方法来计算复杂的 numpy ndarray 的绝对平方值

arr = np.empty((250000, 150), dtype='complex128')  # common size

我还没有找到完全可以做到 np.abs()**2 的 ufunc。

由于这种大小和类型的数组占用大约半 GB,我正在寻找一种主要节省内存的方法。

我也希望它是可移植的,所以最好是一些 ufunc 的组合。

到目前为止,我的理解是这应该是最好的

result = np.abs(arr)
result **= 2

它将不必要地计算(**0.5)**2,但应该就地计算**2。总共峰值内存需求只有原始数组大小 + 结果数组大小,应该是 1.5 * 原始数组大小,因为结果是真实的。

如果我想摆脱无用的**2 电话,我必须做这样的事情

result = arr.real**2
result += arr.imag**2

但如果我没记错的话,这意味着我必须为 实部和虚部计算分配内存,因此峰值内存使用量将是 2.0 * 原始数组大小。 arr.real 属性也返回一个不连续的数组(但这不太重要)。

我有什么遗漏吗?有没有更好的方法来做到这一点?

编辑 1: 抱歉没说清楚,我不想覆盖arr,所以不能作为out使用。

【问题讨论】:

    标签: python numpy complex-numbers memory-efficient numpy-ufunc


    【解决方案1】:

    如果你不想要sqrt(应该比乘法重得多),那么不要abs

    如果你不想要双内存,那就不要real**2 + imag**2

    那你可以试试这个(使用索引技巧)

    N0 = 23
    np0 = (np.random.randn(N0) + 1j*np.random.randn(N0)).astype(np.complex128)
    ret_ = np.abs(np0)**2
    tmp0 = np0.view(np.float64)
    ret0 = np.matmul(tmp0.reshape(N0,1,2), tmp0.reshape(N0,2,1)).reshape(N0)
    assert np.abs(ret_-ret0).max()<1e-7
    

    无论如何,我更喜欢numba 解决方案

    【讨论】:

      【解决方案2】:

      感谢numba.vectorize在最新版本的numba中,为任务创建一个numpy通用函数非常容易:

      @numba.vectorize([numba.float64(numba.complex128),numba.float32(numba.complex64)])
      def abs2(x):
          return x.real**2 + x.imag**2
      

      在我的机器上,与创建中间数组的纯 numpy 版本相比,我发现速度提高了三倍:

      >>> x = np.random.randn(10000).view('c16')
      >>> y = abs2(x)
      >>> np.all(y == x.real**2 + x.imag**2)   # exactly equal, being the same operation
      True
      >>> %timeit np.abs(x)**2
      10000 loops, best of 3: 81.4 µs per loop
      >>> %timeit x.real**2 + x.imag**2
      100000 loops, best of 3: 12.7 µs per loop
      >>> %timeit abs2(x)
      100000 loops, best of 3: 4.6 µs per loop
      

      【讨论】:

      • 我想接受这个作为答案,但我不确定它的便携性。如今,在大多数机器上使用 Anaconda 很容易安装 Numba,但我不确定实际 LLVM 绑定在跨架构之间的可移植性如何。也许您可以添加一些有关此答案可移植性的信息。
      • 好吧,我是 LLVM 专家,但当前版本 (0.31.0) 的文档说:支持 Linux、Windows 7 和 OS X 10.9 及更高版本。
      【解决方案3】:

      编辑:此解决方案的最低内存要求是两倍,而且速度稍快。不过cmets里面的讨论还是可以参考的。

      这是一个更快的解决方案,结果存储在res

      import numpy as np
      res = arr.conjugate()
      np.multiply(arr,res,out=res)
      

      我们利用了复数的绝对值属性,即abs(z) = sqrt(z*z.conjugate),因此abs(z)**2 = z*z.conjugate

      【讨论】:

      • 我也是这么想的,但是这个有个问题,结果还是很复杂。此外,峰值内存消耗为 2.0 * 原始数组大小。我可以简单地取实部(因为 imag 部分应该非常接近 0),但这会进一步增加峰值内存消耗或给我一个不连续的数组。此外,复数的乘法将执行许多我们已经知道没有用的不必要的乘法和加法(因为它们抵消为 0)。
      • 1) 结果为实值,复数dtype,不一样; 2)内存消耗不是两次,我们只分配一次,对于res,这是不可避免的,然后使用out对于multiply(); 3)注意all(res.imag==0)-&gt;True,这样就没有虚部了; 4)您不能将复数乘法视为 4 个实数乘法并得出结论,计算非常耗时。该代码比使用abs() 更快,这就是所要求的。如果您想知道为什么会这样,这可能归结为 CPU 如何实现复数乘法
      • 即使它是实值(理论上),它仍然占用所有零虚部的内存。我在谈论我需要多少内存才能获得最终(真实)结果,假设我没有'|想要覆盖 arr。最小值为 1.5 * arr 大小。您的建议是 2.0,因为它也占用了零虚部的内存。依靠 CPU 优化不是很便携(尽管现在很难找到没有主题的 PC)。
      • 关于 CPU 的“优化”,更多的是依靠numpy 在大多数平台上获得不错的性能,并由您选择具有合理浮点支持的平台。无论如何,我明白了 2 倍内存要求的意义。
      • 在我的时间测试中,conjugate 方法的速度是arr.real**2+arr.imag**2 的一半。它的乘法次数是乘法的 2 倍,因为 multiply 不会短路产生虚值的项。而abs**2 与共轭相乘的时间大致相同。
      【解决方案4】:

      如果您的主要目标是节省内存,NumPy 的 ufunc 采用可选的 out 参数,让您可以将输出定向到您选择的数组。当您想就地执行操作时,它会很有用。

      如果你对你的第一个方法进行了这个小修改,那么你就可以在arr上完全就地执行操作:

      np.abs(arr, out=arr)
      arr **= 2
      

      一种只使用少量额外内存的复杂方法可能是修改arr,计算新的实数值数组,然后恢复arr

      这意味着存储有关符号的信息(除非您知道您的复数都有正实部和虚部)。每个实数或虚数的符号只需要一个位,因此这使用了1/16 + 1/16 == 1/8 的内存arr(除了您创建的新浮点数组之外)。

      >>> signs_real = np.signbit(arr.real) # store information about the signs
      >>> signs_imag = np.signbit(arr.imag)
      >>> arr.real **= 2 # square the real and imaginary values
      >>> arr.imag **= 2
      >>> result = arr.real + arr.imag
      >>> arr.real **= 0.5 # positive square roots of real and imaginary values
      >>> arr.imag **= 0.5
      >>> arr.real[signs_real] *= -1 # restore the signs of the real and imagary values
      >>> arr.imag[signs_imag] *= -1
      

      以存储符号位为代价,arr 保持不变,result 保存我们想要的值。

      【讨论】:

      • 谢谢,不过,我不想覆盖 arr,抱歉没有说清楚。
      • 我明白了...我想不出任何方法可以完全按照您的要求做(a)保留arr,并且(b)只分配一个新的浮点值数组(与arr 的形状相同)。可能需要自定义 ufunc(但这可能会影响可移植性)。
      • 感谢您提供令人费解的示例。我可能最终不得不使用 numexpr。
      【解决方案5】:

      arr.realarr.imag 只是复杂数组的视图。所以没有分配额外的内存。

      【讨论】:

      • 但它是在我计算arr.real**2时分配的。
      猜你喜欢
      • 2016-02-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-10-22
      • 2023-02-07
      • 2013-10-30
      • 2015-09-19
      • 2018-06-22
      相关资源
      最近更新 更多