【问题标题】:Efficiently sum a small numpy array, broadcast across a ginormous numpy array?有效地求和一个小的 numpy 数组,在一个巨大的 numpy 数组中广播?
【发布时间】:2012-04-19 00:36:13
【问题描述】:

我想计算一个大(1,000,000 x 3,000) 布尔 numpy 数组。大型布尔数组更改 很少,但权重是在查询时出现的,我需要答案 非常快,无需复制整个大数组,或扩展 小权重数组到大数组的大小。

结果应该是一个包含 1,000,000 个条目的数组,每个条目都有 与该行的 True 对应的权重数组条目的总和 价值观。

我研究过使用掩码数组,但它们似乎需要构建一个 权重数组我的大型布尔数组的大小。

下面的代码给出了正确的结果,但我买不起那个副本 在乘法步骤中。甚至不需要乘法,因为 values 数组是布尔值,但至少它处理广播 正确。

我是 numpy 的新手,我很喜欢它,但我即将放弃它 这个特殊的问题。我已经学会了足够的 numpy 知道要留下来 远离任何在 python 中循环的东西。

我的下一步是用 C 语言编写这个例程(其中添加了 让我通过使用位而不是字节来节省内存的好处,通过 方式。)

除非你们中的一位 numpy 大师可以将我从 cython 中拯救出来?

from numpy import array, multiply, sum

# Construct an example values array, alternating True and False.
# This represents four records of three attributes each:
#    array([[False,  True, False],
#           [ True, False,  True],
#           [False,  True, False],
#           [ True, False,  True]], dtype=bool)
values = array([(x % 2) for x in range(12)], dtype=bool).reshape((4,3))

# Construct example weights, one for each attribute:
#    array([1, 2, 3])
weights = array(range(1, 4))

# Create expensive NEW array with the weights for the True attributes.
# Broadcast the weights array into the values array.
#    array([[0, 2, 0],
#           [1, 0, 3],
#           [0, 2, 0],
#           [1, 0, 3]])
weighted = multiply(values, weights)

# Add up the weights:
#    array([2, 4, 2, 4])
answers = sum(weighted, axis=1)

print answers

# Rejected masked_array solution is too expensive (and oddly inverts
# the results):
masked = numpy.ma.array([[1,2,3]] * 4, mask=values)

【问题讨论】:

  • 用你需要的例子做得很好。

标签: python matrix numpy


【解决方案1】:

点积(或内积)就是您想要的。它允许您获取一个大小为m×n 的矩阵和一个长度为n 的向量,并将它们相乘产生一个长度为m 的向量,其中每个条目是矩阵的一行与条目的加权和as 权重的向量。

Numpy 将其实现为 array1.dot(array2)(或旧版本中的 numpy.dot(array1, array2))。例如:

from numpy import array

values = array([(x % 2) for x in range(12)], dtype=bool).reshape((4,3))

weights = array(range(1, 4))

answers = values.dot(weights)
print answers
# output: [ 2 4 2 4 ]

(不过,您应该使用 timeit module 进行基准测试。)

【讨论】:

  • senderle 在他的回答中包含了一个快速基准;这表现不错。
  • 这太棒了,我在浏览文档时根本不了解点函数。我确实计时了,不幸的是,即使在我的高 CPU ec2 实例上它也不够快,但这正是我所要求的,我很高兴知道它,谢谢!
【解决方案2】:

dbaupp 的答案似乎是正确的。但只是为了多样性,这里有另一种节省内存的解决方案。这甚至适用于没有内置 numpy 等效项的操作。

>>> values = numpy.array([(x % 2) for x in range(12)], dtype=bool).reshape((4,3))
>>> weights = numpy.array(range(1, 4))
>>> weights_stretched = numpy.lib.stride_tricks.as_strided(weights, (4, 3), (0, 8))

numpy.lib.stride_tricks.as_strided 是一个很棒的小功能!它允许您指定 shapestrides 值,这些值允许一个小数组模拟一个更大的数组。观察——这里没有真的四行;它看起来就是这样:

>>> weights_stretched[0][0] = 4
>>> weights_stretched 
array([[4, 2, 3],
       [4, 2, 3],
       [4, 2, 3],
       [4, 2, 3]])

