\[ \begin{align}\begin{aligned}\newcommand{\ba}{\boldsymbol{a}} \newcommand{\bb}{\boldsymbol{b}} \newcommand{\be}{\boldsymbol{e}} \newcommand{\bq}{\boldsymbol{q}} \newcommand{\bk}{\boldsymbol{k}} \newcommand{\bw}{\boldsymbol{w}} \newcommand{\bx}{\boldsymbol{x}} \newcommand{\by}{\boldsymbol{y}} \newcommand{\bz}{\boldsymbol{z}} \newcommand{\bd}{\boldsymbol{d}} \newcommand{\bv}{\boldsymbol{v}} \newcommand{\bs}{\boldsymbol{s}}\\\newcommand{\btheta}{\boldsymbol{\theta}} \newcommand{\bbeta}{\boldsymbol{\beta}} \newcommand{\bgamma}{\boldsymbol{\gamma}} \newcommand{\bsigma}{\boldsymbol{\sigma}} \newcommand{\md}{\mbox{d}} \newcommand{\bmu}{\boldsymbol{\mu}} \newcommand{\bone}{\boldsymbol{1}} \newcommand{\bzero}{\boldsymbol{0}} \newcommand{\bepsilon}{\boldsymbol{\epsilon}} \newcommand{\bphi}{\boldsymbol{\phi}} \newcommand{\bh}{\boldsymbol{h}} \newcommand{\bc}{\boldsymbol{c}} \newcommand{\br}{\boldsymbol{r}} \newcommand{\bQ}{\boldsymbol{Q}} \newcommand{\bK}{\boldsymbol{K}} \newcommand{\bV}{\boldsymbol{V}} \newcommand{\bSigma}{\boldsymbol{\Sigma}} \newcommand{\bg}{\boldsymbol{g}} \newcommand{\bxi}{\boldsymbol{\xi}} \newcommand{\bvarepsilon}{\boldsymbol{\varepsilon}} \newcommand{\bdelta}{\boldsymbol{\delta}} \newcommand{\bq}{\boldsymbol{q}} \newcommand{\bk}{\boldsymbol{k}} \newcommand{\bJ}{\boldsymbol{J}} \newcommand{\bp}{\boldsymbol{p}} \newcommand{\bi}{\boldsymbol{i}} \newcommand{\bo}{\boldsymbol{o}} \newcommand{\bE}{\boldsymbol{E}} \newcommand{\bH}{\boldsymbol{H}} \newcommand{\bL}{\boldsymbol{L}} \newcommand{\bu}{\boldsymbol{u}} \newcommand{\bLambda}{\boldsymbol{\Lambda}} \newcommand{\trans}{^{\rm\scriptsize T}} \newcommand{\var}{\mathrm{var}}\\\newcommand{\bA}{\boldsymbol{A}} \newcommand{\bB}{\boldsymbol{B}} \newcommand{\bC}{\boldsymbol{C}} \newcommand{\bD}{\boldsymbol{D}} \newcommand{\bG}{\boldsymbol{G}} \newcommand{\bI}{\boldsymbol{I}} \newcommand{\bM}{\boldsymbol{M}} \newcommand{\bP}{\boldsymbol{P}} \newcommand{\bS}{\boldsymbol{S}} \newcommand{\bU}{\boldsymbol{U}} \newcommand{\bW}{\boldsymbol{W}} \newcommand{\bX}{\boldsymbol{X}} \newcommand{\bY}{\boldsymbol{Y}} \newcommand{\bZ}{\boldsymbol{Z}} \newcommand{\cotp}{\textcolor[RGB]{48,209,88}{TP}} \newcommand{\cotn}{\textcolor[RGB]{100,210,255}{TN}} \newcommand{\cofp}{\textcolor[RGB]{94,92,230}{FP}} \newcommand{\cofn}{\textcolor[RGB]{191,90,242}{FN}}\\\newcommand{\numcotp}{\textcolor[RGB]{48,209,88}{50}} \newcommand{\numcotn}{\textcolor[RGB]{100,210,255}{30}} \newcommand{\numcofp}{\textcolor[RGB]{94,92,230}{10}} \newcommand{\numcofn}{\textcolor[RGB]{191,90,242}{10}} \DeclareMathOperator*{\argmin}{arg\,min}\end{aligned}\end{align} \]

