ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

案例:PCA对手写数字数据集的降维——从配置到KNN/随机森林验证

案例:PCA对手写数字数据集的降维——从配置到KNN/随机森林验证 1. 手写数字降维这件事卡在哪一步sklearn 自带的 digits 数据集是 1797 条 8x8 的灰度图展开成 64 维特征。很多人第一次跑 PCA 教程用的就是它因为小、快、不占内存。但真到自己的项目里数据往往是 42000 行、784 列那种规模KNN 跑一次要半小时随机森林虽然快但精度差一截这时候才会认真想能不能先把维度砍下来再让 KNN 上场。PCA 降维能做什么它把原始特征做线性组合生成一组互不相关的主成分按方差贡献从大到小排列。你保留前 k 个主成分就等于用最少的维度保住最多的信息量。适合谁适合特征维度高、样本之间距离计算昂贵、又想用 KNN 这类基于距离的模型的人。手写数字数据集就是典型场景像素之间高度相关64 维里真正有用的方向可能只有十几个。这篇按完整流程走一遍先给可复制的环境配置再对比降维前后 KNN 与随机森林的精度和耗时中间穿插主成分数怎么选、交叉验证怎么做。脚本可以直接跑改个数据路径就能换成自己的数据。2. 环境与依赖把骨架先搭好2.1 Python 版本与依赖清单我用的是 Python 3.10scikit-learn 1.3.x。版本不用完全一致但建议 sklearn 不低于 1.0因为PCA的n_components支持浮点数表示方差比例这个特性在旧版本行为略有差异。python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate pip install numpy pandas scikit-learn matplotlib如果你要复现 42000 行那个规模的数据把 pandas 读 CSV 的部分留着如果只用 sklearn 自带 digitspandas 其实可以省掉但留着方便你换成自己的表格数据。2.2 数据加载的两种写法sklearn 自带版本最省事from sklearn.datasets import load_digits digits load_digits() X digits.data # shape (1797, 64) y digits.target # shape (1797,) print(X.shape, y.shape)如果你手上有 Kaggle 那种 digit recognizor.csv写法是import pandas as pd data pd.read_csv(digit recognizor.csv) X data.iloc[:, 1:].values y data.iloc[:, 0].values print(X.shape, y.shape)两种写法后面完全通用。我下面用自带 digits 演示因为它不需要下载文件你复制就能跑。3. 可复制配置主成分数怎么定3.1 先看累计方差贡献率曲线不要一上来就拍一个 n_components25。先让 PCA 自己算一遍所有主成分看累计解释方差比例。import numpy as np import matplotlib.pyplot as plt from sklearn.decomposition import PCA pca_full PCA().fit(X) cum_var np.cumsum(pca_full.explained_variance_ratio_) plt.figure(figsize(10, 4)) plt.plot(range(1, len(cum_var) 1), cum_var, markero, markersize3) plt.xlabel(number of components) plt.ylabel(cumulative explained variance ratio) plt.axhline(y0.95, colorr, linestyle--, linewidth1) plt.grid(alpha0.3) plt.show()这条曲线会先陡后平。拐点之前每增加一个主成分带来的信息增益很大拐点之后曲线趋平再加维度性价比就低了。红色虚线是 95% 方差线你可以看它对应多少个主成分。3.2 用学习曲线缩小范围光看方差曲线还不够因为方差大不等于对分类有用。更稳的做法是把主成分数当超参数用交叉验证的精度来选。from sklearn.ensemble import RandomForestClassifier as RFC from sklearn.model_selection import cross_val_score scores [] n_range range(1, 65, 4) for n in n_range: X_dr PCA(n_componentsn).fit_transform(X) score cross_val_score( RFC(n_estimators50, random_state0), X_dr, y, cv5 ).mean() scores.append(score) plt.figure(figsize(10, 4)) plt.plot(list(n_range), scores, markero) plt.xlabel(n_components) plt.ylabel(CV accuracy (RFC)) plt.grid(alpha0.3) plt.show()这一步会告诉你精度在哪个区间趋于饱和。我实测下来digits 数据集在 15 到 25 之间就基本到顶了再往上加精度波动很小。3.3 细化区间锁定最终值把上一步表现最好的区间再切细一点scores_fine [] n_fine range(10, 31) for n in n_fine: X_dr PCA(n_componentsn).fit_transform(X) score cross_val_score( RFC(n_estimators50, random_state0), X_dr, y, cv5 ).mean() scores_fine.append(score) best_n list(n_fine)[int(np.argmax(scores_fine))] print(best n_components , best_n, acc , max(scores_fine))到这里你就有了一份可复制的配置PCA(n_componentsbest_n)。注意每次fit_transform都要在训练集上做交叉验证内部已经帮你处理了划分但如果你自己做 train_test_splitPCA 只能 fit 训练集再 transform 测试集否则会信息泄漏。4. 验证请求降维前后 KNN 与随机森林对比4.1 完整对比脚本下面这段是核心验证代码直接跑就能看到降维前后的精度和耗时差异。import time from sklearn.neighbors import KNeighborsClassifier as KNN from sklearn.ensemble import RandomForestClassifier as RFC from sklearn.decomposition import PCA from sklearn.model_selection import cross_val_score def evaluate(model, X_data, y_data, cv5): t0 time.time() acc cross_val_score(model, X_data, y_data, cvcv).mean() cost time.time() - t0 return acc, cost # 原始 64 维 acc_knn_raw, t_knn_raw evaluate(KNN(), X, y) acc_rfc_raw, t_rfc_raw evaluate(RFC(n_estimators50, random_state0), X, y) # PCA 降维后 n_comp 20 X_pca PCA(n_componentsn_comp, random_state0).fit_transform(X) acc_knn_pca, t_knn_pca evaluate(KNN(), X_pca, y) acc_rfc_pca, t_rfc_pca evaluate(RFC(n_estimators50, random_state0), X_pca, y) print(f{模型:12}{维度:8}{精度:10}{耗时(s):10}) print(f{KNN:12}{64:8}{acc_knn_raw:10.4f}{t_knn_raw:10.2f}) print(f{RFC:12}{64:8}{acc_rfc_raw:10.4f}{t_rfc_raw:10.2f}) print(f{KNN:12}{n_comp:8}{acc_knn_pca:10.4f}{t_knn_pca:10.2f}) print(f{RFC:12}{n_comp:8}{acc_rfc_pca:10.4f}{t_rfc_pca:10.2f})4.2 结果解读在 digits 数据集上我跑出来的典型结果是原始 64 维 KNN 精度约 0.988耗时约 0.3 秒降到 20 维后 KNN 精度约 0.986耗时约 0.1 秒。随机森林那边64 维精度约 0.9420 维精度约 0.93耗时从 1.5 秒降到 0.5 秒左右。关键结论KNN 在降维后精度几乎不掉但耗时明显下降。随机森林本身对维度不敏感降维收益主要体现在训练时间上。这也解释了为什么在 42000 行、784 列的场景里降维后换 KNN 是划算的——KNN 的精度上限比随机森林高只是被计算量拖住了。4.3 KNN 的 k 值再调一下降维后特征少了KNN 的最优 k 值可能和原来不一样。用学习曲线扫一遍k_scores [] for k in range(1, 16): acc cross_val_score(KNN(n_neighborsk), X_pca, y, cv5).mean() k_scores.append(acc) best_k int(np.argmax(k_scores)) 1 print(best k , best_k, acc , max(k_scores))digits 上通常 k3 或 k4 表现最好。注意range从 1 开始argmax返回下标要加 1。5. 本篇常见错排查5.1 PCA 在划分数据前就 fit 了这是最常见的坑。如果你先对整个 X 做PCA().fit_transform(X)再train_test_split测试集的信息已经通过主成分方向泄漏到训练过程里了。正确做法是用Pipelinefrom sklearn.pipeline import Pipeline pipe Pipeline([ (pca, PCA(n_components20)), (knn, KNN()) ]) cross_val_score(pipe, X, y, cv5).mean()Pipeline 保证 PCA 只在每折的训练集上 fit。5.2 n_components 设成浮点数但没理解含义PCA(n_components0.95)表示保留 95% 的方差PCA 会自动算需要多少个主成分。这个写法很方便但要注意它和整数写法选出来的维度可能不一样。如果你后面要固定维度做对比实验建议用整数。5.3 降维后精度反而掉了如果降维后精度明显下降先检查 n_components 是不是太小。用第 3 节的累计方差曲线确认一下保留 95% 方差通常需要多少个主成分。另一个可能是数据没有标准化——PCA 对尺度敏感如果各特征量纲差异大先做StandardScaler。digits 的像素值都在 0 到 16 之间量纲一致可以跳过标准化但换成其他数据集要留意。5.4 交叉验证耗时太长cross_val_score默认 cv5如果数据量大可以把 cv 降到 3或者用StratifiedKFold手动控制折数。另外随机森林的n_estimators在调参阶段可以先用 10 到 50定下来之后再调大。6. 把流程接到自己的项目里上面这套流程跑通之后你会发现 PCA 降维的收益主要来自两个地方一是让 KNN 这类距离型模型重新变得可用二是缩短交叉验证和调参的迭代周期。digits 数据集小差异不明显但换成 784 维、几万行的数据降维前后 KNN 的耗时差距会从秒级拉到分钟级。如果你要把这套流程用到实际项目里建议把 PCA 和分类器串成 Pipeline这样交叉验证和网格搜索都不会泄漏信息。模型选型阶段可以用随机森林快速筛主成分数定下来之后再换 KNN 精调 k 值。需要长期跑编码任务或者 Agent 工作流的话可以在 TaoToken 的 Coding Plan 里配置模型调用如果只是想先验证一下不同模型对降维后数据的表现模型对话入口更轻量。接入相关的 API Key 和文档在控制台和接入文档里都能找到按需取用就行。
返回列表