哈喽,大家好~
最近,有收到同学们的提问,“老师,模型的准确率挺高,但为什么每次训练出来的特征系数都不一样?”
这也是面试和实战里经常遇到的问题。很多时候,问题不在模型不够复杂,而在于特征之间存在多重共线性。
下面,我们把多重共线性讲清楚:它是什么、为什么会影响模型、如何用Python检测,以及Ridge回归为什么能够缓解这个问题~
01先用一个例子理解多重共线性
假设我们要预测房价,特征包括:
-
房屋面积 -
房间数量 -
房屋总面积 -
建筑年限
其中,“房屋面积”和“房屋总面积”高度相关,“房间数量”也常常和面积相关。
模型会发现:这几个特征都可以解释房价变化。
于是问题来了:到底应该把功劳分给哪个特征?
在线性回归中,模型通常写成:
这里, 表示第 个特征对预测结果的影响。
当特征之间高度相关时,模型很难单独判断每个特征的贡献。结果就是:整体预测可能还不错,但每个系数会变得不稳定,甚至出现符号反转。
一句话,就是多重共线性不一定让预测立刻变差,但会让系数解释变得不可靠~
02多重共线性到底影响什么?
我们可以把两个高度相关的特征想象成两个一起搬箱子的人。
如果两个人总是同时行动,最后箱子搬到了目的地,但我们很难判断每个人到底贡献了多少。模型中的系数估计也是一样。
它主要带来三个问题:
-
系数对数据变化非常敏感; -
系数标准误变大,显著性判断不可靠; -
解释模型时容易得出错误结论。
检测多重共线性时,经常使用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(1, 2, figsize=(13, 5))
# 图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(-3, 3, 30), cv=5)
)
model.fit(X_train, y_train)
print("best alpha:", model[-1].alpha_)
当然,Ridge并不是唯一办法。你还可以删除高度相关的特征、合并特征,或者使用Lasso进行特征选择。但如果这些特征本身都有业务价值,Ridge通常更稳妥。
总结
多重共线性描述的是特征之间存在高度相关关系。它不一定直接摧毁模型的预测能力,却会让系数解释和模型稳定性出现问题。
我们可以先用VIF检测,再结合Bootstrap观察系数波动,最后使用Ridge或交叉验证进行处理。
接下来大家可以继续尝试两件事:调整 和 的相关程度,观察VIF如何变化;或者绘制不同 下的系数路径,直观看看正则化是怎样压缩系数的。

