大数跨境

导师:“模型准确率这么高,你敢解释这些系数吗?”我:“Codex都跑通了啊!”导师:“先把多重共线性搞明白!”

导师:“模型准确率这么高,你敢解释这些系数吗?”我:“Codex都跑通了啊!”导师:“先把多重共线性搞明白!” 机器学习和人工智能AI
2026-08-25
2

哈喽,大家好~

最近,有收到同学们的提问,“老师,模型的准确率挺高,但为什么每次训练出来的特征系数都不一样?”

这也是面试和实战里经常遇到的问题。很多时候,问题不在模型不够复杂,而在于特征之间存在多重共线性

下面,我们把多重共线性讲清楚:它是什么、为什么会影响模型、如何用Python检测,以及Ridge回归为什么能够缓解这个问题~

01先用一个例子理解多重共线性

假设我们要预测房价,特征包括:

  • 房屋面积
  • 房间数量
  • 房屋总面积
  • 建筑年限

其中,“房屋面积”和“房屋总面积”高度相关,“房间数量”也常常和面积相关。

模型会发现:这几个特征都可以解释房价变化。

于是问题来了:到底应该把功劳分给哪个特征?

在线性回归中,模型通常写成:

这里, 表示第 个特征对预测结果的影响。

当特征之间高度相关时,模型很难单独判断每个特征的贡献。结果就是:整体预测可能还不错,但每个系数会变得不稳定,甚至出现符号反转。

一句话,就是多重共线性不一定让预测立刻变差,但会让系数解释变得不可靠~

02多重共线性到底影响什么?

我们可以把两个高度相关的特征想象成两个一起搬箱子的人。

如果两个人总是同时行动,最后箱子搬到了目的地,但我们很难判断每个人到底贡献了多少。模型中的系数估计也是一样。

它主要带来三个问题:

  1. 系数对数据变化非常敏感;
  2. 系数标准误变大,显著性判断不可靠;
  3. 解释模型时容易得出错误结论。

检测多重共线性时,经常使用VIF,也就是方差膨胀因子:

其中, 表示用其他特征预测第 个特征时得到的决定系数。

如果一个特征几乎可以被其他特征解释, 就接近1,VIF会非常大。

经验上, 需要关注, 通常说明共线性比较严重。

03完整案例

下面构造一个数据集:

  • 代表特征A;
  • 高度相关;
  • 是另一个相对独立的特征;
  • 真实目标由 决定。

我们分别训练普通线性回归和Ridge回归,再通过Bootstrap重复抽样,观察系数的变化范围~

import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import make_pipeline
from sklearn.linear_model import LinearRegression, Ridge
from sklearn.metrics import r2_score

np.random.seed(42)
n = 3000

x1 = np.random.normal(size=n)
x2 = 0.95 * x1 + np.random.normal(scale=0.12, size=n)
x3 = np.random.normal(size=n)
x4 = 0.5 * x3 + np.random.normal(scale=0.7, size=n)

X = np.c_[x1, x2, x3, x4]
y = 3*x1 + 2*x3 + np.random.normal(scale=0.8, size=n)

# 用R2计算VIF
vif = []
for i in range(X.shape[1]):
    other = np.delete(X, i, axis=1)
    r2 = LinearRegression().fit(other, X[:, i]).score(other, X[:, i])
    vif.append(1 / (1 - r2))

print("VIF:", np.round(vif, 2))

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.25, random_state=7
)

ols = make_pipeline(StandardScaler(), LinearRegression())
ridge = make_pipeline(StandardScaler(), Ridge(alpha=2.0))

ols.fit(X_train, y_train)
ridge.fit(X_train, y_train)

print("OLS R2:", round(r2_score(y_test, ols.predict(X_test)), 3))
print("Ridge R2:", round(r2_score(y_test, ridge.predict(X_test)), 3))

# Bootstrap观察系数稳定性
coef_ols, coef_ridge = [], []
for _ in range(300):
    idx = np.random.choice(len(X_train), len(X_train), replace=True)
    ols.fit(X_train[idx], y_train[idx])
    ridge.fit(X_train[idx], y_train[idx])
    coef_ols.append(ols[-1].coef_)
    coef_ridge.append(ridge[-1].coef_)

