AI降维踩坑记录
不做降维的时候:
- 训练时间:单轮 epoch 2小时,要跑10轮就几乎过夜
- 内存占用:一个 batch 要十几GB,GPU根本不够用
- 模型效果:测试集准确率78%,而且训练集和测试集差距很大,明显过拟合
- 推理速度:线上推理超过500ms,完全受不了
这不是理论分析,是实打实的痛点。
为什么一定要降维
直接说结果吧。不做降维的时候:
- 训练时间:单轮 epoch 2小时,要跑10轮就几乎过夜
- 内存占用:一个 batch 要十几GB,GPU根本不够用
- 模型效果:测试集准确率78%,而且训练集和测试集差距很大,明显过拟合
- 推理速度:线上推理超过500ms,完全受不了
这不是理论分析,是实打实的痛点。高维数据带来的"维数灾难"不只是课本上的名词,它会真金白银地吃掉你的计算资源和时间。
我在做特征工程的时候也犯了一个常见的错误:觉得特征越多越好,模型就能学到更多东西。但实际上,很多特征之间是高度相关的,有的甚至是噪声。这些冗余特征不仅没用,还会让模型更难训练。
我尝试过的几种方案
方案一:特征选择(硬砍)
最简单的想法就是:直接把不那么重要的特征砍掉。
我用的是基于树模型的特征重要性排序。训练一个 LightGBM 模型,然后按重要性排序,只保留前100个特征。
import lightgbm as lgb
from sklearn.datasets import make_classification
# 假设 X_train 是你的高维特征,y_train 是标签
lgb_model = lgb.LGBMClassifier(n_estimators=100, random_state=42)
lgb_model.fit(X_train, y_train)
# 获取特征重要性
feature_importance = lgb_model.feature_importances_
sorted_idx = np.argsort(feature_importance)[::-1]
# 保留前100个最重要的特征
top_100_features = sorted_idx[:100]
X_train_selected = X_train[:, top_100_features]
X_test_selected = X_test[:, top_100_features]
这个方案的效果:
- 训练时间:直接降到原来的1/5
- 准确率:从78%掉到74%,损失比较大
- 问题:砍掉的特征里可能包含一些非线性的有用信息
我意识到硬砍虽然快,但容易误杀。特征重要性排序是基于某个特定模型的,被砍掉的特征可能对别的模型有用。
方案二:PCA(主成分分析)
PCA 是最常见的降维方法之一,但我一开始挺犹豫的。主要担心两点:一是主成分不好解释,二是如果特征之间不是线性关系,效果可能会打折扣。
但考虑到我当时的特征主要是数值型的,而且有不少统计特征,还是值得一试。
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
# 先标准化
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
# PCA降维,保留95%的方差
pca = PCA(n_components=0.95, random_state=42)
X_train_pca = pca.fit_transform(X_train_scaled)
X_test_pca = pca.transform(X_test_scaled)
print(f"原始特征数: {X_train.shape[1]}")
print(f"降维后特征数: {X_train_pca.shape[1]}")
print(f"解释方差比例: {pca.explained_variance_ratio_.sum():.4f}")
这个方案的效果:
- 特征数:从1000+降到120左右
- 训练时间:约原来的1/4
- 准确率:77%,基本没损失
- 问题:主成分确实不好解释,业务方问起来很尴尬
有个细节要注意:做 PCA 之前一定要标准化。我一开始没注意这个,结果降维效果很差,排查了半天才想起来要标准化。
方案三:t-SNE + 聚类(可视化驱动)
这个方案其实不是用来做训练数据降维的,而是用来帮我理解数据的结构。
我先用 t-SNE 把高维数据降到2维,看看数据点的分布情况,再决定用什么方式降维更合适。
from sklearn.manifold import TSNE
import matplotlib.pyplot as plt
# t-SNE降维到2维
tsne = TSNE(n_components=2, random_state=42, perplexity=30)
X_tsne = tsne.fit_transform(X_train_scaled[:5000]) # 只用前5000个样本
plt.figure(figsize=(10, 8))
plt.scatter(X_tsne[:, 0], X_tsne[:, 1], c=y_train[:5000], cmap='viridis', alpha=0.6)
plt.colorbar()
plt.title('t-SNE Visualization of High-Dimensional Data')
plt.xlabel('t-SNE Component 1')
plt.ylabel('t-SNE Component 2')
plt.show()
这个可视化帮我发现了一个问题:数据其实分成了几个比较明显的簇,说明用聚类预处理可能有用。
方案四:混合方案(最终采用)
结合前面的经验,我最后用了一个混合方案:
- 先用特征选择去掉明显没用的特征(重要性排名后300位的)
- 再用 PCA 降维到目标维度
- 训练时再对主成分做进一步筛选
这个流程可以画成一张图,看起来更清楚:
# 第一步:特征选择
lgb_model = lgb.LGBMClassifier(n_estimators=100, random_state=42)
lgb_model.fit(X_train, y_train)
feature_importance = lgb_model.feature_importances_
top_700_idx = np.argsort(feature_importance)[::-1][:700]
X_train_selected = X_train[:, top_700_idx]
X_test_selected = X_test[:, top_700_idx]
# 第二步:标准化 + PCA
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train_selected)
X_test_scaled = scaler.transform(X_test_selected)
pca = PCA(n_components=100, random_state=42)
X_train_pca = pca.fit_transform(X_train_scaled)
X_test_pca = pca.transform(X_test_scaled)
# 第三步:基于主成分的重要性筛选
final_model = lgb.LGBMClassifier(n_estimators=200, random_state=42)
final_model.fit(X_train_pca, y_train)
踩过的坑
坑一:忘记标准化
这是我犯的一个低级错误。PCA 是基于方差的,如果特征的尺度差异很大,结果会完全被大方差的特征主导。
症状:降维后主成分几乎只由某个特征决定,其他特征的影响很小。
解决:在做 PCA 之前一定要用 StandardScaler 标准化,或者至少用 MinMaxScaler 缩放到[0,1]。
坑二:保留的维度太少
一开始我想把维度压到50以内,结果准确率掉得厉害。
症状:准确率从78%掉到70%以下,明显欠拟合。
解决:用肘部法则找到合适的维度。我画了累计方差贡献率曲线,发现在100左右有个明显拐点。

