在前面的部分,我们了解了如何通过对其输入进行加权求和、添加偏置,然后应用激活函数来计算单个神经元的输出。虽然这为我们提供了基本构成要素,但想象一下,如果一个层有数百或数千个神经元,你一个接一个地进行这种计算,然后对数百万个输入样本在多个层上重复此操作。这很快就会变得计算上不可行。幸运的是,我们可以运用线性代数和优化的数值库(例如 Python 中的 NumPy),使用矩阵运算更有效地进行这些计算。这个过程通常被称为 向量化。我们不再逐个神经元或逐个输入连接地迭代,而是同时处理整个层或批量数据。用矩阵表示层计算让我们重新思考网络中单个层的计算。假设这一层有 $n_{out}$ 个神经元,并从前一层(或输入特征)接收 $n_{in}$ 个数值的输入。输入: 对于单个训练示例,层的输入可以表示为一个大小为 ($n_{in} \times 1$) 的列向量 $x$。权重: 连接 $n_{in}$ 个输入到当前层 $n_{out}$ 个神经元的权重可以组织成一个权重矩阵 $W$。$W$ 的每一行对应当前层的一个神经元,每一列对应来自输入特征或前一层神经元的连接。$W$ 的维度将是 ($n_{out} \times n_{in}$)。偏置: 当前层的每个 $n_{out}$ 个神经元都有自己的偏置项。这些可以归纳为一个大小为 ($n_{out} \times 1$) 的偏置向量 $b$。高效计算加权和回顾层中单个神经元 $j$ 的加权和: $z_j = (\sum_{i=1}^{n_{in}} w_{ji} x_i) + b_j$使用矩阵记法,我们可以在一次操作中计算该层中 所有 $n_{out}$ 个神经元的加权和:$$ Z = Wx + b $$让我们分解一下维度:$W$ 是 ($n_{out} \times n_{in}$)$x$ 是 ($n_{in} \times 1$)$b$ 是 ($n_{out} \times 1$)矩阵乘法 $Wx$ 得到一个大小为 ($n_{out} \times 1$) 的向量。这个结果向量中的每个元素都是 $W$ 中一行(一个神经元的权重)与输入向量 $x$ 的点积。这精确地同时计算了每个神经元的 $\sum w_i x_i$ 部分。然后,我们将偏置向量 $b$(大小也是 $n_{out} \times 1$)添加到 $Wx$ 的结果中。这个加法是按元素进行的,将相应的偏置 $b_j$ 加到为神经元 $j$ 计算的加权和中。最终结果 $Z$ 是一个大小为 ($n_{out} \times 1$) 的列向量,$Z_j$ 包含层中第 $j$ 个神经元的加权和 $z_j$。digraph G { rankdir=LR; node [shape=box, style=filled, fillcolor="#e9ecef"]; edge [arrowhead=vee]; subgraph cluster_input { label = "输入 / 前一层"; style=dashed; X [label="输入向量\nx (n_in x 1)", shape= Mrecord, fillcolor="#a5d8ff"]; } subgraph cluster_params { label = "当前层参数"; style=dashed; W [label="权重矩阵\nW (n_out x n_in)", shape= Mrecord, fillcolor="#bac8ff"]; b [label="偏置向量\nb (n_out x 1)", shape= Mrecord, fillcolor="#bac8ff"]; } subgraph cluster_computation { label = "层计算"; style=dashed; Z_node [label="加权和向量\nZ = Wx + b\n(n_out x 1)", shape= Mrecord, fillcolor="#ffec99"]; A_node [label="激活向量\nA = g(Z)\n(n_out x 1)", shape= Mrecord, fillcolor="#ffe066"]; Z_node -> A_node [label="按元素应用 g()"]; } X -> Z_node [label="输入"]; W -> Z_node [label="权重"]; b -> Z_node [label="偏置"]; A_node -> Output [label="输出 (到下一层)", shape=none, fontcolor="#495057"]; Output [shape=point, style=invis]; }使用矩阵运算进行单层正向传播。输入向量 $x$ 使用层的权重矩阵 $W$ 和偏置向量 $b$ 进行转换,生成加权和向量 $Z$。然后,激活函数 $g$ 被按元素应用于 $Z$,得到激活向量 $A$。应用激活函数一旦我们有了包含层中所有神经元加权和的向量 $Z$,我们就应用该层的激活函数 $g$(如 Sigmoid、Tanh 或 ReLU)。这个操作是在向量 $Z$ 上 按元素 执行的:$$ A = g(Z) $$如果 $Z = [z_1, z_2, ..., z_{n_{out}}]^T$,那么得到的激活向量 $A$ 将是 $A = [g(z_1), g(z_2), ..., g(z_{n_{out}})]^T$。这个向量 $A$ 的大小也是 ($n_{out} \times 1$),它代表当前层的输出,并作为网络中下一层的输入。处理批量数据神经网络通常一次使用多个训练示例的批量进行训练,而不是一次一个示例。矩阵运算可以很好地处理这种扩展。现在,我们不再使用单个输入向量 $x$ ($n_{in} \times 1$),而是有一个输入矩阵 $X$,它的每列代表一个不同的训练示例。如果我们有一个包含 $m$ 个示例的批量,则输入矩阵 $X$ 的维度将是 ($n_{in} \times m$)。整个批量的正向传播计算变为:$$ Z = WX + b $$让我们再次检查维度:$W$ 是 ($n_{out} \times n_{in}$)$X$ 是 ($n_{in} \times m$)$b$ 是 ($n_{out} \times 1$)矩阵乘法 $WX$ 现在得到一个大小为 ($n_{out} \times m$) 的矩阵。该结果矩阵的每列 $j$ 都包含层中所有神经元的加权和,这些加权和是使用批量 $X$ 中的第 $j$ 个输入示例计算的。将偏置向量 $b$($n_{out} \times 1$)添加到矩阵 $WX$($n_{out} \times m$)时,大多数数值库会使用一个称为 广播 的功能。偏置向量 $b$ 被有效地复制或“广播”到 $WX$ 矩阵的所有 $m$ 列,因此对于批量中的每个示例,偏置 $b_i$ 都被添加到第 $i$ 个神经元的加权和中。结果矩阵 $Z$ 的维度是 ($n_{out} \times m$)。最后,激活函数 $g$ 被按元素应用于整个矩阵 $Z$:$$ A = g(Z) $$得到的激活矩阵 $A$ 的维度也是 ($n_{out} \times m$)。$A$ 的每一列代表该层神经元对批量中一个输入示例的激活。然后,这个矩阵 $A$ 成为下一层的输入。重要性使用矩阵运算将遍历神经元和连接这种计算量大的过程,转换为高度优化的单行命令(例如 NumPy 中的 Z = W @ X + b)。这使用了通常用 C 或 Fortran 编写的底层库(如 BLAS/LAPACK),使得计算比显式 Python 循环快得多。这种速度提升对于在大型数据集上训练现代神经网络极其重要,这些网络可能拥有数百万甚至数十亿个参数。这种使用矩阵进行正向传递的高效计算构成了神经网络生成预测的支柱,也是理解训练期间使用的后续反向传播算法的前提条件。