线性回归 (Linear Regression)

基本概念

利用 回归方程(函数) 对 一个或多个自变量(特征值)和因变量(目标值)之间 关系进行建模的一种分析方式。

目的

建立一个线性模型,用一个或多个自变量(特征)来预测一个连续型因变量(目标值)

假设

因变量 yyy 与自变量 x1,x2,...,xnx1,x2,...,xnx1,x2,...,xn 之间存在近似线性关系

模型形式

简单线性回归(一元)

y=β0+β1x+εy=β_0+β_1x+εy=β0+β1x+ε

  • β0β_0β0 :截距(intercept)
  • β1β_1β1 :斜率(slope)
  • εεε :误差项(随机噪声)
多元线性回归

y=β0+β1x1+β2x2+⋯+βpxp+εy=β_0+β_1x_1+β_2x_2+⋯+β_px_p+εy=β0+β1x1+β2x2++βpxp+ε

向量形式:

y=Xβ+εy=Xβ+εy=Xβ+ε

  • X∈Rn×(p+1)X∈\mathbb{R}^{n×(p+1)}XRn×(p+1) :设计矩阵(第一列为1,对应截距)
  • β∈Rp+1β∈\mathbb{R}^{p+1}βRp+1 :参数向量
  • y∈Rny∈\mathbb{R}^nyRn :观测值向量

参数估计方法

最小二乘法(Ordinary Least Squares, OLS)
目标

最小化残差平方和(RSS)
RSS=∑i=1n(yi−y^i)2=∥y−Xβ∥2 \text{RSS} = \sum_{i=1}^{n} (y_i - \hat{y}_i)^2 = \| \mathbf{y} - \mathbf{X}\boldsymbol{\beta} \|^2 RSS=i=1n(yiy^i)2=yXβ2
📌 左边∑i=1n(yi−y^i)2\sum_{i=1}^{n} (y_i - \hat{y}_i)^2i=1n(yiy^i)2

  • yiy_iyi :第iii个样本的真实观测值
  • y^i\hat{y}_iy^i:第iii 个样本的预测值
  • yi−y^iy_i - \hat{y}_iyiy^i:残差(误差)
  • 平方后求和 → 得到 残差平方和(Residual Sum of Squares, RSS)

⚠️ 注意:这里是对所有样本的残差平方求和,不取平均。