单隐藏层神经网络——多样本向量化#

学习目标与记号#

  1. 准确解释样本按行排列的默认规则、批量前向传播与平均损失;

  2. 根据公式和张量维度分析批量后向传播,并识别常见实现错误;

  3. 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}\)⁠。

模型参数

  1. \(\bb^{[1]}\in\mathbb{R}^{d^{[1]}\times 1}\) 为隐藏层中每个神经元对应的偏置项,每一行对应一个神经元,该层共有 \(d^{[1]}\) 个神经元。

  2. \(\bW^{[1]}\in\mathbb{R}^{d^{[1]}\times d}\) 为隐藏层中每个神经元对应的权重项,每一行对应一个神经元。

  3. \(\bb^{[2]}\in\mathbb{R}^{1\times1}\) 为输出层神经元对应的偏置项。

  4. \(\bW^{[2]}\in\mathbb{R}^{1\times d^{[1]}}\) 为输出层神经元对应的权重矩阵。

  需要指出的是,我们将神经网络中的每个参数都表达成了二维矩阵。这样做有助于借助于 广播机制 对本节所涉及到的向量化进行讨论。

前向传播#

  与上一节的式 (21) 类似,在给定当前模型参数时,我们需要 针对每个训练样本 计算前向传播各个“神经元”的函数值。即对于 \(i=1,\ldots,n\)⁠,计算

\[\begin{split}\begin{eqnarray} \bz_i^{[1]} &=& \bb^{[1]}+\bW^{[1]}\cdot\bx_i, \\ \ba_i^{[1]} &=& \sigma(\bz_i^{[1]}), \\ z_i^{[2]} &=& \bb^{[2]}+\bW^{[2]}\cdot\ba_i^{[1]},\\ a_i^{[2]} &=&\sigma(z_i^{[2]}). \end{eqnarray}\end{split}\]

  对应的损失函数为

(29)#\[\mathcal{J}(\btheta)=-\frac{1}{n}\sum_{i=1}^n \left\{y_i\log a_i^{[2]}+(1-y_i)\log\left(1-a_i^{[2]}\right)\right\},\]

其中,\(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)#\[\begin{split}\begin{eqnarray} \bZ^{[1]} &=& \bX\cdot(\bW^{[1]})\trans+\bone\cdot(\bb^{[1]})\trans, \\ \bA^{[1]} &=& \sigma(\bZ^{[1]}), \\ \bZ^{[2]} &=& \bA^{[1]}\cdot(\bW^{[2]})\trans+\bone\cdot(\bb^{[2]})\trans,\\ \bA^{[2]} &=&\sigma(\bZ^{[2]}). \end{eqnarray}\end{split}\]

  式 (30) 显式使用 \(\bone\in\mathbb{R}^{n\times1}\) 将偏置复制到每一行,激活函数 \(\sigma(\cdot)\) 逐元素作用于 \(\bZ^{[l]}\)⁠。在 NumPy 中可以依靠 广播机制 省略这个乘法,但偏置应存为 \(d^{[l]}\)\(1\times d^{[l]}\)⁠,而不是 \(d^{[l]}\times1\)⁠,否则可能沿错误的轴广播。也就是说,如果使用 Python 的广播机制,对应的计算结果如下:

\[\begin{split}\begin{eqnarray} \bZ^{[1]} &=& \bX\cdot(\bW^{[1]})\trans+(\bb^{[1]})\trans, \\ \bA^{[1]} &=& \sigma(\bZ^{[1]}), \\ \bZ^{[2]} &=& \bA^{[1]}\cdot(\bW^{[2]})\trans+(\bb^{[2]})\trans,\\ \bA^{[2]} &=&\sigma(\bZ^{[2]}). \end{eqnarray}\end{split}\]

  于是,平均损失(或者代价函数)可写成

(31)#\[\mathcal{J}(\btheta)=-\frac{1}{n}\bone\trans\cdot \left[\by\odot\log\bA^{[2]} +(\bone-\by)\odot\log(\bone-\bA^{[2]})\right],\]

其中,\(\bone\in\mathbb{R}^{n\times1}\) 为全 1 向量,\(\odot\) 表示逐元素乘积,对数运算是逐元素进行的。这个写法把“逐样本计算损失”和“对样本求平均”明确区分开。

后向传播#

  为了简化记号,我们记