这张图的核心是那根蓝色曲线:横轴是主成分数量,纵轴是累计解释的方差比例。曲线在接近100维的时候开始变平缓,这意味着前100个主成分已经解释了绝大部分的信息。再往后增加主成分,收益越来越小。
# 找合适的维度数
pca_full = PCA().fit(X_train_scaled)
cumulative_variance = np.cumsum(pca_full.explained_variance_ratio_)
plt.figure(figsize=(10, 6))
plt.plot(range(1, len(cumulative_variance) + 1), cumulative_variance, 'b-')
plt.axhline(y=0.95, color='r', linestyle='--')
plt.axvline(x=100, color='g', linestyle='--')
plt.xlabel('Number of Components')
plt.ylabel('Cumulative Explained Variance')
plt.title('Elbow Method for Choosing n_components')
plt.grid(True)
plt.show()
坑三:测试集泄露
这个问题我一开始没注意,后来代码review的时候才发现。
症状:降维时用了全部数据的统计信息(比如标准化的参数),导致测试集信息泄露到训练过程中。
解决:只用训练集拟合 scaler 和 PCA,然后用它们去转换测试集。
# 错误做法
scaler = StandardScaler()
X_all_scaled = scaler.fit_transform(np.vstack([X_train, X_test]))
# 正确做法
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
坑四:非数值型特征处理不当
我的数据里有些类别型特征,我一开始直接 one-hot 编码后丢进去,结果维度爆炸,而且 PCA 效果很差。
症状:降维后主成分很难解释,类别特征的信息基本丢失。
解决:对类别特征单独处理,可以用 Target Encoding 或者 Embedding,而不是简单 one-hot。
最终效果
用了混合方案后,对比一下原始情况:

这张图上的数据比我之前说的几句话直接多了。最左边是特征数,从1000+直接砍到100,减少了90%。训练时间和推理速度的改善更夸张,分别快了8倍和6倍多。
准确率这块其实没怎么掉:从78%降到77.5%,几乎可以忽略。但过拟合情况好了很多——训练集和测试集的差距从8%降到了3%,说明模型泛化能力明显提升了。
一些经验总结
降维这件事,教科书上讲了很多算法,但实际用起来还是有不少细节:
- 不要盲目降维:先看数据结构,搞清楚为什么维度高,是特征冗余还是噪声多
- 标准化是必须的:除非你很有把握所有特征的尺度都已经一致
- 别一上来就追求极致维度:我一开始想把维度压到50以内,结果损失太大;80-100这个区间对大多数情况来说是个比较安全的起点
- 多尝试几种方案:特征选择、PCA、t-SNE 可视化,每个都能给你一些不同的信息
- 关注业务限制:线上推理速度、内存占用这些硬指标,有时候比准确率更重要
如果这篇文章能帮你少踩几个坑,那就值了。降维不是什么高深的黑科技,但做对了能让你的项目活下来,做错了也能让你掉坑里。
反正我现在是信了:数据多不一定好,但降维一定要做。
可用性说明:本文发布于 2020 年 8 月,距今已超过五年。文中涉及的软件版本、接口、下载地址、命令参数和操作界面可能已经发生变化,部分方案在当前环境下可能失效。请结合官方最新文档核对后再操作,生产环境使用前务必先行验证。
版权声明: 本文首发于 指尖魔法屋-AI降维踩坑记录(https://blog.thinkmoon.cn/post/322-ai-dimensionality-reduction-high-low-dim/) 转载或引用必须申明原指尖魔法屋来源及源地址!
评论
使用 GitHub 账号登录后即可留言,支持 Markdown。