哈喽,大家好~
很常见的一个问题:特征是不是越多越好?模型怎么训练集效果不错,一到测试集就不行了?
很多同学刚接触机器学习时,会下意识觉得:多给模型一些特征,信息肯定更丰富。可实际情况经常相反,特征一多,模型反而更难学。这就是机器学习里非常经典的概念:维度灾难。
维度灾难
可以先想象一个一维空间。
假设我们要在数轴上找数据点,区间长度是 1,平均放 10 个样本,点与点之间并不算稀疏。
现在换成二维平面。如果还想保持同样密度,就不能只要 10 个样本了,而是大约需要 个样本。
继续升到 10 维呢?需要的样本数量会变成:
这里 表示每个维度上希望划分的数量, 表示特征维度。
假设每个维度都分成 10 份,10 维空间就需要:
也就是 100 亿个样本。
一句话概括:维度升高后,空间体积会指数级膨胀,原本看起来不少的数据,瞬间就变稀疏了。
而机器学习模型,尤其是 KNN、聚类、核方法这类依赖“距离”的模型,就会开始迷路。
为什么距离会失效?
维度灾难有一个很关键的现象,叫作距离集中。
在低维空间里,最近邻和最远邻的距离差别很明显。你找最近的样本,确实能找到“和我很像”的人。
但在高维空间中,最近点和最远点的距离会越来越接近:
其中, 是最远样本距离, 是最近样本距离。
这意味着什么?
原来 KNN 可以说:“离我最近的几个点,大概率和我属于一类。”
可高维时它会发现:“大家离我都差不多远,那我到底该信谁?”
所以,维度灾难并不是说高维数据一定不能用,而是告诉我们:维度增加后,数据量、特征质量和模型选择都要跟着升级。
一个通俗例子
两个有用特征,加上一堆噪声,我们构造一个二分类问题。
数据真正有价值的只有两个特征,分别记作 和 。它们能把两类样本分开。然后我们不断加入随机噪声特征,看看 KNN 的效果会怎样。
同时,我们生成两张分析图:
-
第一张图同时观察维度升高后的分类准确率、距离集中程度,以及 PCA 降维后的改善效果。 -
第二张图展示原始高维 KNN 和 PCA 降维后 KNN 的分类边界。
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_moons
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.decomposition import PCA
from sklearn.pipeline import Pipeline
from sklearn.metrics import accuracy_score
np.random.seed(42)
# 两个月牙形状的数据:前两个特征是真正有效的信息
X_signal, y = make_moons(n_samples=1200, noise=0.22, random_state=42)
dims = [2, 5, 10, 20, 50, 100]
raw_scores, pca_scores, distance_ratios = [], [], []
for dim in dims:
noise = np.random.normal(0, 1, size=(len(X_signal), dim - 2))
X = np.hstack([X_signal, noise])
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.3, stratify=y, random_state=42
)
# 直接在高维空间训练 KNN
knn = KNeighborsClassifier(n_neighbors=15)
knn.fit(X_train, y_train)
raw_scores.append(accuracy_score(y_test, knn.predict(X_test)))
# PCA 保留 2 个主成分后,再训练 KNN
pca_knn = Pipeline([
("pca", PCA(n_components=2, random_state=42)),
("knn", KNeighborsClassifier(n_neighbors=15))
])
pca_knn.fit(X_train, y_train)
pca_scores.append(accuracy_score(y_test, pca_knn.predict(X_test)))
# 计算测试集中每个样本到最近点、最远点的距离差异
sample = X_test[:200]
dist = np.sqrt(((sample[:, None, :] - sample[None, :, :]) ** 2).sum(axis=2))
dist[dist == 0] = np.nan
d_min = np.nanmin(dist, axis=1).mean()
d_max = np.nanmax(dist, axis=1).mean()
distance_ratios.append((d_max - d_min) / d_min)
# 图 1:维度、准确率、距离集中现象
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
axes[0].plot(dims, raw_scores, "o-", lw=3, ms=8,
color="#ff3b30", label="原始高维 KNN")
axes[0].plot(dims, pca_scores, "s-", lw=3, ms=8,
color="#007aff", label="PCA(2维) + KNN")
axes[0].set_xlabel("特征维度")
axes[0].set_ylabel("测试集准确率")
axes[0].set_title("维度升高后,噪声如何影响分类效果")
axes[0].legend()
axes[1].plot(dims, distance_ratios, "D-", lw=3, ms=8, color="#af52de")
axes[1].set_xlabel("特征维度")
axes[1].set_ylabel("(最远距离 - 最近距离) / 最近距离")
axes[1].set_title("距离集中:高维下远近差异不断缩小")
plt.tight_layout()
plt.show()
# 图 2:50维时,观察原始模型与降维模型的分类边界
noise = np.random.normal(0, 1, size=(len(X_signal), 48))
X_50 = np.hstack([X_signal, noise])
raw_model = KNeighborsClassifier(n_neighbors=15).fit(X_50, y)
pca_model = Pipeline([
("pca", PCA(n_components=2, random_state=42)),
("knn", KNeighborsClassifier(n_neighbors=15))
]).fit(X_50, y)
xx, yy = np.meshgrid(
np.linspace(-2, 3, 250),
np.linspace(-1.5, 2, 250)
)
grid_signal = np.c_[xx.ravel(), yy.ravel()]
grid_noise = np.zeros((len(grid_signal), 48))
grid_50 = np.hstack([grid_signal, grid_noise])
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
for ax, model, title in zip(
axes,
[raw_model, pca_model],
["原始 50 维 KNN", "PCA 降维后 KNN"]
):
Z = model.predict(grid_50).reshape(xx.shape)
ax.contourf(xx, yy, Z, alpha=0.35, cmap="coolwarm")
ax.scatter(X_signal[:, 0], X_signal[:, 1], c=y,
cmap="coolwarm", edgecolor="white", s=28)
ax.set_title(title)
ax.set_xlabel("有效特征 $x_1$")
ax.set_ylabel("有效特征 $x_2$")
plt.tight_layout()
plt.show()
第一张图里,随着维度从 2 增加到 100,原始 KNN 的测试准确率会明显下降。
原因很简单:后面加入的特征全是噪声,但 KNN 计算欧氏距离时,会把这些噪声也算进去。原来真正有区分度的 、 ,反而被淹没了。
右侧曲线则展示了距离集中现象。数值越低,说明最近邻和最远邻之间的差距越小。高维空间里,“近”这个概念越来越不可靠。
第二张图更直观。左图的原始高维 KNN 边界会变得混乱,模型被噪声带偏。
右图通过 PCA 把主要信息压缩到低维后,边界重新贴合了两个月牙形状。
总之就是说:降维不只是为了画图,它还能过滤噪声、减少计算量,并让部分模型重新找到有效距离。
面对维度灾难,我们这里一般从三个方向入手。
第一,做特征选择。不要把所有字段都塞进模型,优先保留和目标变量真正相关的特征。
第二,做降维。PCA、LDA、UMAP 这些方法都很常见。PCA 适合先快速压缩冗余信息,LDA 更适合带标签的分类任务。
第三,换模型。树模型对无关特征通常比 KNN 更稳一些;正则化模型,例如 Lasso、Ridge,也能抑制无用特征的影响。
总结
维度灾难的核心,不是“高维不好”,而是高维空间需要更多数据,也更容易让距离和噪声主导模型判断。
我们今天用 KNN 看到了两个现象:维度升高会让测试效果下降,最近邻和最远邻会越来越难区分。实验中,大家可以继续尝试用 PCA 对比不同保留维度,也可以把 KNN 换成随机森林、XGBoost,观察不同模型对高维噪声的敏感程度。

