线性回归 (Linear Regression)
线性回归 (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)}X∈Rn×(p+1) :设计矩阵(第一列为1,对应截距)
- β∈Rp+1β∈\mathbb{R}^{p+1}β∈Rp+1 :参数向量
- y∈Rny∈\mathbb{R}^ny∈Rn :观测值向量
参数估计方法
最小二乘法(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=1∑n(yi−y^i)2=∥y−Xβ∥2
📌 左边: ∑i=1n(yi−y^i)2\sum_{i=1}^{n} (y_i - \hat{y}_i)^2i=1∑n(yi−y^i)2
- yiy_iyi :第iii个样本的真实观测值
- y^i\hat{y}_iy^i:第iii 个样本的预测值
- yi−y^iy_i - \hat{y}_iyi−y^i:残差(误差)
- 平方后求和 → 得到 残差平方和(Residual Sum of Squares, RSS)
⚠️ 注意:这里是对所有样本的残差平方求和,不取平均。
📌 右边: ∥y−Xβ∥2\| \mathbf{y} - \mathbf{X}\boldsymbol{\beta} \|^2∥y−Xβ∥2
- yyy :真实值向量( n×1n×1n×1)
- XβXβXβ :预测值向量( n×1n×1n×1 )
- y−Xβy−Xβy−Xβ :残差向量
- ∥⋅∥2∥⋅∥^2∥⋅∥2 :表示该向量的L₂ 范数的平方,即各分量平方和
👉 所以两边等价!
残差平方和(RSS) 是衡量模型拟合优劣的重要指标:
- 越小越好:说明预测值与真实值越接近
- 是最小二乘法的目标函数:我们希望找到 βββ,使得 RSS 最小
解析解(当 X⊤XX^⊤XX⊤X 可逆时):
β^=(XTX)−1XTy \hat{\boldsymbol{\beta}} = (\mathbf{X}^T \mathbf{X})^{-1} \mathbf{X}^T \mathbf{y} β^=(XTX)−1XTy
公式推导简要(通过求导):
-
定义损失函数:
L(β)=(y−Xβ)T(y−Xβ)L(β)=(y−Xβ)^T(y−Xβ)L(β)=(y−Xβ)T(y−Xβ)
-
对 βββ 求梯度并令其为 0:
∂L∂β=−2XT(y−Xβ)=0\frac{∂L}{∂β}=−2X^T(y−Xβ)=0∂β∂L=−2XT(y−Xβ)=0
-
解得:
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=1∑n(yi−y^i)2=n1∥y−Xβ∥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(y−Xβ)
梯度推导简要:
从 MSE 出发:
MSE=1n(y−Xβ)T(y−Xβ)\text{MSE} = \frac{1}{n} (y−Xβ)^T(y−Xβ)MSE=n1(y−Xβ)T(y−Xβ)
对 βββ 求偏导(使用矩阵微积分):
∇βMSE=−2nXT(y−Xβ)\nabla_{\boldsymbol{\beta}} \text{MSE} = -\frac{2}{n} \mathbf{X}^T (\mathbf{y} - \mathbf{X}\boldsymbol{\beta})∇βMSE=−n2XT(y−Xβ)
👉 这个结果也可以理解为:
- y−Xβy−Xβy−Xβ 是残差向量
- XTX^TXT 是设计矩阵的转置
- 所以梯度是“残差加权后的特征贡献”
分类
-
批量梯度下降法(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=1∑m∇θL(y(i),y^(i))
其中:
- mmm :训练样本总数
- ααα :学习率
- LLL :单个样本的损失函数
优点:
- 梯度方向准确,收敛路径平滑
- 理论上能稳定收敛到全局最小值(凸函数)或局部最小值(非凸)
缺点:
- 计算开销大:每次都要遍历全部数据
- 内存压力大:大数据集无法一次性加载进内存
- 速度慢:尤其在大规模数据上不实用
💡 工业界几乎不用纯 BGD,除非数据极小。
-
随机梯度下降法(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)i∼Uniform(1,m)
优点:
- 计算快:每次只需处理一个样本
- 内存友好:适合流式数据或超大数据集
- 有“噪声”优势:有助于跳出鞍点和局部最优(尤其在深度学习中)
缺点:
- 更新路径震荡剧烈(像“醉汉走路”)
- 收敛不稳定,可能在最优解附近来回波动
- 需要精心调整学习率衰减策略
💡 虽然叫“随机”,但现代优化器(如 Adam)已很少直接用原始 SGD。
-
小批量梯度下降法(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)θ:=θ−α⋅b1∑i∈B∇θL(y(i),y^(i))
其中:
- BBB :当前 mini-batch(大小为 bb )
- b≪mb≪mb≪m ,通常 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)^2n1∑i=1n(yi−y^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 \vertn1∑i=1n∣yi−y^i∣ | 对异常值鲁棒;越小越好 | 与目标同单位 | ❌ 否 | 异常值多的数据 |
| R²(决定系数) | 1−∑(yi−y^i)2∑(yi−yˉ)21 - \frac{\sum (y_i - \hat{y}_i)^2}{\sum (y_i - \bar{y})^2}1−∑(yi−yˉ)2∑(yi−y^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有效性前提)
- 线性性: yyy与 xxx 线性相关
- 独立性:误差项相互独立(无自相关)
- 同方差性(Homoscedasticity):误差方差恒定
- 正态性:误差项服从正态分布(用于推断)
- 无多重共线性:自变量之间不高度相关
若违反假设,可能导致估计有偏、标准误不准、预测失效等。
✅ 线性性:模型结构正确
✅ 独立性:误差不互相影响
✅ 同方差性:误差波动稳定
✅ 正态性:支持统计推断
✅ 无多重共线性:参数估计稳定
| 假设 | 全称 | 是否影响无偏性? | 是否影响有效性? | 是否影响推断? |
|---|---|---|---|---|
| 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=1∑n∣wi∣
- λ∑i=1n∣wi∣\lambda \sum_{i=1}^{n} |w_i|λ∑i=1n∣wi∣:L1 正则化项,用于防止过拟合并实现特征选择。
- λ\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=1∑nwi2
- ∑i=1nwi2\sum_{i=1}^{n} {w_i}^2∑i=1nwi2:L2 范数的平方,即所有系数平方和
- λ\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)−α(∇βMSE−2λ∗β(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)=(1−2αβ)β(t)−α∇βMSE
L2正则项远小于1,仅使权重衰减,但不等于0
如何选择
| 场景 | 推荐方法 |
|---|---|
| 特征少、无共线性 | 普通线性回归 |
| 特征多、有共线性 | Ridge |
| 特征非常多( p≫np≫np≫n),希望自动选特征 | 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 拟合值图)
优缺点
✅ 优点:
- 简单、可解释性强
- 计算高效
- 理论基础扎实
❌ 缺点:
- 假设强(线性、无共线性等)
- 对非线性关系建模能力弱
- 易受异常值影响
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)