3
线性回归
Linear Regression & Regularization
线性回归是 ML 的"Hello World":用一条直线(或超平面)拟合数据,预测连续值。它最简单、最可解释,也是理解所有后续模型的基石。
数学原理
论假设函数
给定 x,预测 y = θ₀ + θ₁·x(一元),推广到多元就是 y = θ₀ + θ₁x₁ + θ₂x₂ + ... + θₙxₙ。θ 是模型要学的参数。我们要找一组 θ,让预测 ŷ 和真实 y 差得最小。
损失函数(MSE):J(θ) = (1/2m) · Σ(ŷᵢ - yᵢ)²
MSE 越小,线拟合越好。两个求解方法:正规方程(解析解,一次性算出最优 θ)和梯度下降(迭代优化,适合大数据)。
正规方程:θ = (XᵀX)⁻¹ Xᵀ y(小数据用这个,10 万行以上 XᵀX 求逆太慢,用梯度下降)
过拟合与正则化:Ridge / Lasso / ElasticNet
| 方法 | 在损失里加什么 | 效果 |
|---|---|---|
| Ridge (L2) | + α·Σθⱼ² | 把参数压小但不归零,适合所有特征都有用。 |
| Lasso (L1) | + α·Σ|θⱼ| | 会把不重要的参数压到 0,自带特征选择。 |
| ElasticNet | L1 + L2 混合 | 兼顾两者,特征相关时用。 |
正则化为什么能防过拟合
论奥卡姆剃刀
过拟合的模型参数很大、很"曲折"。正则化在损失里加一个惩罚项(L1 或 L2),让参数尽量小。参数小 = 模型简单 = 不曲折 = 不背答案。L2 让参数平滑变小,L1 让不重要参数变 0(自动特征选择)。这就是奥卡姆剃刀:如无必要,勿增实体。
Scikit-learn 实现:加州房价回归
import numpy as np
from sklearn.datasets import fetch_california_housing
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LinearRegression, Ridge, Lasso
from sklearn.metrics import mean_squared_error, r2_score, mean_absolute_error
# 1. 数据
data = fetch_california_housing()
X, y = data.data, data.target
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
# 2. 标准化(线性模型必须做)
scaler = StandardScaler()
X_train_s = scaler.fit_transform(X_train)
X_test_s = scaler.transform(X_test)
# 3. 训练普通线性回归
lr = LinearRegression()
lr.fit(X_train_s, y_train)
pred_lr = lr.predict(X_test_s)
# 4. 训练 Ridge(L2 正则)
ridge = Ridge(alpha=1.0)
ridge.fit(X_train_s, y_train)
pred_ridge = ridge.predict(X_test_s)
# 5. 训练 Lasso(L1 正则,会稀疏化系数)
lasso = Lasso(alpha=0.01)
lasso.fit(X_train_s, y_train)
pred_lasso = lasso.predict(X_test_s)
# 6. 评估
for name, pred in zip(["Linear", "Ridge", "Lasso"], [pred_lr, pred_ridge, pred_lasso]):
rmse = np.sqrt(mean_squared_error(y_test, pred))
r2 = r2_score(y_test, pred)
mae = mean_absolute_error(y_test, pred)
print(f"{name:8s} | RMSE={rmse:.4f} | MAE={mae:.4f} | R2={r2:.4f}")
# Linear | RMSE=0.7456 | MAE=0.5333 | R2=0.5758
# Ridge | RMSE=0.7456 | MAE=0.5333 | R2=0.5758
# Lasso | RMSE=0.7500 | MAE=0.5378 | R2=0.5708
# 7. 看系数:Lasso 把哪些特征压成 0 了
print("Lasso 非零系数个数:", (lasso.coef_ != 0).sum(), "/", len(lasso.coef_))
# Lasso 非零系数个数:7 / 8
回归评估指标
| 指标 | 含义 |
|---|---|
| MSE | 均方误差,对大误差敏感(平方放大)。 |
| RMSE | MSE 开方,和目标量纲一致,好解释。 |
| MAE | 平均绝对误差,对异常值不敏感。 |
| R² | 决定系数,0~1,1 是完美拟合,0 等于直接预测均值。 |
| 调整 R² | 加了惩罚项的 R²,特征多了也不虚假上升。 |
手写梯度下降(理解原理)
import numpy as np
# 构造数据:y = 3x + 2 + 噪声
np.random.seed(42)
X = np.random.rand(100, 1)
y = 3 * X + 2 + np.random.randn(100, 1) * 0.1
# 初始化参数
w = 0.; b = 0.
lr = 0.1
epochs = 1000
for i in range(epochs):
y_pred = w * X + b
loss = np.mean((y_pred - y)**2)
# MSE 对 w, b 的偏导
dw = 2 * np.mean((y_pred - y) * X)
db = 2 * np.mean(y_pred - y)
w -= lr * dw
b -= lr * db
if i % 100 == 0:
print(f"Epoch {i:4d}: w={w:.3f} b={b:.3f} loss={loss:.4f}")
# Epoch 0: w=0.098 b=0.153 loss=14.9784
# Epoch 100: w=1.342 b=2.841 loss=0.5032
# Epoch 500: w=2.704 b=2.241 loss=0.0135
# Epoch 900: w=2.942 b=2.069 loss=0.0098
# 最终 w≈3.0, b≈2.0,和真实值一致
多项式回归:线性模型拟合非线性
from sklearn.preprocessing import PolynomialFeatures
from sklearn.pipeline import make_pipeline
# 把 x 升到二次,再套线性回归
poly_model = make_pipeline(
PolynomialFeatures(degree=2),
LinearRegression()
)
poly_model.fit(X_train, y_train)
# degree=3 可能过拟合,degree=1 就是普通线性回归
本章面试题
面试 · 线性回归
Q1. L1 和 L2 正则化区别?
查看答案
L1 产生稀疏解(自动特征选择),L2 让参数小但不归零。L1 在特征相关时不稳定,L2 更稳定。
Q2. 为什么线性回归要标准化?
查看答案
量纲不同会让某些特征在损失里被放大,正则化对大系数惩罚也不公平;标准化后每个特征"话语权"一样,梯度下降收敛也快。
Q3. R² 为负是什么意思?
查看答案
说明模型还不如直接预测均值,模型完全不行。
Q4. 正规方程 vs 梯度下降怎么选?
查看答案
特征数 < 1 万、样本不大用正规方程(一次出解);特征多、样本大用梯度下降(可扩展)。
Q5. 多重共线性有什么影响?
查看答案
特征之间高度相关时,回归系数会变得不稳定、方差很大。对策:去掉冗余特征、用 Ridge、PCA 降维。VIF(方差膨胀因子)大于 10 就要警惕。