10、Numpy广播(Broadcast)

1、番外说明

大家好,我是小P,本系列是本人对Python模块Numpy的一些学习记录,总结于此一方面方便其它初学者学习,另一方面害怕自己遗忘,希望大家喜欢。此外,对“目标检测/模型压缩/语义分割”感兴趣的小伙伴,欢迎加入QQ群 813221712 讨论交流,进群请看群公告!(可以点击如下连接直接加入!)
点击链接加入群聊【Object Detection】:https://jq.qq.com/?_wv=1027&k=5kXCXF8

2、正题

参考链接:
https://www.runoob.com/numpy/numpy-broadcast.html
https://cloud.tencent.com/developer/article/1106669

广播(Broadcast)是 numpy 对不同形状(shape)的数组进行数值计算的方式, 对数组的算术运算通常在相应的元素上进行。

广播的规则:

● 让所有输入数组都向其中形状最长的数组看齐,形状中不足的部分都通过在前面加 1 补齐。
● 输出数组的形状是输入数组形状的各个维度上的最大值。
● 如果输入数组的某个维度和输出数组的对应维度的长度相同或者其长度为 1 时,这个数组能够用来计算,否则出错。
● 当输入数组的某个维度的长度为 1 时,沿着此维度运算时都用此维度上的第一组值。
在这里插入图片描述

简单理解:对两个数组,分别比较他们的每一个维度(若其中一个数组没有当前维度则忽略),满足:
● 数组拥有相同形状。
● 当前维度的值相等。
● 当前维度的值有一个是 1。

若条件不满足,抛出 “ValueError: frames are not aligned” 异常。

如果两个数组 a 和 b 形状相同,即满足 a.shape == b.shape,那么 a*b 的结果就是 a 与 b 数组对应位相乘。这要求维数相同,且各维度的长度相同。

实例:计算a*b(shape相同时)

import numpy as np 
a = np.array([1,2,3,4]) 
b = np.array([10,20,30,40]) 
c = a * b 
print (c)

输出结果为:

[ 10  40  90 160]

当运算中的 2 个数组的形状不同时,numpy 将自动触发广播机制。如:

实例:计算a*b(shape不相同)

import numpy as np  
a = np.array([[ 0, 0, 0],
           [10,10,10],
           [20,20,20],
           [30,30,30]])
b = np.array([1,2,3])
print(a + b)

输出结果为:

[[ 1  2  3]
 [11 12 13]
 [21 22 23]
 [31 32 33]] 

下面的图片展示了数组 b 如何通过广播来与数组 a 兼容。
在这里插入图片描述
4x3 的二维数组与长为 3 的一维数组相加,等效于把数组 b 在二维上重复 4 次再运算

实例:首先将b数组元素在二维上复制4次再运算

import numpy as np 
a = np.array([[ 0, 0, 0],
           [10,10,10],
           [20,20,20],
           [30,30,30]])
b = np.array([1,2,3])
bb = np.tile(b, (4, 1))
print(a + bb)

输出结果为:

[[ 1  2  3]
 [11 12 13]
 [21 22 23]
 [31 32 33]]

其中,np.tile(b, (4, 1))是根据数组b来构造数组,复制的维度为(4,1),首先从低维度开始复制

实例:通过减去列平均值的方式对数组的每一列进行距平化处理

In [82]: arr = np.random.randn(4, 3) 
In [83]: arr.mean(0)
Out[83]: array([-0.3928, -0.3824, -0.8768])

In [84]: demeaned = arr - arr.mean(0)
In [85]: demeaned
Out[85]: 
array([[ 0.3937,  1.7263,  0.1633],
       [-0.4384, -1.9878, -0.9839],
       [-0.468 ,  0.9426, -0.3891],
       [ 0.5126, -0.6811,  1.2097]])

In [86]: demeaned.mean(0)
Out[86]: array([-0.,  0., -0.])

其中:arr.mean(0)用于计算arr矩阵每一列的平均值,为一个1X3的数组

再来看一下上面那个例子,假设你希望对各行减去那个平均值。由于arr.mean(0)的长度为3,所以它可以在0轴向上进行广播:因为arr的后缘维度是3,所以它们是兼容的。根据该原则,要在1轴向上做减法(即各行减去行平均值),较小的那个数组的形状必须是(4,1):

In [87]: arr
Out[87]: 
array([[ 0.0009,  1.3438, -0.7135],
       [-0.8312, -2.3702, -1.8608],
       [-0.8608,  0.5601, -1.2659],
       [ 0.1198, -1.0635,  0.3329]])

In [88]: row_means = arr.mean(1)

In [89]: row_means.shape
Out[89]: (4,)

In [90]: row_means.reshape((4, 1))
Out[90]: 
array([[ 0.2104],
       [-1.6874],
       [-0.5222],
       [-0.2036]])

In [91]: demeaned = arr - row_means.reshape((4, 1)) 
In [92]: demeaned.mean(1)
Out[92]: array([ 0., -0.,  0.,  0.])

