是否有一个标准的方法来广播一维numpy数组沿着第一轴的高维数组?

acruukt9  于 2023-10-19  发布在  其他
关注(0)|答案(1)|浏览(136)

我的数据通常采用以下形式

  • 由任意形状的N个n维数组的堆栈组成的单个numpy数组(* 例如 * data.shape = (N, a, b, c, ...),其中a, b, c, ...提前未知),以及
  • 一个对应的一维系数数组(e.g.coefs.shape = (N,)),我需要沿着沿着第一个轴广播。

例如:

import numpy as np

N = 10
sub_array_shape = 3, 3

# Random data representing arbitrary data from external source
raw_data = np.stack([np.random.randn(*sub_array_shape) for i in range(N)])
corrections = np.random.randn(N)

现在我需要将两个数组一起广播,以便对数据进行校正。我发现有两种方法有效:
1.在操作之前和之后调换数据,如

corrected_data = (raw_data.T + corrections).T

1.用单例维数填充系数数组,如

corrected_data = raw_data + corrections.reshape(-1, *(1,) * (raw_data.ndim - 1))

在我看来,这两种方法都不是非常优雅或可读,所以我想知道是否有更好的方法。在使用numpy数组时,有没有一个标准的方法来解决这个问题?如果不是,我所展示的两种方法中的任何一种都是首选的吗?
我主要关心的是代码的可读性,但我也想知道不同方法之间是否有性能差异。

6rqinv9w

6rqinv9w1#

为了避免歧义,广播规则可以根据需要添加前导维度,但是尾随维度必须是显式的。我不认为有任何“优雅”的方式来添加可变数量的尾随维度,尽管你总是使用函数 Package 器来使任何方法“优雅”。
有两个数组:

In[41]: x = np.ones((3,2,4,5),int)
In [42]: y = np.arange(3)

对我来说,作为广播调整的语法是:

In [43]: z=y[:,None,None,None]    
In [45]: (x*z).shape
Out[45]: (3, 2, 4, 5)

但它并不适合于可变的维度。
reshape更容易编程实现,如你的(2)所示:

In [46]: z=y.reshape(-1,1,1,1); z.shape
Out[46]: (3, 1, 1, 1)
In [47]: (x*z).shape
Out[47]: (3, 2, 4, 5)

我本来打算建议broadcast_to,但这需要那些尾部尺寸:

In [49]: z = np.broadcast_to(y,x.shape); z.shape
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[49], line 1
----> 1 z = np.broadcast_to(y,x.shape); z.shape

File <__array_function__ internals>:200, in broadcast_to(*args, **kwargs)

File ~\miniconda3\lib\site-packages\numpy\lib\stride_tricks.py:413, in broadcast_to(array, shape, subok)
    367 @array_function_dispatch(_broadcast_to_dispatcher, module='numpy')
    368 def broadcast_to(array, shape, subok=False):
    369     """Broadcast an array to a new shape.
    370 
    371     Parameters
   (...)
    411            [1, 2, 3]])
    412     """
--> 413     return _broadcast_to(array, shape, subok=subok, readonly=True)

File ~\miniconda3\lib\site-packages\numpy\lib\stride_tricks.py:349, in _broadcast_to(array, shape, subok, readonly)
    346     raise ValueError('all elements of broadcast shape must be non-'
    347                      'negative')
    348 extras = []
--> 349 it = np.nditer(
    350     (array,), flags=['multi_index', 'refs_ok', 'zerosize_ok'] + extras,
    351     op_flags=['readonly'], itershape=shape, order='C')
    352 with it:
    353     # never really has writebackifcopy semantics
    354     broadcast = it.itviews[0]

ValueError: operands could not be broadcast together with remapped shapes [original->remapped]: (3,)  and requested shape (3,2,4,5)

如果它有必要的尾部尺寸,那么它可以使用0步幅执行第二个广播步骤:

In [50]: w = np.broadcast_to(z,x.shape); w.shape
Out[50]: (3, 2, 4, 5)    
In [51]: w.strides
Out[51]: (4, 0, 0, 0)

expand_dims也可以使用,但内部我相信它使用reshape。该守则可能具有指导意义:

In [52]: np.expand_dims(y,(1,2,3)).shape
Out[52]: (3, 1, 1, 1)

broadcast_to可以与“反向”形状一起使用,以添加前导尺寸,然后转置回来。但这只是你的第一个想法:

In [54]: np.broadcast_to(y, x.T.shape).shape
Out[54]: (5, 4, 2, 3)
In [55]: np.broadcast_to(y, x.T.shape).T.shape
Out[55]: (3, 2, 4, 5)    
In [56]: np.broadcast_to(y, x.T.shape).T.strides
Out[56]: (4, 0, 0, 0)

相关问题