逐元素运算按照对应位置处理向量、矩阵和更高维张量中的元素。在 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 的广播机制允许兼容形状的数组进行逐元素运算,但仍需明确各轴含义。

打开网易新闻 查看精彩图片

点赞有美意,赞赏是鼓励