过程如下图:
在这里插入图片描述
高维度数组的广播似乎更难以理解,而实际上它也是遵循广播原则的。如果不然,你就会得到下面这样一个错误:

In [93]: arr - arr.mean(1)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-93-7b87b85a20b2> in <module>()
----> 1 arr - arr.mean(1)
ValueError: operands could not be broadcast together with shapes (4,3) (4,)

人们经常需要通过算术运算过程将较低维度的数组在除0轴以外的其他轴向上广播。根据广播的原则,较小数组的“广播维”必须为1。在上面那个行距平化的例子中,这就意味着要将行平均值的形状变成(4,1)而不是(4,):

In [94]: arr - arr.mean(1).reshape((4, 1))
Out[94]: 
array([[-0.2095,  1.1334, -0.9239],
       [ 0.8562, -0.6828, -0.1734],
       [-0.3386,  1.0823, -0.7438],
       [ 0.3234, -0.8599,  0.5365]])

对于三维的情况,在三维中的任何一维上广播其实也就是将数据重塑为兼容的形状而已。下图说明了要在三维数组各维度上广播的形状需求。
在这里插入图片描述
于是就有了一个非常普遍的问题(尤其是在通用算法中),即专门为了广播而添加一个长度为1的新轴。虽然reshape是一个办法,但插入轴需要构造一个表示新形状的元组。这是一个很郁闷的过程。因此,NumPy数组提供了一种通过索引机制插入轴的特殊语法。下面这段代码通过特殊的np.newaxis属性以及“全”切片来插入新轴:

In [95]: arr = np.zeros((4, 4))

In [96]: arr_3d = arr[:, np.newaxis, :]

In [97]: arr_3d.shape
Out[97]: (4, 1, 4)

In [98]: arr_1d = np.random.normal(size=3)

In [99]: arr_1d[:, np.newaxis]
Out[99]: 
array([[-2.3594],
       [-0.1995],
       [-1.542 ]])

In [100]: arr_1d[np.newaxis, :]
Out[100]: array([[-2.3594, -0.1995, -1.542 ]])

因此,如果我们有一个三维数组,并希望对轴2进行距平化,那么只需要编写下面这样的代码就可以了:

In [101]: arr = np.random.randn(3, 4, 5)

In [102]: depth_means = arr.mean(2)

In [103]: depth_means
Out[103]: 
array([[-0.4735,  0.3971, -0.0228,  0.2001],
       [-0.3521, -0.281 , -0.071 , -0.1586],
       [ 0.6245,  0.6047,  0.4396, -0.2846]])

In [104]: depth_means.shape
Out[104]: (3, 4)

In [105]: demeaned = arr - depth_means[:, :, np.newaxis]

In [106]: demeaned.mean(2)
Out[106]: 
array([[ 0.,  0., -0., -0.],
       [ 0.,  0., -0.,  0.],
       [ 0.,  0., -0., -0.]])

有些读者可能会想,在对指定轴进行距平化时,有没有一种既通用又不牺牲性能的方法呢?实际上是有的,但需要一些索引方面的技巧:

def demean_axis(arr, axis=0):
    means = arr.mean(axis)

    # This generalizes things like [:, :, np.newaxis] to N dimensions
    indexer = [slice(None)] * arr.ndim
    indexer[axis] = np.newaxis
    return arr - means[indexer]

通过广播设置数组的值

算术运算所遵循的广播原则同样也适用于通过索引机制设置数组值的操作。对于最简单的情况,我们可以这样做:

In [107]: arr = np.zeros((4, 3))

In [108]: arr[:] = 5

In [109]: arr
Out[109]: 
array([[ 5.,  5.,  5.],
       [ 5.,  5.,  5.],
       [ 5.,  5.,  5.],
       [ 5.,  5.,  5.]])

但是,假设我们想要用一个一维数组来设置目标数组的各列,只要保证形状兼容就可以了:

In [110]: col = np.array([1.28, -0.42, 0.44, 1.6])
In [111]: arr[:] = col[:, np.newaxis]

In [112]: arr
Out[112]: 
array([[ 1.28,  1.28,  1.28],
       [-0.42, -0.42, -0.42],
       [ 0.44,  0.44,  0.44],
       [ 1.6 ,  1.6 ,  1.6 ]])

In [113]: arr[:2] = [[-1.37], [0.509]]

In [114]: arr
Out[114]: 
array([[-1.37 , -1.37 , -1.37 ],
       [ 0.509,  0.509,  0.509],
       [ 0.44 ,  0.44 ,  0.44 ],
       [ 1.6  ,  1.6  ,  1.6  ]])