\[\mathrm{d}\cdot=\frac{\partial\mathcal{J}}{\partial\cdot}.\]

  基于向量化以及上一节中式 (27) 的计算过程,我们可以得到如下后向传播结果。

(32)#\[\begin{split}\begin{eqnarray} \mathrm{d}\bZ^{[2]} &=&n^{-1}(\bA^{[2]}-\by),\\ \mathrm{d}\bb^{[2]} &=& (\mathrm{d}\bZ^{[2]})\trans\cdot\bone,\\ \mathrm{d}\bW^{[2]} &=& (\mathrm{d}\bZ^{[2]})\trans\cdot\bA^{[1]},\\ \mathrm{d}\bZ^{[1]} &=& \mathrm{d}\bZ^{[2]}\cdot\bW^{[2]}\odot\sigma'(\bZ^{[1]}),\\ \mathrm{d}\bb^{[1]} &=& (\mathrm{d}\bZ^{[1]})\trans\cdot\bone,\\ \mathrm{d}\bW^{[1]} &=& (\mathrm{d}\bZ^{[1]})\trans\cdot\bX, \end{eqnarray}\end{split}\]

其中,\(\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) 对模型参数进行更新。

核心推导与实现核验#

核心关系

(33)#\[\bZ^{[l]}=\bA^{[l-1]}\cdot(\bW^{[l]})\trans+\bone\cdot(\bb^{[l]})\trans.\]

  推导路径。\(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]}\) 等表示成二维数组。

数值稳定性与规模

  对于二分类问题,应采用数值稳定的方法计算损失函数。初始化权重时,应根据该层的输入神经元数量确定权重的初始大小。训练过程中,应记录各层激活值和梯度的大小。如果梯度过大,应先检查后向传播公式是否正确以及学习率是否过大;确认计算无误后,再考虑限制所有参数梯度的整体大小。

本节小结#

  1. 本材料的样本按行进行堆叠,形成设计矩阵。

  2. 偏置广播必须与目标轴一致。

  3. 乘子 \(n^{-1}\) 只在计算输出层时使用,批量和逐样本结果应可互相验证。

综合练习#

  本组练习围绕批量矩阵公式、逐样本计算与批量计算的等价性以及向量化实现展开。程序题应固定数据划分、随机种子集合和训练预算,写出维度断言并报告运行环境。全部参考答案见 多样本向量化前向与后向传播答案⁠。

  1. 批量前向传播。 从第 \(i\) 个样本的逐样本公式出发,推导式 (30)⁠。写出 \(\bX\)⁠、\(\bW^{[l]}\)⁠、\(\bb^{[l]}\)⁠、\(\bZ^{[l]}\)\(\bA^{[l]}\) 的规模,并说明全 1 向量在偏置复制中的作用。

  2. 批量后向传播。 从式 (29) 给出的平均损失出发,推导式 (32)⁠。说明因子 \(1/n\) 应在哪一步引入,并证明 \(\mathrm d\bW^{[l]}\)\(\bW^{[l]}\)⁠、\(\mathrm d\bb^{[l]}\)\(\bb^{[l]}\) 具有相同的规模。

  3. 逐样本与批量计算的等价性。 按矩阵元素展开,证明批量前向传播的每一行等于对应样本的前向结果,并证明批量参数梯度等于逐样本参数梯度的平均值。

  4. 矩阵手算。 给定 \(\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\)⁠,并核对对应结果的规模。

  5. 向量化实现。 仅使用 NumPy 实现单隐藏层网络的批量 forward_backward样本按行存放,偏置显式保存为一维数组或 \(1\times d^{[l]}\) 行向量;损失按样本取平均,并确保平均因子只出现一次。返回全部中间量和梯度,用第 4 题核验仿射层实现。

  6. 循环与向量化结果核验。 编写逐样本循环版和批量矩阵版前向与后向传播。在多个随机维度上比较损失函数值和参数梯度,并构造一个能够发现“重复除以 \(n\)⁠”以及“偏置沿错误轴广播”的测试。

  7. 循环与向量化效率。 对不同的 \(n\)⁠、\(d\) 和隐藏神经元个数,比较逐样本循环版与批量矩阵版的前向时间、前向加后向时间、固定批量预测时间和峰值内存。预热后重复计时,核验结果一致后再讨论效率,并解释为什么小规模问题上计时差异可能不稳定。