单隐藏层神经网络——多样本向量化#
学习目标与记号#
准确解释样本按行排列的默认规则、批量前向传播与平均损失;
根据公式和张量维度分析批量后向传播,并识别常见实现错误;
用
Python实现或验证广播偏置;在其他条件相同的情况下,只改变一个需要研究的因素进行比较,并根据实验结果分析该因素可能带来的影响。
本节沿用统一记号:普通小写字母表示标量,粗体小写字母表示向量,粗体大写字母表示矩阵或高阶张量;层编号写作上标 \([l]\),样本或时间编号写作下标;转置写作 \(\trans\)。除非另有说明,批量样本按行存放。正文与练习中的程序都应同时检查数值结果和数组维度。本节练习的参考答案见 多样本向量化前向与后向传播答案。
本节是 单样本前向传播 与 单样本后向传播 的批量化推广;所涉及的广播机制运算规则请参见 广播机制。
上一节从单个样本推导了前向传播和后向传播。本节把 \(n\) 个样本按行堆叠,以一次矩阵运算完成整个训练集或一个小批量的计算。记 \(\bx_i\in\mathbb{R}^{d\times 1}\)、\(y_i\in\{0,1\}\),并令 \(\bX=[\bx_1\trans;\ldots;\bx_n\trans]\in\mathbb{R}^{n\times d}\)、\(\by=(y_1,\ldots,y_n)\trans\in\mathbb{R}^{n\times1}\)。
模型参数
\(\bb^{[1]}\in\mathbb{R}^{d^{[1]}\times 1}\) 为隐藏层中每个神经元对应的偏置项,每一行对应一个神经元,该层共有 \(d^{[1]}\) 个神经元。
\(\bW^{[1]}\in\mathbb{R}^{d^{[1]}\times d}\) 为隐藏层中每个神经元对应的权重项,每一行对应一个神经元。
\(\bb^{[2]}\in\mathbb{R}^{1\times1}\) 为输出层神经元对应的偏置项。
\(\bW^{[2]}\in\mathbb{R}^{1\times d^{[1]}}\) 为输出层神经元对应的权重矩阵。
需要指出的是,我们将神经网络中的每个参数都表达成了二维矩阵。这样做有助于借助于 广播机制 对本节所涉及到的向量化进行讨论。
前向传播#
与上一节的式 (21) 类似,在给定当前模型参数时,我们需要 针对每个训练样本 计算前向传播各个“神经元”的函数值。即对于 \(i=1,\ldots,n\),计算
对应的损失函数为
其中,\(a_i^{[2]}\) 为模型参数 \(\btheta\) 的函数,对应于第 \(i\) 个训练样本的输出,即给定特征 \(\bx_i\) 时,对应标签属于类别 1 的条件概率。与单训练样本的情况不同,式 (29) 使用平均损失。
记 \(\bX=[\bx_1\trans;\ldots;\bx_n\trans]\in\mathbb{R}^{n\times d}\) 和 \(\by=(y_1,\ldots,y_n)\trans\in\mathbb{R}^{n\times1}\),则前向传播可写为
式 (30) 显式使用 \(\bone\in\mathbb{R}^{n\times1}\) 将偏置复制到每一行,激活函数 \(\sigma(\cdot)\) 逐元素作用于 \(\bZ^{[l]}\)。在 NumPy 中可以依靠 广播机制 省略这个乘法,但偏置应存为 \(d^{[l]}\) 或 \(1\times d^{[l]}\),而不是 \(d^{[l]}\times1\),否则可能沿错误的轴广播。也就是说,如果使用 Python 的广播机制,对应的计算结果如下:
于是,平均损失(或者代价函数)可写成
其中,\(\bone\in\mathbb{R}^{n\times1}\) 为全 1 向量,\(\odot\) 表示逐元素乘积,对数运算是逐元素进行的。这个写法把“逐样本计算损失”和“对样本求平均”明确区分开。
后向传播#
为了简化记号,我们记
基于向量化以及上一节中式 (27) 的计算过程,我们可以得到如下后向传播结果。
其中,\(\mathrm{d}\bZ^{[2]}\in\mathbb{R}^{n\times1}\) 已包含因子 \(1/n\),因此其余梯度不再重复除以 \(n\)。请大家自行验证:\(\mathrm{d}\bW^{[l]}\) 与 \(\bW^{[l]}\) 同规模,\(\mathrm{d}\bb^{[l]}\) 与 \(\bb^{[l]}\) 同规模。
当我们得到损失函数对模型参数的导数后,我们仍然用式 (28) 对模型参数进行更新。
核心推导与实现核验#
核心关系
推导路径。 将 \(n\) 个样本按行组成输入矩阵后,可以用一次矩阵乘法同时计算所有样本的结果。偏置向量会加到每个样本的计算结果上。由于损失函数是所有样本损失的平均值,因此参数梯度等于各样本梯度之和除以样本数。
关键条件
在式 (33) 中,矩阵乘法结果的第 \((i,j)\) 个元素恰好是第 \(i\) 个样本与第 \(j\) 个神经元权重的内积,因此该式与逐样本计算的结果逐元素相等。
数据规模
若一次处理 \(n\) 个样本,第 \(l-1\) 层每个样本有 \(d^{[l-1]}\) 个特征,第 \(l\) 层有 \(d^{[l]}\) 个神经元,则输入 \(\bA^{[l-1]}\) 的规模为 \(n\times d^{[l-1]}\),权重 \(\bW^{[l]}\) 的规模为 \(d^{[l]}\times d^{[l-1]}\),输出 \(\bZ^{[l]}\) 的规模为 \(n\times d^{[l]}\)。
常见误区
式 (32) 中的 \(\mathrm{d}\bZ^{[2]}\) 已经包含平均因子 \(1/n\);如果在计算后续权重梯度或偏置梯度时再次除以 \(n\),最终梯度会额外缩小 \(n\) 倍。式 (33) 使用 \(\bone\cdot(\bb^{[l]})\trans\) 将同一个偏置向量加到每个样本的计算结果上;若将偏置存成一维数组,虽然 NumPy 可以自动完成广播,但数组不再明确保留行轴和列轴,后续进行转置或求和时更难发现轴使用错误。从这个意义上讲,我们建议将偏置项 \(\bb^{[1]}\) 等表示成二维数组。
数值稳定性与规模
对于二分类问题,应采用数值稳定的方法计算损失函数。初始化权重时,应根据该层的输入神经元数量确定权重的初始大小。训练过程中,应记录各层激活值和梯度的大小。如果梯度过大,应先检查后向传播公式是否正确以及学习率是否过大;确认计算无误后,再考虑限制所有参数梯度的整体大小。
本节小结#
本材料的样本按行进行堆叠,形成设计矩阵。
偏置广播必须与目标轴一致。
乘子 \(n^{-1}\) 只在计算输出层时使用,批量和逐样本结果应可互相验证。
综合练习#
本组练习围绕批量矩阵公式、逐样本计算与批量计算的等价性以及向量化实现展开。程序题应固定数据划分、随机种子集合和训练预算,写出维度断言并报告运行环境。全部参考答案见 多样本向量化前向与后向传播答案。
批量前向传播。 从第 \(i\) 个样本的逐样本公式出发,推导式 (30)。写出 \(\bX\)、\(\bW^{[l]}\)、\(\bb^{[l]}\)、\(\bZ^{[l]}\) 和 \(\bA^{[l]}\) 的规模,并说明全 1 向量在偏置复制中的作用。
批量后向传播。 从式 (29) 给出的平均损失出发,推导式 (32)。说明因子 \(1/n\) 应在哪一步引入,并证明 \(\mathrm d\bW^{[l]}\) 与 \(\bW^{[l]}\)、\(\mathrm d\bb^{[l]}\) 与 \(\bb^{[l]}\) 具有相同的规模。
逐样本与批量计算的等价性。 按矩阵元素展开,证明批量前向传播的每一行等于对应样本的前向结果,并证明批量参数梯度等于逐样本参数梯度的平均值。
矩阵手算。 给定 \(\bA=\begin{pmatrix}1&2\\-1&0\end{pmatrix}\)、 \(\bW=\begin{pmatrix}1&-1\\2&1\end{pmatrix}\)、 \(\bb=(0.5,-0.5)\trans\) 和 \(\mathrm d\bZ=\begin{pmatrix}1&2\\3&4\end{pmatrix}\),手算 \(\bZ=\bA\cdot\bW\trans+\bone\cdot\bb\trans\)、\(\mathrm d\bW=(\mathrm d\bZ)\trans\cdot\bA\)、\(\mathrm d\bb=(\mathrm d\bZ)\trans\cdot\bone\) 与 \(\mathrm d\bA=\mathrm d\bZ\cdot\bW\),并核对对应结果的规模。
向量化实现。 仅使用
NumPy实现单隐藏层网络的批量forward_backward。样本按行存放,偏置显式保存为一维数组或 \(1\times d^{[l]}\) 行向量;损失按样本取平均,并确保平均因子只出现一次。返回全部中间量和梯度,用第 4 题核验仿射层实现。循环与向量化结果核验。 编写逐样本循环版和批量矩阵版前向与后向传播。在多个随机维度上比较损失函数值和参数梯度,并构造一个能够发现“重复除以 \(n\)”以及“偏置沿错误轴广播”的测试。
循环与向量化效率。 对不同的 \(n\)、\(d\) 和隐藏神经元个数,比较逐样本循环版与批量矩阵版的前向时间、前向加后向时间、固定批量预测时间和峰值内存。预热后重复计时,核验结果一致后再讨论效率,并解释为什么小规模问题上计时差异可能不稳定。