【问题标题】:How can I efficiently add X modulo Y to every element in a numpy array?如何有效地将 X 模 Y 添加到 numpy 数组中的每个元素?
【发布时间】:2020-03-31 09:32:18
【问题描述】:

假设我有一个非常大的值数组,介于 1 和 180 之间,这些值位于 uint8s 的数组中(最高可达 255)。我想为每个值加上 90(模 180):

original = np.array([1, 2, 3, 170, 171, 172], dtype=np.uint8)
modified = (original + 90) % 180

不幸的是,这会产生不正确的结果,因为在添加 90 时,较大的数字会在第一步中溢出它们的 uint8 整数:170 + 90 = 260,它大于 255。

# (170 + 90) % 180 is 80, not 4 :(
array([91, 92, 93,  4,  5,  6], dtype=uint8)

我在对性能非常敏感的环境中操作,我的输入列表非常大。因此,我想避免将此数组转换为更大数据类型的惩罚,并且我想使用高效的操作(例如,避免循环遍历数组并单独处理每个值)。

我怎样才能做到这一点?

【问题讨论】:

  • 使用import numpynumpy.mod(numpy1arr + 90, numpy2arr)。在您的情况下,我假设 numpy2arr 将是 180 的 n 向量。
  • >>> ((original.astype(np.uint16) + 90) % 180).astype(np.uint8) array([91, 92, 93, 80, 81, 82], dtype=uint8) ?或者你不想投射?
  • @furkanayd:这会产生相同的问题,因为溢出发生在numpy1arr + 90 步骤中。
  • @GiacomoAlzetta 感谢您的建议,这将产生正确的结果。然而,正如我在描述中提到的,这个数组非常大,转换为更大的数据类型非常昂贵。
  • 这是一个有趣的问题,尽管出于实际目的,请记住创建多个中间数组(掩码、每个操作等)的解决方案可能比向上转换(最终只需要如果您重用原始数组来存储向下转换的结果,则为 16 位的原始大小的两倍)。高级索引(选择带有掩码的数组子集)也很昂贵。我将这里的解决方案与简单的向上转换解决方案进行基准测试。

标签: python arrays numpy modulo


【解决方案1】:

您可以简单地将数组转换为np.unit16

>>> ((original.astype(np.uint16) + 90) % 180).astype(np.uint8)
array([91, 92, 93, 80, 81, 82], dtype=uint8)

如果您只想使用 uint8 来实现这一点,您可以只增加那些大于 255-90 的元素:

>>> modified = (original + 90) % 180
>>> modified[original >= 255-90] += 256-180
>>> modified
array([91, 92, 93, 80, 81, 82], dtype=uint8)

【讨论】:

  • 哇,您的第二个解决方案看起来正是我所需要的。我不知道你可以有条件地选择这样的 numpy 数组元素。现在对其进行测试,但我怀疑这会起作用。谢谢!
【解决方案2】:
import numpy as np

分配一个由大小为 (10, 10) 的整数组成的随机 numpy 数组,其元素的最大值可以为 255。

np_array = np.random.randint(255, size=(10, 10))

对每个元素加 90 并对其进行取模。

np_array = (np_array + 90) % 180

【讨论】:

    【解决方案3】:

    这是一个有一些数学但没有向上转换的 -

    def add_with_modulus(original, addval, modulusval=180):
        v = original + addval
        v[modulusval-original<=addval] += 256-modulusval
        return v
    

    用法:add_with_modulus(original, addval=90, modulusval=180).

    【讨论】:

    • v2 似乎不太好用。 add_with_modulus_v2(np.array(range(180), dtype=np.uint8), 100) 生成一个列表,其中的值最多为 200。
    • @Mala 是的,这确实是掩码检查中的一个错误。请检查修改。
    【解决方案4】:

    一个非常简单的选项,因为您正在处理uint8,所以只需提前计算数组中每个可能值的结果并使用它:

    import numpy as np
    original = np.array([1, 2, 3, 170, 171, 172], dtype=np.uint8)
    value_map = ((np.arange(256) + 90) % 180).astype(np.uint8)
    modified = value_map[original]
    print(modified)
    # [91 92 93 80 81 82]
    

    这样做的好处是它不会占用超过 256 个元素 value_map 的任何额外内存,并且对于任何更大的数组,您也将节省大部分计算。

    针对强制转换运行时间基准:

    import numpy as np
    
    def add_val_mod_cast(a, val, mod):
        return ((a.astype(np.uint16) + val) % mod).astype(np.uint8)
    
    def add_val_mod_map(a, val, mod):
        value_map = ((np.arange(256) + val) % mod).astype(np.uint8)
        return value_map[a]
    
    np.random.seed(0)
    a = np.random.randint(256, size=10_000_000).astype(np.uint8)
    val = 90
    mod = 180
    print((add_val_mod_cast(a, val, mod) == add_val_mod_map(a, val, mod)).all())
    # True
    %timeit add_val_mod_cast(a, val, mod)
    # 72.6 ms ± 2.7 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
    %timeit add_val_mod_map(a, val, mod)
    # 40.8 ms ± 606 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
    

    【讨论】:

    • 对于非常大的数组来说似乎是个好主意。你真的需要 256 的整个范围吗?
    • @Divakar 啊,如果输入值已经在[0, mod) 中,也许不是......尽管它最多会产生数百个字节的差异,并且它会“修复”超出预期范围的输入值(或者对他们来说不会失败),所以我猜可能会或可能不会保留它......
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-02-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多