单样本单隐藏层网络:参考答案

目录

\[ \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} \]

单样本单隐藏层网络:参考答案#

返回正文练习 · 返回答案索引

说明#

以下答案与正文10道题逐题对应。推导题给出关键等式;编程题给出可扩展的代码骨架;实验题给出公平比较的要求与能够由实验支持的结论边界。

  1. 记隐藏层有 \(h=d^{[1]}\) 个神经元,则

    \[\begin{split}\begin{aligned} \bz^{[1]}&=\bW^{[1]}\cdot\bx+\bb^{[1]}, &\ba^{[1]}&=g^{[1]}(\bz^{[1]}),\\ z^{[2]}&=\bW^{[2]}\cdot\ba^{[1]}+b^{[2]}, &a^{[2]}&=\sigma(z^{[2]}). \end{aligned}\end{split}\]

    对 sigmoid 输出与二元交叉熵,链式法则给出

    \[\delta^{[2]}=\frac{\partial\mathcal J}{\partial z^{[2]}} =a^{[2]}-y, \qquad \boldsymbol\delta^{[1]} =(\bW^{[2]})\trans\delta^{[2]} \odot g^{[1]\prime}(\bz^{[1]}).\]

    因此

    \[\begin{split}\begin{aligned} \frac{\partial\mathcal J}{\partial\bW^{[2]}} &=\delta^{[2]}(\ba^{[1]})\trans, &\frac{\partial\mathcal J}{\partial b^{[2]}}&=\delta^{[2]},\\ \frac{\partial\mathcal J}{\partial\bW^{[1]}} &=\boldsymbol\delta^{[1]}\cdot\bx\trans, &\frac{\partial\mathcal J}{\partial\bb^{[1]}} &=\boldsymbol\delta^{[1]}, \end{aligned}\end{split}\]

    其中 \(\bW^{[1]}\in\mathbb R^{h\times d}\)⁠、\(\bb^{[1]},\bz^{[1]},\ba^{[1]},\boldsymbol\delta^{[1]}\in\mathbb R^h\)⁠、\(\bW^{[2]}\in\mathbb R^{1\times h}\)⁠,其余输出层量为标量。若先更新 \(\bW^{[2]}\) 再用新值计算 \(\boldsymbol\delta^{[1]}\)⁠,得到的四组梯度便不再对应同一个损失函数值处的梯度,因而不能视为一次标准的梯度下降更新。

  2. \(\bz^{[1]}=\bW^{[1]}\cdot\bx+\bb^{[1]}\) 可得 \(\mathrm d\bz^{[1]}=\mathrm d\bW^{[1]}\cdot\bx\)⁠。于是

    \[\begin{split}\begin{aligned} \mathrm d\mathcal J &=(\boldsymbol\delta^{[1]})\trans\cdot \mathrm d\bW^{[1]}\cdot\bx\\ &=\operatorname{tr}\!\left( \bx\cdot(\boldsymbol\delta^{[1]})\trans\cdot \mathrm d\bW^{[1]}\right) =\operatorname{tr}\!\left[ (\boldsymbol\delta^{[1]}\cdot\bx\trans)\trans\cdot \mathrm d\bW^{[1]}\right]. \end{aligned}\end{split}\]

    \(\mathrm d\mathcal J=\operatorname{tr}[(\partial\mathcal J/\partial\bW^{[1]})\trans\cdot\mathrm d\bW^{[1]}]\) 对照,即得所需结论。分量法也给出 \(\partial\mathcal J/\partial W_{jk}^{[1]}=\delta_j^{[1]}x_k\)⁠。外积 \(\boldsymbol\delta^{[1]}\cdot\bx\trans\) 的维度为 \(h\times d\)⁠;交换次序后得到 \(d\times h\)⁠,通常既不同形也代表不同的分量排列。

  3. 本题的前向计算结果为

    \[\begin{split}\begin{aligned} \bz^{[1]}&=(1,-1)\trans,\\ \ba^{[1]}&=(0.731059,0.268941)\trans,\\ z^{[2]}&=0.462117,\qquad a^{[2]}=0.613516,\qquad \mathcal J=0.488548. \end{aligned}\end{split}\]

    进一步有 \(\delta^{[2]}=-0.386484\)⁠,且

    \[\boldsymbol\delta^{[1]} =(-0.075987,0.075987)\trans.\]

    四组梯度为

    \[\begin{split}\begin{aligned} \frac{\partial\mathcal J}{\partial\bW^{[2]}} &=(-0.282542,-0.103941), &\frac{\partial\mathcal J}{\partial b^{[2]}}&=-0.386484,\\ \frac{\partial\mathcal J}{\partial\bW^{[1]}} &=\begin{pmatrix}-0.075987&0.075987\\0.075987&-0.075987\end{pmatrix}, &\frac{\partial\mathcal J}{\partial\bb^{[1]}} &=(-0.075987,0.075987)\trans. \end{aligned}\end{split}\]

    程序复核时应逐项比较上述中间量,而不能只比较最终损失;例如可使用 np.testing.assert_allclose 并将 rtolatol 设为 \(10^{-6}\) 量级。

  4. \(g^{[1]}(z)=z\) 时,

    \[a^{[2]} =\sigma\!\left[ \bW^{[2]}\cdot(\bW^{[1]}\cdot\bx+\bb^{[1]})+b^{[2]} \right] =\sigma(\bbeta\trans\cdot\bx+\beta_0),\]

    其中 \(\bbeta=(\bW^{[2]}\cdot\bW^{[1]})\trans\)⁠,\(\beta_0=\bW^{[2]}\cdot\bb^{[1]}+b^{[2]}\)⁠。所以两者的预测函数具有相同形式。但是,神经网络用多个矩阵的乘积表示 \(\bbeta\)⁠,同一个 \(\bbeta\) 往往对应多组网络参数;由此形成的参数优化问题通常是非凸的。逻辑回归关于 \((\bbeta,\beta_0)\) 的负对数似然则是凸函数。因此,预测函数等价并不保证训练路径、参数唯一性、正则化效果或计算成本相同。

  5. 以下代码给出单样本实现的核心骨架。这里直接根据 \(z^{[2]}\) 计算损失,避免先取接近 0 或 1 的概率再计算对数。

    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 = np.asarray(x, dtype=np.float64)
        W1 = np.asarray(W1, dtype=np.float64)
        b1 = np.asarray(b1, dtype=np.float64)
        W2 = np.asarray(W2, dtype=np.float64)
        assert x.ndim == 1
        assert W1.shape == (b1.size, x.size)
        assert W2.shape == (1, b1.size)
    
        z1 = W1 @ x + b1
        a1 = sigmoid(z1)
        z2 = (W2 @ a1).item() + float(b2)
        a2 = sigmoid(z2)
        loss = np.logaddexp(0.0, z2) - float(y) * z2
    
        delta2 = a2 - float(y)
        delta1 = (W2.reshape(-1) * delta2) * a1 * (1.0 - a1)
        grads = {
            "W1": delta1[:, None] * x[None, :],
            "b1": delta1,
            "W2": delta2 * a1[None, :],
            "b2": np.asarray(delta2),
        }
        cache = {"z1": z1, "a1": a1, "z2": z2, "a2": a2,
                 "delta1": delta1, "delta2": delta2}
        for name, value in {**cache, **grads}.items():
            assert np.all(np.isfinite(value)), name
        return loss, a2, cache, grads
    

    还应断言 grads["W1"].shape == W1.shapegrads["W2"].shape == W2.shape并使用第 3 题的全部中间值做回归测试。该实现只对应隐藏层使用 sigmoid 函数的情形;更换激活函数时必须同步更换 \(g^{[1]\prime}\)⁠。

  6. 设展平后的参数为 \(\btheta\)⁠,第 \(j\) 个分量的 中心差分

    \[\widehat g_j= \frac{\mathcal J(\btheta+\epsilon\be_j) -\mathcal J(\btheta-\epsilon\be_j)}{2\epsilon}.\]

    可按以下骨架核验:

    def finite_difference(loss_from_theta, theta, eps=1e-6):
        numeric = np.empty_like(theta, dtype=np.float64)
        for j in range(theta.size):
            plus, minus = theta.copy(), theta.copy()
            plus[j] += eps
            minus[j] -= eps
            numeric[j] = (loss_from_theta(plus) -
                          loss_from_theta(minus)) / (2.0 * eps)
        return numeric
    
    abs_err = np.max(np.abs(analytic - numeric))
    rel_err = (np.linalg.norm(analytic - numeric) /
               max(1e-12, np.linalg.norm(analytic) +
                            np.linalg.norm(numeric)))
    assert abs_err < 1e-6 and rel_err < 1e-6
    

    差分步长过大时,截断误差不可忽略;步长过小时,两次相近损失相减会放大浮点舍入误差。上述阈值适用于使用 float64参数规模较小且远离不可导点的例子,不应机械用于低精度或大规模模型。

  7. 若样本按行存放为 \(\bX=[\bx_1\trans;\ldots;\bx_n\trans]\in\mathbb R^{n\times d}\)⁠,则可写成

    \[\bZ^{[1]}=\bX\cdot(\bW^{[1]})\trans+\boldsymbol 1\cdot(\bb^{[1]})\trans, \qquad \bA^{[1]}=g^{[1]}(\bZ^{[1]}).\]

    对样本平均损失,批量梯度的核心实现为:

    Z1 = X @ W1.T + b1[None, :]
    A1 = sigmoid(Z1)
    z2 = (A1 @ W2.T).reshape(-1) + b2
    p = sigmoid(z2)
    delta2 = (p - y) / X.shape[0]
    delta1 = (delta2[:, None] @ W2) * A1 * (1.0 - A1)
    dW2 = delta2[None, :] @ A1
    db2 = delta2.sum()
    dW1 = delta1.T @ X
    db1 = delta1.sum(axis=0)
    

    测试时,将每个样本的单样本梯度相加后除以 \(n\)⁠,应与批量梯度在数值容差内一致。在一个很小且可学习的数据集上,可用较小学习率检查若干次更新后的损失总体下降,但不应要求每个随机小批量的损失都严格下降。数据尺度化所需的均值和标准差只能由训练集计算,再原样用于验证集和测试集;测试集不能用于选择学习率、停止轮数或分类阈值。

  8. 可以用线性模型生成第一组数据,用同心圆或双月形数据生成第二组数据。每个种子都应先生成一次数据划分,然后让两个模型共享该划分。尺度化参数只由训练集估计;超参数只根据验证集确定;测试集在方案确定后评价一次。训练时间应包含参数更新但排除数据生成,预测时间应在预先运行后对同一固定批量重复计时。参数量分别为 \(d+1\)\(h(d+2)+1\)⁠。

    结果表至少包含准确率、AUC、对数损失、训练时间、固定批量预测时间、参数量、峰值内存及多个种子的均值与标准差。通常,逻辑回归在线性数据上已能取得有竞争力的结果,而且参数更少、预测更快;具有非线性激活的隐藏层网络能够表示弯曲的决策边界,因而可能在非线性数据上更好。但这一方向不是必然结论:样本量、噪声、优化是否收敛以及超参数选择都会改变结果,实验只能支持所用数据分布和预算内的结论。

  9. 对输入维度为 \(d\)⁠、隐藏宽度为 \(h\) 的网络,参数量为

    \[hd+h+h+1=h(d+2)+1.\]

    因此参数存储量随 \(h\) 线性增加,主要矩阵乘法的训练和预测计算量也随 \(h\) 增加。实验可用一张表记录每个宽度在各个种子下的指标,再汇总均值与标准差;曲线横轴为 \(h\)⁠,纵轴可分别画验证集对数损失、测试集 AUC、训练时间和固定批量预测时间。

    很小的 \(h\) 可能因表达能力不足而欠拟合;增大 \(h\) 可能改善非线性边界的拟合,也可能扩大训练集与验证集之间的差距,并增加时间和内存。预测性能不必随宽度单调提高。若所有宽度使用相同更新步数,这是“固定更新预算”的比较;若使用相同运行时间,则是“固定时间预算”的比较。报告中必须说明采用哪一种,不能把二者混为一谈。

  10. 可令学习率取 \(10^{-4},10^{-3},10^{-2},10^{-1}\)⁠,并令第一层权重初始化标准差为 \(c/\sqrt d\)⁠,其中 \(c\in\{0.1,1,3\}\)⁠。对每个组合使用相同的数据划分、种子集合、隐藏宽度和更新次数,保存每轮损失及参数、梯度的范数。模型选择仍只依据验证集;确定组合后才报告测试指标。训练和预测计时及峰值内存应使用与第 8 题相同的测量方法。

    学习率过小时,有限训练预算内的参数变化和损失下降通常较小;学习率过大时,损失可能振荡、上升或出现无效数值。对 sigmoid 隐藏层,初始化尺度过大容易使 \(|z^{[1]}|\) 较大并进入导数接近 0 的饱和区;尺度过小虽可避免饱和,但若各层信号和梯度都过弱,也会减慢学习。不同隐藏神经元可以互换次序,因此不宜直接比较两次训练所得参数的逐元素差异;更有意义的是比较损失曲线、预测概率和任务指标。上述现象仍取决于数据尺度、优化器和训练预算,不能直接外推到其他设置。