岭回归
岭回归(Ridge Regression)是数学建模竞赛中处理「多重共线性」和「高维小样本」最常用的回归方法之一。它通过给最小二乘目标函数加一个 L2 惩罚项,牺牲少量偏差换取估计方差的大幅下降,从而得到更稳定、泛化能力更强的模型。本文从原理、适用场景、评价指标、可视化、完整代码到论文写作话术,一站式讲透岭回归。
一、算法含义
1.1 一句话理解
普通最小二乘(OLS)的解是 。当自变量之间存在近似线性关系(多重共线性)时,矩阵 接近奇异(行列式接近 0),求逆时会把数据中微小的噪声放大成巨大的系数波动——同一个模型换一批数据,系数可能从 2 变成 200,甚至符号反转。
岭回归的解决办法非常「暴力但优雅」:在 的对角线上统一加上一个正数 ,把它变成 。因为 是单位矩阵,对角线上每个位置都被「垫高」了 ,矩阵从此远离奇异、永远可逆,系数估计也随之稳定下来:
其中 称为岭参数(正则化强度), 是 单位矩阵。 越大,估计越稳定但偏差越大; 时退化为 OLS。这个「对角垫高」的操作,就是所谓的 L2 正则化(也叫 Tikhonov 正则化)。
1.2 目标函数的推导
岭回归的闭式解并不是凭空捏造的,它来自下面这个带惩罚的目标函数的最小化:
其中 是所有系数(不含截距)的平方和。直观理解:
- 第一项要求「拟合误差尽量小」——与 OLS 相同;
- 第二项要求「系数绝对值尽量小」——防止某些系数无节制地膨胀去拟合噪声;
- 是两者的「谈判筹码」: 大则偏向小系数, 小则偏向拟合精度。
对 求梯度并令其为 0:
整理得到岭回归的正规方程:
注意:惩罚项通常不包含截距项 ——截距只负责平移,不参与共线性,收缩它没有意义。实际做法是先把 和 中心化(或先标准化 ),再对不含截距的部分做岭回归。
1.3 的两个极端
- :目标函数与 OLS 完全相同,,即岭回归是 OLS 的推广。
- :惩罚项占据主导,最优解只能是 ,即所有系数被压缩到 0,模型退化为「只预测均值 」。
可以证明,系数向量的长度 随 单调递减(这就是「参数收缩 / shrinkage」名字的由来),而训练集残差平方和随 单调递增。因此 的选取本质上是偏差-方差权衡(bias-variance tradeoff):岭回归是有偏估计,但方差比 OLS 小,二者的总误差(MSE)在某个适中的 处达到最小。
1.4 为什么能解决共线性——条件数的视角
对 做特征分解 ,其中 , 是特征值。多重共线性意味着某个 非常接近 0(甚至等于 0,如 时)。求逆 要把 放大到无穷,这就是 OLS 不稳定的根源。
岭回归做的是 ,把所有特征值整体平移 ,最小的特征值从 变成 ,逆矩阵中的元素从 变成 ,不再爆炸。
用条件数(condition number)定量刻画矩阵的「病态程度」:
条件数衡量的是「解的相对误差最多被放大多少倍」。经验上:
- :共线性轻微,OLS 可用;
- :中等共线性,需要警惕;
- (甚至上千上万):严重共线性,OLS 的系数会剧烈波动,此时岭回归加一个很小的 就能把条件数降几个数量级。
1.5 与 OLS、Lasso 的对比
| 对比维度 | OLS 最小二乘 | 岭回归(L2) | Lasso(L1) |
|---|---|---|---|
| 目标函数 | |||
| 惩罚项 | 无 | ||
| 闭式解 | 无闭式解,需坐标下降等迭代算法 | ||
| 解的性质 | 无偏,方差大 | 有偏,方差小,系数收缩但不为零 | 有偏,能把系数压缩到恰好为 0 |
| 共线性 | 崩溃(系数爆炸) | 稳定 | 稳定,但可能随机丢掉某个共线变量 |
| 变量选择 | 无 | 无(所有变量都保留) | 有(自动稀疏化) |
| 时 | 无唯一解 | 可用 | 最多选出 个变量 |
几何直觉:L2 惩罚的可行域是圆(球),最优解通常落在圆周的「光滑」位置,系数一般不为 0;L1 惩罚的可行域是菱形,角点与等高线相切时系数恰好为 0,因此 Lasso 具有稀疏性。一句话区分:岭回归让系数变小,Lasso 让系数变没。
1.6 为什么必须先做数据标准化(量纲敏感)
岭回归的惩罚项 对所有系数一视同仁,但系数的大小与自变量的量纲直接相关。举例:同样一个变量,用「元」做单位时系数是 0.0003,用「万元」做单位时系数是 3——同样的惩罚力度 对两者的收缩效果天差地别。如果 各列量纲不同而不做标准化,岭回归会「无差别」地惩罚数值尺度大的变量对应的系数,收缩结果毫无意义。
因此使用岭回归的铁律:先对每个自变量做 z-score 标准化(均值 0、方差 1),再做岭回归。标准化后所有系数在同一把尺子上,惩罚才是公平的。注意两点:
- 标准化用的均值和标准差只能由训练集计算,测试集复用同一组统计量,否则会发生信息泄漏;
- 拟合完成后,若要解释原始量纲下的系数,需要按 反标准化还原( 为标准差),本文第六节代码给出了完整实现。
1.7 优缺点小结
优点
- 彻底解决 奇异/近奇异问题, 的高维小样本也能解出唯一解;
- 系数估计方差小、稳定性高,预测泛化能力通常优于 OLS;
- 闭式解简单,计算量与 OLS 同阶(配合 SVD 几乎零额外成本);
- 可用交叉验证/GCV 自动选取,无需人工调参技巧。
缺点
- 有偏估计,无法获得像 OLS 那样的标准 检验、 值等显著性推断结果;
- 系数只是收缩、不会变为 0,无法做变量选择,模型解释性下降;
- 结果对标准化非常敏感,忘记标准化会得到错误结论;
- 多了一个超参数 ,需要用 CV 等额外计算来确定。
二、何时使用(适用场景与条件)
2.1 适合用岭回归的情形
- 自变量存在多重共线性。判断信号:相关系数矩阵中某两个变量相关系数 ;方差膨胀因子 ( 是把 对其余自变量回归的拟合优度);设计矩阵条件数 。此时 OLS 系数会剧烈波动甚至符号异常。
- 高维小样本( 或 )。当变量个数超过样本量时, 奇异,OLS 根本无解;岭回归由于 永远可逆,依然能给出唯一稳定的解。
- 防止过拟合、提升预测泛化能力。变量多而样本少时,OLS 容易把噪声也「拟合」进去,岭回归通过收缩降低模型复杂度(有效自由度),测试集表现通常更好。
- OLS 系数符号与常识/理论矛盾时。例如理论上「收入对消费应有正向影响」,OLS 却估计出负系数,且该变量与其他变量高度相关——这就是共线性「搅浑水」的典型症状,换岭回归后符号往往恢复正常。
- 要求平滑稳定解的场合:需要预测结果随 连续变化、不出现突变(Lasso 的解路径是分段线性的,会有变量突然进出模型)。
2.2 数学建模竞赛中的典型场景
- 大量相关指标建模:经济类题目动辄十几个宏观指标(GDP、工业增加值、进出口……彼此高度相关),直接 OLS 必然共线性爆炸;
- 预测类题目(房价、销量、能源负荷、传染病传播等):预测精度优先于系数解释,岭回归 + 交叉验证是最稳的基线模型;
- 问卷/环境监测类:几十上百个特征但样本只有几十条(),岭回归几乎是唯一能直接跑通的线性方法;
- 稳定性展示型题目:论文中可用「OLS vs 岭回归」的对照实验展示「方法改进带来的稳定性提升」,是很好的亮点段落。
2.3 使用前提
- 必须先标准化(z-score),否则 的惩罚不公平,结论错误;
- 训练集标准化统计量必须用于测试集(禁止信息泄漏);
- 需要足够样本量做交叉验证来选 (一般 用 5 折 CV 比较稳);
- 的候选网格要覆盖「欠拟合到过拟合」的完整区间(典型做法:对数均匀网格
np.logspace(-4, 4, 100)或从 的量级出发定范围)。
2.4 不适用情形(考虑替代方法)
- 需要稀疏解/变量选择时:题目要求「找出关键影响因素」或论文需要精简的变量集 → 用 Lasso(L1 正则把无关变量系数压到 0)或弹性网;
- 需要显著性检验、置信区间: 值、 检验是 OLS 的专属,岭回归没有标准的显著性推断(需要 bootstrap 等额外手段)→ 若推断是硬需求,优先 OLS(先处理共线性,如删除变量/主成分);
- 共线性不严重且 :OLS 已经很好,岭回归引入偏差得不偿失;
- 需要绝对可解释的系数:岭收缩后的系数有偏,不能像 OLS 那样解释「每增加一单位 , 平均变化多少」(严格说 OLS 在共线性下也解释不了,但至少无偏)。
2.5 岭回归、Lasso、弹性网的选择速查
| 情形 | 推荐方法 | 理由 |
|---|---|---|
| 共线性严重,只做预测 | 岭回归 | 稳定、有闭式解、CV 易调 |
| 变量很多,想筛出关键变量 | Lasso | L1 惩罚自动稀疏化 |
| 变量多且高度分组相关,既要稳定又要筛选 | 弹性网 | L1+L2 混合惩罚,组内变量一起保留 |
| 且变量高度相关 | 弹性网(或岭) | 纯 Lasso 在 时最多选 个变量且选择不稳定 |
| 共线性不重、、要推断 | OLS | 无偏 + 完整显著性检验 |
弹性网的目标函数:,是岭与 Lasso 的凸组合,兼具二者优点。
三、算法指标
本节逐一介绍岭回归建模中最常用的 6 类指标。每个指标给出中文名、公式、取值范围和解读要点,最后汇总成表格。
3.1 交叉验证均方误差与均方根误差(CV-MSE / CV-RMSE)
中文名:交叉验证均方误差 / 交叉验证均方根误差。这是选择 的核心依据。
- 取值范围:,取值越小越好;与 同量纲的是 RMSE(所以 RMSE 更直观,如「RMSE=0.85 千元」)。
- 解读:对每个候选 ,用 K 折交叉验证(常用 或 10)计算验证集上的平均 RMSE,画出「CV 误差–λ」曲线,取 CV-RMSE 最小的 作为 。曲线的典型形状是 U 型(或先降后平): 太小 → 近似 OLS → 方差大、验证误差高; 太大 → 系数收缩过度 → 欠拟合、验证误差升高。
3.2 决定系数
中文名:决定系数(拟合优度)。
- 取值范围:。等于 1 为完美拟合,等于 0 表示模型和「只预测均值」一样,负值(测试集上可能出现)表示比预测均值还差。
- 解读:表示 的变异中被模型解释的比例。训练集 衡量拟合程度;测试集 衡量泛化能力。训练 高而测试 低 → 过拟合。岭回归通常会小幅牺牲训练 换取测试 的提升,这恰恰是「用偏差换方差」的体现。
3.3 有效自由度
中文名:有效自由度(effective degrees of freedom)。
其中 是岭回归的「帽子矩阵」(), 是 的特征值(等于 的奇异值平方)。
- 取值范围:。 时 (OLS 用满全部 个自由度,含截距则为 ); 时 。
- 解读:有效自由度是衡量模型复杂度的通用标尺,可以理解为「被惩罚掉的变量个数」。 越小模型越简单。它还有一个重要作用——GCV 和 AIC/BIC 等准则都要用到它。论文中写一句「在最优 处模型有效自由度从 降至 」,比单纯说「系数变小」专业得多。
3.4 广义交叉验证 GCV
中文名:广义交叉验证(Generalized Cross-Validation)。
- 取值范围:与 MSE 相同量级的正数,越小越好。
- 解读:GCV 是留一交叉验证(LOOCV)的解析近似,不需要真正做 次训练,借助奇异值分解 就能算出全部 网格上的值,计算量远小于 K 折 CV。取 GCV 最小的 作为 。小样本时 GCV 的选参结果通常与 5 折 CV 非常接近;sklearn 的
RidgeCV在传入 数组且不指定cv时, 默认使用高效的留一交叉验证(LOOCV)——GCV 正是 LOOCV 的解析近似。本文示例中 GCV 选出的 ,与 5 折 CV 的 非常接近(详见第六、七节)。
3.5 系数收缩幅度
中文名:系数收缩幅度(shrinkage ratio)。
- 取值范围:,越小表示收缩越强。
- 解读:直接度量岭回归把系数「压小了多少」。 表示系数整体长度只剩 OLS 的 60%。也可以逐系数看相对变化 ,那些被大幅压缩的系数通常正是共线性/噪声的载体。竞赛论文中「岭回归将异常膨胀的系数收缩至合理范围」的对比表就基于此指标。
3.6 条件数改善
中文名:条件数(condition number)及其改善倍数。
- 取值范围:,越接近 1 数值稳定性越好; 视为严重共线性。
- 解读:条件数衡量「扰动被放大的倍数」——若 ,数据里 1% 的噪声可能导致解变动约 1000%。岭回归把 抬到 ,条件数断崖式下降(如从 降到 ),这是「岭回归稳定」最硬核的量化证据。论文中给出「条件数从 X 降至 Y,改善 Z 倍」非常加分。
3.7 指标汇总表
| 指标(中文名) | 公式 | 取值范围 | 解读要点 |
|---|---|---|---|
| 交叉验证 RMSE | (验证集上) | 选 的核心依据,取 CV-RMSE 最小的 ;U 型曲线 | |
| 交叉验证 MSE | 与 RMSE 等价(平方关系),sklearn 内部常用 | ||
| 决定系数 | 训练/测试差距反映过拟合;岭回归以小幅牺牲训练 换测试 | ||
| 有效自由度 | 模型复杂度标尺;GCV/AIC/BIC 的输入 | ||
| 广义交叉验证 | LOOCV 的解析近似,计算极快,取最小值选 | ||
| 系数收缩幅度 | 量化「收缩」程度,越小收缩越强 | ||
| 条件数 | 严重共线性;加 后断崖式下降即「稳定性改善」 | ||
| 方差膨胀因子 VIF | 共线性诊断: 严重, 需警惕 |
四、可视化图表
岭回归相关的论文图几乎形成了一套固定模板。以下 4 张图是「标配」,每张图都有明确的论证任务。
4.1 四张标准图汇总
| 图名 | 用途 | 关键解读点 |
|---|---|---|
| ① 岭迹图(ridge trace) | 展示各系数随 的变化轨迹,论证「共线性导致系数不稳定,收缩后趋于平稳」 | 横轴取 刻度( 跨度多个数量级);(图左端)标注 OLS 系数(可用圆点标在左侧边缘);看系数从大幅波动/符号翻转逐渐收敛到平稳的「平台期」;用竖虚线标注 CV 选出的最优 位置 |
| ② CV 误差随 变化曲线 | 论证 的选取过程 | 横轴对数刻度;曲线呈 U 型(左端近似 OLS、方差大,右端欠拟合);用红点和箭头标注最小 CV-RMSE 对应的 ;若 U 型不明显说明 网格范围太窄,需向两端扩展 |
| ③ OLS 与岭回归系数对比柱状图 | 直观展示「收缩」效果 | 每组(每个变量)并排画 OLS 柱与岭回归柱(可加真实系数做参照);看 OLS 异常膨胀的柱被岭回归大幅压低到合理水平;若 OLS 柱高到压扁其他柱子,可用断轴(broken axis)或对数纵轴 |
| ④ 最优岭模型的实际-预测散点图 | 展示最终模型的预测精度 | 测试集样本画散点;画 对角线(完美预测线);点越贴近对角线预测越准;标题中标注测试集 与 RMSE;点系统性偏离对角线(如大值偏低)说明模型有偏或有非线性结构未捕捉 |
4.2 好图与异常图的特征
好图的特征
- 岭迹图:系数曲线随 增大「先剧烈波动、后平稳收敛」,平稳段对应合理的 区间; 虚线落在系数已基本稳定但尚未被过度压缩的区域;
- CV 曲线:存在清晰的最小值点,且最小值附近曲线较平缓(说明 的选择稳健,不敏感);
- 系数对比图:岭回归柱明显矮于 OLS 异常柱,且与真实系数(若有)接近;
- 预测散点:点均匀分布在对角线两侧,无明显喇叭口或弯曲趋势。
异常图的特征(出现即需要排查)
- 岭迹图全程单调发散或根本没有波动 → 数据没有共线性,用岭回归必要性存疑;
- CV 曲线单调递减且最小值在最左端 → 网格下界不够小,应把 网格向 0 扩展(此时模型可能根本不需要正则化);
- CV 曲线单调递增 → 网格上界不够大,或数据噪声极大;
- 预测散点整体偏离对角线 → 忘记了中心化/截距处理错误,或数据存在非线性结构;
- 系数对比图中岭回归柱仍很大 → 选得过小或标准化没做好。
五、符号说明
| 符号 | 含义 | 示例/单位 |
|---|---|---|
| 样本量(观测条数) | 150 条 | |
| 自变量(特征)个数 | 4 个特征 | |
| 设计矩阵, | 每行一个样本,每列一个特征 | |
| 因变量向量, | 目标变量观测值 | |
| 模型预测值向量 | 与 同量纲 | |
| 回归系数向量, | 各特征系数 | |
| OLS 估计的系数 | ||
| 岭回归估计的系数(随 变化) | ||
| 截距项 | 如 3.0(无单位) | |
| (sklearn 中记为 ) | 岭参数/正则化强度 | 0.01~1000,越大收缩越强 |
| 单位矩阵 | 对角线全 1 | |
| L2 范数(欧氏长度) | ||
| L1 范数 | ||
| 的特征值(= 奇异值的平方) | ||
| 矩阵条件数 | 无量纲, 严重共线性 | |
| 岭回归帽子矩阵 | ||
| 有效自由度 | ,无量纲 | |
| RSS | 残差平方和 | 与 同量纲 |
| TSS | 总平方和 | 与 同量纲 |
| 决定系数 | ,无量纲 | |
| RMSE / MSE | 均方根误差 / 均方误差 | 与 / 同量纲 |
| GCV | 广义交叉验证准则值 | 与 MSE 同量纲 |
| VIF | 第 个变量的方差膨胀因子 | 严重共线性 |
| 随机误差项 | , | |
| 第 列特征的标准差(标准化用) | 与 同量纲 | |
| 交叉验证折数 | 常取 5 或 10 |
六、可运行程序(完整代码)
运行环境:Python 3.12,仅依赖 numpy / scikit-learn / matplotlib / pandas(本机若未安装可用
pip install numpy scikit-learn matplotlib pandas安装)。以下所有代码块按顺序拼接保存为一个.py文件即可直接运行,无需任何外部数据文件——数据由np.random.seed(42)在脚本内生成。
数据设计说明:,4 个特征,其中 微小噪声(,人为制造强共线性),真实模型为 。由于 ,真实关系里 ,即 方向几乎不含信号,OLS 必然在该方向「拟合噪声」而出现系数爆炸/符号异常——这正是展示岭回归价值的最佳场景。
# -*- coding: utf-8 -*-
# ============================================================
# 岭回归(Ridge Regression)完整示例脚本
# 环境要求:Python 3.12 + numpy / scikit-learn / matplotlib / pandas
# 运行方式:python ridge_demo.py(首次运行会自动创建 figures/ 目录保存图片)
# 数据:np.random.seed(42) 生成的高共线性合成数据(无需任何外部文件)
# 内容:共线性诊断 → 手写岭回归(标准化 + 闭式解 + 岭迹 + K 折交叉验证选 λ)
# → sklearn Ridge/RidgeCV 对照 → 指标汇总 → 四张标准图
# ============================================================
import os
import numpy as np
import pandas as pd
import matplotlib
# ========== 0. 基础设置:matplotlib 后端 + 中文字体 ==========
# 先尝试交互式后端(本地运行 plt.show() 会弹出图像窗口);
# 若当前环境无图形界面(服务器 / CI / 沙箱),自动回退到 Agg 后端,
# 此时 plt.show() 为空操作,脚本照常保存图片、不会报错。
try:
import matplotlib.pyplot as _plt_probe
_plt_probe.figure() # 探测:能否真正创建图形窗口
_plt_probe.close("all")
except Exception:
matplotlib.use("Agg", force=True)
import matplotlib.pyplot as plt
from sklearn.model_selection import KFold
from sklearn.linear_model import Ridge, RidgeCV
from sklearn.metrics import r2_score, mean_squared_error
# 中文字体设置(macOS / Windows 常见字体,按顺序回退)
plt.rcParams["font.sans-serif"] = ["PingFang SC", "Arial Unicode MS", "SimHei"]
plt.rcParams["axes.unicode_minus"] = False # 正常显示负号
os.makedirs("figures", exist_ok=True) # 图片输出目录
# ========== 1. 生成高共线性合成数据 ==========
np.random.seed(42) # 固定随机种子,结果可复现
n = 150 # 样本量
x1 = np.random.randn(n)
x2 = 2.0 * x1 + 0.05 * np.random.randn(n) # x2 ≈ 2*x1 + 微小噪声 → 强共线性
x3 = np.random.randn(n)
x4 = np.random.randn(n)
eps = 0.8 * np.random.randn(n) # 误差项 ε
# 真实模型:y = 3 + 2*x1 - 1*x2 + 1.5*x3 + 0.5*x4 + ε
y = 3.0 + 2.0 * x1 - 1.0 * x2 + 1.5 * x3 + 0.5 * x4 + eps
X = np.column_stack([x1, x2, x3, x4]) # 设计矩阵 X:(150, 4)
feat = ["x1", "x2", "x3", "x4"]
beta_true = np.array([2.0, -1.0, 1.5, 0.5])
b0_true = 3.0
print("=" * 62)
print("【1】数据生成完成:n = %d,p = %d" % (X.shape[0], X.shape[1]))
print(" 真实模型:y = 3 + 2*x1 - 1*x2 + 1.5*x3 + 0.5*x4 + ε")
print(" x2 ≈ 2*x1 + 0.05*N(0,1),x1 与 x2 相关系数 = %.4f"
% np.corrcoef(x1, x2)[0, 1])
# ========== 2. 共线性诊断:相关系数矩阵 / 条件数 / VIF ==========
print("\n" + "=" * 62)
print("【2】共线性诊断")
print("特征相关系数矩阵:")
print(pd.DataFrame(np.corrcoef(X.T), index=feat, columns=feat).round(3))
# 条件数:κ(X'X) = (s_max/s_min)^2,其中 s 为 X(中心化后)的奇异值
_, S, _ = np.linalg.svd(X - X.mean(axis=0), full_matrices=False)
cond_ols = (S.max() / S.min()) ** 2
print("设计矩阵 X 的条件数 κ(X'X) = %.3e(远大于 30,存在严重共线性)" % cond_ols)
# 方差膨胀因子 VIF_j = 1/(1-R_j^2):VIF > 10 视为严重共线性
def vif(X):
v = np.zeros(X.shape[1])
for j in range(X.shape[1]):
others = [i for i in range(X.shape[1]) if i != j]
Xo = np.column_stack([np.ones(X.shape[0]), X[:, others]])
b, *_ = np.linalg.lstsq(Xo, X[:, j], rcond=None)
r2_j = 1 - np.sum((X[:, j] - Xo @ b) ** 2) / np.sum((X[:, j] - X[:, j].mean()) ** 2)
v[j] = 1 / (1 - r2_j)
return v
print("各特征方差膨胀因子 VIF:", dict(zip(feat, np.round(vif(X), 2).tolist())))
print(" → x1、x2 的 VIF 远大于 10,证实存在多重共线性,OLS 系数将极不稳定")
# ========== 3. 划分训练/测试集并做 z-score 标准化 ==========
rng = np.random.default_rng(7) # 独立的打乱生成器(不干扰数据生成种子)
idx = rng.permutation(n)
X, y = X[idx], y[idx]
X_train, X_test = X[:120], X[120:] # 训练集 120,测试集 30
y_train, y_test = y[:120], y[120:]
n_train, p = X_train.shape
n_test = X_test.shape[0]
# z-score 标准化(重要!岭回归对量纲敏感,必须先用训练集的均值/标准差标准化)
# 注意:均值、标准差只由训练集计算,测试集复用同一组统计量(避免信息泄漏)
mu = X_train.mean(axis=0) # 训练集每列均值
sg = X_train.std(axis=0, ddof=0) # 标准差(ddof=0 与 sklearn StandardScaler 一致)
X_train_s = (X_train - mu) / sg # 标准化训练特征
X_test_s = (X_test - mu) / sg # 标准化测试特征(复用训练统计量)
y_train_c = y_train - y_train.mean() # 中心化 y:去掉截距,岭惩罚只作用于斜率
print("\n" + "=" * 62)
print("【3】训练/测试划分与标准化完成(训练 %d / 测试 %d)" % (n_train, n_test))
# ========== 4. 手写 OLS 与手写岭回归闭式解 ==========
# OLS:β̂ = (X'X)^(-1) X'y(含截距列;用 lstsq 数值求解,避免直接求逆的不稳定性)
X_design = np.column_stack([np.ones(n_train), X_train]) # 第一列全 1 对应截距
beta_ols, *_ = np.linalg.lstsq(X_design, y_train, rcond=None)
def ridge_closed_form(Xs, yc, lam):
"""岭回归闭式解(标准化特征 + 中心化 y):
β̂_λ = (X'X + λI)^(-1) X'y
实际用 np.linalg.solve 解线性方程组 (X'X+λI)β = X'y,比显式求逆数值更稳定"""
A = Xs.T @ Xs + lam * np.eye(Xs.shape[1])
return np.linalg.solve(A, Xs.T @ yc)
def std2orig(beta_std, mu, sg, y_mean):
"""标准化空间系数 → 原始空间系数。
本例 y 只做中心化、未缩放,故 β_j^orig = β_j^std / s_xj,
截距 b0 = ȳ - Σ_j β_j^orig * x̄_j。返回 [截距, β1, ..., βp]"""
beta_orig = beta_std / sg
b0 = y_mean - np.sum(beta_orig * mu)
return np.append(b0, beta_orig)
# OLS 系数标准误:se = sqrt(diag(σ̂² (X'X)^(-1))),共线性下会非常大
resid_ols = y_train - X_design @ beta_ols
sigma2_hat = np.sum(resid_ols ** 2) / (n_train - p - 1)
se_ols = np.sqrt(np.diag(sigma2_hat * np.linalg.inv(X_design.T @ X_design)))
print("\n" + "=" * 62)
print("【4】OLS 在高共线性下的表现(注意 x1、x2 系数符号反转且标准误巨大)")
print(pd.DataFrame({
"特征": ["截距"] + feat,
"真值": [b0_true] + list(beta_true),
"OLS 估计": np.round(beta_ols, 3),
"OLS 标准误": np.round(se_ols, 3),
}).to_string(index=False))
# ========== 5. 手写岭迹:系数随 λ 的变化 + 有效自由度 + GCV ==========
alphas_trace = np.logspace(-4, 5, 100) # λ 网格:1e-4 ~ 1e5,对数均匀
trace = np.zeros((len(alphas_trace), p))
df_lam = np.zeros(len(alphas_trace))
gcv_lam = np.zeros(len(alphas_trace))
_, S, _ = np.linalg.svd(X_train_s, full_matrices=False) # 标准化训练矩阵的奇异值分解
d = S ** 2 # X'X 的特征值(= 奇异值平方)
for i, lam in enumerate(alphas_trace):
trace[i] = ridge_closed_form(X_train_s, y_train_c, lam) # 标准空间系数
df_lam[i] = np.sum(d / (d + lam)) # 有效自由度 df(λ) = tr(H_λ) = Σ d_j/(d_j+λ)
rss = np.sum((y_train_c - X_train_s @ trace[i]) ** 2)
gcv_lam[i] = (rss / n_train) / (1 - df_lam[i] / n_train) ** 2 # GCV(λ)
# OLS 的标准空间系数(λ→0 的极限),用于岭迹图左端标注
beta_ols_std, *_ = np.linalg.lstsq(X_train_s, y_train_c, rcond=None)
print("\n" + "=" * 62)
print("【5】岭迹计算完成:λ 网格共 %d 个点,同时得到 df(λ) 与 GCV(λ)" % len(alphas_trace))
# ========== 6. 手写 K 折交叉验证选 λ ==========
alphas_cv = np.logspace(-3, 3, 40) # CV 用的候选 λ 网格
k = 5 # 5 折交叉验证
kf = KFold(n_splits=k, shuffle=True, random_state=0)
cv_rmse = np.zeros(len(alphas_cv))
# 正确做法:每个折内部用「训练折」的均值/标准差标准化(避免信息泄漏)
for tr_idx, va_idx in kf.split(X_train):
mu_f = X_train[tr_idx].mean(axis=0)
sg_f = X_train[tr_idx].std(axis=0, ddof=0)
Xtr = (X_train[tr_idx] - mu_f) / sg_f
Xva = (X_train[va_idx] - mu_f) / sg_f
yc_f = y_train[tr_idx] - y_train[tr_idx].mean()
for j, lam in enumerate(alphas_cv):
b = ridge_closed_form(Xtr, yc_f, lam)
yp = Xva @ b + y_train[tr_idx].mean() # 验证集预测(加回训练折均值)
cv_rmse[j] += np.sqrt(np.mean((y_train[va_idx] - yp) ** 2))
cv_rmse /= k
lam_best = alphas_cv[np.argmin(cv_rmse)] # 最优 λ:CV-RMSE 最小
cv_rmse_min = cv_rmse.min()
print("\n" + "=" * 62)
print("【6】手写 %d 折交叉验证完成" % k)
print(" 最优 λ* = %.4f,对应 CV-RMSE = %.4f" % (lam_best, cv_rmse_min))
# ========== 7. 用最优 λ 拟合最终岭模型(手写)并评估 ==========
beta_ridge_std = ridge_closed_form(X_train_s, y_train_c, lam_best)
beta_ridge = std2orig(beta_ridge_std, mu, sg, y_train.mean()) # 还原到原始空间
b0_ridge, beta_ridge_coef = beta_ridge[0], beta_ridge[1:]
# 训练/测试集预测与 R²
y_pred_train = b0_ridge + X_train @ beta_ridge_coef
y_pred_test = b0_ridge + X_test @ beta_ridge_coef
r2_train = r2_score(y_train, y_pred_train)
r2_test = r2_score(y_test, y_pred_test)
rmse_test = np.sqrt(mean_squared_error(y_test, y_pred_test))
# 有效自由度、GCV 与条件数(最优 λ 处)
df_best = np.sum(d / (d + lam_best))
cond_ridge = (d.max() + lam_best) / (d.min() + lam_best)
gcv_best = gcv_lam[np.argmin(np.abs(alphas_trace - lam_best))] # 取 λ 网格最近点
lam_gcv = alphas_trace[np.argmin(gcv_lam)] # GCV 准则选出的 λ
# 系数收缩幅度(相对 OLS 的 L2 范数比)
shrink = np.linalg.norm(beta_ridge_coef) / np.linalg.norm(beta_ols[1:])
print("\n" + "=" * 62)
print("【7】手写岭回归最终模型(λ* = %.4f)" % lam_best)
print(" 训练集 R² = %.4f,测试集 R² = %.4f,测试集 RMSE = %.4f"
% (r2_train, r2_test, rmse_test))
print(" 有效自由度 df(λ*) = %.3f(OLS 为 p = %d)" % (df_best, p))
print(" GCV 准则选出的 λ = %.4f(与 5 折 CV 的 %.4f 接近)" % (lam_gcv, lam_best))
print(" GCV(λ*) ≈ %.4f" % gcv_best)
print(" 条件数 κ(X'X+λI) = %.3e(OLS 为 %.3e,改善 %.0f 倍)"
% (cond_ridge, cond_ols, cond_ols / cond_ridge))
print(" 系数收缩幅度 ‖β̂_ridge‖/‖β̂_OLS‖ = %.4f" % shrink)
# OLS 的测试集表现(对照,共线性下通常明显差于岭回归)
y_pred_test_ols = np.column_stack([np.ones(n_test), X_test]) @ beta_ols
r2_test_ols = r2_score(y_test, y_pred_test_ols)
rmse_test_ols = np.sqrt(mean_squared_error(y_test, y_pred_test_ols))
print(" 对照:OLS 测试集 R² = %.4f,RMSE = %.4f" % (r2_test_ols, rmse_test_ols))
# ========== 8. sklearn 对照:Ridge 与 RidgeCV ==========
# 与手写完全一致的口径:在同样的标准化特征、中心化 y 上拟合(fit_intercept=False)
# 注意:sklearn 的 Ridge 不会自动标准化,实际使用应配合 Pipeline(StandardScaler, Ridge)
ridge_sk = Ridge(alpha=lam_best, fit_intercept=False)
ridge_sk.fit(X_train_s, y_train_c)
beta_sk_std = ridge_sk.coef_
beta_sk = std2orig(beta_sk_std, mu, sg, y_train.mean())
# RidgeCV:与手写相同的 5 折划分、以 MSE 最小为准则自动选 α
ridge_cv = RidgeCV(alphas=alphas_cv, cv=kf, scoring="neg_mean_squared_error",
fit_intercept=False)
ridge_cv.fit(X_train_s, y_train_c)
lam_sk_best = ridge_cv.alpha_
print("\n" + "=" * 62)
print("【8】sklearn 对照结果")
print(" RidgeCV 选出的最优 α* = %.4f(手写 CV:%.4f)" % (lam_sk_best, lam_best))
print(" 说明:两者选出的 α 略有差异,是因为手写 CV 按『折内标准化』(与 sklearn")
print(" Pipeline + 交叉验证的严格做法一致),RidgeCV 则在全局标准化数据上")
print(" 做 CV;且 CV 曲线底部很平坦(两个 α 处 RMSE 仅差约 0.0002),")
print(" 因此选参结果略有不同但最终模型几乎相同(见下方验证)。")
print(" 系数一致性对照表(相同 λ* = %.4f 下):" % lam_best)
print(pd.DataFrame({
"特征": feat,
"手写岭回归": np.round(beta_ridge_coef, 4),
"sklearn Ridge": np.round(beta_sk[1:], 4),
"真值": beta_true,
}).to_string(index=False))
# 用 RidgeCV 选出的 α 重建模型,验证两种选参下的模型几乎相同
ridge_sk2 = Ridge(alpha=lam_sk_best, fit_intercept=False)
ridge_sk2.fit(X_train_s, y_train_c)
b_sk2 = std2orig(ridge_sk2.coef_, mu, sg, y_train.mean())
yp_sk2 = b_sk2[0] + X_test @ b_sk2[1:]
print(" 用 α=%.4f 重建模型:测试集 R² = %.4f(与 λ*=%.4f 时几乎相同)"
% (lam_sk_best, r2_score(y_test, yp_sk2), lam_best))
# ========== 9. 系数对比总表(OLS vs 岭 vs 真值) ==========
print("\n" + "=" * 62)
print("【9】OLS 与岭回归系数对比(真值参考)")
print(pd.DataFrame({
"特征": ["截距"] + feat,
"真值": [b0_true] + list(beta_true),
"OLS 估计": np.round(beta_ols, 3),
"OLS 标准误": np.round(se_ols, 3),
"岭回归估计": np.round(beta_ridge, 3),
}).to_string(index=False))
# ========== 10. 图 1:岭迹图(横轴 log10(λ),左端圆点标注 OLS 系数) ==========
fig, ax = plt.subplots(figsize=(8, 5.5))
colors = plt.cm.tab10(np.arange(p))
for j in range(p):
ax.plot(np.log10(alphas_trace), trace[:, j], color=colors[j], lw=1.8,
label="β_%s" % feat[j])
for j in range(p): # 左端圆点:λ→0 时的 OLS 系数(标准空间)
ax.plot(np.log10(alphas_trace[0]), beta_ols_std[j], "o", color=colors[j],
ms=7, mec="black", zorder=5)
ax.axvline(np.log10(lam_best), color="red", ls="--", lw=1.5,
label="最优 λ* = %.3f" % lam_best)
ax.set_xlabel("log10(λ)")
ax.set_ylabel("标准化系数 β̂(λ)")
ax.set_title("图1 岭迹图:各系数随 λ 的变化(左端圆点为 λ→0 时的 OLS 系数)")
ax.legend(ncol=2, fontsize=9)
ax.grid(alpha=0.3)
plt.tight_layout()
plt.savefig("figures/ridge_trace.png", dpi=200, bbox_inches="tight")
plt.show(block=False) # 非阻塞显示:本地运行会弹出窗口且脚本可继续执行
plt.pause(0.1)
# ========== 11. 图 2:CV 误差随 λ 的变化曲线(标注最优 λ*) ==========
fig, ax = plt.subplots(figsize=(8, 5.5))
ax.semilogx(alphas_cv, cv_rmse, "o-", color="#1f77b4", lw=1.8, ms=5,
label="%d 折 CV-RMSE" % k)
ax.axvline(lam_best, color="red", ls="--", lw=1.5)
ax.annotate("最优 λ* = %.3f\nCV-RMSE = %.4f" % (lam_best, cv_rmse_min),
xy=(lam_best, cv_rmse_min),
xytext=(lam_best * 8, cv_rmse.min() + 0.05),
fontsize=10, color="red",
arrowprops=dict(arrowstyle="->", color="red"))
ax.set_xscale("log")
ax.set_xlabel("λ(对数刻度)")
ax.set_ylabel("交叉验证 RMSE")
ax.set_title("图2 CV 误差曲线:λ 过大欠拟合、过小趋近 OLS,中间存在最优值")
ax.legend()
ax.grid(alpha=0.3)
plt.tight_layout()
plt.savefig("figures/ridge_cv_curve.png", dpi=200, bbox_inches="tight")
plt.show(block=False)
plt.pause(0.1)
# ========== 12. 图 3:OLS 与岭回归系数对比柱状图(展示收缩) ==========
xpos = np.arange(p)
width = 0.27
fig, ax = plt.subplots(figsize=(8, 5.5))
ax.bar(xpos - width, beta_ols[1:], width, label="OLS 系数",
color="#d62728", alpha=0.85)
ax.bar(xpos, beta_ridge_coef, width, label="岭回归系数(λ* = %.3f)" % lam_best,
color="#1f77b4", alpha=0.85)
ax.bar(xpos + width, beta_true, width, label="真实系数",
color="#2ca02c", alpha=0.85)
ax.axhline(0, color="black", lw=0.8)
ax.set_xticks(xpos)
ax.set_xticklabels(feat, fontsize=11)
ax.set_ylabel("系数估计值")
ax.set_title("图3 OLS 与岭回归系数对比:岭回归明显收缩 OLS 的异常系数")
ax.legend()
ax.grid(alpha=0.3, axis="y")
plt.tight_layout()
plt.savefig("figures/ridge_coef_compare.png", dpi=200, bbox_inches="tight")
plt.show(block=False)
plt.pause(0.1)
# ========== 13. 图 4:最优岭模型的实际-预测散点图 ==========
fig, ax = plt.subplots(figsize=(6.5, 6))
ax.scatter(y_test, y_pred_test, s=60, color="#1f77b4", alpha=0.8,
edgecolors="white", linewidths=0.6, label="测试集样本")
lims = [min(y_test.min(), y_pred_test.min()) - 0.5,
max(y_test.max(), y_pred_test.max()) + 0.5]
ax.plot(lims, lims, "r--", lw=1.5, label="y = ŷ(完美预测线)")
ax.set_xlim(lims)
ax.set_ylim(lims)
ax.set_xlabel("实际值 y")
ax.set_ylabel("预测值 ŷ")
ax.set_title("图4 最优岭模型(λ* = %.3f)测试集预测效果\nR² = %.4f,RMSE = %.4f"
% (lam_best, r2_test, rmse_test))
ax.legend()
ax.grid(alpha=0.3)
plt.tight_layout()
plt.savefig("figures/ridge_pred_scatter.png", dpi=200, bbox_inches="tight")
plt.show(block=False)
plt.pause(0.1)
print("\n" + "=" * 62)
print("【10】四张图已保存至 figures/ 目录:")
print(" figures/ridge_trace.png 岭迹图")
print(" figures/ridge_cv_curve.png CV 误差曲线")
print(" figures/ridge_coef_compare.png OLS 与岭系数对比")
print(" figures/ridge_pred_scatter.png 实际-预测散点图")
print("=" * 62)
代码要点速览:
- 手写实现:标准化(
mu/sg仅由训练集计算)→ 中心化 → 岭闭式解 (用np.linalg.solve数值求解)→ 反标准化还原系数(,截距 ); - 手写岭迹:对 对数网格逐个求解,同时用 SVD 计算有效自由度 与 GCV;
- 手写 K 折 CV:折内标准化(避免信息泄漏),以平均验证 RMSE 最小选 ;
- sklearn 对照:相同 下
Ridge与手写闭式解系数完全一致;RidgeCV在相同折划分上自动选 ,因标准化/中心化约定略不同而选出相近但不同的 ,重建模型后预测几乎相同(CV 曲线底部平坦所致,详见第七节解读)。
七、结果解读与注意事项
7.1 以第六节代码输出为例的完整解读
运行第六节代码(输出结果见 【1】~【10】 各节),按下面的思路把每个数字「翻译」成论文语言。
(1) 共线性诊断 → 论证用岭回归的必要性
- 相关系数矩阵:,几乎完全线性相关;
- VIF: 高达 1364(远大于 10 的警戒线), 约为 1(健康);
- 条件数 ,严重病态。
三个指标互相印证:数据存在极强的多重共线性,OLS 的估计将极不稳定。
(2) OLS 的表现 → 展示「系数爆炸/符号异常」
OLS 估计 的系数为 -2.003(真值 +2.0,符号反转), 为 +0.991(真值 -1.0,符号反转);两者的标准误高达 3.096 和 1.551,而 的标准误只有 0.078——相差约 20~40 倍。原因:,真实模型中 ,这个方向本来就没有信号,OLS 却在该方向「拟合噪声」,于是系数被噪声吹得面目全非。如果论文数据出现这种「系数符号与常识矛盾」的现象,就是共线性的铁证。
(3) 岭回归的表现 → 展示稳定性
- 5 折 CV 选出 ,CV-RMSE=0.828;
- 岭回归系数:、(从符号反转的异常值收缩到接近 0——该方向无信号,收缩到 0 恰恰是最优答案)、、(与真值 1.5、0.5 非常接近),截距 2.963(真值 3.0);
- 有效自由度从 4 降到 ,相当于「用掉约 3 个变量」;
- 条件数从 降到 ,改善约 60 倍;
- 系数收缩幅度 ;
- GCV 准则选出的 ,与 5 折 CV 的 1.70 接近,两种准则互相印证。
(4) 预测精度
本例中岭回归测试集 R²=0.671、RMSE=0.876,OLS 为 R²=0.667、RMSE=0.881——岭回归略优但差距不大。这并不意外:本例 OLS 虽然系数错得离谱,但它拟合出的 组合方向 ,预测上恰好「歪打正着」接近真实贡献(约为 0),而模型误差主要由 的方差主导,任何线性模型都无法再压缩。这说明岭回归的核心收益首先是系数稳定性,其次才是预测精度——在竞赛中若测试 RMSE 下降不明显,就重点展示系数/条件数/标准误的改善,这是同样有说服力的证据。
(5) 四张图的解读
- 图1 岭迹图:左端()圆点处 的 OLS 系数一个约 -2、一个约 +0.5(标准空间),符号与真值完全相反;随 增大,两条曲线迅速回落并收敛到 0 附近的平稳平台, 的曲线则几乎不动(它们没有共线性,不需要收缩)。红色虚线处 恰好落在「波动已平复」的区域——这就是好的岭迹图。
- 图2 CV 曲线:左端( 小)接近 OLS、误差偏高;右端( 大)系数被压光、欠拟合、误差飙升;中间 U 型底部即为 。注意底部非常平坦( 在 0.6~1.7 之间 RMSE 只差约 0.0002),说明最优 的选择并不敏感,模型稳健。
- 图3 系数对比柱状图:红色 OLS 柱在 上「反向倒挂」(-2.0、+1.0),蓝色岭柱被压到几乎看不见的高度(≈0),绿色真值柱则与蓝柱在 上基本齐平——一张图讲完「收缩」的故事。
- 图4 预测散点图:测试集 30 个点均匀分布在对角线两侧,无喇叭口、无弯曲,说明线性假设成立、残差近似同方差,R²=0.671 是可信的。
7.2 常见坑(务必避开)
- 忘记标准化。这是岭回归的头号错误:量纲不同时 惩罚不公平,CV 选出的 也失去意义。牢记「先标准化,再岭回归」,且标准化统计量只来自训练集。sklearn 的
Ridge本身不会自动标准化,务必使用Pipeline([StandardScaler(), Ridge()])或在外部手动标准化。 - 用测试集(或全量数据)选 。 只能在训练集内部用交叉验证/GCV 确定,测试集只能用来最后报告一次指标,否则 R²/RMSE 被乐观化,属于信息泄漏。
- 在 CV 中泄漏标准化统计量。正确做法是「折内标准化」(每个折只用训练折的均值/标准差),本文代码即如此;先在全量训练集上标准化再做 CV 虽然误差通常不大,但严格来说是泄漏。
- 把 sklearn
RidgeCV的 直接套用到未标准化数据。RidgeCV是在你传入的数据上选 的,若传的是原始量纲数据,选出的 只在那个量纲下有效;换标准化数据后要重新选。本文中手写 CV 与RidgeCV选出的 不同(1.70 vs 0.59)正是标准化/中心化约定不同所致,但两种模型预测几乎相同——选参不敏感是好事,不是 bug。 - 把岭回归当变量选择工具。岭回归的系数只会变小、不会变 0,特征一个不少。若题目要求「筛选关键变量」,请换 Lasso 或弹性网。
- 取得过大。 时所有系数 ,模型退化为「预测均值」,训练/测试 R² 都会崩。CV 曲线右端翘起就是在警告你欠拟合。
- 网格范围不当。CV 曲线若单调递减(最小值在最左端),说明网格下界不够小;单调递增则说明上界不够大。对数均匀网格
np.logspace(-4, 4, 100)是安全的起点。 - 把截距也放进惩罚。截距只做平移、不参与共线性,应通过中心化 (本文做法)或
fit_intercept=True(sklearn 自动不惩罚截距)处理,否则结果失真。 - 只看训练集 R²。共线性下训练 R² 具有欺骗性(OLS 可以完美拟合噪声方向),必须报告测试集指标与 CV 指标。
7.3 竞赛论文写作建议(话术模板)
论文中岭回归的段落建议「三步走」:诊断 → 建模 → 验证。直接可用的模板句:
诊断句:「计算得各自变量的方差膨胀因子 VIF 最大达 1364,设计矩阵条件数为 ,表明自变量间存在较强的多重共线性,若直接采用最小二乘估计,系数估计将极不稳定(本例中 OLS 给出的 系数为负,与理论预期相反),故采用岭回归建模。」
选参句:「以 5 折交叉验证均方根误差(CV-RMSE)最小为准则确定岭参数,得到 ,对应 CV-RMSE=0.828;广义交叉验证(GCV)准则给出的最优 ,两种准则结果接近,说明选参结果稳健。」
效果句:「岭回归后设计矩阵条件数由 降至 ,改善约 60 倍;有效自由度由 4 降至 2.99;各系数符号恢复正常、量级与理论预期一致,模型稳定性显著提升。测试集 R²=0.671、RMSE=0.876,预测精度优于 OLS。」
通用模板(预测类题目):「针对自变量间较强的多重共线性,采用岭回归建模,通过 5 折交叉验证确定 ,测试集 RMSE 较 OLS 下降 30%,显著提升模型稳定性。」
写作细节建议:① 系数对比表(OLS vs 岭 vs 理论预期)是性价比最高的表格,务必附上;② 岭迹图 + CV 曲线两张图足以讲清选参过程,放在「模型求解」小节;③ 若测试集精度提升不明显(如本文合成数据),不要硬吹精度,改打「稳定性」牌:标准误、条件数、符号合理性都是硬证据;④ 论文中 与 sklearn 参数名 是同一个量,行文中统一即可;⑤ 预测类题目建议再做 Lasso/弹性网对照实验,说明「为何选择岭回归」。
八、延伸阅读
- Lasso 回归(L1 正则化):把惩罚项换成 ,能得到稀疏解(部分系数恰好为 0),兼具回归与变量选择功能。没有闭式解,常用坐标下降求解(sklearn 的
Lasso/LassoCV)。当竞赛题目要求「找出关键影响因素」时,Lasso 是岭回归的自然替代。注意:共线性严重时 Lasso 会在相关变量中「随机」保留一个,选择结果不稳定。 - 弹性网(Elastic Net):目标函数 ,L1 与 L2 惩罚的凸组合。既有岭的稳定性(共线变量倾向于一起保留或一起剔除),又有 Lasso 的稀疏性,是高维共线数据下的首选正则化方法(sklearn 的
ElasticNet/ElasticNetCV,需要调 两个参数)。 - 主成分回归(PCR):先对 做主成分分析(PCA)降维,再用前 个主成分做 OLS。与岭回归同属「收缩型」方法,区别在于 PCR 是硬截断(小特征值方向直接丢掉),岭回归是软收缩(每个方向按 平滑衰减)。理论上岭回归的收缩方式通常更优,但 PCR 附带「主成分解释」的额外收获,论文叙事空间大。
- 偏最小二乘回归(PLSR):与 PCR 类似但构造成分时利用了 的信息(找与 协方差最大的方向),在 且自变量高度相关的化工/光谱/经济数据中很常用(sklearn 的
PLSRegression)。与岭回归的最大区别:PLSR 可以处理「多输出 」且天然降维。 - 广义交叉验证(GCV):,由 Craven & Wahba (1979) 提出,是 LOOCV 的旋转不变近似。借助 SVD 可以在 内对任意多 求值,比 K 折 CV 快得多,小样本下选参结果与 CV 接近。经典文献:Hoerl & Kennard (1970) Ridge Regression: Biased Estimation for Nonorthogonal Problems(岭回归开山之作);《统计学习导论》(ISLR)第 6 章对岭/Lasso/主成分回归有极好的入门讲解。