多样本向量化前向与后向传播:参考答案#
说明#
以下答案与正文7道题逐题对应。推导题给出关键等式;编程题给出可扩展的代码骨架;效率实验必须先核验循环版与向量化版的结果一致。
设 \(\bA^{[l-1]}=[(\ba_1^{[l-1]})\trans;\ldots;(\ba_n^{[l-1]})\trans]\in\mathbb R^{n\times d^{[l-1]}}\),且 \(\bW^{[l]}=[(\bw_1^{[l]})\trans;\ldots;(\bw_{d^{[l]}}^{[l]})\trans]\in\mathbb R^{d^{[l]}\times d^{[l-1]}}\)。第 \(i\) 个样本在第 \(l\) 层的线性运算为
\[z_{ij}^{[l]}=(\bw_j^{[l]})\trans\cdot\ba_i^{[l-1]}+b_j^{[l]}.\]把所有 \(i,j\) 同时排列,得到
\[\bZ^{[l]}=\bA^{[l-1]}\cdot(\bW^{[l]})\trans +\bone\cdot(\bb^{[l]})\trans, \qquad \bA^{[l]}=g^{[l]}(\bZ^{[l]}),\]其中 \(\bone\in\mathbb R^{n\times1}\)、\(\bb^{[l]}\in\mathbb R^{d^{[l]}\times1}\),两项和输出均为 \(n\times d^{[l]}\)。全 1 向量与偏置转置的外积把同一偏置行复制 \(n\) 次;在
NumPy中写成Z = A @ W.T + b.reshape(1, -1)即可由广播完成。对平均损失,输出层误差信号已经包含平均因子:
\[\mathrm d\bZ^{[2]}=\frac1n(\bA^{[2]}-\by).\]链式法则随后给出
\[\begin{split}\begin{aligned} \mathrm d\bW^{[2]}&=(\mathrm d\bZ^{[2]})\trans\cdot\bA^{[1]}, &\mathrm d\bb^{[2]}&=(\mathrm d\bZ^{[2]})\trans\cdot\bone,\\ \mathrm d\bZ^{[1]}&=\mathrm d\bZ^{[2]}\cdot\bW^{[2]} \odot g^{[1]\prime}(\bZ^{[1]}),\\ \mathrm d\bW^{[1]}&=(\mathrm d\bZ^{[1]})\trans\cdot\bX, &\mathrm d\bb^{[1]}&=(\mathrm d\bZ^{[1]})\trans\cdot\bone. \end{aligned}\end{split}\]一般地,若 \(\mathrm d\bZ^{[l]}\in\mathbb R^{n\times d^{[l]}}\),则 \((\mathrm d\bZ^{[l]})\trans\cdot\bA^{[l-1]}\) 为 \(d^{[l]}\times d^{[l-1]}\),与 \(\bW^{[l]}\) 同形;偏置梯度为 \(d^{[l]}\times1\)。若在 \(\mathrm d\bZ^{[2]}\) 中已经除以 \(n\),后续参数梯度不得再次除以 \(n\)。
批量前向公式的第 \((i,j)\) 个元素满足
\[[\bA^{[l-1]}\cdot(\bW^{[l]})\trans]_{ij}+b_j^{[l]} =\sum_{k=1}^{d^{[l-1]}}a_{ik}^{[l-1]}W_{jk}^{[l]}+b_j^{[l]},\]这正是第 \(i\) 个样本、第 \(j\) 个神经元的逐样本公式。对参数梯度同样有
\[[\mathrm d\bW^{[l]}]_{jk} =\frac1n\sum_{i=1}^n\delta_{ij}^{[l]}a_{ik}^{[l-1]},\]即逐样本外积 \(\boldsymbol\delta_i^{[l]}\cdot(\ba_i^{[l-1]})\trans\) 的平均值。这里把 \(1/n\) 写在式中;若它已包含在 \(\boldsymbol\delta_i^{[l]}\) 中,外侧不再出现。若循环计算期间更新了参数,不同样本便使用了不同模型,此时其平均值不再等于同一组参数处的批量梯度。
直接计算得到
\[\begin{split}\bZ= \begin{pmatrix}-0.5&3.5\\-0.5&-2.5\end{pmatrix}, \qquad \mathrm d\bW= \begin{pmatrix}-2&2\\-2&4\end{pmatrix}, \qquad \mathrm d\bb=\begin{pmatrix}4\\6\end{pmatrix},\end{split}\]以及
\[\begin{split}\mathrm d\bA= \begin{pmatrix}5&1\\11&1\end{pmatrix}.\end{split}\]\(\bZ\) 和 \(\mathrm d\bA\) 均为 \(2\times2\),\(\mathrm d\bW\) 与 \(\bW\) 同为 \(2\times2\),\(\mathrm d\bb\) 与 \(\bb\) 同为 \(2\times1\)。本题给定的 \(\mathrm d\bZ\) 可视为已经包含损失中所需的缩放,因此不再额外除以样本数。
以下骨架使用 sigmoid 隐藏层和二分类输出层:
import numpy as np def sigmoid(z): return np.exp(-np.logaddexp(0.0, -z)) def forward_backward(X, y, W1, b1, W2, b2): X, y = np.asarray(X, float), np.asarray(y, float) W1, b1 = np.asarray(W1, float), np.asarray(b1, float) W2 = np.asarray(W2, float) assert X.ndim == 2 and y.shape == (X.shape[0],) assert W1.shape == (b1.size, X.shape[1]) assert W2.shape == (1, b1.size) Z1 = X @ W1.T + b1.reshape(1, -1) A1 = sigmoid(Z1) z2 = (A1 @ W2.T).reshape(-1) + float(b2) p = sigmoid(z2) loss = np.mean(np.logaddexp(0.0, z2) - y * z2) dZ2 = (p - y) / X.shape[0] dW2 = dZ2[None, :] @ A1 db2 = dZ2.sum() dZ1 = (dZ2[:, None] @ W2) * A1 * (1.0 - A1) dW1 = dZ1.T @ X db1 = dZ1.sum(axis=0) cache = {"Z1": Z1, "A1": A1, "z2": z2, "p": p} grads = {"W1": dW1, "b1": db1, "W2": dW2, "b2": db2} assert dW1.shape == W1.shape and db1.shape == b1.shape assert dW2.shape == W2.shape and np.ndim(db2) == 0 return loss, cache, grads
提交时还应检查所有返回值是否有限。第 4 题对应的仿射层可单独写成
A @ W.T + b.reshape(1, -1),其后向三式分别为dZ.T @ A、dZ.sum(axis=0)和dZ @ W。循环版必须先在固定参数下完成全部样本的前向与后向传播,再对损失和梯度取平均。可按以下方式核验:
loop_loss = np.mean(sample_losses) loop_grads = { name: np.mean(np.stack([g[name] for g in sample_grads]), axis=0) for name in sample_grads[0] } np.testing.assert_allclose(loop_loss, batch_loss, rtol=1e-10, atol=1e-12) for name in loop_grads: np.testing.assert_allclose(loop_grads[name], batch_grads[name], rtol=1e-10, atol=1e-12)
用 \(n>1\) 且梯度非零的数据可发现重复除以 \(n\):错误结果恰为正确梯度的 \(1/n\)。偏置广播测试应选择 \(n\ne d^{[l]}\),并断言中间量维度;若恰有 \(n=d^{[l]}\),错误列向量可能在不报错的情况下产生错误结果。
单层仿射计算的主要运算量约为 \(O(ndh)\)。循环版与向量化版的渐近运算量相同,但向量化版可调用高度优化的矩阵乘法并减少解释器循环开销。计时应先预先运行,使用同一输入和数据类型重复多次,以中位数或分位数概括,并分开记录纯计算时间与数据准备时间。
向量化会保存大小约为 \(n\times h\) 的中间矩阵,批量很大时峰值内存可能高于逐样本处理。小问题的单次时间接近计时器分辨率,线程启动、缓存和系统调度会主导结果,因此不能依据一次计时判断优劣。只有在数值和维度核验通过后,效率比较才有意义。