19.2 广播

广播(Broadcasting)是指 Octave 的二元运算符和函数在其矩阵或数组操作数(参数)大小不同时的行为方式。自 3.6.0 版本起,Octave 在使用逐元素二元运算符和函数时,会自动广播向量、矩阵和数组。广义而言,较小的数组会被"广播"到较大的数组上,直到它们具有兼容的形状。规则是:对应的数组维度必须满足以下条件之一:

  1. 相等,或者
  2. 其中之一为 1。

如果所有维度都相等,则不发生广播,执行普通的逐元素算术运算。对于高维数组,如果维度数量不同,则缺失的尾部维度被视为 1。当某个维度为 1 时,具有该单一维度的数组会沿该维度复制,直到与另一个数组的维度匹配。例如,考虑

x = [1 2 3;
     4 5 6;
     7 8 9];

y = [10 20 30];

x + y

如果没有广播,x + y 会因维度不匹配而产生错误。然而,有了广播,其效果相当于执行了以下操作:

x = [1 2 3
     4 5 6
     7 8 9];

y = [10 20 30
     10 20 30
     10 20 30];

x + y
⇒     11   22   33
      14   25   36
      17   28   39

也就是说,较小的数组(大小为 [1 3])沿单一维度(行数)被复制,直到成为 [3 3]。然而,实际并未发生真正的复制。内部实现通过沿必要维度重用元素来达到预期效果,而无需在内存中复制数据。

两个数组可以相互广播,例如,计算向量元素与其自身的所有成对差值:

y - y'
⇒     0   10   20
    -10    0   10
    -20  -10    0

这里,大小为 [1 3][3 1] 的两个向量都被广播为 [3 3] 的矩阵,然后执行普通的矩阵减法。

广播的一种特殊情况可能为人熟知:当被广播数组的所有维度都为 1 时,即该数组是一个标量。因此,例如,x - 42max (x, 2) 这样的操作就是广播的基本示例。

再看一个高维示例:假设 img 是一幅大小为 [m n 3] 的 RGB 图像,我们希望将每种颜色乘以不同的标量。以下代码通过广播实现这一操作:

img .*= permute ([0.8, 0.9, 1.2], [1, 3, 2]);

注意此处使用了 permute 来匹配 [0.8, 0.9, 1.2] 向量与 img 的维度。

对于未按广播语义编写的函数,可以使用 bsxfun 来强制它们进行广播。

 
C = bsxfun (f, A, B)

将二元函数 f 逐元素应用于两个数组参数 AB,必要时扩展任一输入参数中的单一维度。

f 是一个函数句柄、内联函数或包含要计算的函数名称的字符串。函数 f 必须能够接受两个等长的列向量参数,或一个列向量参数和一个标量。

AB 的维度必须相等或为单一维度。数组的单一维度将被扩展到与另一个数组相同的维度。

另请参阅: arrayfuncellfun

广播仅在满足两个广播条件之一时应用。但和通常一样,当两个维度不同且都不为 1 时,广播不适用:

x = [1 2 3
     4 5 6];
y = [10 20
     30 40];
x + y

这将产生参数不兼容的错误。

除了常见的算术运算外,一些接受两个参数的函数也会进行广播。支持广播的函数和运算符的完整列表如下:

      plus      +
      minus     -
      times     .*
      rdivide   ./
      ldivide   .\
      power     .^
      lt        <
      le        <=
      eq        ==
      gt        >
      ge        >=
      ne        !=  ~=
      and       &
      or        |
      atan2
      hypot
      max
      min
      mod
      rem
      xor

      +=  -=  .*=  ./=  .\=  .^=  &=  |=

下面是一个展示广播威力的实际例子。Floyd-Warshall 算法用于计算图中每对顶点之间的最短路径长度。对于 n 阶图的邻接矩阵,朴素的实现可能如下所示:

for k = 1:n
  for i = 1:n
    for j = 1:n
      dist(i,j) = min (dist(i,j), dist(i,k) + dist(k,j));
    endfor
  endfor
endfor

对最内层循环进行向量化后,可能变成这样:

for k = 1:n
  for i = 1:n
    dist(i,:) = min (dist(i,:), dist(i,k) + dist(k,:));
  endfor
endfor

使用双向广播后,变成这样:

for k = 1:n
  dist = min (dist, dist(:,k) + dist(k,:));
endfor

对于一个包含 100 个顶点的图,三种技术的相对时间性能为:朴素代码 7.3 秒,单向量化代码 87 毫秒,完全广播代码 1.3 毫秒。对于一个包含 1000 个顶点的图,向量化需要 11.7 秒,而广播仅需 1.15 秒。因此,通常值得编写具有广播语义的代码以获得更好的性能。

然而,请注意:如果简单的操作即可满足需求,不要盲目使用广播。对于矩阵 ab,考虑以下代码:

c = sum (permute (a, [1, 3, 2]) .* permute (b, [3, 2, 1]), 3);

该操作在逐元素乘法过程中,将两个经过维度置换的矩阵相互广播,从而得到一个更大的三维数组,然后沿第三维对该数组求和。稍加思考便会发现,该操作其实就是快得多的普通矩阵乘法:c = a*b;

术语说明:"广播"(broadcasting)这个术语是由 Python 编程语言中的 Numpy 数值环境推广开来的。在其他编程语言和环境中,广播也可能被称为二元单例扩展(Binary Singleton Expansion,简称 BSX,在 MATLAB 中使用,也是 bsxfun 函数名称的由来)、回收(Recycling,R 编程语言)、单指令多数据(Single-Instruction Multiple Data,SIMD)或复制(Replication)。


版权所有 © 2024-2026 Octave中文网

ICP备案/许可证号:黑ICP备2024030411号-2