线性回归、逻辑回归与优化基础:参考答案

目录

\[ \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\pi\sigma^2)^{-1/2}\exp\{-(y_i-\tilde{\bx}_i\trans\cdot\btheta)^2/(2\sigma^2)\}\)⁠。由误差项的独立性,联合似然函数为

    \[L(\btheta,\sigma^2) =(2\pi\sigma^2)^{-n/2} \exp\left\{-\frac{1}{2\sigma^2} \sum_{i=1}^n \left(y_i-\tilde{\bx}_i\trans\cdot\btheta\right)^2\right\}.\]

    因此,对数似然函数为

    \[\ell(\btheta,\sigma^2) =-\frac{n}{2}\log(2\pi\sigma^2) -\frac{n}{2\sigma^2}\mathcal{J}(\btheta).\]

    \(\sigma^2>0\) 固定时,第一项与 \(\btheta\) 无关,第二项中 \(n/(2\sigma^2)>0\)⁠,所以最大化 \(\ell(\btheta,\sigma^2)\) 与最小化 \(\mathcal{J}(\btheta)\) 得到相同的 \(\btheta\)⁠。若 \(\sigma^2\) 也未知,则固定 \(\btheta\) 后有 \(\widehat{\sigma}^2(\btheta)=\mathcal{J}(\btheta)\)⁠;当残差平方和为正时,将其代回对数似然所得的剖面对数似然随 \(\mathcal{J}(\btheta)\) 单调递减,因此关于 \(\btheta\) 的最优解仍是最小二乘解。

  2. 展开平方损失或直接使用矩阵微分可得

    \[\nabla_{\btheta}\mathcal{J}(\btheta) =\frac{2}{n}\bX\trans\cdot (\bX\cdot\btheta-\by), \qquad \boldsymbol{H}_{\mathcal{J}}(\btheta) =\frac{2}{n}\bX\trans\cdot\bX.\]

    对任意向量 \(\boldsymbol{v}\)⁠,有

    \[\boldsymbol{v}\trans\cdot \boldsymbol{H}_{\mathcal{J}}(\btheta)\cdot \boldsymbol{v} =\frac{2}{n} \lVert\bX\cdot\boldsymbol{v}\rVert_2^2 \geq0.\]

    因此 Hessian 矩阵半正定,\(\mathcal{J}(\btheta)\) 是凸函数。若 \(\bX\) 满列秩,则任意非零 \(\boldsymbol{v}\) 都满足 \(\bX\cdot\boldsymbol{v}\neq\boldsymbol{0}\)⁠,所以 Hessian 矩阵正定,损失函数严格凸并且最小值唯一。若 \(\bX\) 不满列秩,Hessian 矩阵仍然半正定,凸性仍然成立;但零空间中的不同参数可能给出相同预测,因此最小值不一定唯一。

  3. \(a_i(\btheta)=\sigma(\tilde{\bx}_i\trans\cdot\btheta)\)⁠。由 \(\sigma'(z)=\sigma(z)\{1-\sigma(z)\}\) 和链式法则可得 \(\partial a_i(\btheta)/\partial\btheta=a_i(\btheta)\{1-a_i(\btheta)\}\tilde{\bx}_i\)⁠。先对平均负对数似然求一次导数,再对梯度中的每一项求导,得到

    \[\begin{split}\begin{aligned} \nabla_{\btheta}\mathcal{J}(\btheta) &=\frac{1}{n}\sum_{i=1}^n \{a_i(\btheta)-y_i\}\tilde{\bx}_i,\\ \boldsymbol{H}_{\mathcal{J}}(\btheta) &=\frac{1}{n}\sum_{i=1}^n a_i(\btheta)\{1-a_i(\btheta)\} \tilde{\bx}_i\cdot\tilde{\bx}_i\trans\\ &=\frac{1}{n}\bX\trans\cdot \boldsymbol{R}(\btheta)\cdot\bX, \end{aligned}\end{split}\]

    其中,\(\boldsymbol{R}(\btheta)\) 是以 \(a_i(\btheta)\{1-a_i(\btheta)\}\) 为第 \(i\) 个对角元素的对角矩阵。对任意向量 \(\boldsymbol{v}\)⁠,有

    \[\boldsymbol{v}\trans\cdot \boldsymbol{H}_{\mathcal{J}}(\btheta)\cdot \boldsymbol{v} =\frac{1}{n}\sum_{i=1}^n a_i(\btheta)\{1-a_i(\btheta)\} (\tilde{\bx}_i\trans\cdot\boldsymbol{v})^2 \geq0.\]

    因此 Hessian 矩阵半正定,负对数似然是凸函数。参数 \(\btheta\) 取有限值时,\(0<a_i(\btheta)<1\)⁠,所以所有权重均为正;若 \(\bX\) 满列秩,则任意非零 \(\boldsymbol{v}\) 至少对应一个非零的 \(\tilde{\bx}_i\trans\cdot\boldsymbol{v}\)⁠,Hessian 矩阵正定,负对数似然严格凸。严格凸只能保证“若有限最小值存在,则它至多有一个”,不能单独保证最小值一定存在。数据完全分离时,可以沿某个参数方向不断增大参数范数,使负对数似然趋近其下确界却不在任何有限参数处达到,因此有限的最大似然估计可能不存在。

  4. 交叉熵非负,因为概率在 \((0,1)\) 内时对数不大于0;在标签确定的单样本情形,正确类别概率趋近1时损失下确界为0。 这段论证中的假设不能省略;删除假设时,应用一个最小反例说明结论在哪一步失效。

  5. 样本按行时 \(\bX=[\bx_1\trans;\ldots;\bx_n\trans]\in\mathbb{R}^{n\times d}\)⁠、\(\bw\in\mathbb{R}^{d}\)⁠、\(\bz=\bX\cdot\bw+b\bone\in\mathbb{R}^{n}\)⁠。进一步检查:矩阵乘法的内维必须相同,求和轴在结果中消失,广播轴长度只能为1或与目标轴相等,最终梯度必须与对应参数同形。

  6. 直接计算 log(sigmoid(z)) 会在线性运算结果的绝对值很大时下溢;把分类阈值固定为0.5也不一定符合代价不对称任务。 最小测试应只改变触发错误的一个条件,并以手算值、维度断言或性质不变量使失败可重复。

  7. 可以把截距并入设计矩阵的第一列,只使用 NumPy 完成计算。下面的二元交叉熵直接根据线性运算结果计算,避免先求概率后出现 \(\log(0)\)⁠;梯度下降返回每一步的损失,便于检查收敛过程。

    import numpy as np
    
    def sigmoid(z):
        z = np.asarray(z, dtype=float)
        out = np.empty_like(z)
        positive = z >= 0
        out[positive] = 1.0 / (1.0 + np.exp(-z[positive]))
        exp_z = np.exp(z[~positive])
        out[~positive] = exp_z / (1.0 + exp_z)
        return out
    
    def binary_cross_entropy_from_logits(y, z):
        y = np.asarray(y, dtype=float)
        z = np.asarray(z, dtype=float)
        if y.shape != z.shape:
            raise ValueError("y 和 z 的维度必须相同")
        if not np.all((y == 0) | (y == 1)):
            raise ValueError("y 只能包含 0 和 1")
        loss = np.maximum(z, 0.0) + np.log1p(np.exp(-np.abs(z))) - y * z
        return loss.mean()
    
    def logistic_gradient_descent(X, y, learning_rate=0.05, n_steps=5000):
        X = np.asarray(X, dtype=float)
        y = np.asarray(y, dtype=float)
        if X.ndim != 2 or y.ndim != 1 or X.shape[0] != y.size:
            raise ValueError("X 应为二维数组,且样本数必须与 y 的长度相同")
        if not np.all(np.isfinite(X)) or not np.all(np.isfinite(y)):
            raise ValueError("X 和 y 必须只包含有限数值")
        if not np.all((y == 0) | (y == 1)):
            raise ValueError("y 只能包含 0 和 1")
        if learning_rate <= 0 or n_steps <= 0:
            raise ValueError("学习率和迭代次数必须为正")
    
        X_design = np.column_stack((np.ones(X.shape[0]), X))
        theta = np.zeros(X_design.shape[1])
        loss_history = np.empty(n_steps)
    
        for step in range(n_steps):
            z = X_design @ theta
            p = sigmoid(z)
            gradient = X_design.T @ (p - y) / y.size
            theta -= learning_rate * gradient
            loss_history[step] = binary_cross_entropy_from_logits(
                y, X_design @ theta
            )
    
        return theta, loss_history
    

    核对时,应把“自行实现”和“库函数验证”分开:实现部分只依赖 NumPy验证部分可以使用 scipy.special.expit 检查 sigmoid 函数,使用 sklearn.metrics.log_loss 检查交叉熵,并使用不含正则项的 sklearn.linear_model.LogisticRegression 检查最终预测概率。示例为

    from scipy.special import expit
    from sklearn.metrics import log_loss
    
    z_test = np.array([-100.0, -2.0, 0.0, 2.0, 100.0])
    y_test = np.array([0.0, 0.0, 1.0, 1.0, 1.0])
    
    np.testing.assert_allclose(sigmoid(z_test), expit(z_test))
    np.testing.assert_allclose(
        binary_cross_entropy_from_logits(y_test, z_test),
        log_loss(y_test, expit(z_test), labels=[0, 1]),
    )
    

    比较逻辑回归时,应让两种实现使用相同的数据、截距设置和正则化设置。梯度下降充分收敛后,应重点比较预测概率、交叉熵和分类结果;由于停止条件和求解算法不同,参数不必逐位完全相同。

  8. sigmoid 函数采用正负分支计算,交叉熵根据线性运算结果并使用 logaddexp 直接计算;这样避免 exp 溢出和 log(0)Newton 步用线性方程求解并监控 Hessian 条件数。对 logaddexp 感兴趣的同学,请参见 logaddexp 的含义与提出背景⁠。

  9. 记目标成功概率为 \(\pi=\Pr(Y=1)\)⁠。可以选择 \(\pi\in\{0.2,0.5,0.8\}\) 和若干学习率,例如 \(\eta\in\{10^{-3},10^{-2},10^{-1}\}\)⁠。固定特征分布和真实斜率 \(\bw_0\)⁠,然后针对每个 \(\pi\) 调整截距 \(b_0\)⁠,使

    \[\frac{1}{n}\sum_{i=1}^{n} \sigma\!\left(b_0+\bx_i\trans\cdot\bw_0\right) \approx\pi,\]

    再按照 \(Y_i\sim\operatorname{Bernoulli}\{\sigma(b_0+\bx_i\trans\cdot\bw_0)\}\) 生成观测标签。这样只通过截距改变总体成功概率,同时保留特征与观测标签之间的关系。若直接令 \(Y_i\sim\operatorname{Bernoulli}(\pi)\) 且不依赖特征,则真实斜率为零,实验主要检验截距估计,必须在报告中明确说明。

    对每个成功概率和随机种子,先生成并保存一份训练集、验证集和测试集;比较不同学习率时必须复用完全相同的数据划分、参数初值和迭代次数。训练过程中保存 \(\btheta^{(t)}\)⁠、训练损失和梯度范数,由此绘制参数更新路径与损失曲线。学习率过小时路径通常移动缓慢,过大时可能振荡或发散;成功概率远离 \(0.5\) 时类别更不平衡,截距也会发生系统变化。

    测试集只在训练方案确定后评价一次。除测试交叉熵和准确率外,还应报告 Precision、Recall、F1 或 ROC-AUC,并给出多个固定随机种子下的均值与标准差。准确率会受到成功概率影响,因此不能只凭准确率比较不同 \(\pi\) 下的模型。最终结果表以学习率和成功概率为两个分组变量;其他设置保持不变,才能把参数路径和测试表现的差异归因于这两个研究因素。

  10. 每次蒙特卡洛模拟都应先生成一份数据,并让 Newton-Raphson 算法和所有学习率下的梯度下降法复用这份数据、同一个训练集与测试集划分以及同一个参数初值。两种算法分别执行 \(100\) 步参数更新,并在第 \(0,1,\ldots,100\) 步保存参数、训练损失和梯度范数。梯度下降法的更新为

    \[\btheta^{(t+1)} =\btheta^{(t)}-eta\nabla\mathcal{J}\!\left(\btheta^{(t)}\right),\]

    Newton-Raphson 算法则先通过线性方程

    \[\boldsymbol{H}_{\mathcal{J}}\!\left(\btheta^{(t)}\right) \cdot\boldsymbol{\Delta}^{(t)} =\nabla\mathcal{J}\!\left(\btheta^{(t)}\right),\]

    求出 Newton 方向,再令 \(\btheta^{(t+1)}=\btheta^{(t)}-\boldsymbol{\Delta}^{(t)}\)⁠。程序中应使用线性方程求解函数,不要显式计算 Hessian 矩阵的逆。

    计时只包括 \(100\) 步参数更新,不包括数据生成、绘图和结果写入。可先预运行一次,再重复计时若干次并报告中位数,同时报告每步平均时间。每次模拟至少记录最终训练损失、测试损失、参数误差 \(\lVert\btheta^{(100)}-\btheta_0\rVert_2\) 和是否出现无穷大或无效数值;多次蒙特卡洛模拟后报告这些量的均值与标准差。还应画出损失曲线和参数更新路径。只比较“都运行了 \(100\) 步”并不完全公平,因此可以同时报告达到同一目标损失所需的步数和时间。

    对梯度下降法可选择从小到大的学习率,例如 \(10^{-3}\)⁠、\(10^{-2}\)⁠、\(10^{-1}\)\(1\)⁠。较小学习率通常使损失稳定但下降缓慢;适中的学习率通常能在 \(100\) 步内取得较好的结果;过大的学习率可能使参数在最优点附近振荡,甚至导致损失上升或出现无效数值。这个结论依赖特征尺度,因此比较学习率时必须保持特征预处理方式不变。

    Newton-Raphson 算法利用 Hessian 矩阵的曲率信息,在参数维度较低且 Hessian 矩阵条件良好时通常需要较少的更新步数,对学习率也不敏感;但每一步都要构造 Hessian 矩阵并求解线性方程,时间和内存成本较高,在共线性、近完全分离或高维问题中还可能不稳定。梯度下降法每一步只需要梯度,单步计算和存储成本较低,更适合高维或大样本问题;其不足是收敛速度和更新路径对学习率及特征尺度较敏感。最终讨论应以实际记录的时间、损失和参数误差为依据,不能仅根据理论上的迭代次数判断哪种算法更好。