简介这份资源是《1. 机器学习前置知识》的配套数据集面向刚入门机器学习、需要动手练习数据处理与建模的初学者及自学者帮助解决缺少真实数据、难以复现教程案例的问题。压缩包共6个文件以csv为主另含1个h5和1个json整体约2.76MB涵盖电影信息、讽刺新闻标题、星巴克门店目录、股票日线行情及网约车出行记录等典型场景可分别用于回归、分类、文本情感分析与时间序列等练习。其中h5文件适合演示高效读写与结构化存储json则便于处理嵌套文本数据。目前已有4364人学习下载说明该数据集在入门阶段具有较高参考价值。读者可借助它完成数据清洗、特征工程、可视化探索到模型训练的完整流程快速建立对机器学习前置环节的直观理解为后续算法学习打下扎实的实操基础。1. 机器学习前置知识配套数据集从零跑通第一个分类任务需要准备什么很多人学机器学习卡住的地方不是算法推导而是打开教程发现第一行就是pd.read_csv(data.csv)可这个data.csv从哪来、长什么样、字段什么含义没人讲。你搜「机器学习入门」出来的全是算法分类图你搜「数据集下载」跳出来的又是各种网盘和注册墙。中间这段空白就是「机器学习前置知识配套数据集」要填的坑。这个方向解决的不是模型精度问题而是让你在学任何算法之前手里先有一套干净、可解释、规模可控的数据。它适合两类人一是刚学完 Python 基础、想跑通第一个分类或回归任务的新手二是带课、带实训需要一套不依赖外网、能离线分发的教学数据集的工程师。核心诉求就一个——数据要能自己造、自己改、自己验证而不是下载完一个压缩包却不知道里面每个字段为什么存在。我一般会把配套数据集拆成三块一份结构化表格数据用于分类和回归一份小规模图像数据用于理解特征和标签的关系再加一份带缺失值和异常值的「脏数据」用于练预处理。下面按这个思路把选型、生成、验证和踩坑讲清楚。2. 配套数据集该放什么三类数据的分工与选型理由2.1 为什么表格数据仍然是前置知识的主战场机器学习入门阶段最容易被低估的是表格数据。iris 数据集之所以被用了这么多年不是因为它简单而是因为它把「特征矩阵 标签向量」这个核心结构暴露得足够干净。四个特征列三个类别一百五十行你可以在脑子里跟踪每一行的走向。但 iris 有个问题太干净了。真实场景里你会遇到缺失值、类别不平衡、量纲差异这些在 iris 上完全练不到。所以配套数据集里的表格部分我建议用「生成 注入噪声」的方式自己造而不是直接拿现成数据。常见做法是用sklearn.datasets.make_classification生成基础数据再手动挖几个洞、改几个异常值。选型理由很直接自己生成的数据你知道每一列的分布、每一个噪声点的位置出问题时能定位到具体行。用现成数据你只能猜。2.2 图像数据在前置阶段的作用不是训练而是理解很多人一上来就想用 yolov8 训练自己的数据集结果卡在标注格式转换上连images和labels的目录结构都没搞明白。前置阶段的图像数据目的不是训出一个能用的检测器而是让你理解三件事图像怎么变成张量、标签怎么和图像对齐、数据增强到底改了什么。所以这部分数据规模要小类别要少最好是自己用脚本生成的几何图形。比如生成一百张 64x64 的图片里面画圆或方块标签就是类别编号。这样你既能练ImageFolder的目录组织又能肉眼验证增强后的图有没有问题。注意前置阶段不要碰 COCO、DOTA 这类大规模数据集下载和预处理的时间成本会直接劝退。2.3 脏数据集的构造原则可控、可复现、可验证脏数据不是随便删几行、改几个值。它需要满足三个条件第一缺失值的比例和位置可控比如固定在第 3 列和第 7 列各挖 10%第二异常值的偏离程度可调比如把某一列乘以 10 倍第三每次生成的脏数据要能通过随机种子复现。我一般会写一个inject_noise函数接收原始 DataFrame 和噪声配置字典返回注入后的数据和一份「噪声日志」。日志里记录哪一行哪一列被改成了什么这样你在练完填充和异常检测后可以拿日志对照验证自己的处理是否正确。这个习惯在真实项目里同样有用——数据清洗最怕的就是改完不知道改了什么。3. 用 Python 生成配套数据集从 make_classification 到脏数据注入3.1 生成基础分类数据并固定随机种子第一步是生成一份干净的基础数据。这里用sklearn.datasets.make_classification它允许你控制样本数、特征数、类别数和类别间的分离度。关键参数是n_informative和n_redundant前者决定有多少个特征真正携带类别信息后者决定有多少个特征是由信息特征线性组合出来的。import numpy as np import pandas as pd from sklearn.datasets import make_classification # 固定随机种子保证每次生成的数据完全一致 RANDOM_STATE 42 X, y make_classification( n_samples500, # 样本数前置阶段控制在 1000 以内 n_features8, # 总特征数 n_informative5, # 其中 5 个是真正有区分度的 n_redundant2, # 2 个由信息特征线性组合而来 n_classes3, # 三个类别方便画散点图观察 n_clusters_per_class1, # 每个类别一个簇避免太复杂 flip_y0.02, # 2% 的标签翻转模拟标注噪声 random_stateRANDOM_STATE ) # 组装成 DataFrame列名用 feature_0 到 feature_7 columns [ffeature_{i} for i in range(X.shape[1])] df pd.DataFrame(X, columnscolumns) df[label] y print(df.head()) print(df[label].value_counts())这段代码的逻辑是先生成一个线性可分但有噪声的三分类数据集然后转成 DataFrame 方便后续操作。flip_y0.02这个参数容易被忽略它的作用是把 2% 的样本标签随机翻转模拟真实场景里标注错误的情况。如果你想让数据更干净把它设为 0如果想练鲁棒性可以调到 0.05。n_informative5意味着有 3 个特征是不携带类别信息的它们的存在是为了让你练特征选择。很多新手拿到数据就把所有列扔进模型结果发现精度上不去原因就是冗余特征太多。前置阶段就要养成「先看特征重要性再决定用哪些列」的习惯。3.2 注入缺失值和异常值的具体写法基础数据生成后下一步是注入噪声。这里要区分两种噪声缺失值用np.nan替换异常值用倍数放大或偏移。注入的位置和比例都要记录方便后续验证。def inject_noise(df, missing_cols, missing_rate, outlier_cols, outlier_scale, seed42): 向 DataFrame 注入缺失值和异常值返回注入后的数据和噪声日志。 missing_cols: 需要挖缺失值的列名列表 missing_rate: 每列缺失的比例 outlier_cols: 需要注入异常值的列名列表 outlier_scale: 异常值的放大倍数 rng np.random.default_rng(seed) df_noisy df.copy() log [] # 注入缺失值 for col in missing_cols: n_missing int(len(df_noisy) * missing_rate) idx rng.choice(df_noisy.index, sizen_missing, replaceFalse) df_noisy.loc[idx, col] np.nan log.append({type: missing, column: col, rows: idx.tolist()}) # 注入异常值把指定列的部分值乘以放大倍数 for col in outlier_cols: n_outlier int(len(df_noisy) * 0.05) # 固定 5% 异常 idx rng.choice(df_noisy.index, sizen_outlier, replaceFalse) df_noisy.loc[idx, col] df_noisy.loc[idx, col] * outlier_scale log.append({type: outlier, column: col, rows: idx.tolist(), scale: outlier_scale}) return df_noisy, pd.DataFrame(log) # 对 feature_2 和 feature_5 挖 10% 缺失值对 feature_1 注入 10 倍异常值 df_noisy, noise_log inject_noise( df, missing_cols[feature_2, feature_5], missing_rate0.1, outlier_cols[feature_1], outlier_scale10.0 ) print(df_noisy.isnull().sum()) print(noise_log.head())参数说明missing_rate0.1表示每列挖掉 10% 的值这个比例在前置阶段比较合适既能让你感受到缺失值处理的必要性又不至于让数据不可用。outlier_scale10.0表示把选中行的值放大 10 倍这个倍数足够让异常值在箱线图上明显暴露出来。噪声日志的作用在后面会体现。当你用均值填充或中位数填充处理完缺失值后可以拿日志里的行号去对照看看填充值和原始值的差距有多大。异常值处理也一样你可以验证自己用的 IQR 或 Z-score 方法有没有把该抓的异常抓出来。3.3 生成小规模图像数据并组织成 ImageFolder 结构图像部分不需要下载任何东西用 PIL 画几何图形就够了。目标是生成一百张 64x64 的图片分圆和方块两类按train/val和类别组织目录。import os from PIL import Image, ImageDraw import random def generate_shapes(root_dir, n_per_class50, img_size64, seed42): 生成圆形和方形两类图片按 ImageFolder 要求的目录结构存放。 目录结构root_dir/train/circle/、root_dir/train/square/ 等 random.seed(seed) splits [train, val] classes [circle, square] for split in splits: for cls in classes: os.makedirs(os.path.join(root_dir, split, cls), exist_okTrue) for cls in classes: for i in range(n_per_class): img Image.new(RGB, (img_size, img_size), white) draw ImageDraw.Draw(img) # 随机位置和大小增加一点变化 x0 random.randint(5, 20) y0 random.randint(5, 20) x1 random.randint(40, 58) y1 random.randint(40, 58) if cls circle: draw.ellipse([x0, y0, x1, y1], fillblue, outlineblack) else: draw.rectangle([x0, y0, x1, y1], fillred, outlineblack) # 前 80% 放 train后 20% 放 val split train if i int(n_per_class * 0.8) else val filename f{cls}_{i:03d}.png img.save(os.path.join(root_dir, split, cls, filename)) print(f生成完毕目录结构{root_dir}) generate_shapes(./shape_dataset, n_per_class50)这段代码的关键在于目录结构。ImageFolder要求每个类别一个子目录图片直接放在类别目录下。train和val分开是为了让你从一开始就养成划分数据集的习惯而不是把所有图片混在一起。生成完后可以用torchvision.datasets.ImageFolder加载验证from torchvision import datasets, transforms transform transforms.Compose([ transforms.Resize((64, 64)), transforms.ToTensor(), ]) train_set datasets.ImageFolder(./shape_dataset/train, transformtransform) print(f训练集样本数{len(train_set)}类别{train_set.classes})如果这一步报错说找不到类别目录八成是目录层级多了一层或少了一层。ImageFolder的规则是根目录下每个子目录是一个类别类别目录下直接是图片文件。中间不能再嵌套。4. 数据集验证与预处理怎么确认自己造的数据没翻车4.1 用描述性统计和可视化做第一轮体检数据生成完第一件事不是扔进模型而是做描述性统计。看每一列的均值、标准差、最小值、最大值以及标签的分布。这一步能抓出大部分低级错误比如某列全是 NaN、标签只有一类、异常值把均值拉飞。import matplotlib.pyplot as plt # 基础统计 print(df_noisy.describe()) # 标签分布 print(df_noisy[label].value_counts(normalizeTrue)) # 箱线图看异常值 fig, axes plt.subplots(2, 4, figsize(16, 8)) for i, col in enumerate([c for c in df_noisy.columns if c ! label]): ax axes[i // 4, i % 4] ax.boxplot(df_noisy[col].dropna()) ax.set_title(col) plt.tight_layout() plt.show()箱线图是前置阶段最实用的异常值检测工具。如果某一列的箱线图上方有一串孤立的点那就是注入的异常值在起作用。你可以对照噪声日志看看这些点的行号是否和日志里记录的一致。如果不一致说明注入逻辑有问题需要回头检查inject_noise函数。提示describe()里如果看到某列的均值远大于中位数通常意味着右偏严重或存在极端异常值。这时候不要急着删先确认是真实分布还是注入的噪声。4.2 缺失值填充的三种策略与选择依据缺失值填充没有万能方法但前置阶段需要掌握三种基本策略均值/中位数填充、众数填充、以及基于模型的填充。选择依据是缺失机制和列的类型。策略适用场景优点缺点均值/中位数填充数值列缺失比例低实现简单不改变均值会压缩方差低估不确定性众数填充类别列或离散数值列保持类别分布可能强化多数类KNN 填充特征间有相关性利用特征关系更合理计算量大对量纲敏感我一般会先用中位数填充数值列因为中位数对异常值不敏感。如果数据里已经注入了异常值用均值填充会把异常值的影响扩散到整个列。填充完要重新看一遍describe()确认均值和标准差没有发生剧烈变化。from sklearn.impute import SimpleImputer # 只对数值列做中位数填充 num_cols [c for c in df_noisy.columns if c ! label] imputer SimpleImputer(strategymedian) df_filled df_noisy.copy() df_filled[num_cols] imputer.fit_transform(df_noisy[num_cols]) print(填充后缺失值总数, df_filled[num_cols].isnull().sum().sum())注意fit_transform和transform的区别训练集用fit_transform验证集和测试集只能用transform否则会数据泄露。这个坑在前置阶段就要刻进肌肉记忆。4.3 特征缩放标准化和归一化到底选哪个特征缩放是另一个容易被跳过但影响巨大的步骤。标准化Z-score把数据变成均值 0、方差 1归一化Min-Max把数据压到 [0,1] 区间。选择依据是算法对数据分布的假设。KNN、SVM、逻辑回归这类基于距离或梯度的算法必须做缩放。决策树和随机森林不需要因为它们只看分裂点不看绝对距离。前置阶段建议统一用标准化因为它的结果更稳定不容易受异常值影响。from sklearn.preprocessing import StandardScaler scaler StandardScaler() X_scaled scaler.fit_transform(df_filled[num_cols]) # 验证缩放结果均值接近 0标准差接近 1 print(缩放后均值, X_scaled.mean(axis0).round(2)) print(缩放后标准差, X_scaled.std(axis0).round(2))如果缩放后某一列的均值或标准差偏离很大先检查这列是不是有极端异常值。标准化对异常值敏感一个 10 倍放大的异常值会把均值和标准差都拉偏。这种情况下要么先处理异常值再缩放要么改用鲁棒缩放RobustScaler。5. 避坑与排查造数据集时最容易翻车的五个地方5.1 随机种子没固定每次生成的数据都不一样现象跑完生成脚本训练模型得到 0.85 的准确率第二天重跑变成 0.72。原因make_classification或train_test_split没有指定random_state每次运行都重新随机。解决所有涉及随机的函数都显式传入random_state并在脚本开头定义一个全局常量。我一般会在文件顶部写RANDOM_STATE 42然后所有地方引用它。这样任何人拿到脚本跑出来的数据都和你完全一致。5.2 缺失值填充时把标签列也填了现象填充完缺失值发现标签列多出了小数原来是SimpleImputer把label列也当成数值列处理了。原因num_cols列表里包含了label。解决在构造列列表时显式排除标签列或者在填充前把标签列单独拿出来。更稳妥的做法是先把特征和标签拆成X和y只对X做填充和缩放最后再拼回去。5.3 ImageFolder 加载时报「Found 0 files」现象目录明明有图片ImageFolder却说找不到文件。原因通常是目录层级不对比如多了一层images文件夹或者图片放在了类别目录的子目录里。解决用os.walk打印实际目录结构对照ImageFolder的要求——根目录下直接是类别文件夹类别文件夹下直接是图片。如果图片格式不是常见的.png或.jpg也要检查后缀名是否被正确识别。5.4 异常值处理把真实极值也删了现象用 IQR 方法删掉异常值后模型精度反而下降了。原因注入的异常值和真实分布的极值混在一起一刀切把两者都删了。解决先拿噪声日志对照确认哪些行是人为注入的哪些是原始数据本身的。如果无法区分改用盖帽法capping而不是删除把超出范围的值截断到边界保留样本量。前置阶段的数据量本来就小删太多行会导致训练集不够。5.5 训练集和验证集用了同一个 scaler 的 fit现象验证集准确率异常高但换一批新数据就崩了。原因在划分训练集和验证集之前就做了缩放导致验证集的信息泄露到了训练过程中。解决严格按「先划分、再 fit、后 transform」的顺序。训练集fit_transform验证集只transform。这个顺序在 sklearn 的Pipeline里会自动处理前置阶段建议手动写一遍把流程刻清楚。6. 把配套数据集用起来从生成到第一个分类器的完整验证造完数据只是开始真正验证这套数据集有没有用的是跑通一个完整流程。我一般会用一个简单的逻辑回归做基线因为它的可解释性强出问题容易定位。流程是加载脏数据、填充缺失、缩放特征、划分训练验证、训练、评估。from sklearn.model_selection import train_test_split from sklearn.linear_model import LogisticRegression from sklearn.metrics import classification_report from sklearn.pipeline import Pipeline # 拆特征和标签 X df_noisy.drop(columns[label]) y df_noisy[label] # 先划分再在 Pipeline 里做填充和缩放避免数据泄露 X_train, X_val, y_train, y_val train_test_split( X, y, test_size0.2, random_state42, stratifyy ) pipe Pipeline([ (imputer, SimpleImputer(strategymedian)), (scaler, StandardScaler()), (clf, LogisticRegression(max_iter1000, random_state42)), ]) pipe.fit(X_train, y_train) y_pred pipe.predict(X_val) print(classification_report(y_val, y_pred))这段代码的关键在Pipeline。它把填充、缩放、分类串在一起fit的时候只在训练集上计算中位数和均值方差predict的时候直接应用到验证集。这样既避免了数据泄露又让流程可复现。如果你手动分步做很容易在验证集上误用fit_transform。跑完基线后可以做一个对比实验把注入的异常值用盖帽法处理掉再跑一遍同样的流程看精度有没有提升。如果提升明显说明异常值确实影响了模型如果没变化说明逻辑回归对异常值不敏感可以换 KNN 再试。这个对比过程本身就是前置知识里最重要的部分——理解不同算法对数据质量的敏感度差异。图像部分也可以用同样的思路验证。用ImageFolder加载生成的形状数据跑一个简单的 CNN 或直接用预训练模型做特征提取确认标签和图像对齐没有问题。如果准确率在 90% 以上说明数据生成和目录组织都是对的如果接近随机猜测先检查标签有没有和图像错位。我自己的习惯是每造一份数据集都会写一个validate.py里面包含描述性统计、缺失值检查、标签分布、可视化抽样、以及一个基线模型。这个脚本跑通数据才算可用。这个习惯帮我省了很多后悔药——有几次模型精度上不去回头跑validate.py才发现是某一列在生成时就被写成了常数。希望帮到你。本文还有配套的精品资源点击获取