📌 右边∥y−Xβ∥2\| \mathbf{y} - \mathbf{X}\boldsymbol{\beta} \|^2yXβ2

  • yyy :真实值向量( n×1n×1n×1
  • XβXβXβ :预测值向量( n×1n×1n×1
  • y−Xβy−XβyXβ :残差向量
  • ∥⋅∥2∥⋅∥^22 :表示该向量的L₂ 范数的平方,即各分量平方和

👉 所以两边等价!

残差平方和(RSS) 是衡量模型拟合优劣的重要指标:

  • 越小越好:说明预测值与真实值越接近
  • 是最小二乘法的目标函数:我们希望找到 βββ,使得 RSS 最小
解析解(当 X⊤XX^⊤XXX 可逆时):

β^=(XTX)−1XTy \hat{\boldsymbol{\beta}} = (\mathbf{X}^T \mathbf{X})^{-1} \mathbf{X}^T \mathbf{y} β^=(XTX)1XTy

公式推导简要(通过求导):

  1. 定义损失函数:

    L(β)=(y−Xβ)T(y−Xβ)L(β)=(y−Xβ)^T(y−Xβ)L(β)=(yXβ)T(yXβ)

  2. βββ 求梯度并令其为 0:

    ∂L∂β=−2XT(y−Xβ)=0\frac{∂L}{∂β}=−2X^T(y−Xβ)=0βL=2XT(yXβ)=0

  3. 解得:

    XTy=XTXβ⇒β^=(XTX)−1XTyX^Ty=X^TXβ⇒\hat{β}=(X^TX)^{−1}X^TyXTy=XTXββ^=(XTX)1XTy

这个方程也叫 正规方程(Normal Equation)

优点
  • 计算简单(有解析解)
  • 理论成熟,统计性质清晰
  • 无需迭代,不像梯度下降那样需要调学习率
  • 在小数据集上表现稳定
缺点
  • p>np>np>n(特征比样本多)时, XTXX^TXXTX 不可逆 → 无法求解
  • 对异常值敏感(因为用的是平方误差)
  • 不能自动做特征选择
  • 高维时计算 (XTX)−1(X^TX)^{−1}(XTX)1 开销大( O(p3)O(p3)O(p3)

改进方法:岭回归(Ridge)、Lasso、梯度下降等。

代码sklearn
from sklearn.linear_model import LinearRegression
model = LinearRegression(fit_intercept=True)
model.fit(X[:, 1:], y)  # 自动加截距
print(model.coef_, model.intercept_)
梯度下降法(适用于大规模数据)

沿着梯度下降的方向求解 loss函数极小值从而获得模型最优的参数

目标

最小化均方误差(MSE)
MSE=1n∑i=1n(yi−y^i)2=1n∥y−Xβ∥2 \text{MSE} = \frac{1}{n}\sum_{i=1}^{n} (y_i - \hat{y}_i)^2 = \frac{1}{n}\| \mathbf{y} - \mathbf{X}\boldsymbol{\beta} \|^2 MSE=n1i=1n(yiy^i)2=n1yXβ2
我们要做的就是:找到最优参数 βββ,使得 MSE 最小。

迭代更新参数

作用:通过迭代优化参数
β(t+1)=β(t)−α∇βMSE \boldsymbol{\beta}^{(t+1)} = \boldsymbol{\beta}^{(t)} - \alpha \nabla_{\boldsymbol{\beta}} \text{MSE} β(t+1)=β(t)αβMSE

梯度下降思想:

  • 梯度:表示损失函数在当前点的变化方向和速度
  • 负梯度方向:是损失函数下降最快的方向
  • 迭代更新:每次沿着负梯度方向移动一小步

其中:

  • β(t)β(t)β(t) :第 ttt 次迭代的参数
  • ααα :学习率(step size),控制每一步的大小,过大容易造成错过最低点,易震荡,甚至梯度爆炸,过小收敛慢
  • ∇βMSE∇_βMSEβMSE :MSE 对 βββ 的梯度

参数更新过程(伪代码):

for t in range(max_iter):
    # 计算残差
    residuals = y - X @ beta
    
    # 计算梯度
    gradient = -2/n * X.T @ residuals
    
    # 更新参数
    beta = beta - alpha * gradient
梯度

作用:指示下降方向
∇βMSE=−2nXT(y−Xβ) \nabla_{\boldsymbol{\beta}} \text{MSE} = -\frac{2}{n} \mathbf{X}^T (\mathbf{y} - \mathbf{X}\boldsymbol{\beta}) βMSE=n2XT(yXβ)

梯度推导简要:

从 MSE 出发:

MSE=1n(y−Xβ)T(y−Xβ)\text{MSE} = \frac{1}{n} (y−Xβ)^T(y−Xβ)MSE=n1(yXβ)T(yXβ)

βββ 求偏导(使用矩阵微积分):

∇βMSE=−2nXT(y−Xβ)\nabla_{\boldsymbol{\beta}} \text{MSE} = -\frac{2}{n} \mathbf{X}^T (\mathbf{y} - \mathbf{X}\boldsymbol{\beta})βMSE=n2XT(yXβ)

👉 这个结果也可以理解为:

  • y−Xβy−XβyXβ 是残差向量
  • XTX^TXT 是设计矩阵的转置
  • 所以梯度是“残差加权后的特征贡献”
分类
  1. 批量梯度下降法(Batch Gradient Descent, BGD)

    原理

    • 每次更新参数时,使用整个训练数据集计算损失函数的梯度。
    • 梯度是所有样本梯度的平均值。

    更新公式

    θ:=θ−α⋅∇θJ(θ)=θ−α⋅1m∑i=1m∇θL(y(i),y^(i))\theta := \theta - \alpha \cdot \nabla_\theta J(\theta) = \theta - \alpha \cdot \frac{1}{m} \sum_{i=1}^{m} \nabla_\theta L\left(y^{(i)}, \hat{y}^{(i)}\right)θ:=θαθJ(θ)=θαm1i=1mθL(y(i),y^(i))

    其中:

    • mmm :训练样本总数
    • ααα :学习率
    • LLL :单个样本的损失函数

    优点

    • 梯度方向准确,收敛路径平滑
    • 理论上能稳定收敛到全局最小值(凸函数)或局部最小值(非凸)

    缺点

    • 计算开销大:每次都要遍历全部数据
    • 内存压力大:大数据集无法一次性加载进内存
    • 速度慢:尤其在大规模数据上不实用

    💡 工业界几乎不用纯 BGD,除非数据极小。

  2. 随机梯度下降法(Stochastic Gradient Descent, SGD)

    原理

    • 每次只用一个随机样本来估计梯度并立即更新参数。
    • “随机”体现在每次选哪个样本是随机的。

    更新公式

    θ:=θ−α⋅∇θL(y(i),y^(i))\theta := \theta - \alpha \cdot \nabla_\theta L\left(y^{(i)}, \hat{y}^{(i)}\right)θ:=θαθL(y(i),y^(i))

    其中 i∼Uniform(1,m)i∼Uniform(1,m)iUniform(1,m)

    优点

    • 计算快:每次只需处理一个样本
    • 内存友好:适合流式数据或超大数据集
    • 有“噪声”优势:有助于跳出鞍点和局部最优(尤其在深度学习中)

    缺点

    • 更新路径震荡剧烈(像“醉汉走路”)
    • 收敛不稳定,可能在最优解附近来回波动
    • 需要精心调整学习率衰减策略

    💡 虽然叫“随机”,但现代优化器(如 Adam)已很少直接用原始 SGD。

  3. 小批量梯度下降法(Mini-batch Gradient Descent, MBGD)

    原理

    • 折中方案:每次从数据集中随机抽取一小批(mini-batch)样本(如 32、64、128 个)来计算梯度。
    • 是目前最主流、最广泛使用的梯度下降变体

    更新公式

    θ:=θ−α⋅1b∑i∈B∇θL(y(i),y^(i))\theta := \theta - \alpha \cdot \frac{1}{b} \sum_{i \in \mathcal{B}} \nabla_\theta L\left(y^{(i)}, \hat{y}^{(i)}\right)θ:=θαb1iBθL(y(i),y^(i))

    其中:

    • BBB :当前 mini-batch(大小为 bb )
    • b≪mb≪mbm ,通常 b=32,64,128,256b=32,64,128,256b=32,64,128,256

    优点

    • 兼顾效率与稳定性:比 SGD 平稳,比 BGD 快
    • 充分利用 GPU 并行计算:矩阵运算加速明显
    • 适合大规模训练:是深度学习的标准做法

    缺点

    • 需要选择合适的 batch size(太小→噪声大;太大→接近 BGD)
    • 引入额外超参数(batch size)

    几乎所有深度学习框架(PyTorch/TensorFlow)默认使用 Mini-batch GD

    特性 批量 GD (BGD) 随机 GD (SGD) 小批量 GD (MBGD)
    每次用多少数据 全部 mmm 1 个 bbb个(如 64)
    梯度准确性 最高 最低(噪声大) 中等
    收敛稳定性 非常平稳 震荡剧烈 较平稳
    计算效率 低(每轮 O(m)) 高(每轮 O(1)) 高(GPU 并行)
    内存需求 极低 中等
    是否常用 ❌ 很少 ⚠️ 基础但少直接用 ✅ 工业标准

    “梯度越准越好?”——这是一个误区!

    • 虽然 BGD 梯度最准,但它容易陷入尖锐的局部最小值,泛化能力差。
    • 而 SGD/MBGD 的梯度噪声反而有助于找到平坦的最小值,提升模型泛化性能。

    现代优化器(如 Adam、RMSProp)都是基于 Mini-batch GD 的改进,加入了动量、自适应学习率等机制。

    总结

    批量梯度下降太慢,随机梯度下降太抖,小批量梯度下降刚刚好 —— 它是理论与实践的最佳平衡点。

代码sklearn

scikit-learn 不直接暴露“梯度下降”API,但部分模型内部使用它,并允许你控制优化方式:

SGDRegressor(显式使用随机梯度下降)

from sklearn.linear_model import SGDRegressor

model = SGDRegressor(
    learning_rate='constant',
    eta0=0.01,        # 初始学习率
    max_iter=1000,
    tol=1e-3
)
model.fit(X, y)

参数说明

参数 说明
learning_rate='constant' 学习率策略: - 'constant': 固定为 eta0 - 'optimal': 自适应(默认) - 'adaptive': 当 loss 停止下降时减小学习率
eta0=0.01 learning_rate='constant''adaptive' 时生效,即固定学习率值
max_iter=1000 最大迭代轮数(epochs)
tol=1e-3 早停阈值:若连续几轮 loss 下降小于 tol,则提前停止

⚠️ 重要提示SGDRegressor 对特征尺度敏感!必须进行标准化(StandardScaler)或归一化,否则可能不收敛。

对比
对比维度 梯度下降法(GD) 最小二乘法(OLS)
求解方式 迭代优化:逐步逼近最优解 解析求解:直接计算闭式解
是否需要迭代 ✅ 是(需设定迭代次数或收敛条件) ❌ 否(一步得出结果)
是否需要学习率 α ✅ 是(关键超参数,影响收敛速度和稳定性) ❌ 否
是否需要特征缩放 ✅ 是(如标准化/归一化,否则收敛慢或震荡) ❌ 否
时间复杂度 每次迭代 O(np)O(np)O(np) ,共 kkk 次 → O(knp)O(knp)O(knp)kkk 为迭代次数) O(p3)O(p3)O(p3)(主要来自$ (X{T}X){−1}$ 的矩阵求逆)
空间复杂度 O(np)O(np)O(np) O(np)O(np)O(np)
对异常值敏感度 中等(可通过鲁棒损失函数改进) 高(基于最小二乘,平方误差放大异常值影响)
大数据集( $n $大) ✅ 高效(尤其 mini-batch GD + GPU 加速) ⚠️ 可行但不高效(仍需全量数据;实际中常改用数值分解如 SVD,但本质仍是 OLS 思想)
高维特征( ppp 大) ✅ 适用(如 p=105p=10^5p=105 ❌ 不适用( p3p^3p3 计算不可行,且 XTXX^{T}XXTX 可能奇异)
是否要求 XTXX^{T}XXTX可逆 ❌ 不要求 ✅ 必须可逆(否则需用伪逆或正则化)
数值稳定性 较好(可通过学习率、动量等控制) 较差( XTXX^{T}XXTX易病态,实践中常用 SVD 或 QR 分解替代直接求逆)
扩展性 ✅ 极强:适用于逻辑回归、神经网络、SVM 等几乎所有可微模型 ❌ 仅适用于线性模型(且目标为最小化平方误差)
支持正则化 ✅ 容易加入 L1/L2 正则项(如 Lasso、Ridge) ⚠️ 标准 OLS 不含正则化;L2 正则(岭回归)有解析解,L1 正则(Lasso)无解析解
典型应用场景 大数据、深度学习、在线学习、非线性模型 小规模线性回归、教学演示、快速原型验证

模型评估指标

指标 公式 说明 单位 是否对异常值敏感 适用场景
MSE(均方误差) 1n∑i=1n(yi−y^i)2\frac{1}{n} \sum_{i=1}^{n} (y_i - \hat{y}_i)^2n1i=1n(yiy^i)2 越小越好;对异常值敏感 平方单位 ✅ 是 优化目标函数
RMSE(均方根误差) MSE\sqrt{\text{MSE}}MSE 与目标同单位;越小越好 与目标同单位 ✅ 是 报告误差大小
MAE(平均绝对误差) 1n∑i=1n∣yi−y^i∣\frac{1}{n} \sum_{i=1}^{n}\vert y_i - \hat{y}_i \vertn1i=1nyiy^i 对异常值鲁棒;越小越好 与目标同单位 ❌ 否 异常值多的数据
(决定系数) 1−∑(yi−y^i)2∑(yi−yˉ)21 - \frac{\sum (y_i - \hat{y}_i)^2}{\sum (y_i - \bar{y})^2}1(yiyˉ)2(yiy^i)2 越接近 1 越好;表示模型解释的方差比例 无量纲 ❌ 否 比较不同模型解释力
模型评估指标 VS loss函数
对比维度 损失函数(Loss Function) 评估指标(Evaluation Metric)
定义 模型在训练过程中用于优化参数的目标函数 训练后用于衡量模型性能的量化标准
目的 指导模型学习:通过最小化 loss 来更新参数 评价模型效果:判断模型是否“好用”
使用阶段 ✅ 训练阶段(每一步都计算) ✅ 验证/测试阶段(训练后评估)
是否参与参数更新 ✅ 是(通过梯度下降等优化算法) ❌ 否(仅用于观察和报告)
是否必须可导 ✅ 是(否则无法用梯度下降) ❌ 否(可以是任意合理指标,如准确率、F1)
典型例子 - MSE(回归) - Cross-Entropy(分类) - Huber Loss - RMSE、MAE(回归) - Accuracy、Precision、Recall、F1、AUC(分类)
是否必须与业务目标一致 ❌ 不一定(只要可导、能引导学习即可) ✅ 必须一致(直接反映业务价值)
能否自定义 ⚠️ 可以,但需保证可导、数值稳定 ✅ 非常灵活(如“预测误差 < 5% 的样本比例”)
对异常值敏感性 可设计(如用 MAE 替代 MSE) 根据业务需求选择(如用 MAE 而非 MSE)

关键区别:

  • Loss 是给模型“看”的(用于反向传播)
  • Metric 是给人“看”的(用于决策和比较)
案例

📌 案例 1:回归问题

  • Loss:MSE(因为可导,适合优化)
  • Metric:MAE 或 RMSE(更易解释,单位与目标一致)

💡 为什么不用 MAE 做 loss?
因为 ∣x∣∣x∣x 在 0 处不可导,梯度下降不稳定(尽管可用次梯度,但不如 MSE 平滑)。

📌 案例 2:分类问题(不平衡数据)

  • Loss:Cross-Entropy(标准、可导)
  • Metric:F1-score 或 AUC(因为 Accuracy 会误导)

💡 即使 loss 下降,Accuracy 可能不变(如多数类占 99%),所以必须用 F1 等指标评估。

📌 案例 3:业务目标 ≠ loss

  • 业务要求:“预测房价误差超过 10 万元的订单不能超过 5%”
  • Loss:仍可用 MSE(便于训练)
  • Metric:自定义指标 P(|y - ŷ| > 10万)

✅ 这时 loss 和 metric 完全不同,但这是合理的!

常见误区
误区 正确理解
“Loss 越小,模型越好” ❌ 不一定!可能过拟合,或 loss 与业务目标不一致
“评估指标也可以当 loss 用” ❌ 如果不可导(如 Accuracy),无法用于梯度下降
“Loss 和 Metric 应该一样” ❌ 通常不同:loss 重优化,metric 重解释和业务对齐

假设条件(OLS有效性前提)

  1. 线性性yyyxxx 线性相关
  2. 独立性:误差项相互独立(无自相关)
  3. 同方差性(Homoscedasticity):误差方差恒定
  4. 正态性:误差项服从正态分布(用于推断)
  5. 无多重共线性:自变量之间不高度相关

若违反假设,可能导致估计有偏、标准误不准、预测失效等。

线性性:模型结构正确
独立性:误差不互相影响
同方差性:误差波动稳定
正态性:支持统计推断
无多重共线性:参数估计稳定

假设 全称 是否影响无偏性? 是否影响有效性? 是否影响推断?
1. 线性性 Linearity ✅ 是 ✅ 是 ❌ 否
2. 独立性 No Autocorrelation ✅ 是 ✅ 是 ✅ 是
3. 同方差性 Homoscedasticity ❌ 否 ✅ 是 ✅ 是
4. 正态性 Normality ❌ 否 ❌ 否 ✅ 是
5. 无多重共线性 No Multicollinearity ❌ 否 ✅ 是 ❌ 否

应用建议

场景 推荐做法
做预测 关注 MSE、RMSE;可放宽正态性和同方差性
做因果推断 必须检查所有假设,尤其是独立性和无共线性
数据有时间趋势 检查自相关(Durbin-Watson)
特征高度相关 使用 VIF 或降维方法

线性回归API

普通最小二乘法(OLS)

无正则化,解析解(非梯度下降)

# 导入模型
from sklearn.linear_model import LinearRegression

# 创建模型对象
model = LinearRegression(
    fit_intercept=True,   # 是否计算截距(默认 True)
    copy_X=True,          # 是否复制 X(默认 True,避免修改原数据)
    n_jobs=None,          # 并行任务数(None 表示单线程)
    positive=False        # 是否强制系数为正(仅当 fit_intercept=False 时有效)
)

# 核心方法
model.fit(X, y)		# 训练模型(X: 特征矩阵, y: 目标向量)
model.predict(X)	# 预测输出
model.score(X, y)	# 返回 R² 决定系数

# 关键属性
print(model.coef_)		# 特征权重(斜率),形状 (n_features,)
print(model.intercept_)		# 截距(偏置项)

⚠️ 注意

  • 适用于中小规模数据(n_samples < 10^5
  • 不支持在线学习(不能 partial_fit
  • 对多重共线性敏感(可用 Ridge 替代)

正则化变种(推荐用于高维/共线性数据)

模型 API 特点
岭回归(L2 正则) Ridge(alpha=1.0) 稳定,适合共线性
Lasso(L1 正则) Lasso(alpha=0.1) 自动特征选择
from sklearn.linear_model import Ridge
model = Ridge(alpha=1.0)  # alpha 越大,正则越强
model.fit(X, y)
随机梯度下降

适合大数据

from sklearn.linear_model import SGDRegressor
model = SGDRegressor(
    loss='squared_error',  # 线性回归损失
    penalty='l2',          # 可选 'l1', 'elasticnet' 或 None
    alpha=0.0001,          # 正则强度
    learning_rate='constant',
    eta0=0.01,
    max_iter=1000
)
model.fit(X, y)

✅ 优势:支持大规模数据、在线学习(partial_fit)、自定义损失函数

波士顿房价预测
# 导包
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.datasets import fetch_openml
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score

# 从 OpenML 加载波士顿房价数据集
boston = fetch_openml(name="boston", version=1, as_frame=True)

# 转换为 DataFrame
df = boston.frame  # 包含特征和目标
# 或手动组合:
# X = pd.DataFrame(boston.data, columns=boston.feature_names)
# y = boston.target
# df = pd.concat([X, y], axis=1)

# 数据探索(可选但推荐)
print("数据形状:", df.shape)        # (506, 14)
print("\n前5行:\n", df.head())
print("\n目标变量统计:\n", df['MEDV'].describe())
# 查看相关性热力图
plt.figure(figsize=(10, 8))
sns.heatmap(df.corr(), annot=True, fmt=".2f", cmap='coolwarm')
plt.title("特征相关性热力图")
plt.show()

# 准备特征与标签
X = df.drop('MEDV', axis=1)  # 13个特征
y = df['MEDV']               # 目标:房价

# 划分训练集与测试集
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)

#(可选)特征标准化
'''
线性回归不需要标准化也能工作,但标准化后:
	系数值更易比较(特征重要性)
	某些优化算法(如梯度下降)收敛更快
注意:若使用 LinearRegression()(解析解),标准化不影响预测结果,只影响 coef_ 的值。
'''
#scaler = StandardScaler()
#X_train_scaled = #scaler.fit_transform(X_train)
#X_test_scaled = scaler.transform(X_test)

# 训练模型
# 方式1:不标准化(直接使用原始数据)
model = LinearRegression()
model.fit(X_train, y_train)

# 方式2:使用标准化数据(结果相同,但 coef_ 不同)
# model.fit(X_train_scaled, y_train)

# 模型预测与评估
# 预测
y_pred = model.predict(X_test)

# 计算评估指标
mse = mean_squared_error(y_test, y_pred)
rmse = np.sqrt(mse)
mae = mean_absolute_error(y_test, y_pred)
r2 = r2_score(y_test, y_pred)

print(f"均方误差 (MSE): {mse:.2f}")
print(f"均方根误差 (RMSE): {rmse:.2f}")
print(f"平均绝对误差 (MAE): {mae:.2f}")
print(f"决定系数 (R²): {r2:.4f}")

# 可视化预测效果
# 实际 vs 预测
plt.figure(figsize=(8, 6))
plt.scatter(y_test, y_pred, alpha=0.7)
plt.plot([y_test.min(), y_test.max()], [y_test.min(), y_test.max()], 'r--', lw=2)
plt.xlabel("实际房价 (千美元)")
plt.ylabel("预测房价 (千美元)")
plt.title("实际 vs 预测房价")
plt.show()

# 查看特征重要性(系数)
# 创建系数 DataFrame
coef_df = pd.DataFrame({
    'Feature': X.columns,
    'Coefficient': model.coef_
}).sort_values('Coefficient', key=abs, ascending=False)

print("\n特征系数(按绝对值排序):")
print(coef_df)

# 可视化
plt.figure(figsize=(10, 6))
sns.barplot(data=coef_df, x='Coefficient', y='Feature')
plt.title("线性回归特征系数")
plt.show()

关键结果解读

均方误差 (MSE): 21.89
均方根误差 (RMSE): 4.68
平均绝对误差 (MAE): 3.33
决定系数 (R²): 0.7406

R² ≈ 0.74:模型解释了约 74% 的房价方差,效果良好。
RMSE ≈ 4.68 千美元:平均预测误差约 4680 美元。
最重要特征:通常为 RM(房间数,正相关)和 LSTAT(低收入比例,负相关)。

数据集可用性

  • fetch_openml 失败,可手动下载 CSV 并用 pd.read_csv() 加载。
  • 替代数据集:California housing(sklearn 内置,无伦理问题)。

为什么不用标准化?

  • LinearRegression() 使用正规方程(解析解),标准化不改变预测结果,仅改变系数解释。

改进方向

  • 添加多项式特征(PolynomialFeatures
  • 使用正则化(Ridge/Lasso)
  • 特征工程(处理非线性关系)

正则化线性回归(防止过拟合)

欠拟合

模型太简单,无法捕捉数据中的基本模式

表现

  • 训练误差高
  • 测试误差高
  • 高偏差(Bias)

代码展示欠拟合

数据是抛物线非线性的,用线性模型去拟合。模型过于简单,出现欠拟合。

import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error


def load_under_fitting():
    np.random.seed(66)
    """
    函数: np.random.uniform(low, high, size)
    该函数从均匀分布 (Uniform Distribution) 中采样。
    这意味着在指定范围内,任何值被选中的概率都是相等的。
    参数:
        low=-3.0: 生成随机数的下界(包含)。
        high=3.0: 生成随机数的上界(不包含)。
        size=100: 生成的随机数的数量。
        
    
    函数: np.random.normal(loc, scale, size)
    该函数从正态分布 (Normal/Gaussian Distribution) 中采样。
    它使得 y 的值不会完美地落在抛物线上,而是在其上下波动
    参数:
    size=100: 生成 100 个随机噪声值,与 x 的数量对应。
    loc (均值) 和 scale (标准差) 未指定,因此使用默认值 loc=0.0 和 scale=1.0。
    这被称为标准正态分布。
    """
    # 1.准备数据x,y(增加噪声)
    x = np.random.uniform(-3.0, 3.0, size=100)
    y = 0.5*x**2+x+2+np.random.normal(size=100)

    # 2.实例化线性回归模型
    estimator = LinearRegression()
    # 3.训练模型
    """
    array.reshape(-1, 1):
    第一个参数 -1:代表“自动计算”。
    它告诉程序:“这个维度的大小我不知道,你根据数据总数和另一个维度的大小自己算出来。”
    第二个参数 1:代表“显式指定”。
    它告诉程序:“这个维度的大小必须是 1。”
    关键点: 只要你传递了两个参数(即使其中一个是 -1),生成的数组就必然拥有两个维度。
    """
    # 变成二维向量
    X = x.reshape(-1, 1)
    estimator.fit(X, y)

    # 4.模型预测
    y_predict = estimator.predict(X)

    # 5.模型评估
    mse = mean_squared_error(y, y_predict)
    print(f"均方误差 (MSE): {mse:.2f}")

    # 6.可视化
    plt.scatter(x, y)
    plt.plot(x, y_predict, color='r')
    plt.show()


load_under_fitting()

在这里插入图片描述

过拟合

模型太复杂,记住了训练数据的噪声和细节

表现:

  • 训练误差很低
  • 测试误差很高
  • 高方差(Variance)

理想状态:模型在训练集和测试集上都表现良好(低偏差 + 低方差)

代码展示过拟合

数据是抛物线形状的,给模型送入的数据,增加x2、x3、x4 …高次项特征,再用线性模型去拟合。模型过于复杂,出现过拟合。

import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error


def load_under_fitting():
    np.random.seed(66)

    # 1.准备数据x,y(增加噪声)
    x = np.random.uniform(-3.0, 3.0, size=100)
    y = 0.5 * x ** 2 + x + 2 + np.random.normal(size=100)

    # 2.实例化线性回归模型
    estimator = LinearRegression()
    # 3.训练模型
    # 变成二维向量
    X = x.reshape(-1, 1)
    """
    
    """
    # 数据增加高次项
    X2 = np.hstack([X, X ** 2, X**3, X**4, X**5, X**6, X**7, X**8, X**9, X**10])
    estimator.fit(X2, y)

    # 4.模型预测
    y_predict = estimator.predict(X2)

    # 5.模型评估
    mse = mean_squared_error(y, y_predict)
    print(f"均方误差 (MSE): {mse:.2f}")

    # 6.可视化
    plt.scatter(x, y)
    # np.sort(array) —— 排序后的数据
    # np.argsort(array) —— 排序后的索引
    # 因为 x 是乱序的,线会在图上左右来回穿梭,变成一团乱麻,根本看不出函数的形状, 所以这里对x排序
    plt.plot(np.sort(x), y_predict[np.argsort(x)], color='r')
    plt.show()


load_under_fitting()

在这里插入图片描述

📌 数据增加次项

# 数据增加次项

# 1.使用Scikit - Learn的PolynomialFeatures(最推荐,工业界标准)
from sklearn.preprocessing import PolynomialFeatures
# 假设 X 已经是 (100, 1) 的二维数组
X = x.reshape(-1, 1)
# 初始化转换器:degree=2 表示生成直到二次项的所有组合
poly = PolynomialFeatures(degree=2, include_bias=False)
# 转换数据
X_poly = poly.fit_transform(X)

# 2.使用NumPy的column_stack(比hstack语义更清晰)
import numpy as np
X = x.reshape(-1, 1)
# column_stack 专门用于将一维数组作为列堆叠成二维数组
# 这里我们显式地列出需要的列:[原始列, 平方列]
X2 = np.column_stack([X, X ** 2])
# 或者如果你想加三次项:
X3 = np.column_stack([X, X ** 2, X ** 3])

# 3.直接数学构造(针对单变量特定场景)
# x 是一维数组 (100,)
# 直接构造一个 (100, 2) 的数组
# 第一列是 x,第二列是 x**2
X2 = np.vstack((x, x ** 2)).T
# .T 是转置,因为 vstack 默认是竖着堆叠变成 (2, 100),转置后变成 (100, 2)

# 4.Pandas方式(适合数据分析流程)
import pandas as pd
df = pd.DataFrame({'x': x})
# 直接新增一列
df['x_squared'] = df['x'] ** 2
# 提取特征矩阵
X2 = df[['x', 'x_squared']].values
方法 欠拟合 过拟合
训练误差 vs 测试误差 两者都高 训练误差 << 测试误差
学习曲线 两条曲线收敛于高误差 两条曲线 gap 很大
模型复杂度 vs 误差 增加复杂度可降低误差 增加复杂度使测试误差上升
如何应对欠拟合,过拟合
问题 解决方案
欠拟合 - 增加模型复杂度(如添加多项式特征)
- 减少正则化强度
- 使用更强大的模型(如树模型、神经网络)
过拟合 - 增加正则化(Ridge/Lasso)
- 减少特征数量
- 增加训练数据
- 使用交叉验证调参

💡 正则化是解决过拟合最常用、最有效的方法之一

Lasso 回归(Least Absolute Shrinkage and Selection Operator)—— L1 正则化

📌 损失函数
JLasso(β)=MSE(β)+λ∑i=1n∣wi∣ J_{\text{Lasso}}(\boldsymbol{\beta}) = \text{MSE}(\boldsymbol{\beta}) + \lambda \sum_{i=1}^{n} |w_i| JLasso(β)=MSE(β)+λi=1nwi

  • λ∑i=1n∣wi∣\lambda \sum_{i=1}^{n} |w_i|λi=1nwiL1 正则化项,用于防止过拟合并实现特征选择。
  • λ\lambdaλ:正则化强度超参数(惩罚系数),该值越大则权重调整的幅度就越大
  • nnn:特征数量

💡 Lasso 的关键特性:由于 L1 正则化的“尖角”性质,它可以使某些系数精确为零,从而实现自动特征选择

L1的权重更新公式

β(t+1)=β(t)−α∇βMSE\boldsymbol{\beta}^{(t+1)} = \boldsymbol{\beta}^{(t)} - \alpha \nabla_{\boldsymbol{\beta}} \text{MSE}β(t+1)=β(t)αβMSE

损失函数对参数β\betaβ求导:

∇βJLasso=∇βMSE+λ∗sign(β)\nabla_{\boldsymbol{\beta}} J_{\text{Lasso}}=\nabla_{\boldsymbol{\beta}} \text{MSE} + \lambda*sign(\beta)βJLasso=βMSE+λsign(β)

权重更新为:

β(t+1)=β(t)−α∇βMSE−αλ∗sign(β(t))\boldsymbol{\beta}^{(t+1)} = \boldsymbol{\beta}^{(t)} - \alpha \nabla_{\boldsymbol{\beta}} \text{MSE}-\alpha \lambda*sign(\beta^{(t)})β(t+1)=β(t)αβMSEαλsign(β(t))

无论β\betaβ的值图和更新,此项为αλ∗sign(β(t))\alpha \lambda*sign(\beta^{(t)})αλsign(β(t))为恒定值,将β(t+1)\boldsymbol{\beta}^{(t+1)}β(t+1)拖向0

岭回归(Ridge Regression)—— L2 正则化

📌 损失函数
JLasso(β)=MSE(β)+λ∑i=1nwi2 J_{\text{Lasso}}(\boldsymbol{\beta}) = \text{MSE}(\boldsymbol{\beta}) + \lambda \sum_{i=1}^{n} {w_i}^2 JLasso(β)=MSE(β)+λi=1nwi2

  • ∑i=1nwi2\sum_{i=1}^{n} {w_i}^2i=1nwi2L2 范数的平方,即所有系数平方和
  • λ\lambdaλ:正则化强度超参数(惩罚系数),该值越大则权重调整的幅度就越大。
  • nnn:特征数量

控制模型复杂度,抑制过拟合,但不产生稀疏性(即不会让某些系数精确为零)

L2的权重更新公式

β(t+1)=β(t)−α∇βMSE\boldsymbol{\beta}^{(t+1)} = \boldsymbol{\beta}^{(t)} - \alpha \nabla_{\boldsymbol{\beta}} \text{MSE}β(t+1)=β(t)αβMSE

损失函数对参数β\betaβ求导:

∇βJLasso=∇βMSE+2λ∗β\nabla_{\boldsymbol{\beta}} J_{\text{Lasso}}=\nabla_{\boldsymbol{\beta}} \text{MSE} + 2\lambda* \betaβJLasso=βMSE+2λβ

权重更新为:

β(t+1)=β(t)−α(∇βMSE−2λ∗β(t))\boldsymbol{\beta}^{(t+1)} = \boldsymbol{\beta}^{(t)} - \alpha (\nabla_{\boldsymbol{\beta}} \text{MSE}-2 \lambda*\beta^{(t)})β(t+1)=β(t)α(βMSE2λβ(t))

β(t+1)=(1−2αβ)β(t)−α∇βMSE\boldsymbol{\beta}^{(t+1)} = (1-2\alpha\beta )\boldsymbol{\beta}^{(t)} - \alpha \nabla_{\boldsymbol{\beta}} \text{MSE}β(t+1)=(12αβ)β(t)αβMSE

L2正则项远小于1,仅使权重衰减,但不等于0

如何选择
场景 推荐方法
特征少、无共线性 普通线性回归
特征多、有共线性 Ridge
特征非常多( p≫np≫npn),希望自动选特征 Lasso
代码
import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression, Ridge, Lasso
from sklearn.preprocessing import PolynomialFeatures, StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.metrics import mean_squared_error


def compare_regularization():
    np.random.seed(66)

    # 1. 准备数据
    x = np.random.uniform(-3.0, 3.0, size=100)
    y = 0.5 * x ** 2 + x + 2 + np.random.normal(size=100)
    X = x.reshape(-1, 1)

    # 2. 设置多项式次数
    degree = 10

    # 3. 构建三种模型管道(自动处理多项式 + 标准化 + 回归)
    models = {
        "Linear (No Reg)": Pipeline([
            ("poly", PolynomialFeatures(degree=degree, include_bias=False)),
            ("linear", LinearRegression())
        ]),
        "Ridge (L2)": Pipeline([
            ("poly", PolynomialFeatures(degree=degree, include_bias=False)),
            ("scaler", StandardScaler()),  # L2 必须标准化!
            ("ridge", Ridge(alpha=1.0))    # alpha 控制正则强度
        ]),
        "Lasso (L1)": Pipeline([
            ("poly", PolynomialFeatures(degree=degree, include_bias=False)),
            ("scaler", StandardScaler()),  # L1 必须标准化!
            ("lasso", Lasso(alpha=0.01, max_iter=10000))  # Lasso 需要更小 alpha
        ])
    }

    # 4. 训练并预测
    plt.figure(figsize=(15, 4))
    x_plot = np.linspace(-3, 3, 300).reshape(-1, 1)  # 平滑曲线用

    for i, (name, model) in enumerate(models.items(), 1):
        # 训练
        model.fit(X, y)
        y_pred_train = model.predict(X)
        y_pred_plot = model.predict(x_plot)

        # 评估
        mse = mean_squared_error(y, y_pred_train)
        
        # 获取系数(用于分析稀疏性)
        if "linear" in name.lower():
            coef = model.named_steps['linear'].coef_
        elif "ridge" in name.lower():
            coef = model.named_steps['ridge'].coef_
        else:
            coef = model.named_steps['lasso'].coef_
        
        # 统计非零系数数量
        n_nonzero = np.sum(np.abs(coef) > 1e-5)

        # 绘图
        plt.subplot(1, 3, i)
        plt.scatter(x, y, alpha=0.6, label="Data")
        plt.plot(x_plot, y_pred_plot, color='r', linewidth=2, label=f"{name}\nMSE: {mse:.2f}\nNon-zero coeffs: {n_nonzero}")
        plt.title(name)
        plt.legend()
        plt.ylim(-2, 12)

        print(f"{name}:")
        print(f"  - MSE: {mse:.4f}")
        print(f"  - Non-zero coefficients: {n_nonzero} / {len(coef)}")
        print(f"  - Coefficients: {coef.round(3)}\n")

    plt.tight_layout()
    plt.show()


compare_regularization()

💡 提示:Lasso 的 alpha 需要仔细调整。太大 → 欠拟合;太小 → 接近普通线性回归。

📌可以尝试使用交叉验证选择最佳 alpha:

# 使用交叉验证选择最佳 alpha
from sklearn.model_selection import GridSearchCV

param_grid = {'alpha': [0.001, 0.01, 0.1, 1.0, 10.0]}
ridge_cv = GridSearchCV(Ridge(), param_grid, cv=5, scoring='neg_mean_squared_error')
ridge_cv.fit(X_poly_scaled, y)
print("Best alpha:", ridge_cv.best_params_)
总结
  • 欠拟合 → 模型太简单 → 增加复杂度
  • 过拟合 → 模型太复杂 → 增加正则化 / 减少特征 / 增加数据
  • 正则化通过在损失函数中加入惩罚项,控制模型复杂度,是防止过拟合的核心手段
  • Ridge:收缩系数,保留所有特征
  • Lasso:收缩 + 自动特征选择
  • 务必标准化特征后再使用正则化!

⚠️ 重要:使用 Ridge/Lasso必须对特征标准化!否则不同量纲的特征会被不公平惩罚。

总结

  • 特征缩放:对使用正则化的模型(如 Ridge/Lasso)很重要(标准化/归一化)
  • 异常值检测:线性回归对异常值敏感
  • 交叉验证:用于选择超参数(如正则化强度 λ)
  • 残差分析:检验模型假设是否成立(如绘制残差 vs 拟合值图)

优缺点

✅ 优点:

  • 简单、可解释性强
  • 计算高效
  • 理论基础扎实

❌ 缺点:

  • 假设强(线性、无共线性等)
  • 对非线性关系建模能力弱
  • 易受异常值影响
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