逐元素运算按照对应位置处理向量、矩阵和更高维张量中的元素。在 NumPy 中,这些对象通常由不同维度的数组表示。
Hadamard 积是最常见的逐元素运算之一,用于将两个同形状数组的对应元素相乘。逐元素规则还可以用于加法、减法、除法、乘方、比较和函数变换。在 NumPy 中,广播机制还允许部分形状不同但兼容的数组进行逐元素运算。
一、什么是逐元素运算
1、相同位置分别计算
逐元素运算(Element-wise Operation)对输入中的各个元素独立执行运算。对于二元逐元素运算,两个输入中对应位置的元素分别参与同一种标量运算。
设两个 n 维向量为:
如果对两个向量执行同一种逐元素运算,则第 i 个输出元素只由两个输入向量中的第 i 个元素决定:
其中,符号 ⋆ 表示某种给定的标量运算,例如加法、减法、乘法或除法。
每个输出元素都不会与其他位置的元素发生组合。
2、同形状运算保持形状
如果两个向量都属于 ℝⁿ,那么同形状逐元素运算的结果仍属于 ℝⁿ:
对于两个同形状矩阵:
因此,同形状逐元素运算具有三个基本特点:
相同位置分别计算;
不组合不同位置的元素;
结果与输入具有相同形状。
需要提醒的是,在 NumPy 中使用广播时,两个输入不必具有完全相同的形状,结果形状由广播规则决定。
二、Hadamard 积
1、Hadamard 积的定义
Hadamard 积(Hadamard Product)是两个同形状向量、矩阵或张量之间的逐元素乘法,通常使用符号 ⊙ 表示。
对于两个 n 维向量:
对于两个 m × n 矩阵:
结果中第 i 行、第 j 列的元素为:
标准 Hadamard 积要求两个输入具有相同形状。
NumPy 中形状不同但可以广播的逐元素乘法,是先按照广播规则对齐形状,再执行对应位置乘法,不属于两个原始同形状矩阵之间的标准 Hadamard 积。
2、手工计算示例
设:
则:
每个结果都来自两个矩阵中对应位置元素的乘积。
3、Hadamard 积的基本性质
设 A、B、C 是形状相同的实矩阵或张量,c 是实数。以下性质均以参与对象的形状相同为前提。
Hadamard 积满足交换律:
满足结合律:
对矩阵加法满足分配律:
设 表示与 A 形状相同的全 1 矩阵,则:
设 表示与 A 形状相同的零矩阵,则:
标量与 Hadamard 积满足:
4、与其他乘法的区别
Hadamard 积:只计算对应位置元素的乘积,结果与输入具有相同形状。
点积:先计算对应分量的乘积,再将这些乘积求和,结果是标量。
外积:计算两个向量所有分量之间的两两乘积,结果是矩阵。
矩阵乘法:按照左矩阵的行与右矩阵的列进行点积,结果形状由两个输入矩阵的外侧尺寸决定。
判断一种乘法时,可以从以下方面区分:
输入对象的形状;
是否只处理对应位置;
是否在乘法后继续求和;
输出结果是标量、向量还是矩阵。
三、其他常见逐元素运算
1、逐元素加法与减法
对于两个同形状矩阵:
向量、矩阵和更高维数组的逐元素加减,都遵循对应位置分别计算的规则。
2、逐元素除法
逐元素除法可以使用符号 ⊘ 表示:
例如:
逐元素除法要求各对应位置的除数不为 0,即 bᵢⱼ ≠ 0。若使用浮点数组,除以 0 通常会产生 inf 或 nan,并伴随运行时警告。
3、逐元素乘方
对矩阵中的每个元素分别求 p 次幂,可以写成:
其中:
若 p 为非负整数,各实数元素均可计算;若 p 取其他实数,还需要满足相应的实数定义域条件。
逐元素平方可以写成:
逐元素乘方不同于矩阵幂。
逐元素乘方分别处理每个元素;矩阵幂只适用于方阵,并通过矩阵乘法定义。
4、逐元素比较
两个同形状数组可以逐元素比较。例如:
b_{ij} \\ \mathrm{False}, & a_{ij}\le b_{ij} \end{cases} $$" data-nest-level="3">
结果是布尔数组,可以用于筛选、条件计算和掩码处理。
5、逐元素函数
给定一个标量函数 f,将它逐元素作用于矩阵 A,可以定义结果矩阵 B,其中:
绝对值、平方根、指数、对数和三角函数,都可以按照逐元素方式作用于数组。
四、使用 Python 基本语法进行逐元素运算
1、使用列表推导式
可以使用 配对两个列表中的对应元素:
print(hadamard_product)输出:
[8, -5, -6]这里没有继续对乘积求和,因此结果是列表,而不是点积得到的标量。
2、二维列表的逐元素乘法
对于二维列表,需要分别处理每一行中的对应元素:
print(result)输出:
[[2, -2, -5], [0, 0, 8]]使用嵌套列表时,应同时检查行数和对应行的长度,避免 zip() 在较短序列结束时停止。
Python 列表本身不支持 NumPy 式广播,需要在程序中明确编写相应的循环或推导式。
五、使用 NumPy 进行逐元素运算
1、逐元素乘法
在 NumPy 中,* 表示逐元素乘法:
print(result)输出:
[ 0 0 8]]也可以使用 np.multiply():
result = np.multiply(A, B)* 更简洁,np.multiply() 则明确表示调用逐元素乘法函数。
2、常见逐元素运算符
NumPy 中常见的逐元素运算包括:
A > B它们分别表示逐元素加法、减法、乘法、除法、平方和比较。
例如:
print(A > B)输出:
[ True False]]3、逐元素函数
NumPy 的许多数值函数会自动作用于数组中的每个元素:
print(clipped_values)其中:
np.abs() 逐元素取绝对值;
np.exp() 逐元素计算 e 的相应次幂;
np.clip() 将每个元素限制在指定范围内。
还可以使用 np.sqrt()、np.log()、np.sin() 和 np.cos() 等函数进行逐元素变换。
4、广播的基本规则
广播(Broadcasting)是 NumPy 在逐元素运算中处理不同形状数组的一套规则。
当两个数组形状不同但兼容时,NumPy 在运算过程中将长度为 1 的轴或缺失的前导轴视为可扩展,原数组本身的形状通常不会因此被修改,也通常不需要复制完整数据。
NumPy 从两个数组形状的最后一个轴开始向前比较。
对应轴满足以下任一条件时,可以广播:
两个对应轴的长度相同;
其中一个对应轴的长度为 1;
较短形状缺少相应的前导轴。
例如,(4, 3) 与 (3,):
最后一个轴的长度都是 3,可以广播,结果形状为:(4, 3)。
(4, 3) 与 (4, 1) 这两个形状也可以广播:
最后一个轴的长度分别为 3 和 1,长度为 1 的轴可以扩展,因此结果形状为:(4, 3)。
但是,(4, 3) 与 (4,) 则不能广播,因为从末尾比较时,最后一个轴的长度分别为 3 和 4,既不相同,也没有一个为 1。
标量可以与任意形状的数组进行逐元素运算:
print(A * 0.5)标量会作用于数组中的每个元素,结果形状与数组相同。
5、一维数组与矩阵的广播
设 data 的形状为 (2, 3),scale 的形状为 (3,):
print(result.shape)输出:
(2, 3)形状关系为:(2, 3) 与 (3,) → (2, 3)。
一维数组 scale 会与矩阵的最后一个轴对齐,因此三个元素分别作用于矩阵的三列。
可以使用 np.broadcast_shapes() 函数检查多个形状广播后的结果:
print(shape)输出:
(2, 3)6、增加轴与形状对齐
如果希望一个长度为 2 的一维数组分别作用于矩阵的两行,不能直接将形状 (2,) 与形状 (2, 3) 相乘。
例如:
row_scale = np.array([10.0, 100.0])此时 row_scale.shape 结果为:(2,)。
NumPy 从最后一个轴开始比较,形状 (2, 3) 和 (2,) 的最后一个轴长度分别为 3 和 2,因此不能广播。
可以在数组索引中使用 np.newaxis,为 row_scale 增加一个长度为 1 的轴:
print(row_scale.shape)输出:
(2, 1)此时形状关系为:(2, 3) 与 (2, 1) → (2, 3)。
完整示例如下:
print(result)输出:
[400. 500. 600.]]还可以使用数组的 reshape() 方法增加轴:
row_scale = row_scale.reshape(-1, 1)也可使用 np.expand_dims() 函数:
)增加轴不会改变元素数量,只会改变数组的形状,使轴能够按照预期方向对齐。
7、keepdims 参数与维度保持
聚合运算默认移除被聚合的轴。若结果还要与原数组按对应轴运算,可以使用 keepdims=True 将该轴保留为长度 1,从而形成可广播的形状。
例如,沿每一行计算均值:
print(row_mean.shape)输出:
(2,)形状 (2,) 不能直接与形状 (2, 3) 按行对应。
使用参数 keepdims=True,可以将被聚合的轴保留为长度 1:
print(row_mean.shape)输出:
(2, 1)此时可以直接通过广播完成按行中心化:
print(centered)输出结果:
[-1. 0. 1.]]8、更高维数组的广播
广播规则也适用于更高维数组。
例如,(5, 2, 3) 与 (3,),可以广播为:(5, 2, 3)。
长度为 3 的一维数组与最后一个轴对齐。
又如,(5, 2, 3) 与 (2, 1),也可以广播为:(5, 2, 3)。
第二个数组缺少最前面的轴,可以视为其形状为 (1, 2, 1),长度为 1 的轴再按照广播规则扩展。
9、使用布尔数组处理条件
逐元素比较可以生成布尔数组:
print(mask)输出:
[ True False True]]布尔数组可以用于筛选,也可以参与逐元素计算。
例如,将非正数替换为 0:
print(result)输出:
[1. 0. 4. ]]在 np.where(condition, x, y) 中,条件成立的位置取 x 中的值,否则取 y 中的值。当前示例中,x 是原数组 data,y 是标量 0.0。
六、常见问题及使用建议
1、确认运算符的含义
在 NumPy 中,* 表示逐元素乘法,@ 或 np.matmul() 表示按矩阵乘法规则处理数组,** 表示逐元素乘方。
对于一维数组,a * b 得到逐元素乘积数组,a @ b 得到点积标量。使用前应根据运算目标和输出形状选择正确的运算符。
2、检查广播方向
广播成功只表示形状兼容,不表示计算含义正确。
形状为 (n,) 的一维数组默认与矩阵的最后一个轴对齐;需要按其他轴计算时,应通过增加轴或保留维度明确形状。
3、检查除数和数据类型
逐元素除法前应检查除数中的 0 或接近 0 的元素,避免产生 inf 或 nan。
整数数组执行原地除法时,可能因浮点结果无法写回整数数组而报错。需要浮点结果时,可以预先转换数据类型,或使用普通除法生成新数组:
A /= 2或者:
result = A / 2小结
逐元素运算按照对应位置分别计算向量、矩阵和更高维张量中的元素。Hadamard 积是同形状对象之间的逐元素乘法,在 NumPy 中通常使用 * 或 np.multiply() 实现。NumPy 的广播机制允许兼容形状的数组进行逐元素运算,但仍需明确各轴含义。
“点赞有美意,赞赏是鼓励”
热门跟贴