沿着给定的轴,将numpy的ndarray与1d数组相乘

28

似乎我在做一些可能很愚蠢的事情。我有一个n维的numpy数组,我想沿着某个维度(可以改变)将它与向量(1d数组)相乘。

举个例子,假设我想将一个2d数组沿着第0维与一个1d数组相乘,我可以这样做:

a=np.arange(20).reshape((5,4))
b=np.ones(5)
c=a*b[:,np.newaxis]

容易,但我想将这个想法扩展到n维(其中a是n维的,而b始终为1d)并且应用于任意轴。换句话说,我想知道如何在正确的位置生成带有np.newaxis的切片。假设a是3d,我想沿axis=1进行乘法,我想生成能正确给出以下结果的切片:

c=a*b[np.newaxis,:,np.newaxis]

即:给定一个数组的维数(比如说3),以及想要进行乘法的轴(比如说axis=1),我该如何生成并传递切片:

np.newaxis,:,np.newaxis

谢谢。


我有一个ndarray上的数据,我想沿某个轴将数据乘以一个过滤器。 - user3498123
6个回答

20

解决方案代码 -

import numpy as np

# Given axis along which elementwise multiplication with broadcasting 
# is to be performed
given_axis = 1

# Create an array which would be used to reshape 1D array, b to have 
# singleton dimensions except for the given axis where we would put -1 
# signifying to use the entire length of elements along that axis  
dim_array = np.ones((1,a.ndim),int).ravel()
dim_array[given_axis] = -1

# Reshape b with dim_array and perform elementwise multiplication with 
# broadcasting along the singleton dimensions for the final output
b_reshaped = b.reshape(dim_array)
mult_out = a*b_reshaped

演示步骤的示例运行 -

In [149]: import numpy as np

In [150]: a = np.random.randint(0,9,(4,2,3))

In [151]: b = np.random.randint(0,9,(2,1)).ravel()

In [152]: whos
Variable   Type       Data/Info
-------------------------------
a          ndarray    4x2x3: 24 elems, type `int32`, 96 bytes
b          ndarray    2: 2 elems, type `int32`, 8 bytes

In [153]: given_axis = 1

现在,我们想要沿着给定的轴 axis = 1 执行元素乘法。让我们创建 dim_array

In [154]: dim_array = np.ones((1,a.ndim),int).ravel()
     ...: dim_array[given_axis] = -1
     ...: 

In [155]: dim_array
Out[155]: array([ 1, -1,  1])

最后,重塑b并执行逐元素乘法:

In [156]: b_reshaped = b.reshape(dim_array)
     ...: mult_out = a*b_reshaped
     ...: 

再次查看whos信息,并特别注意b_reshapedmult_out

In [157]: whos
Variable     Type       Data/Info
---------------------------------
a            ndarray    4x2x3: 24 elems, type `int32`, 96 bytes
b            ndarray    2: 2 elems, type `int32`, 8 bytes
b_reshaped   ndarray    1x2x1: 2 elems, type `int32`, 8 bytes
dim_array    ndarray    3: 3 elems, type `int32`, 12 bytes
given_axis   int        1
mult_out     ndarray    4x2x3: 24 elems, type `int32`, 96 bytes

好的,我的错,我没有提到这一点:我无法生成与a匹配大小的b的副本,因为a可能非常非常大。 - user3498123
嘿,不好意思,实际上这是一个解决方案,我之前误解了。太棒了,谢谢! - user3498123
@AJC 没事的!我刚才的评论已经删除了。 - Divakar
我总是惊讶于numpy的强大。太酷了!再次感谢。 - user3498123
@AJC 我在过去的一个月里也发现了同样的事情! :) - Divakar
一行代码:shape = [ -1 if ax == given_axis else 1 for ax in range(a.ndim)],然后进行计算 a*b.reshape(shape) - Puco4

6

避免复制数据和浪费资源!

使用类型转换和视图,而不是将数据 N 次复制到具有适当形状的新数组中(如现有答案所做),这样更加高效地利用内存。以下是一种基于 @ShuxuanXU 代码的方法:

