【问题标题】:what happens under the hood of broadcasting a numpy array广播 numpy 数组的幕后发生了什么
【发布时间】:2019-08-03 03:59:14
【问题描述】:

我探索了关于广播https://docs.scipy.org/doc/numpy-1.13.0/user/basics.broadcasting.html的标准python文档

每次我探索这个主题时,我都找不到足够的内部细节来说明广播在现实中是如何工作的,这是否与矢量化有关?

根据文档,广播是一种内存效率高的操作,它不会在内存中进行实际复制,因此算术计算在内部是如何工作的,

任何源代码或任何谈论底层概念的资源都将有助于澄清我的疑问。

【问题讨论】:

  • “诀窍”在于如何在广播数组中设置strides(参见How to understand numpy strides for layman?)。例如。您可以使用0 步幅沿一个维度无限重复数组,而无需使用更多内存。
  • @jdehesa 虽然我现在理解了 strides 背后的概念,感谢上面的链接,但我仍然无法将点与广播联系起来,我想我在这里遗漏了一些非常基本的东西,因为算术运算在一维数组和标量值之间通过将标量广播到数组的相同大小来产生向量化代码,那么标量值如何跨步或拉伸或扩展到数组的相同大小以便元素明智它们之间的操作是可能的,
  • 我不认为深入研究源代码会对您有所帮助 - 很难遵循 C 代码。但是您可能会发现使用np.broadcast_tonp.broadcast_arrays(其代码在np.lib.stride_tricks 中)很有启发性。注意他们如何操纵strides 属性。
  • 开发人员一直在尝试将广播迭代合并为一个强大的功能,nditer。编译后的代码通过 c-api 调用它,但您可以通过np.nditer 使用它。我不愿提及它,因为它不是速度工具。但作为一种学习工具,它可能会有所帮助。
  • 请注意,broadcast_to 使用了我刚才提到的np.nditer

标签: python numpy array-broadcasting


【解决方案1】:

广播的简单说明 - 带有标量的 1d:

In [18]: x = np.arange(10)                                                      
In [19]: X,Y = np.broadcast_arrays(x,3)                                         
In [20]: X                                                                      
Out[20]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
In [21]: Y                                                                      
Out[21]: array([3, 3, 3, 3, 3, 3, 3, 3, 3, 3])
In [22]: Y.strides                                                              
Out[22]: (0,)
In [23]: X+Y                                                                    
Out[23]: array([ 3,  4,  5,  6,  7,  8,  9, 10, 11, 12])
In [24]: [i+j for i,j in zip(X,Y)]                                              
Out[24]: [3, 4, 5, 6, 7, 8, 9, 10, 11, 12]

通过这种结构,很难证明Y 占用的内存不如x。所以让我们扩展x

In [30]: x1 = np.broadcast_to(x,(3,10))                                         
In [31]: x1                                                                     
Out[31]: 
array([[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
       [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
       [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]])
In [32]: x.__array_interface__                                                  
Out[32]: 
{'data': (30797968, False),
 'strides': None,
 'descr': [('', '<i8')],
 'typestr': '<i8',
 'shape': (10,),
 'version': 3}
In [33]: x1.__array_interface__                                                 
Out[33]: 
{'data': (30797968, True),
 'strides': (0, 8),
 'descr': [('', '<i8')],
 'typestr': '<i8',
 'shape': (3, 10),
 'version': 3}

x1 共享 x 数据缓冲区。没有使用额外的内存(除了数组对象本身)。

并将标量变成二维:

In [34]: x2,y2 = np.broadcast_arrays(x1,Y)                                      
In [35]: y2.strides                                                             
Out[35]: (0, 0)

【讨论】:

  • 感谢@hpaulj 的一些好建议,但是我希望在上述声明中获得更多说明“x1 共享 x 数据缓冲区。不使用额外的内存(数组对象本身除外)。”顺便说一句,有一个疑问:x1 只是 x 的视图,或者它在内存中有实际的物理副本。?
  • __array_interface__ 是一个数组对象的字典视图,显示了它的基本属性。每个数组都有自己的形状和步幅,但可以与其他数组共享data 缓冲区(如果是view)。
猜你喜欢
  • 2011-10-19
  • 1970-01-01
  • 2010-09-06
  • 1970-01-01
  • 2011-07-12
  • 1970-01-01
  • 2018-03-29
  • 2019-06-05
  • 1970-01-01
相关资源
最近更新 更多