coef_ols = np.array(coef_ols)
coef_ridge = np.array(coef_ridge)
names = ["x1""x2""x3""x4"]

fig, ax = plt.subplots(12, figsize=(135))

# 图1:系数Bootstrap区间
for j, name in enumerate(names):
    ax[0].errorbar(
        j - 0.12, coef_ols[:, j].mean(),
        yerr=np.std(coef_ols[:, j]),
        fmt="o", color="#ff3366", capsize=5, label="OLS" if j == 0 else ""
    )
    ax[0].errorbar(
        j + 0.12, coef_ridge[:, j].mean(),
        yerr=np.std(coef_ridge[:, j]),
        fmt="o", color="#00a6ff", capsize=5, label="Ridge" if j == 0 else ""
    )

ax[0].axhline(0, color="gray", linestyle="--")
ax[0].set_xticks(range(4), names)
ax[0].set_title("Coefficient stability")
ax[0].set_ylabel("standardized coefficient")
ax[0].legend()

# 图2:预测值与残差大小
pred_ols = ols.predict(X_test)
pred_ridge = ridge.predict(X_test)
residual = np.abs(y_test - pred_ridge)

ax[1].scatter(
    pred_ols, y_test, c=residual, cmap="turbo",
    s=55, alpha=0.85, edgecolors="white"
)
low, high = y_test.min(), y_test.max()
ax[1].plot([low, high], [low, high], "k--", label="ideal")
ax[1].set_xlabel("OLS prediction")
ax[1].set_ylabel("actual value")
ax[1].set_title("Prediction and residual magnitude")
ax[1].legend()

plt.tight_layout()
plt.show()

第一张图展示的是300次Bootstrap训练后,模型系数的均值和波动范围。

由于 高度相关,普通OLS会在它们之间反复分配解释权重。你会看到两个系数的波动范围明显更大,甚至可能出现正负变化。

Ridge的系数通常更稳定,因为它会压缩过大的系数,让相关特征共同承担影响,而不是让某一个特征独自承担全部解释。

第二张图把预测值和真实值放在一起,颜色表示残差大小。点越接近虚线,预测越准确;颜色越亮,说明误差越大。

这里要注意:Ridge的主要价值不是保证每次R²都大幅提升,而是让模型在特征相关时更加稳定。

04Ridge为什么能缓解共线性?

普通线性回归最小化的是:

Ridge在此基础上增加了L2正则项:

其中, 控制正则化强度。 越大,系数被压缩得越明显。

通俗理解就是:模型不允许某几个特征的系数无限变大,而是让相关特征一起分担任务。

实际使用时, 不要凭感觉设置,可以通过交叉验证选择:

from sklearn.linear_model import RidgeCV

model = make_pipeline(
    StandardScaler(),
    RidgeCV(alphas=np.logspace(-3330), cv=5)
)
model.fit(X_train, y_train)

print("best alpha:", model[-1].alpha_)

当然,Ridge并不是唯一办法。你还可以删除高度相关的特征、合并特征,或者使用Lasso进行特征选择。但如果这些特征本身都有业务价值,Ridge通常更稳妥。

总结

多重共线性描述的是特征之间存在高度相关关系。它不一定直接摧毁模型的预测能力,却会让系数解释和模型稳定性出现问题。

我们可以先用VIF检测,再结合Bootstrap观察系数波动,最后使用Ridge或交叉验证进行处理。

接下来大家可以继续尝试两件事:调整 的相关程度,观察VIF如何变化;或者绘制不同 下的系数路径,直观看看正则化是怎样压缩系数的。

【声明】内容源于网络
0
0
机器学习和人工智能AI
让我们一起期待 AI 带给我们的每一场变革!推送最新行业内最新最前沿人工智能技术!
内容 381
粉丝 0
机器学习和人工智能AI 让我们一起期待 AI 带给我们的每一场变革!推送最新行业内最新最前沿人工智能技术!
总阅读5.0k
粉丝0
内容381