YAOTU INSIGHTS

sklearn数据集加载与可复现基线:Bunch、fetch与make全解析

sklearn数据集加载与可复现基线:Bunch、fetch与make全解析
简介面向机器学习初学者与算法实验者的 SKlearn 数据集压缩包聚焦用内置经典数据快速完成分类、回归、文本挖掘与模型对比练习适合课堂教学、课程设计及算法验证等场景。包内共18个文件以csv数据表为主体辅以xml项目配置、txt与data说明文件及py脚本整体约207KB体积轻便便于本地解压后直接读取和整理。已有1409人学习说明其在入门实践与样例复现中有较高参考价值。内容覆盖鸢尾花、手写数字、乳腺癌诊断、新闻组文本、房价回归和葡萄酒等典型任务可支撑逻辑回归、决策树、随机森林、支持向量机、K近邻及神经网络等算法的训练、评估与参数调优也方便进阶者比较不同模型表现、观察特征与标签关系并用于教学演示和实验记录整理。1. 解压之后先看什么sklearn 数据集包的三层结构上周有同学发来一个压缩包问「解压完怎么没有 csv」。点开一看是 sklearn 数据集里面全是.pkz、.joblib、.csv.gz混着放。这一类资源的第一个坑就在这它不是一个表格文件而是 scikit-learn 里datasets模块的分层数据。这份 sklearn 数据集压缩包对应三层内置玩具数据load_*随库分发几百到几千行、需要下载的真实数据fetch_*走缓存目录、以及用make_*现场生成的合成数据不落盘。适合谁正在搭分类/回归基线、要做教学演示、或者内网机器没法联网拉数据的人。搞清楚每层怎么加载、返回什么结构比急着read_csv重要得多。2. 内置玩具数据集Bunch 结构与 load_* 五个函数的边界2.1 为什么 sklearn 不给你 csvBunch 对象的四个键load_iris()返回的不是 DataFrame也不是 ndarray而是一个继承自dict的Bunch。它支持属性访问data.data也支持键访问data[data]四个固定键是data、target、feature_names、DESCR图像类还会多一个images。这套设计的用意是把「特征矩阵 标签 列名 元信息」打包成一个整体避免你到处传三个变量。from sklearn.datasets import load_iris iris load_iris() print(type(iris)) # class sklearn.utils.Bunch print(iris.keys()) # dict_keys([data,target,frame,target_names,DESCR,feature_names,filename,data_module]) print(iris.data.shape) # (150, 4) 特征矩阵float64 print(iris.target.shape) # (150,) 标签int print(iris.feature_names) # [sepal length (cm), ...] print(iris.target_names) # [setosa versicolor virginica] print(iris.DESCR[:300]) # 数据来源、特征含义、统计量纯文本data是二维ndarray形状(n_samples, n_features)target是一维整型标签取值是0..n_classes-1的编码不是原始字符串feature_names是列名列表长度必须等于data.shape[1]DESCR是长字符串很多人直接跳过但它写清了每个特征的单位和量纲做特征工程前值得扫一眼。frame键在新版本里才会出现需as_frameTrue老版本访问会报KeyError这是版本差异带来的第一个小坑。2.2 load_iris / load_wine / load_breast_cancer 的规模对比与选型三个分类数据集经常被混着用但它们的难度完全不在一个量级。选错了模型表现会很玄学在 iris 上 0.97 的准确率换到 breast_cancer 上可能只剩 0.9不是模型退步是任务变难了。数据集样本数特征数类别数类别是否均衡典型用途load_iris15043均衡各 50入门、可视化、教学load_wine178133不均衡59/71/48多分类、特征相关性分析load_breast_cancer569302不均衡212/357二分类基线、ROC/AUCload_digits17976410近似均衡图像分类、降维可视化load_diabetes44210回归—回归基线、正则化对比选型原则很简单验证「流程能不能跑通」用 iris验证「模型在真实分布上是否稳」用 breast_cancer验证「多分类 特征冗余」用 wine。iris 太干净四个特征几乎线性可分拿它调参容易得出过于乐观的结论。from sklearn.datasets import load_breast_cancer, load_wine from sklearn.linear_model import LogisticRegression from sklearn.model_selection import cross_val_score for loader in (load_breast_cancer, load_wine): ds loader() scores cross_val_score(LogisticRegression(max_iter5000), ds.data, ds.target, cv5) print(f{loader.__name__:22s} mean{scores.mean():.4f} std{scores.std():.4f})cross_val_score的cv5表示 5 折分层交叉验证分类任务默认StratifiedKFold返回 5 个分数。看std比看mean更有信息量wine 上标准差通常比 breast_cancer 大因为样本只有 178 条折与折之间的分布波动更明显。2.3 load_digits 与图像型数据的 reshapeload_digits的data是(1797, 64)每行是一张 8×8 灰度图被拉平的结果像素值 0–16。要做可视化或用卷积得先 reshape 回去。from sklearn.datasets import load_digits import matplotlib.pyplot as plt digits load_digits() print(digits.data.shape) # (1797, 64) print(digits.images.shape) # (1797, 8, 8) Bunch 里已带好三维版本 print(digits.data.min(), digits.data.max()) # 0.0 16.0 # 取第 0 张确认拉平与还原一致 flat digits.data[0] img flat.reshape(8, 8) assert (img digits.images[0]).all() plt.imshow(img, cmapgray_r) plt.title(flabel{digits.target[0]}) plt.axis(off) plt.show()注意cmapgray_rsklearn 的约定是数值越大颜色越深0 表示白底和常规灰度图相反用gray会看到反色效果。这个细节调错的人不少。images键是data的视图级冗余内存翻倍样本量大时优先只用data再按需 reshape。2.4 把 Bunch 转成 DataFrame 的标准写法多数人的真实需求是丢进 pandas 做 EDA。直接pd.DataFrame(iris.data)会丢掉列名是后续一堆feature_0、feature_1血泪经验的源头。import pandas as pd from sklearn.datasets import load_iris def bunch_to_df(bunch): 把分类 Bunch 转成带列名和标签的 DataFrame df pd.DataFrame(bunch.data, columnsbunch.feature_names) # target 是整数编码映射回可读标签 df[target] bunch.target if hasattr(bunch, target_names): df[target_name] df[target].map( dict(enumerate(bunch.target_names)) ) return df df bunch_to_df(load_iris()) print(df.head()) print(df[target_name].value_counts())更省事的写法是新版本里的as_frameTrueload_iris(as_frameTrue).frame直接给 DataFrame。但要注意这个参数在 0.23 才加入且回归数据集如load_diabetes的frame里 target 列名是target分类数据集也是target不会自动带target_name。跨版本协作的项目里我一般自己写转换函数可控性比依赖参数更高。3. fetch_* 下载类数据集缓存目录、离线部署与超时处理3.1 data_home 与缓存目录的真实结构fetch_*和load_*最大的区别是它第一次调用会走网络把原始文件下到本地缓存之后才从缓存读。默认缓存在~/scikit_learn_dataLinux/macOS或%USERPROFILE%\scikit_learn_dataWindows。这个路径是所有fetch_*共享的也正因为共享多人共用一台机器时容易出权限问题。from sklearn.datasets import get_data_home, fetch_california_housing print(get_data_home()) # 默认缓存根目录 # 换到项目内目录避免污染用户主目录 ds fetch_california_housing(data_home./data_cache, download_if_missingTrue) print(ds.data.shape) # (20640, 8) print(ds.target.shape) # (20640,) print(ds.feature_names)data_home参数决定缓存根目录download_if_missingTrue表示缓存缺失时允许联网下载。生产环境里通常把download_if_missing设成False让缺数据的情况直接抛IOError而不是卡在网络上等超时——这个改动能把「构建卡住 3 分钟」变成「立刻失败并给出明确信息」。目录结构大致长这样路径内容data_home/cal_housing.pkz压缩后的 California 房价数据data_home/20news-bydate.tar.gz20newsgroups 原始压缩包data_home/joblib/joblib 的 memmap 缓存、交叉验证中间结果data_home/openml/openml 数据集按data_id分目录存放joblib/目录经常被忽略它是n_jobs1时并行任务的临时落盘位置长时间跑实验后可能涨到几个 GB清理时别误删别的。3.2 fetch_california_housing 与 fetch_openml 的差别fetch_california_housing是「固定 URL 固定校验和」的下载体积小、稳定。fetch_openml是「按名字或 ID 从 OpenML 仓库拉」灵活性高但不确定性也大——同名数据集可能有多个版本。from sklearn.datasets import fetch_openml # 按 name 拉取指定版本更可控 mnist fetch_openml(mnist_784, version1, as_frameFalse, parserauto) print(mnist.data.shape) # (70000, 784) print(mnist.target.shape) # (70000,) print(mnist.data.dtype) # float64version1指定版本号as_frameFalse强制返回 ndarray 而不是 DataFrame大表转 DataFrame 会有明显内存开销parserauto让 sklearn 自己选解析器。MNIST 原始下载约 50 MB解压后(70000, 784)的 float64 数组约 440 MB内存紧张时用as_frameFalse更稳。fetch_openml的坑在于target类型分类任务返回的target常是字符串数组如0、1直接喂给 sklearn 模型会报「unknown label type」。必须先编码from sklearn.preprocessing import LabelEncoder y LabelEncoder().fit_transform(mnist.target) print(y.dtype, y[:10]) # int64 [5 0 4 1 9 ...]LabelEncoder会按字典序分配整数09和9这种字符串排序和数值排序不一致做回归或需要保序的场景要换OrdinalEncoder或手动映射。3.3 无外网环境下怎么把数据搬进去内网机器跑不动fetch_*是最常见的落地障碍。标准做法是在能联网的机器上预热缓存再把整个data_home目录打包拷过去。# 机器 A可联网预热到指定目录 python -c from sklearn.datasets import fetch_20newsgroups fetch_20newsgroups(data_home./sk_cache, subsetall) # 打包整个缓存目录 tar czf sk_cache.tar.gz ./sk_cache # 机器 B内网解压后指向同一目录 tar xzf sk_cache.tar.gz -C /opt/data/# 机器 B 上后续调用必须显式传同一个 data_home from sklearn.datasets import fetch_20newsgroups ds fetch_20newsgroups(data_home/opt/data/sk_cache, subsetall, download_if_missingFalse)三个参数缺一不可data_home必须和打包时的路径结构一致、download_if_missingFalse阻止联网尝试、subset必须与预热时相同train、test、all三者在缓存里对应不同文件只预热了train却请求all会直接报错。这三点不匹配就是玄学报错的主要来源。3.4 文本数据集 fetch_20newsgroups 的 subset 与 categories20newsgroups 有 20 个类别约 18000 篇文档做文本分类基线很合适。但它默认只返回train子集很多人以为数据丢了。from sklearn.datasets import fetch_20newsgroups from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.naive_bayes import MultinomialNB from sklearn.pipeline import make_pipeline # 只取 4 个类减少噪声和训练时间 cats [alt.atheism, comp.graphics, sci.med, talk.religion.misc] train fetch_20newsgroups(subsettrain, categoriescats, remove(headers, footers, quotes), data_home./data_cache) test fetch_20newsgroups(subsettest, categoriescats, remove(headers, footers, quotes), data_home./data_cache) model make_pipeline(TfidfVectorizer(max_features20000), MultinomialNB()) model.fit(train.data, train.target) print(model.score(test.data, test.target))remove(headers,footers,quotes)会把邮件头、签名和引用块剥掉避免模型靠邮件域名「作弊」这是 20newsgroups 上最重要的一个预处理开关不加的话准确率会虚高十几个点。categories一旦在 train 和 test 传得不一致target的整数编码含义就会错位预测看着准其实全错。我一般把cats定义成一个常量两处引用同一个变量。4. 合成数据make_classification 的六个关键参数怎么调4.1 参数表每个旋钮实际控制什么真实数据不够用时make_classification是造分类样本的主力。它默认返回 20 维、2 类、100 个样本但默认值几乎必然不是你要的。参数默认值作用调错的后果n_samples100样本总数太少导致交叉验证折内类别缺失n_features20总特征数与n_informative冲突时静默改值n_informative2有信息特征数太小任务过易太大接近噪声n_redundant2信息特征的线性组合制造多重共线性n_repeated0直接复制的信息特征重复列对树模型无影响n_classes2类别数与n_clusters_per_class搭配失衡weightsNone各类别样本比例不设则均衡与实际业务不符flip_y0.01标签随机翻转比例调大模拟标注噪声class_sep1.0类间距离越小越难分random_stateNone随机种子不复现约束关系是n_classes * n_clusters_per_class 2 ** n_informative也就是每个簇至少要占信息空间里的一个超立方体顶点。超了会抛ValueError这是设计上的硬限制不是 bug。4.2 n_informative 与 n_redundant 的配合造一份「像真的」数据真实业务数据的特征往往高度相关。n_redundant就是干这个的每个冗余特征都是若干信息特征的随机线性组合天然带共线性。from sklearn.datasets import make_classification from sklearn.ensemble import RandomForestClassifier from sklearn.linear_model import LogisticRegression from sklearn.model_selection import cross_val_score X, y make_classification( n_samples5000, n_features30, n_informative8, # 8 个真正决定标签的特征 n_redundant10, # 10 个线性组合出来的相关特征 n_repeated2, # 2 个直接复制 n_classes3, n_clusters_per_class1, weights[0.7, 0.2, 0.1], # 模拟真实的不均衡分布 flip_y0.03, # 3% 标签噪声 class_sep0.8, # 类间重叠更多更难分 random_state42, ) print(X.shape, y.shape) # (5000, 30) (5000,) print((y 0).mean(), (y 1).mean(), (y 2).mean()) # 约 0.7/0.2/0.1 for clf in (LogisticRegression(max_iter2000), RandomForestClassifier(random_state42)): s cross_val_score(clf, X, y, cv5, scoringf1_macro) print(f{type(clf).__name__:24s} f1_macro{s.mean():.4f})weights三个值的和会自动归一化不要求恰好为 1。class_sep0.8让类簇重叠配合flip_y0.03能让模型分数落在 0.8–0.9 这个「有难度但不至于不可分」的区间比默认参数下动辄 0.99 的结果更接近真实场景。用f1_macro而不是accuracy是因为在 7:2:1 的不均衡下全预测多数类的准确率也有 0.7看不出模型好坏。注意n_informative n_redundant n_repeated的总和要小于等于n_features余下的特征全是纯噪声。噪声特征比例我一般控制在 30% 以内太高会让树模型的特征重要性排序变得难以判断。4.3 make_blobs / make_moons / make_regression 各自的适用场景make_classification不是万能的不同任务该用不同生成器。from sklearn.datasets import make_blobs, make_moons, make_regression # 聚类球状簇最容易验证 KMeans 的 n_clusters X_blob, y_blob make_blobs(n_samples1000, centers4, cluster_std1.2, random_state42) # 非线性分类两个月牙树模型和 RBF 核 SVM 的主场 X_moon, y_moon make_moons(n_samples800, noise0.15, random_state42) # 回归指定噪声和特征相关性 X_reg, y_reg make_regression(n_samples1000, n_features15, n_informative6, noise12.0, coefTrue, random_state42) print(X_reg.shape, y_reg.shape) # (1000, 15) (1000,)make_blobs的cluster_std控制簇的松散程度簇之间靠centers拉开是无监督里最干净的 sanity check。make_moons的noise建议 0.1–0.2再大就变成纯噪声点集线性模型和 RBF SVM 的差距会消失。make_regression的coefTrue会额外返回真实系数做特征重要性验证时可以直接对比模型学到的权重这是别的生成器给不了的对照物。4.4 random_state 与数据版本可复现性的两个前提合成数据的「随机」是可复现的前提是种子和库版本都不变。import numpy as np from sklearn.datasets import make_classification # 固定 numpy 全局种子并不能保证 make_* 完全一致 np.random.seed(0) A, _ make_classification(n_samples200, random_state42) # 显式传 random_state 才可靠 B, _ make_classification(n_samples200, random_state42) print(np.array_equal(A, B)) # True # 但不传 random_state 时即使全局种子相同也可能不同 C, _ make_classification(n_samples200) D, _ make_classification(n_samples200) print(np.array_equal(C, D)) # False这是血泪经验make_*内部用的是check_random_state传None时取的是全局np.random状态而全局状态会被中间的任意一次随机调用改掉。所以生成器必须显式传random_state别指望np.random.seed兜底。另外跨版本升级 sklearn 时同样的种子也可能生成不同数据内部算法调整过做实验对照时把库版本写进记录里。5. 常见问题与排查五条踩坑记录5.1 load_boston 报 ImportError现象老代码from sklearn.datasets import load_boston直接ImportError: cannot import name load_boston。原因load_boston因数据伦理问题在 1.0 标记弃用1.2 正式移除不是安装坏了。解决换fetch_california_housing()做回归基线或者用fetch_openml(nameboston, version1, as_frameTrue)显式拿到原始数据。后者的target是 DataFrame 列记得.values.ravel()。别再靠降版本到 0.24 续命那条路会连带锁死一堆其他 API。5.2 fetch_* 卡住几分钟后超时现象fetch_openml或fetch_20newsgroups长时间无响应最后抛URLError或TimeoutError。原因默认走外网且无超时配置内网或无外网环境下会一直重试到系统级超时。解决先确认data_home下缓存是否存在不存在就在联网机器上预热后整目录拷过来代码里统一加download_if_missingFalse让缺失立刻暴露。CI 环境里我还会在读数据前加一句目录存在性断言报错信息比 sklearn 的原始异常清楚得多。5.3 交叉验证分数高得离谱现象模型在测试集上 0.99换一份数据就掉到 0.6。原因缩放、特征选择或采样在train_test_split之前就对全量数据做了测试集信息泄露进训练流程。解决把所有预处理塞进Pipeline只对训练折fit。StandardScaler、SelectKBest、SMOTE都属于「必须在折内拟合」的一类。判断方法很直接如果单独把预处理移出 Pipeline 分数就涨那就是泄露。5.4 fetch_openml 的 target 报 unknown label type现象LogisticRegression().fit(X, y)抛ValueError: Unknown label type: unknown。原因fetch_openml返回的target是object/string类型分类器不认识字符串标签。解决LabelEncoder转成整数或者用target列配合OrdinalEncoder。回归任务要注意别把本来连续的target误编码成类别判断依据是看DESCR里对 target 的描述不是看 dtype。5.5 并行跑实验后磁盘被撑满现象跑完一轮GridSearchCV(n_jobs-1)~/scikit_learn_data/joblib/涨到十几 GB。原因joblib 的 memmap 模式会把大数组落盘异常退出时临时文件不会被清理。解决搜索结束后定期joblib.Memory的clear()或者干脆把data_home指到独立数据盘。更彻底的做法是给GridSearchCV传pre_dispatch限制并发任务数减少同时落盘的数组数量。这个坑不致命但很难受机器磁盘告警时往往先怀疑日志最后才发现是缓存。6. 把玩具数据跑成可复现实验Pipeline 串起缩放、切分与交叉验证单看某个load_*没意义真正能带走的是「一份数据从加载到给出可信分数」的固定套路。我的习惯是把整个流程压成一个函数输入是 Bunch输出是分数和训练好的模型中间不落地任何中间变量。from sklearn.datasets import load_breast_cancer from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler from sklearn.decomposition import PCA from sklearn.svm import SVC from sklearn.model_selection import StratifiedKFold, cross_validate def run_baseline(bunch, n_components8, seed42): 加载 - 标准化 - 降维 - 分类全流程折内拟合 pipe Pipeline([ (scaler, StandardScaler()), # 折内拟合避免泄露 (pca, PCA(n_componentsn_components, random_stateseed)), (clf, SVC(kernelrbf, C1.0, gammascale, random_stateseed)), ]) cv StratifiedKFold(n_splits5, shuffleTrue, random_stateseed) res cross_validate(pipe, bunch.data, bunch.target, cvcv, scoring[accuracy, f1, roc_auc], return_estimatorTrue, n_jobs1) return res res run_baseline(load_breast_cancer()) for k in (test_accuracy, test_f1, test_roc_auc): print(f{k:16s} {res[k].mean():.4f} ± {res[k].std():.4f})三个关键点。第一Pipeline里的每个步骤在每折都重新fitStandardScaler的均值和方差只来自训练折这是防泄露的物理隔离比靠人记「别忘了先切分」可靠。第二StratifiedKFold(shuffleTrue, random_stateseed)保证每折的类别比例一致且切分可复现不传random_state时shuffle的结果每次不同前后两次实验没法对比。第三return_estimatorTrue会把每折训练好的模型一起返回需要看特征重要性或做误差分析时直接取res[estimator][0]不用重跑。n_components8是我在 breast_cancer 上试出来的经验值30 维降到 8 维保留约 95% 方差训练时间下降一个量级而准确率基本不掉。但这不是通用值换数据集要看PCA().fit(X).explained_variance_ratio_.cumsum()自己定。验证这套流程有没有写对有个简单的探针把SVC换成DummyClassifier(strategymost_frequent)test_accuracy应该落在多数类占比附近breast_cancer 约 0.63。如果这个数明显偏离说明切分或标签编码出了问题而不是模型有问题。这个反向检查花不了两分钟能省掉大量「为什么我的模型这么差」的排查时间。检查项期望值不达标说明Dummy 基线准确率≈ 多数类占比切分/标签错位折间 std 0.03样本太少或折数过多Pipeline 与非 Pipeline 分数差 0.005存在数据泄露训练/测试分数差 0.1过拟合或正则不足从那以后我每次拿到一份新数据集都会先跑一遍 Dummy 基线把「及格线」钉死再往上叠模型。没有及格线的分数是没有意义的数字。希望帮到你。本文还有配套的精品资源点击获取