因此,您可以传递一个较小的数组,而不是将一个巨大的数组传递给MaskedArray。 (但正如您已经注意到的,numpy 掩蔽的工作方式与您可能期望的相反;真相掩蔽,而不是揭示,因此您必须存储您的 values 倒置。)如您所见,@987654331 @ 不复制任何数据;它只是反映weights_stretched 中的任何内容:

>>> masked = numpy.ma.MaskedArray(weights_stretched, numpy.logical_not(values))
>>> weights_stretched[0][0] = 1
>>> masked
masked_array(data =
 [[-- 2 --]
 [1 -- 3]
 [-- 2 --]
 [1 -- 3]],
      mask =
 [[ True False  True]
 [False  True False]
 [ True False  True]
 [False  True False]],
      fill_value=999999)

现在我们可以将它传递给 sum:

>>> sum(masked, axis=1)
masked_array(data = [2 4 2 4],
      mask = [False False False False],
      fill_value=999999)

我针对 1,000,000 x 30 数组对 numpy.dot 和上述内容进行了基准测试。这是在相对现代的 MacBook Pro 上的结果(numpy.dotdot1;我的是 dot2):

>>> %timeit dot1(values, weights)
1 loops, best of 3: 194 ms per loop
>>> %timeit dot2(values, weights)
1 loops, best of 3: 459 ms per loop

如您所见,内置的numpy 解决方案更快。但是stride_tricks 无论如何都值得了解,所以我要离开了。

【讨论】:

  • stride_tricks 值得肯定!我想知道这样的事情是否可能,并尝试查看是否可以通过引用构造数组,但放弃了。我可以想象将来会使用它,谢谢!
  • 您是如何提出第三个论点的?为什么是 (0, 8)?
【解决方案3】:

这对你有用吗?

a = np.array([sum(row * weights) for row in values])

这使用sum() 立即对row * weights 值求和,因此您不需要内存来存储所有中间值。然后列表推导收集所有值。

您说过要避免任何“Python 中的循环”。这至少使用 Python 的 C 语言进行循环,而不是显式的 Python 循环,但它不能像 NumPy 解决方案那样快,因为它使用已编译的 C 或 Fortran。

【讨论】:

  • 我会留下这个,但@dbaupp 搞定了。纯 NumPy 解决方案会比这更好。
  • 是的,纯 numpy 是一个胜利,但这也是一个非常简洁的解决方案,谢谢!
【解决方案4】:

我认为你不需要 numpy 来做这样的事情。而 1000000 x 3000 是一个巨大的数组;这很可能不适合您的 RAM。

我会这样做:

假设您的数据最初位于文本文件中:

False,True,False
True,False,True
False,True,False
True,False,True

我的代码:

weight = range(1,4)    
dicto = {'True':1, 'False':0}

with open ('my_data.txt') as fin:

    a = sum(sum(dicto[ele]*w for ele,w in zip(line.strip().split(','),weight)) for line in fin)

结果:

>>> a
12

编辑:

我想我第一次读错了这个问题,并将所有内容总结在一起。这是给出 OP 所追求的确切解决方案的解决方案:

weight = range(1,4)
dicto = {'True':1, 'False':0}

with open ('my_data.txt') as fin:

    a = [sum(dicto[ele]*w for ele,w in zip(line.strip().split(','),weight)) for line in fin]

结果:

>>> a
[2, 4, 2, 4]

【讨论】:

  • 一个 1000000 x 3000 的 32 位浮点值数组计算出大约 11.2 GiB 的数据。如果他的真/假值是单字节值,那只有大约 2.8 GB 的数据。有 64 位计算机具有 32GB 或更多 RAM,因此即使是浮点数组也可能适合他的计算机。但如果他能帮上忙,他不会想要复制它!
  • 好的,我明白了。谢谢。我知道它不可能适合我的 RAM!只是想有这个解决方案,以防万一大小是一个问题。
  • steveha 是对的,它们是单字节 (dtype=bool) 值,将它们保存在 ram 中是可行的。而且以我的性能要求,我真的根本无法触摸磁盘,甚至交换。但我同意这对于希望在更慢的时间范围内用更少的内存做同样事情的人来说是一个有用的补充,谢谢!
猜你喜欢
  • 1970-01-01
  • 2011-05-20
  • 1970-01-01
  • 2017-10-04
  • 2019-03-31
  • 1970-01-01
  • 2012-01-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多