数据挖掘十大算法Python源码实战:从跑通到调优的完整攻略
简介数据挖掘十大算法是数据科学入门与进阶的核心主题一套Python实现合集覆盖Apriori、C4.5、CART、EM、K-means、KNN、PageRank等经典算法面向算法学习者与需要快速上手的开发者帮助理解各算法的原理与落地方式。压缩包共15个文件以7个py源码为主辅以4个xml配置、2个testset数据集、1个md说明和1个iml工程文件整体仅14KB。已有1149人学习下载适合课程实验、竞赛备战和面试复习。通过阅读源码读者可直观对比不同算法的实现细节掌握借助Python生态如sklearn、networkx等完成数据挖掘任务的基本方法并能修改参数、扩展功能加深对十大算法适用边界的理解。1. 数据挖掘十大算法源代码Python为什么别人能跑通你只能看着报错数据挖掘十大算法源代码Python是很多人搜索框里反复出现的一组词。它指的是数据挖掘领域公认的十个经典算法——C4.5、K-Means、SVM、Apriori、EM、PageRank、AdaBoost、kNN、Naive Bayes、CART——在 Python 下的可运行实现。网盘和 GitHub 上存过这份源码的人不少但真正能跑通、能改参数、能讲清每一步的人不多。这份源码的价值不是让你在生产环境部署而是把算法从黑匣子变成可调试的代码。适合课程作业要手写算法的人、准备数据挖掘面试的人以及想在爬虫数据或量化策略之后做特征验证的从业者。我下面按“准备—分类—聚类与关联—踩坑—验证”的顺序把每个算法的核心实现拆开讲。2. 跑通前的通用准备数据清洗、划分与评估一个都不能省十个算法共享同一套数据侧流程读取、清洗、划分、标准化、评估。源代码管理混乱是很多人下载源码后跑不动的第一个原因。我一般会为每个算法建独立目录data 放原始数据output 放结果算法源文件按“读取—清洗—建模—评估”拆函数。如果你把十个算法的数据处理全写在一个文件里后面调参时你会崩溃。这一章先把通用部分解决掉。2.1 用 pandas 和 numpy 做缺失值与离群点清洗数据挖掘源码跑不动八成不是算法问题是数据里有脏数据。缺失值会让 NaN 一路传播到距离计算和树分裂里离群点会让 K-Means 质心漂移、让 SVM 边界变形。常见做法是先看数据的 shape、dtypes、isnull 统计再做针对性处理。import pandas as pd import numpy as np df pd.read_csv(data/raw.csv, encodingutf-8) print(df.shape) print(df.dtypes) print(df.isnull().sum()) # 缺失值处理数值列用中位数填充类别列用众数填充 num_cols df.select_dtypes(include[np.number]).columns cat_cols df.select_dtypes(include[object]).columns df.loc[:, num_cols] df[num_cols].fillna(df[num_cols].median()) df.loc[:, cat_cols] df[cat_cols].fillna(df[cat_cols].mode().iloc[0]) # 离群点用四分位距 IQR 做截断而不是直接删行 for col in num_cols: q1 df[col].quantile(0.25) q3 df[col].quantile(0.75) iqr q3 - q1 lower, upper q1 - 1.5 * iqr, q3 1.5 * iqr df.loc[:, col] df[col].clip(lower, upper)逻辑说明中位数对离群点更稳健比均值填充更适合后续的距离类算法类别列用众数填充避免引入新类别。clip 会把超出 1.5 倍 IQR 的值压到边界而不是删除行样本量不会骤减。1.5 是 Tukey 的默认倍数风控数据里长尾明显时我习惯改成 3.0保留更多极端值。这里特意用 df.loc[:, col] 而不是 df[col] 做赋值是为了避免 pandas 链式赋值的 SettingWithCopyWarning这是 Python 数组切片命令里最常见的坑。2.2 标准化必须在划分之后做先 split 再 fit_transform标准化放错位置是开源源代码里最高频的翻车点。很多人拿到源码后顺手把整个 X 做了一遍标准化再划分训练集和测试集结果测试集的信息已经通过均值和标准差泄漏进了训练过程。这个错误在 KNN、SVM、K-Means 上表现特别明显。from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler X df.drop(label, axis1).values y df[label].values X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42, stratifyy ) scaler StandardScaler() X_train scaler.fit_transform(X_train) X_test scaler.transform(X_test)逻辑说明train_test_split 里的 stratifyy 表示按类别比例分层抽样标签分布不均衡时很有用random_state42 固定随机种子让每次运行结果一致。StandardScaler 在训练集上 fit_transform 会计算每列均值和方差并完成转换测试集只用 transform不会把测试集统计量混进模型。需要注意如果特征是频次类整数计数比如文本 TF 特征可以不做标准化但 KNN、SVM、K-Means 这类基于距离的算法标准化是必选项否则数值大的列会支配距离。提示判断是否泄漏很简单——标准化代码必须写在 train_test_split 之后并且测试集永远不出现 fit_transform。2.3 评估指标分类看混淆矩阵别只打印 accuracy十个算法里大部分是分类器少部分是聚类和关联。评估模块是源码里最容易被忽略的部分很多教学代码只打印 accuracy在类别不平衡时会被严重误导。下面这段评估函数可以在所有分类算法上复用。from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, confusion_matrix y_pred model.predict(X_test) # model 是后续章节中训练好的模型对象 print(accuracy:, accuracy_score(y_test, y_pred)) print(precision:, precision_score(y_test, y_pred, averagemacro)) print(recall:, recall_score(y_test, y_pred, averagemacro)) print(f1:, f1_score(y_test, y_pred, averagemacro)) print(confusion_matrix(y_test, y_pred))逻辑说明averagemacro 表示每个类别单独计算指标后取算术平均比 micro 更能反映少数类表现。混淆矩阵的行是真值、列是预测对角线越亮越好。这里的 y_pred 是模型对象输出的预测结果只要你的模型实现了 fit(X, y) 和 predict(X) 两个接口这一段评估代码可以原封不动地用在手写 KNN、朴素贝叶斯、AdaBoost 上。这就是统一接口的价值算法随便换评估代码不用动。2.4 环境与目录先让 python 命令找到 numpy 和 sklearn下载源码回来后第一件事不是看算法而是确认环境。用 VS Code 打开项目根目录后按 CtrlShiftP 选择解释器常见报错是 ModuleNotFoundError: No module named numpy原因是命令行 python 和 IDE 解释器不是同一个。检查方式如下。python --version python -c import numpy as np, pandas as pd, sklearn; print(np.__version__, pd.__version__, sklearn.__version__)如果打印不出来说明当前环境缺依赖。Windows 下有时还要配置 PYTHONPATH 环境变量指向项目根目录否则命令行能跑、VS Code 里 import 失败。我习惯在项目里放一个 requirements.txt固定 numpy、pandas、scikit-learn、scipy 的版本范围别人拿到源码后只需要 pip install -r requirements.txt。Python 版本建议用 3.9 到 3.11搭配 numpy 1.x之前我在 Python 3.12 上跑旧项目里的 numpy 代码直接编译失败这就是版本兼容的玄学别在环境上浪费时间。3. 分类与回归六大件kNN、朴素贝叶斯、C4.5、CART、SVM、AdaBoost 的 Python 实现十大算法里分类占了六席。这一章给的是可以放进 IDE 直接跑的简版源码重点不是效率而是让你看清每个算法的核心步骤。所有实现都遵循统一的 fit/predict 约定评估就用第 2 章那段代码。3.1 kNN用数组切片写距离再用投票收尾kNN 没有训练过程fit 只是记住训练集预测时计算新样本到所有训练样本的距离取最近的 K 个投票。适合小样本多分类缺点是大数据集上预测很慢。import numpy as np from collections import Counter class KNN: def __init__(self, k3, metriceuclidean): self.k k self.metric metric def fit(self, X, y): self.X_train np.array(X) self.y_train np.array(y) def distance(self, a, b): if self.metric euclidean: return np.sqrt(np.sum((a - b) ** 2)) if self.metric manhattan: return np.sum(np.abs(a - b)) def predict(self, X_test): results [] for a in X_test: dists [self.distance(a, b) for b in self.X_train] k_idx np.argsort(dists)[: self.k] k_labels [self.y_train[i] for i in k_idx] results.append(Counter(k_labels).most_common(1)[0][0]) return np.array(results)参数说明np.argsort 返回从小到大排序的索引[:self.k] 是数组切片操作取前 K 个最近邻。k 太小容易过拟合太大把远处样本也拉进来我一般设奇数并用交叉验证选。metric 换 manhattan 后在高维稀疏特征上距离计算差异明显。这个手写版本在几千样本内可用超过一万条就明显变慢工程上直接用 sklearn.neighbors.KNeighborsClassifier它底层用 KD 树或 BallTree 加速。常见做法是拿 kNN 做基线模型比如把上一份量化交易策略代码里的信号特征丢进去验证分类准确率。3.2 朴素贝叶斯高斯版本覆盖连续特征朴素贝叶斯假设特征之间条件独立后验概率等于先验乘以似然。这个假设在真实数据上经常不成立但它在文本分类和高维稀疏特征上依然能打。class GaussianNB: def fit(self, X, y): self.classes np.unique(y) self.mean {} self.var {} self.prior {} for c in self.classes: X_c X[y c] self.mean[c] X_c.mean(axis0) self.var[c] X_c.var(axis0) self.prior[c] np.log(len(X_c) / len(X)) def predict(self, X): preds [] for x in X: scores {} for c in self.classes: eps 1e-6 likelihood -0.5 * np.sum(np.log(2 * np.pi * (self.var[c] eps))) likelihood - 0.5 * np.sum((x - self.mean[c]) ** 2 / (self.var[c] eps)) scores[c] self.prior[c] likelihood preds.append(max(scores, keyscores.get)) return np.array(preds)逻辑说明高斯朴素贝叶斯假设每个特征服从正态分布fit 阶段按类别统计每列均值和方差。预测时把概率取对数连乘变成连加避免浮点下溢。self.prior 存的是对数先验所以 predict 里可以直接相加。eps 加到方差上防止除零。var 用的是总体方差而不是样本方差sklearn 也是这么做的。如果特征是离散计数比如词频应该换 MultinomialNB原理一样但似然用多项式分布。3.3 C4.5用信息增益率解决 ID3 的偏好问题C4.5 是在 ID3 基础上改进的核心变化是把信息增益换成信息增益率避免模型偏向取值多的特征同时支持连续特征切分。sklearn 没有直接提供 C4.5DecisionTreeClassifier 实际上是 CART所以理解 C4.5 主要靠手写。import numpy as np def entropy(y): _, counts np.unique(y, return_countsTrue) p counts / counts.sum() return -np.sum(p * np.log2(p)) def info_gain_ratio(X, y, feature_idx): values np.unique(X[:, feature_idx]) info 0.0 intrinsic 0.0 for v in values: mask X[:, feature_idx] v sub_y y[mask] weight len(sub_y) / len(y) info weight * entropy(sub_y) if weight 0: intrinsic - weight * np.log2(weight) gain entropy(y) - info return 0 if intrinsic 0 else gain / intrinsic参数说明info 是特征划分后的加权熵越大说明划分后越纯gain 是划分前熵减划分后熵intrinsic 是特征的固有值取值越多的特征 intrinsic 越大从而压低增益率。intrinsic 为 0 说明特征只有一个取值没有划分意义。递归建树时还需要停止条件样本数小于 min_samples_leaf、增益率小于阈值、树深度达到上限否则会无限长。连续特征的处理方法是先排序把相邻值的中点作为候选切分点再算增益率这是 C4.5 区别于 ID3 的关键。3.4 CART用基尼系数做二叉树分裂CART 是 sklearn 默认的决策树实现分类用基尼系数回归用均方误差。它每次都把数据切成两半生成的是二叉树。def gini(y): _, counts np.unique(y, return_countsTrue) p counts / counts.sum() return 1 - np.sum(p ** 2) def find_best_split(X, y): best_gain, best_feat, best_thr -1, None, None for feat in range(X.shape[1]): thresholds np.unique(X[:, feat]) for thr in thresholds: left y[X[:, feat] thr] right y[X[:, feat] thr] if len(left) 0 or len(right) 0: continue weighted len(left) / len(y) * gini(left) len(right) / len(y) * gini(right) gain gini(y) - weighted if gain best_gain: best_gain, best_feat, best_thr gain, feat, thr return best_feat, best_thr逻辑说明gini(y) 接近 0.5 说明正负样本几乎各半越纯越接近 0。find_best_split 遍历每个特征的每个取值用不等式切分计算划分后的加权基尼系数取加权基尼最小的切分点。CART 分类树和回归树的差别只在损失函数回归树用左右子集的加权方差替代基尼系数。生产环境直接用 sklearn 的 DecisionTreeClassifier手写这段的意义在于理解 max_depth、min_samples_split 到底在限制什么。3.5 SVM手写 SMO 太长这里把 sklearn 参数讲到能调SVM 的完整实现是 SMO 算法教学源码通常几百行还容易收敛失败。作为工程方案我建议 SVM 直接用 sklearn 的 SVC把精力放在核函数和正则参数上。这不算偷懒SMO 的数值稳定性问题不是一次博客能解决的。from sklearn.svm import SVC model SVC(kernelrbf, C1.0, gammascale, class_weightbalanced) model.fit(X_train, y_train) print(model.score(X_test, y_test))参数说明C 是误分类惩罚越大越追求完全分对越小越容忍错分C1.0 是常用起点。gamma 控制 RBF 核的影响半径scale 表示按特征数自动计算gamma 太大会过拟合到每个样本太小模型会退化成线性。class_weightbalanced 让少数类获得更高权重。第三个坑是特征缩放SVM 对量纲极度敏感训练前必须做标准化这一点和第 2 章的 scaler 直接相关。3.6 AdaBoost每一轮把错分样本权重加倍AdaBoost 把弱分类器串行组合每轮根据上一轮错误率调整样本权重。实现里最容易被忽视的是标签编码sklearn 的树模型输出 0/1但 AdaBoost 的权重更新公式要求标签是 ±1。class AdaBoost: def __init__(self, n_estimators20): self.n_estimators n_estimators self.models [] self.alphas [] def fit(self, X, y, base_modelNone): n len(y) w np.full(n, 1 / n) for _ in range(self.n_estimators): model base_model() model.fit(X, y, sample_weightw) pred model.predict(X) err np.sum(w * (pred ! y)) / np.sum(w) if err 0.5: break alpha 0.5 * np.log((1 - err) / max(err, 1e-10)) w w * np.exp(-alpha * y * pred) w w / np.sum(w) self.models.append(model) self.alphas.append(alpha) def predict(self, X): preds np.zeros(len(X)) for alpha, model in zip(self.alphas, self.models): preds alpha * model.predict(X) return np.sign(preds)参数说明base_model 传的是弱分类器工厂常见做法是 DecisionTreeClassifier(max_depth1)也就是决策树桩。y 和 pred 必须是 1 和 -1不能是 0 和 1否则权重更新方向会错。err 超过 0.5 时停止因为此时分类器不比随机猜测好。alpha 是该轮模型的权重错误率越低 alpha 越大。错分样本的权重会指数增长下一轮树桩会更关注这些样本。这个版本是二分类实现多分类要换 AdaBoost-SAMME。4. 聚类与关联四件套K-Means、EM、Apriori、PageRank 的实现思路后四个算法分属聚类、概率模型、关联规则和图的排序它们不直接输出类别标签但都是数据挖掘里出场率最高的代码。这一章的手写代码偏向教学版生产环境用 sklearn 和 networkx 替代。4.1 K-Means距离计算、质心更新与空簇兜底K-Means 是最简单的聚类算法但教学源码里有一个真实项目里很常见的坑某个簇在迭代中可能为空直接取均值会报错。下面这版做了空簇兜底。class KMeans: def __init__(self, k3, max_iter100): self.k k self.max_iter max_iter def fit(self, X): rng np.random.default_rng(42) centers X[rng.choice(len(X), self.k, replaceFalse)] for _ in range(self.max_iter): labels np.array([np.argmin(np.sum((X - c) ** 2, axis1)) for c in centers]) new_centers np.array([ X[labels i].mean(axis0) if np.any(labels i) else centers[i] for i in range(self.k) ]) if np.allclose(centers, new_centers): break centers new_centers self.labels_ labels self.cluster_centers_ centers逻辑说明rng.choice 随机从样本里抽 k 个点作为初始质心比随机生成坐标更稳定。分配簇那行对每个质心 c 计算所有样本的欧氏距离argmin 返回最近的簇编号。更新质心时如果某个簇没有样本保留旧质心而不是让程序崩溃。while 循环用 np.allclose 判断质心是否不再移动。K 值的选择常见做法是肘部法画簇内平方和 SSE 随 K 变化的曲线找拐点也可以用轮廓系数辅助判断。K-Means 对初始值敏感真实场景用 sklearn 的 KMeans(n_init10, random_state42)它内部做了多次随机重启取最优。4.2 EM用高斯混合模型理解 E 步和 M 步EM 算法的典型载体是高斯混合模型。E 步计算每个样本属于每个高斯分量的后验概率M 步用这些概率加权更新均值、方差和混合系数。下面是一维高斯混合的教学版本。class GaussianMixture1D: def __init__(self, k2, max_iter100): self.k k self.max_iter max_iter def fit(self, X): n len(X) self.pi np.full(self.k, 1.0 / self.k) self.mu np.linspace(X.min(), X.max(), self.k) self.sigma np.full(self.k, X.var()) for _ in range(self.max_iter): resp np.zeros((n, self.k)) for j in range(self.k): resp[:, j] self.pi[j] * np.exp( -0.5 * (X - self.mu[j]) ** 2 / self.sigma[j] ) / np.sqrt(2 * np.pi * self.sigma[j]) resp resp / resp.sum(axis1, keepdimsTrue) nk resp.sum(axis0) self.pi nk / n for j in range(self.k): self.mu[j] np.sum(resp[:, j] * X) / nk[j] self.sigma[j] np.sum(resp[:, j] * (X - self.mu[j]) ** 2) / nk[j] return resp参数说明self.mu 用 linspace 从最小值到最大值均匀铺开比随机初始化更稳定。E 步结束后 resp 每一行之和为 1表示该样本属于各分量的概率。M 步里 nk 是每个分量的有效样本量mu 是加权平均sigma 是加权平方误差。sigma 如果无限接近 0 会除零实际使用时要加一个 1e-6 的下限。在高维场景直接用 sklearn.mixture.GaussianMixture它的 covariance_type 参数可以控制方差是共享还是独立调参思路和这里一致。EM 的另一大用途是对缺失数据做参数估计这是它入选十大算法的重要原因。4.3 Apriori频繁项集生成与最小支持度阈值Apriori 是关联规则算法核心思想是“频繁项集的子集一定频繁”用这个性质剪掉大量候选集。真实项目里常用于购物篮分析和爬虫数据里的共现关系挖掘。from itertools import combinations def apriori(transactions, min_support0.2): n len(transactions) items sorted(set(i for t in transactions for i in t)) freq {frozenset([i]): sum(1 for t in transactions if i in t) / n for i in items} freq {k: v for k, v in freq.items() if v min_support} all_freq dict(freq) k 2 while True: candidates set() for a in freq: for b in freq: merged a | b if len(merged) k: candidates.add(merged) next_freq {} for c in candidates: support sum(1 for t in transactions if c.issubset(t)) / n if support min_support: next_freq[c] support if not next_freq: break freq next_freq all_freq.update(freq) k 1 return all_freq参数说明第一轮统计单个商品的支持度过滤掉低于 min_support 的项。候选集生成用两个 k-1 项集求并集长度等于 k 才保留集合天然去重。issubset 判断事务是否包含候选集统计支持度。这个实现正确但慢数据量大时换 mlxtend.frequent_patterns.apriori接口是 DataFrame底层做了优化。min_support 是关键参数太小候选集爆炸太大挖不出规则。我的习惯是先设 0.1 看频繁项数量再逐步上调到结果开始稀疏为止。4.4 PageRank用幂迭代算网页权重PageRank 属于图算法但它是十大算法里唯一面向排序的。核心思想一个页面的重要性由链入它的页面数量和这些页面本身的重要性决定。def pagerank(links, damping0.85, max_iter100, tol1e-6): pages list(links.keys()) n len(pages) idx {p: i for i, p in enumerate(pages)} M np.zeros((n, n)) for p, out in links.items(): if out: for q in out: M[idx[q], idx[p]] 1.0 / len(out) r np.full(n, 1.0 / n) for _ in range(max_iter): r_new damping * M r (1 - damping) / n if np.linalg.norm(r_new - r, 1) tol: break r r_new return dict(zip(pages, r))参数说明links 是字典键为页面值为该页面链出的页面列表。M 是列随机矩阵每一列表示某个页面将权重平分给链出的目标页。r 是页面权重向量初始化为均匀分布。迭代公式里 damping 是阻尼系数默认 0.85代表用户按链接跳转的概率剩下 15% 随机跳到任意页面这保证了没有出链的页面不会把权重彻底吸干。tol 是收敛阈值用 L1 范数变化量判断。数据量大时直接用 networkx.pagerank底层同样做幂迭代但对稀疏矩阵做了优化。PageRank 的思想后来也被迁移到了反作弊和推荐系统里比如给用户和商品构建二部图算重要性。5. 避坑数据挖掘十大算法 Python 实现的五个常见翻车点这一章把源码调试中最高频的五个问题集中梳理一遍。每一条都是现象、原因、解决三步直接对应你复现时会遇到的报错。5.1 pandas 读 CSV 报 UnicodeDecodeError现象pd.read_csv(data.csv) 一执行就崩报错信息集中在 utf-8 codec cant decode。原因数据文件是 GBK 或 GB2312 编码Python 3 默认用 utf-8 打开文本文件。解决读取时指定编码编码不确定就多试几次。python df pd.read_csv(data.csv, encodinggbk)如果还报错改成 encodinggb18030Windows 下导出的 Excel CSV 经常是这个情况。另一个更省事的办法是用记事本打开文件后另存为 UTF-8 with BOMpandas 能直接识别 BOM 头。 ### 5.2 标准化放错位置导致分数虚高 现象训练集准确率接近 98%交叉验证也不差但上线后效果崩盘。 原因代码里在 train_test_split 之前就对整个 X 做了 fit_transform测试集的均值和方差被提前用于训练属于数据泄漏。 解决严格按第 2.2 的顺序写——先 split再在训练集上 fit_transform测试集只调用 transform。这个坑在 KNN 和 SVM 上表现最明显因为这两个算法都依赖特征距离。检查方法很简单搜索项目里有没有 fit_transform 出现在 train_test_split 之前的代码。 ### 5.3 K-Means 聚类结果每次运行都不一样 现象同样的数据两次运行聚类标签和质心完全不同报告没法写。 原因K-Means 的初始质心是随机选择的不同初始值会收敛到不同局部最优解。 解决在源码里显式固定随机种子。python from sklearn.cluster import KMeans model KMeans(n_clusters3, n_init10, random_state42)n_init10 表示从 10 组不同初始质心里选 SSE 最小的一组比单次运行更稳定。手写版本里用 np.random.default_rng(42) 代替无种子随机也能复现。这里没有统一答案只要保证报告里所有实验用同一个 random_state 即可。5.4 AdaBoost 训练集 100% 但测试集下降现象训练集准确率接近满分测试集却比决策树还差。原因n_estimators 过大样本权重过度集中在个别噪声样本上模型开始拟合异常点。解决降低 n_estimators同时限制弱分类器复杂度。树桩用 max_depth1 默认就是一个切分点但如果数据噪声大可以把弱分类器换成 max_depth2 再配合早停。另一个容易被忽视的点是标签编码我的 AdaBoost 源码要求 y 和 pred 是 ±1如果直接传入 0/1 标签权重更新方向就是错的导致训练集也飘。调试时可以打印每一轮的 err如果 err 长期不变说明标签编码出了问题。5.5 Apriori 候选集爆炸内存被吃满现象min_support 设了 0.01程序运行十几分钟后内存直线上升最后卡死。原因支持度阈值过低频繁项集数量指数级增长候选集组合数量超出预期。解决先把 min_support 提到 0.2 或 0.3 跑一遍观察频繁项集规模确认逻辑正确后再逐步下调。数据量大时换用 mlxtend.frequent_patterns.apriori它在生成候选集时做了剪枝并且支持稀疏 DataFrame。还有一点很多人踩过事务数据里如果每条记录重复出现支持度会被虚高最好先去重再跑。6. 验证与进阶用交叉验证和特征重要性把结果讲到别人信十个算法的源码都跑通之后下一步不是急着换模型而是做横向验证。交叉验证比单次划分更稳特征重要性则帮你解释模型到底在依赖什么。下面这段代码把五个分类器装进同一套交叉验证流程。from sklearn.model_selection import cross_val_score from sklearn.neighbors import KNeighborsClassifier from sklearn.naive_bayes import GaussianNB from sklearn.tree import DecisionTreeClassifier from sklearn.svm import SVC from sklearn.ensemble import AdaBoostClassifier models { kNN: KNeighborsClassifier(n_neighbors5), NB: GaussianNB(), CART: DecisionTreeClassifier(max_depth5), SVM: SVC(kernelrbf, C1.0, gammascale), AdaBoost: AdaBoostClassifier(n_estimators50), } for name, model in models.items(): scores cross_val_score(model, X_train, y_train, cv5) print(f{name}: {scores.mean():.4f} (/- {scores.std():.4f}))参数说明cv5 表示做 5 折交叉验证每个模型会训练 5 次并得到 5 个分数输出均值和标准差。均值反映模型水平标准差反映稳定性如果某个模型均值高但标准差很大说明它对数据划分很敏感。交叉验证的结果比单次 train_test_split 更可信论文和汇报里也用这个口径。进一步还可以看树模型的特征重要性判断哪些字段在驱动预测。tree DecisionTreeClassifier(max_depth5, random_state42) tree.fit(X_train, y_train) for name, imp in zip(feature_names, tree.feature_importances_): print(f{name}: {imp:.4f})我自己做这类源码项目时有一个习惯拿到任何一份开源实现先不调参数用默认配置跑通再看训练集分数和交叉验证分数是否一致。如果训练集很高而交叉验证很低说明过拟合先限制模型复杂度如果五个算法都卡在同一个准确率问题大概率在特征而不在算法。把这一套验证流程走完你对这份源码的理解会比单纯跑通深得多。希望帮到你。本文还有配套的精品资源点击获取