def mult_along_axis(A, B, axis):

    # ensure we're working with Numpy arrays
    A = np.array(A)
    B = np.array(B)

    # shape check
    if axis >= A.ndim:
        raise AxisError(axis, A.ndim)
    if A.shape[axis] != B.size:
        raise ValueError(
            "Length of 'A' along the given axis must be the same as B.size"
            )

    # np.broadcast_to puts the new axis as the last axis, so 
    # we swap the given axis with the last one, to determine the
    # corresponding array shape. np.swapaxes only returns a view
    # of the supplied array, so no data is copied unnecessarily.
    shape = np.swapaxes(A, A.ndim-1, axis).shape

    # Broadcast to an array with the shape as above. Again, 
    # no data is copied, we only get a new look at the existing data.
    B_brc = np.broadcast_to(B, shape)

    # Swap back the axes. As before, this only changes our "point of view".
    B_brc = np.swapaxes(B_brc, A.ndim-1, axis)

    return A * B_brc

5
你可以创建一个切片对象,并在其中选择所需的维度。
import numpy as np

a = np.arange(18).reshape((3,2,3))
b = np.array([1,3])

ss = [None] * a.ndim    
ss[1] = slice(None)    # set the dimension along which to broadcast

print ss  #  [None, slice(None, None, None), None]

c = a*b[tuple(ss)]  # convert to tuple to avoid FutureWarning from newer versions of Python

我喜欢这个解决方案,因为它避免了复制,遵循了提问者的思路,并且非常容易理解。我认为 None] * a.ndim 等同于 [None for i in range(a.ndim)] 但看起来更简单。 - onestop
Python3:索引错误:只有整数、切片(:)、省略号(...)、numpy.newaxis(None)和整数或布尔数组是有效的索引。 - klmn
@klmn:我刚在Python 3.9.12和NumPy 1.21.5中尝试了这段代码,它没有抛出异常。 - tom10
应该是一个元组,而不是一个列表:ss = tuple((None if i != axis else slice(None) for i in range(a.ndim))) - TheIdealis

3
简化@Neinstein的解决方案,我得出了以下结果
def multiply_along_axis(A, B, axis):
    return np.swapaxes(np.swapaxes(A, axis, -1) * B, -1, axis)

这个例子还避免了复制和浪费内存。通过将A中所需的轴交换到最后一个位置,执行乘法,然后将轴交换回原始位置来避免显式广播。额外的好处是numpy会处理错误处理和类型转换。


2
我在进行一些数值计算时遇到了类似的需求。
假设我们有两个数组(A和B)和一个用户指定的“轴”。 A是一个多维数组。 B是一个一维数组。
基本思路是扩展B,使A和B具有相同的形状。以下是解决方案代码:
"最初的回答"
import numpy as np
from numpy.core._internal import AxisError

def multiply_along_axis(A, B, axis):
    A = np.array(A)
    B = np.array(B)
    # shape check
    if axis >= A.ndim:
        raise AxisError(axis, A.ndim)
    if A.shape[axis] != B.size:
        raise ValueError("'A' and 'B' must have the same length along the given axis")
    # Expand the 'B' according to 'axis':
    # 1. Swap the given axis with axis=0 (just need the swapped 'shape' tuple here)
    swapped_shape = A.swapaxes(0, axis).shape
    # 2. Repeat:
    # loop through the number of A's dimensions, at each step:
    # a) repeat 'B':
    #    The number of repetition = the length of 'A' along the 
    #    current looping step; 
    #    The axis along which the values are repeated. This is always axis=0,
    #    because 'B' initially has just 1 dimension
    # b) reshape 'B':
    #    'B' is then reshaped as the shape of 'A'. But this 'shape' only 
    #     contains the dimensions that have been counted by the loop
    for dim_step in range(A.ndim-1):
        B = B.repeat(swapped_shape[dim_step+1], axis=0)\
             .reshape(swapped_shape[:dim_step+2])
    # 3. Swap the axis back to ensure the returned 'B' has exactly the 
    # same shape of 'A'
    B = B.swapaxes(0, axis)
    return A * B

最初的回答:

以下是一个示例:

In [33]: A = np.random.rand(3,5)*10; A = A.astype(int); A
Out[33]: 
array([[7, 1, 4, 3, 1],
       [1, 8, 8, 2, 4],
       [7, 4, 8, 0, 2]])

In [34]: B = np.linspace(3,7,5); B
Out[34]: array([3., 4., 5., 6., 7.])

In [35]: multiply_along_axis(A, B, axis=1)
Out[34]: 
array([[21.,  4., 20., 18.,  7.],
       [ 3., 32., 40., 12., 28.],
       [21., 16., 40.,  0., 14.]])

-1

你也可以使用一个简单的矩阵技巧

c = np.matmul(a,diag(b))

基本上只是在矩阵a和对角线元素为b的矩阵之间进行矩阵乘法。可能不太高效,但这是一个简洁的单行解决方案。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接