<think>好的,用户想了解NumPy中的广播机制的使用方法和原理。我之前整理过相关的引用资料,现在需要结合这些资料来构建一个清晰易懂的回答。首先,我得回忆一下广播机制的基本概念,然后逐步展开。 首先,广播机制的核心是让不同形状的数组进行算术运算。根据引用[1]和[2],广播机制能自动扩展数组的维度,使得它们的形状兼容。需要解释为什么需要广播,比如当处理不同形状的数据时,比如一个向量和矩阵相加,这时候广播就能自动扩展向量的维度,避免手动复制数据,提高效率。 接下来,原理部分要详细说明广播的规则。根据引用[2],规则主要是从后向前比较维度,要么相等,要么其中一个为1,或者其中一个数组在该维度上不存在。需要举例说明,比如形状(3,4)(4,)的数组如何广播,或者(2,1)(3,1)如何无法广播,因为第二维度不匹配。 然后,应用场景部分可以结合引用[3]和[4],提到broadcast_arrays和broadcast_to方法的使用,比如如何显式地扩展数组形状,或者在实际数值计算中的例子,比如归一化操作,或者图像处理中的通道扩展。 同时要注意用户可能存在的疑问点,比如广播是否复制数据,这时候需要说明广播是虚拟扩展,不会实际复制数据,节省内存,这点在引用[5]中也有提到,PyTorch中的例子说明了一维张量的广播。 最后,生成相关问题时要覆盖原理、常见错误、比较其他库、性能影响和实际应用,这样用户可以有进一步学习的路径。需要确保问题有针对性,帮助用户深入理解。 检查是否遵循了系统级指令:数学公式用$...$,独立公式用$$...$$,中文回答,语法正确。比如在示例中的归一化公式,需要用独立公式格式。同时引用标识要正确添加,比如在提到广播规则时引用[2],提到显式广播方法时引用[3][4]。 确认没有使用任何Markdown格式,保持口语化,但结构清晰。分点讲解,使用标题但避免Markdown符号。可能需要用加粗或者编号来组织内容,但根据用户要求,回答中不能使用Markdown,所以需要用自然分段和序号。</think>### NumPy广播机制原理及使用详解 #### 一、广播机制核心原理 广播机制是NumPy实现不同形状数组间算术运算的智能维度扩展策略,其核心规则可归纳为三个步骤: 1. **维度对齐**:将数组维度从右向左对齐 $$ (3,4) \quad \text{与} \quad (4,) \rightarrow (3,4) \quad \text{与} \quad (1,4) $$ $$ (5,2,3) \quad \text{与} \quad (2,1) \rightarrow (5,2,3) \quad \text{与} \quad (1,2,1) $$ [^2] 2. **维度扩展**:对维度为1的轴进行数据虚拟复制 ```python # 原始数组A(2,3), B(3,) # B被广播(1,3),再扩展为(2,3) [[1 2 3], [[4 5 6], [4 5 6]] + [4 5 6]] ``` 3. **运算执行**:所有维度匹配后执行元素级运算 - 实际不进行物理复制,通过虚拟迭代实现高效计算 - 内存消耗仅与最大数组相关,不产生额外存储开销[^5] #### 二、广播规则详解(含边界案例) | 案例 | 结果 | 说明 | |------|------|------| | (256,256,3)(3,) | ✅ 成功 | 颜色通道运算 | | (8,5,6)(6,) | ✅ 成功 | 最后一维匹配 | | (3,4)(4,3) | ❌ 失败 | 维度不兼容 | | (5,)(3,) | ❌ 失败 | 长度不等且非1 | **特殊处理**:当维度差超过1时,需手动添加维度 ```python a = np.ones((3,1,4)) b = np.ones((2,4)) np.add(a, b[:, np.newaxis, :]) # 显式添加维度 ``` #### 三、工程实践中的应用 1. **数据标准化**: $$ X_{norm} = \frac{X - \mu}{\sigma} $$ ```python # 计算各通道均值(形状(C,)) mean = np.mean(image, axis=(0,1)) # 自动广播(H,W,C) normalized = (image - mean) / std ``` 2. **图像滤波**: ```python # 滤波器(3,3)与图像(H,W)的卷积 filter = np.array([[1,0,-1], [2,0,-2], [1,0,-1]]) result = image * filter[:, :, np.newaxis] # 广播(3,3,H,W) ``` 3. **矩阵外积加速**: ```python a = np.array([1,2,3]) b = np.array([4,5]) # 通过广播实现外积 outer_product = a[:, np.newaxis] * b # 形状(3,2) ``` #### 四、性能优化建议 - **预检查机制**: ```python def safe_broadcast(*arrays): try: return np.broadcast_arrays(*arrays) except ValueError as e: print(f"维度不兼容: {e}") ``` - **显式控制广播**: ```python # 使用broadcast_to控制内存 large_array = np.broadcast_to(small_array, target_shape).copy() ``` - **广播与einsum结合**: ```python # Einstein求和约定中的广播 result = np.einsum('ij,jk->ik', A, B) # 自动处理维度匹配 ``` #### 五、调试技巧 1. 形状诊断工具: ```python def broadcast_shape(*shapes): return np.broadcast_shapes(*shapes) print(broadcast_shape((256,256,3), (3,))) # 输出(256,256,3) ``` 2. 维度可视化: ```python np.lib.stride_tricks.as_strided(arr, shape=new_shape, strides=adjusted_strides) ``` 3. 异常处理模式: ```python try: c = a + b except ValueError as e: print(f"广播失败: {str(e)}") print(f"a形状: {a.shape}, b形状: {b.shape}") ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值