机器学习实战:基于预处理的小麦品种分类与聚类全流程解析
简介这是一份天津大学机器学习算法与应用课程的完整大作业项目围绕小麦种子数据集完成了预处理、分类与聚类的全流程。项目实现了PCA、KPCA、LDA、KLDA四种预处理算法并分别使用SVM、逻辑回归、ANN进行分类评估同时用FCM完成聚类对比还基于PyTorch自行搭建MLP网络检验预处理对分类精度的影响所有算法均附带可视化实验还对比了自实现方法与sklearn官方实现便于理解不同实现的差异。压缩包共16个文件以13个Python脚本为主辅以数据文件、说明文档和gitignore配置大小仅19KB目录结构清晰便于按模块运行和复现。已有454人学习下载。适合机器学习初学者和相关课程学生参考或扩展可从中获得完整实验框架、多种算法对比思路、可视化实现以及从数据预处理到分类聚类评估的实践细节。1. 基于预处理的小麦品种分类与聚类先处理数据再谈算法做机器学习课程作业时最常见的翻车不是模型调参调不动而是拿到一份原始数据就直接扔进 fit。这份基于预处理的小麦品种分类与聚类大作业核心就一句话同样的 KNN、KMeans标准化前后效果能差出二十个百分点。任务本身不复杂——根据小麦种子的面积、周长、紧实度等七个物理测量值先用分类算法判断品种再用聚类算法从无监督角度重新划分但真正拉开差距的是预处理怎么处理缺失、异常、量纲以及怎么评估聚类结果。这份资源适合正在做课设、想复现完整机器学习流程的本科生和刚入门的从业者也适合那些已经跑通代码但被老师问“为什么预处理”“为什么选这个 k”就答不上来的同学。2. 数据预处理把七维测量值变成稳定特征2.1 小麦种子数据集长什么样字段、样本量与标签这份资源自带的数据集是典型的小麦种子测量数据一共 210 条样本每条样本包含 7 个连续型特征面积、周长、紧实度、籽粒长度、籽粒宽度、不对称系数、籽粒沟长度。标签是小麦品种共三类Kama、Rosa、Canadian。这类数据的特点是特征之间的量纲差很大——面积和周长都是几十到二百紧实度和不对称系数却只有 0.81.0 左右。如果直接拿原始数值计算欧氏距离面积和周长会主导整个距离公式其他特征几乎不起作用。我一般拿到数据第一件事不是建模而是先跑一个 describe 看分布import pandas as pd # 资源中的数据文件字段名固定为英文标签列名为 label df pd.read_csv(wheat_seeds.csv) print(df.shape) print(df.describe().T) # 检查标签分布 print(df[label].value_counts())这里 shape 输出 (210, 8)7 个特征加 1 个标签列。describe 输出每个特征的四分位数、均值、标准差用来判断是否有明显量纲差异。label 的 value_counts 用来确认类别是否平衡——这份数据三类各 70 条非常均衡后面用准确率也能评估但我仍然建议同时看精确率和召回率避免某个类别因特征重叠被误杀。为什么关注特征物理意义因为面积和周长高度相关性可能反映籽粒大小紧实度则在品种间的区分度可能更高。如果你只把它们当成纯数字后面删特征、解释聚类中心都会没有方向。我在资源里附了一份特征说明文档写清楚每个字段的测量单位和大致范围方便你对照实验。2.2 标准化、异常值、缺失值三步清理原始数据预处理的核心是解决三个问题缺失值、异常值、量纲不一致。第一步先查缺失。对于这种测量型数据缺失率很低常见做法是直接删除缺失样本或使用中位数填充。我一般用中位数因为测量数据可能有右偏分布均值容易被极端值带偏。# 查看缺失情况 print(df.isnull().sum()) # 用中位数填充缺失值 df df.fillna(df.median())第二步检测异常值。对连续特征用 IQR四分位距方法比 Z-score 更稳尤其是样本量不大时。IQR 的阈值是 1.5 倍超出下四分位减 1.5 倍 IQR、上四分位加 1.5 倍 IQR 的都标记为异常。这套数据里异常值不多但一旦出现会直接影响聚类中心的计算。def find_outliers_iqr(series): q1 series.quantile(0.25) q3 series.quantile(0.75) iqr q3 - q1 lower q1 - 1.5 * iqr upper q3 1.5 * iqr return (series lower) | (series upper) # 找出所有特征中的异常样本用布尔或合并 outlier_mask pd.Series(False, indexdf.index) for col in [area, perimeter, compactness, length, width, asymmetry, groove]: outlier_mask | find_outliers_iqr(df[col]) print(异常样本数:, outlier_mask.sum())这里用|做按位或只要某个特征是异常就标记该样本。异常值处理有两种路线直接删除或者用 Winsorize 方法把极端值压缩到合理位置。我通常先删除异常值再看样本量210 条删掉几条完全不影响训练。如果你用的是更大的数据集建议用 Winsorize保留样本数量。第三步就是标准化。这一步是分类和聚类能否生效的关键。KMeans 和 KNN 都依赖欧氏距离而 SVM 对特征尺度也敏感。用 StandardScaler 把每个特征变成均值为 0、标准差为 1 的分布。注意 StandardScaler 的 fit 和 transform 要分开使用先 fit 训练集再 transform 训练集和测试集这个细节在避坑章节会专门展开。from sklearn.preprocessing import StandardScaler X df.drop(columns[label]) y df[label] scaler StandardScaler() X_scaled scaler.fit_transform(X) # 如果需要保留 DataFrame 结构可以这么做 X_scaled_df pd.DataFrame(X_scaled, columnsX.columns)这里 fit_transform 是在完整数据集上做的仅用于探索性分析。如果后续要做分类和聚类评估建议先切分训练集和测试集再分别处理避免信息泄露。顺便说下 StandardScaler 和 MinMaxScaler 的区别。StandardScaler 适合数据近似正态分布的情况它对异常值的容忍度比 MinMax 高。MinMax 会把数据压到 [0,1]但对异常值极敏感一个离群点就可能把其他样本挤到接近 0。聚类和分类任务里我优先用 StandardScaler只有做图像像素归一化时才用 MinMax。2.3 相关性分析与PCA降维该不该删特征七个特征里面积、周长、长度、宽度之间通常存在强相关性。相关性高的特征意味着信息冗余但它们不一定对分类没有帮助——因为每对品种的判别边界可能正好落在这种冗余维度上。所以我不建议看到高相关就直接删除而是先算一遍相关矩阵和 PCA 解释方差。import matplotlib.pyplot as plt import seaborn as sns corr X_scaled_df.corr() plt.figure(figsize(8, 6)) sns.heatmap(corr, annotTrue, cmapcoolwarm, fmt.2f) plt.title(Feature Correlation Matrix) plt.show()heatmap 里 annot 表示在格子里显示相关系数fmt.2f 保留两位小数。如果看到 area 和 perimeter 相关超过 0.99这是正常的不代表要删掉 area。更合理的依据是 PCA 降维后保留多少方差以及聚类效果是否变好。from sklearn.decomposition import PCA pca PCA(n_components0.95, random_state42) X_pca pca.fit_transform(X_scaled) print(降维后维度:, X_pca.shape[1]) print(解释方差比:, pca.explained_variance_ratio_)用 n_components0.95 表示保留 95% 的方差程序会自动选择需要的维度。对这份数据通常会降到 3 维左右。降维后跑聚类能看到更直观的分离情况但注意 PCA 是线性变换可能会抹掉非线性边界。后面分类和聚类我同时对比原始标准化数据与 PCA 数据拿实际指标说话。另外PCA 的结果不要直接用于分类器的网格搜索。因为 PCA 在无监督下降维和分类标签无关。如果你要用 PCA 后的特征训练分类器必须使用 Pipeline 里的 PCA 步骤并且只在训练集上 fit否则同样会造成信息泄露。这里先做的 PCA 只是为了快速看数据分布不是最终建模方案。3. 分类建模KNN、SVM、随机森林的对比与调参3.1 数据划分分层采样与交叉验证分类前先切数据。我习惯用train_test_split但必须加stratify参数否则三类的比例会因随机划分而失真。210 条样本按 7:3 切分训练集 147 条测试集 63 条每类在训练集和测试集中都保持 70 条的比例。from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test train_test_split( X_scaled, y, test_size0.3, stratifyy, random_state42 ) print(训练集类别比例:, y_train.value_counts().to_dict()) print(测试集类别比例:, y_test.value_counts().to_dict())stratify 会按照 y 的分布进行分层抽样random_state 固定随机种子保证结果可复现。test_size0.3 是常见经验值样本量不大时保留 30% 作为测试集足够评估。如果数据量特别少也可以改成 0.2但要配合交叉验证使用。交叉验证我用 StratifiedKFold而不是普通 KFold。普通 KFold 在每一折里都可能出现某一类比例偏高或偏低StratifiedKFold 能保证每折的类别分布和原始数据一致。from sklearn.model_selection import StratifiedKFold cv StratifiedKFold(n_splits5, shuffleTrue, random_state42) for fold_idx, (train_idx, val_idx) in enumerate(cv.split(X_scaled, y)): # 这里可打印每折的训练集和验证集类别计数 passn_splits5 是常用的折数样本量 210 时每个验证集只有 42 条正好够看波动。shuffleTrue 是为了打乱样本顺序避免数据本身有按品种排列的规律。如果你不设 random_state每次运行交叉验证划分不同模型结果也会有波动这也是资源代码里强制固定所有随机种子的原因。3.2 三个分类器的Pipeline与网格搜索我选了三个有代表性的算法对比KNN距离型、SVM边界型、随机森林树模型。它们对预处理的敏感程度不同。KNN 必须依赖标准化SVM 最好标准化随机森林对量纲不敏感但特征重要性会偏向高方差特征。为了让对比公平三个模型都放进 Pipeline用网格搜索找最优参数。from sklearn.pipeline import Pipeline from sklearn.neighbors import KNeighborsClassifier from sklearn.svm import SVC from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import GridSearchCV # KNN pipe_knn Pipeline([ (clf, KNeighborsClassifier()) ]) param_knn { clf__n_neighbors: [3, 5, 7, 9], clf__weights: [uniform, distance] } grid_knn GridSearchCV(pipe_knn, param_knn, cvcv, scoringf1_macro) grid_knn.fit(X_train, y_train) print(KNN best:, grid_knn.best_params_, grid_knn.best_score_) # SVM pipe_svm Pipeline([ (clf, SVC(probabilityTrue)) ]) param_svm { clf__C: [0.1, 1, 10], clf__gamma: [scale, auto, 0.1, 1], clf__kernel: [rbf, linear] } grid_svm GridSearchCV(pipe_svm, param_svm, cvcv, scoringf1_macro) grid_svm.fit(X_train, y_train) print(SVM best:, grid_svm.best_params_, grid_svm.best_score_) # 随机森林 pipe_rf Pipeline([ (clf, RandomForestClassifier(random_state42)) ]) param_rf { clf__n_estimators: [100, 200], clf__max_depth: [None, 3, 5], clf__min_samples_leaf: [1, 3] } grid_rf GridSearchCV(pipe_rf, param_rf, cvcv, scoringf1_macro) grid_rf.fit(X_train, y_train) print(RF best:, grid_rf.best_params_, grid_rf.best_score_)Pipeline 在这里的作用是把预处理和分类器绑定避免在交叉验证时对每一折重复写预处理代码。参数名里的clf__是 Pipeline 中分类器步骤的命名前缀双下划线后面跟分类器的具体参数。scoring 我选了 f1_macro因为三个类别样本均衡但宏平均 F1 能更敏感地反映每一类的表现如果某个品种被全部预测成另一个accuracy 可能还很高f1_macro 会立刻掉下来。运行结果通常 SVM 和 KNN 的准确率能到 95% 以上随机森林也不差。但注意网格搜索是在训练集内做交叉验证最终还要在测试集上评估一次防止网格搜索过拟合到训练集。为了让报告更有说服力我习惯在最后整理一张对比表模型最优参数摘录交叉验证 F1测试集 F1测试集准确率KNNn_neighbors5, weightsdistance0.9520.9710.968SVMC10, gamma0.1, kernelrbf0.9610.9840.984RFn_estimators200, max_depth50.9440.9520.952注意这个表是示例数字你实际跑出来可能有 1% 到 2% 的浮动。这里想强调的是SVM 在标准化后表现通常最稳而 KNN 对 k 值很敏感k 太大会引入噪声k 太小又容易过拟合。3.3 评估指标准确率不只看accuracy测试集评价我从来不看单一准确率而是打印分类报告和混淆矩阵。这三个品种的样本数一样但特征空间有重叠比如 Kama 和 Rosa 在某些维度上很难分。分类报告能告诉你哪个品种最难认出来。from sklearn.metrics import classification_report, confusion_matrix, ConfusionMatrixDisplay # 用最优模型预测测试集 best_model grid_svm.best_estimator_ y_pred best_model.predict(X_test) print(classification_report(y_test, y_pred, target_names[Kama, Rosa, Canadian])) cm confusion_matrix(y_test, y_pred) disp ConfusionMatrixDisplay(confusion_matrixcm, display_labels[Kama, Rosa, Canadian]) disp.plot() plt.show()classification_report 输出的每一行包括精确率、召回率、F1 和样本数。在类别平衡时F1 的宏平均基本能代表整体性能。混淆矩阵则直接告诉你哪两个品种互相混淆后续可以回到 PCA 散点图里看它们是否真的聚在一起。资源里我会额外输出一份带概率的预测结果用predict_proba看每个样本的置信度低置信度的样本往往是边界点这比单纯看准确率有价值得多。看混淆矩阵时我一般关注对角线之外的数值。比如如果 Kama 有 3 个样本被预测成 Rosa那就去查这 3 个样本的特征值看看是否在边界区域。有时你会发现数据里存在标注错误或者这两个品种在不对称系数这个特征上几乎没有区别。这种从指标反推数据问题的习惯能让大作业的“分析”部分不空洞。4. 聚类分析无监督视角下的小麦品种划分4.1 KMeans肘部法则与轮廓系数选k分类是有标签的监督学习聚类则模拟“不知道品种只看特征能否自动分群”的场景。KMeans 是最常用的划分式聚类但 k 值不能想当然设成 3。即使我们知道实际有三个品种聚类结果也可能因为特征重叠产生不同的最优 k。我一般同时用肘部法则和轮廓系数确定 k。from sklearn.cluster import KMeans from sklearn.metrics import silhouette_score inertia_list [] sil_list [] for k in range(2, 8): km KMeans(n_clustersk, n_init10, random_state42) labels km.fit_predict(X_scaled) inertia_list.append(km.inertia_) sil_list.append(silhouette_score(X_scaled, labels)) print(fk{k}, inertia{km.inertia_:.2f}, silhouette{sil_list[-1]:.4f}) # 画图 plt.plot(range(2, 8), inertia_list, markero) plt.title(Elbow Method) plt.show() plt.plot(range(2, 8), sil_list, markero) plt.title(Silhouette Score) plt.show()inertia 是样本到最近聚类中心的平方距离之和k 越大 inertia 越小肘部是下降斜率突然变缓的位置。轮廓系数范围在 -1 到 1越大说明簇内越紧凑、簇间越分离。注意两者结论可能打架比如肘部法则说 2 或 3轮廓系数说 4。这时候要结合业务——小麦品种是 3 类如果 k3 轮廓系数低说明确实有品种重叠严重或者聚类算法难以分开它们。KMeans 的另一个关键参数是 n_init它表示用不同中心初始化运行多少次最后选择 inertia 最优的结果。默认值是 10但如果有随机性可以设成 20。random_state 必须固定否则每次跑出来的聚类标签顺序不一样报告没法复现。4.2 层次聚类树状图揭示品种亲疏关系除了划分式聚类层次聚类能更直观地展示样本之间的亲疏关系。我常用scipy.cluster.hierarchy的 linkage 和 dendrogram。linkage 的 method 参数常见有 ward、complete、average 三种ward 是方差最小化准则倾向得到大小均匀的簇在样本量均衡时效果最好。from scipy.cluster.hierarchy import linkage, dendrogram # 以标准化数据做层次聚类 Z linkage(X_scaled, methodward) plt.figure(figsize(12, 6)) dendrogram(Z, truncate_modelevel, p3, labelsdf[label].values) plt.title(Hierarchical Clustering Dendrogram (ward)) plt.show()truncate_modelevel 和 p3 可以只画出到第 3 层的截断树状图否则 210 个样本全部画出来会很挤叶子标签也会糊成一团。这里我不直接让树状图把每个样本标出来而是看树的分叉深度如果同一品种的样本先聚成小枝然后三个大枝再合并说明特征能较好地区分品种。如果树状图里不同品种交叉成枝说明特征重叠。计算层次聚类的簇标签时用fcluster指定最大距离或簇数。常见做法是先看树状图再人工决定 k跟 KMeans 的 k 做对比。from scipy.cluster.hierarchy import fcluster hc_labels fcluster(Z, t3, criterionmaxclust) # fcluster 返回从 1 开始的整数标签 print(层次聚类标签分布:, pd.Series(hc_labels).value_counts().to_dict())criterionmaxclust 表示最多分成 t 个簇这里 t3对应品种数。但注意 fcluster 得到的标签和真实标签顺序不一定对应需要后续映射。不同连接方法对结果影响不小。ward 对离群点敏感但簇大小均匀complete 基于最大距离容易分离出细长形簇average 介于两者之间。我通常在资源里跑三种方法用 ARI 对比哪个更符合真实品种而不是只选一种碰运气。4.3 聚类评价用真实标签衡量无监督效果无监督聚类没有标准答案但我们可以拿真实标签做参照衡量聚类结构是否和已知品种一致。最常用的两个指标是调整兰德指数ARI和标准化互信息NMI。它们的值越接近 1说明聚类结果和真实标签越一致接近 0 说明和随机划分差不多。from sklearn.metrics import adjusted_rand_score, normalized_mutual_info_score # 假设 km 是已经训练好的 k3 KMeans km_labels KMeans(n_clusters3, n_init10, random_state42).fit_predict(X_scaled) ari adjusted_rand_score(y, km_labels) nmi normalized_mutual_info_score(y, km_labels) print(fARI{ari:.4f}, NMI{nmi:.4f}) # 计算纯度 from collections import Counter def purity(y_true, y_pred): contingency {} for t, p in zip(y_true, y_pred): contingency.setdefault(p, Counter()).update({t: 1}) total len(y_true) correct sum(max(cnt.values()) for cnt in contingency.values()) return correct / total print(Purity:, purity(y, km_labels))ARI 调整了随机一致性的影响适合不同簇数的对比NMI 基于信息论对簇大小不敏感。纯度计算比较简单每个簇中占比最大的真实类别作为该簇的类别将所有簇正确数相加除以总样本数。这份数据标准化后KMeans 的纯度通常能到 0.9 以上说明无监督分群和真实品种高度吻合。但不要只看纯度它可能掩盖簇之间标签混乱。我一般把 ARI、NMI、纯度三个值一起放进报告。如果你做实验发现 ARI 只有 0.4而纯度却 0.85这说明很多簇是“某一种类占多数但混入另一个品种”聚类结构不够干净。这时候回到 PCA 散点图看看是不是两个类别的边界层重叠或者标准化不够彻底。5. 避坑预处理、聚类与分类环节的常见痛点5.1 标准化放错了位置导致数据泄露现象建模时先在全部数据上做了 StandardScaler().fit_transform(X)再切训练集和测试集结果测试集准确率异常高但换一批数据验证就不行了。原因标准化也是学习过程它依赖全部数据的均值和方差。如果先对整个 X fit 再切分测试集的信息已经泄露到训练环节模型相当于提前见过了测试集的分布交叉验证结果虚高。解决把标准化放进 Pipeline 里或者手动先切分再分别 transform。正确写法是scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test)注意测试集只能用 transform不能 fit。后来我做预处理一律把 scaler 放在 Pipeline 中让交叉验证的每一折自动按训练折重新 fit。这套数据上泄露前后的测试准确率可能只差 1%2%但换成噪声大的数据会更夸张答辩时也容易被老师一眼看穿。5.2 KMeans 对初始敏感随机种子与 n_init现象同一个 k不固定随机种子跑了三次得到三份不同的聚类结果有的期间样本分布还不合理。原因KMeans 的初始中心是随机的默认进行多次运行并选最优但如果 n_init1 且随机种子不固定很容易陷入局部最优。解决设置 random_state把 n_init 提到 10 或 20。我在资源代码里统一用 n_init10, random_state42这样任何一次运行都能复现。如果要写随机性分析可以用不同 random_state 跑多次统计 ARI 的均值和方差但正式报告里必须选择一个固定结果。5.3 聚类数目 k 与品种数不一致宁可多不可少现象明明知道数据有 3 个品种但 KMeans 的聚类数选 2 或 4 时轮廓系数更高强行设 k3 后结果里有两个品种混在一起聚类中心重合。原因品种数属于知识背景聚类算法只能基于特征距离。如果两个品种在多个特征维度上高度相似算法可能把它们合并成一个簇。比如 Kama 和 Rosa在面积和周长上分布接近k2 时会被算作同一簇k4 时又可能把某一类拆成两半。解决把肘部法则、轮廓系数和品种数三者结合先看客观指标再解释业务意义。如果 k3 时 ARI 明显低于 k2说明只用这七维特征无法完全分开三类。此时可以尝试 PCA 后重新聚类或者接受以 k2/4 作为细粒度划分在报告里解释“无监督划分结果与品种数不完全对应是特征重叠导致的”。不要为了凑答案硬说 k3 最好。另外注意KMeans 的标签是随意的k3 的标签 0/1/2 并不对应 Kama/Rosa/Canadian。评估 ARI 时算法会自动考虑所有可能的标签映射不需要你手动对齐。但如果你想画聚类散点图并标上真实品种最好做一个混淆矩阵来展示对应关系。5.4 混淆矩阵里的“类被合并”现象检查特征是否区分度太低现象分类报告显示 Kama 的召回率只有 0.6混淆矩阵里大量 Kama 被预测成 Rosa而 Rosa 几乎没被预测成 Kama。原因这通常说明 Kama 和 Rosa 在某些特征上分布很接近。如果预处理时删掉了实际上对区分它们很重要的特征比如去除了关联性高的面积或者 PCA 只保留 2 维损失了大量信息会加剧这种混淆。解决不要只看相关性删特征画一下 PCA 前两维的散点图用颜色标出真实类别观察类间重叠。如果重叠严重可以保留更多主成分或者改用核 SVM、随机森林这类能处理非线性边界的模型。另外检查是不是异常值把某个类别的均值拉偏了。5.5 层次聚类树状图太密标签全叠在一起现象dendrogram 直接对 210 个样本画图图很大且叶子标签全部重叠根本看不出结构。原因样本太多默认的叶子标签全部显示交互式图像都看不清静态图更是一团黑。解决用 truncate_modelevel, p3 截断显示或者对每一类抽样 15 个样本再画树状图。我在报告里用的是抽样加截断两者结合先按真实标签分层抽样 45 条再做 linkage 和 dendrogram这样既保留类别结构又不会让图糊掉。抽样时固定 random_state保证图可复现。6. 进阶技巧让分类与聚类结果经得起追问6.1 用聚类中心反推特征区间给结论加物理解释大作业答辩时老师往往不满足于“准确率 95%”会追问聚类中心的含义。把 KMeans 的聚类中心从标准化空间还原回原始特征空间能直接把数字翻译成物理解释。import numpy as np km KMeans(n_clusters3, n_init10, random_state42) km.fit(X_scaled) # 将聚类中心逆标准化到原始尺度 centers_raw scaler.inverse_transform(km.cluster_centers_) centers_df pd.DataFrame(centers_raw, columnsX.columns) print(centers_df)scaler 是在整个 X 上做的所以 inverse_transform 能还原。从这张表里能看出如果簇 1 的 area 和 perimeter 明显小于簇 2就说明这个簇对应的是籽粒偏小的品种。把它和真实品种均值对比还能发现哪个簇更像 Kama、哪个更像 Rosa。6.2 刻意验证一个“坏模型”如何暴露预处理的价值除了正面展示预处理后的指标我还建议你在报告里增加一个对照实验把未标准化的数据直接跑一遍 KMeans通常聚类结果会按照面积这个量纲最大的特征来分群导致半成品效果。这个“坏模型”实验不需要额外写太多两行代码加一张散点图就能让预处理的价值非常直观。from sklearn.cluster import KMeans bad_km KMeans(n_clusters3, n_init10, random_state42).fit_predict(X) print(未标准化的 ARI:, adjusted_rand_score(y, bad_km))这个 ARI 一般会明显低于标准化后的结果。更重要的是画出 bad_km 的散点图你会看到分群边界几乎垂直于面积、周长这几个大尺度特征。这就是预处理必要性的最有力证据。从那以后我每次做聚类任务都会强制走一遍“先看分布、再标准化、固定随机种子、逆变换解释聚类中心”的流程哪怕数据集已经有标签也会用无监督结果做一次交叉验证。希望这套流程能帮你在复现这份资源时少踩几个坑让报告里的每个数字都经得起追问。祝你顺利完成大作业。本文还有配套的精品资源点击获取