ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

数据挖掘十大算法Python源码详解:从能跑到跑明白

2026/9/29 19:46:01 拓冰建站 浏览量
数据挖掘十大算法Python源码详解:从能跑到跑明白 简介数据挖掘十大算法源代码Python面向数据挖掘初学者和算法研究者精选Apriori、C4.5、CART、EM、K-means、KNN、PageRank等经典算法的Python实现帮助读者在真实代码中理解关联规则、决策树、聚类、分类与网页排序的核心原理。压缩包共包含15个文件以7个Python脚本为主体搭配XML工程配置、testset测试数据集、Markdown说明文档和项目元文件整体体积仅14KB结构简明便于快速下载、运行与修改。Markdown文档概括了整体结构与用法testset目录提供验证算法的实验数据各模块按算法名称分目录组织便于单独调试。目前已有1149人学习适合入门级数据挖掘学习者作为算法源码参考。通过阅读这些实现读者可掌握各算法的参数设置与流程设计理解不同算法在真实数据上的表现并利用自带测试集验证效果为后续项目中的算法选型与调优奠定扎实基础。1. 数据挖掘十大算法源代码Python让十个经典算法跑通容易跑明白很难上周有个正在做毕业设计的读者问我网上那些“数据挖掘十大算法源代码Python”到底能不能直接用我的回答是能跑起来的概率不小能跑明白的概率很低。标题里的十大算法多数场合指 ICDM 评选出的 C4.5、K-means、SVM、Apriori、EM、PageRank、AdaBoost、kNN、朴素贝叶斯、CART覆盖分类、聚类、关联规则和图上排序四类问题。对课程实验、面试手撕算法、或者刚接量化交易和风控建模项目想补模型底座的人来说这十段代码是最值得反复读的说明书。这类源码包的正确用法不是下载下来双击运行而是把数据接口、评估方式和参数边界统一起来。它真正能解决的问题是在同一个 Python 环境里用一套格式约定把十种算法的原理实现、参数影响和边界条件对照清楚。因此这篇不会去介绍某个神奇仓库也不装成谁的原稿我按自己复现这套题目的思路来写先分组选型再搭最小管线然后把最常见的五类坑和参数选择讲透。2. 十大算法分成三组手写实现和直接调库之间怎么划界如果对着“源代码”三个字把十个算法都从头手写通常会在 SVM 和 PageRank 上浪费大量时间。我的习惯是先分组分类组六个聚类与 EM 组两个关联与图组两个。不同组的代码策略完全不同能在一百行内写清原理的就手写优化器复杂或数值细节密集的就用 sklearn 做对照。2.1 分类六件套kNN 和朴素贝叶斯手写SVM 和 AdaBoost 交给 sklearnkNN 是分类组里最适合手写的算法因为核心只有距离计算和多数投票。这是常见源码包里最精简的一版import numpy as np from collections import Counter def knn_predict(X_train, y_train, X_test, k3): preds [] for x in X_test: # 每个测试样本与全部训练样本的欧式距离 dists np.linalg.norm(X_train - x, axis1) k_idx np.argsort(dists)[:k] k_labels y_train[k_idx] # 多数投票平票时按 Counter 的枚举顺序取其一 preds.append(Counter(k_labels).most_common(1)[0][0]) return np.array(preds)逻辑很简单np.linalg.norm(X_train - x, axis1)一次算出该样本到所有训练样本的欧式距离argsort取最近的前 k 个Counter.most_common(1)做多数投票。但很多源码里漏掉最关键的一步特征标准化。如果第一维是厘米、第二维是克距离会被数值大的特征完全主宰kNN 结果立刻失真。所以调用前先做StandardScaler。朴素贝叶斯手写版也不难常见实现会直接用高斯概率密度import numpy as np class GaussianNB: def fit(self, X, y): self.classes np.unique(y) self.means {} self.vars {} self.priors {} for c in self.classes: Xc X[y c] self.means[c] Xc.mean(axis0) self.vars[c] Xc.var(axis0) 1e-6 # 防除零 self.priors[c] len(Xc) / len(y) def predict(self, X): preds [] for x in X: log_prob {} for c in self.classes: var self.vars[c] # 对数域计算避免连乘下溢 prob -0.5 * np.sum(np.log(2 * np.pi * var)) \ - 0.5 * np.sum((x - self.means[c]) ** 2 / var) log_prob[c] prob np.log(self.priors[c]) preds.append(max(log_prob, keylog_prob.get)) return np.array(preds)这里的两个细节常被忽略。一是var加1e-6高维特征如果某一维方差接近 0分母会变成无穷大二是对数域计算连续特征的概率密度连乘之后很容易下溢成 0取对数后就安全。手写版本跑通后再用 sklearn 的GaussianNB对同一份数据做对比分数一致就说明核心公式写对了。SVM 和 AdaBoost 我一般不会手写。SVM 的 SMO 优化器没有几百行很难调稳AdaBoost 的权重更新公式虽短但和决策树桩组合时弱学习器深度设错会让结果比随机还差。所以这里直接用 sklearn 封装源码里的价值是展示“训练管线的标准写法”from sklearn.pipeline import make_pipeline from sklearn.preprocessing import StandardScaler from sklearn.svm import SVC from sklearn.ensemble import AdaBoostClassifier from sklearn.tree import DecisionTreeClassifier svm make_pipeline( StandardScaler(), SVC(C1.0, kernelrbf, gammascale, random_state42) ) ada AdaBoostClassifier( estimatorDecisionTreeClassifier(max_depth1), n_estimators200, learning_rate1.0, random_state42 )make_pipeline保证每次 fit 先算标准化参数再进入 SVC 训练。SVM 对特征尺度极度敏感这一步省掉后面调 C 都白调。AdaBoost 里DecisionTreeClassifier(max_depth1)是典型的树桩弱学习器n_estimators从 200 起比较稳妥太低欠拟合太高在小数据集上容易过拟合。若你在旧版 sklearn 上看到base_estimator参数名那是因为版本升级后改名成了estimator参数名不同不代表算法变了。C4.5 和 CART 放在同一组讲。严格说 sklearn 的DecisionTreeClassifier是 CART 的变体不是 C4.5。C4.5 的源码实现通常围绕信息增益率展开要在 iris 这种连续特征上工作必须先做离散化。核心代码一般长这样import numpy as np def entropy(y): _, counts np.unique(y, return_countsTrue) p counts / counts.sum() return - (p * np.log2(p)).sum() def gain_ratio(X_col, y): base entropy(y) total len(y) split_ent 0.0 for v in np.unique(X_col): mask X_col v split_ent (mask.sum() / total) * entropy(y[mask]) gain base - split_ent iv entropy(X_col) # 固有值 return gain / iv if iv 1e-9 else 0.0entropy是标准信息熵gain_ratio把信息增益除以固有值这就是 C4.5 相对 ID3 的核心改进不再偏向取值更多的特征。但完整树构建还需要递归选择最佳分裂特征、处理缺失值和剪枝这里只展示最核心的评分函数。源码里的坑是这个函数只接受离散列直接把花萼长度塞进去每个值都自成一类信息增益比会严重失真。我一般先用pd.qcut分箱再调gain_ratio。2.2 聚类与 EM手写 K-meansEM 用 GaussianMixture聚类组只有 K-means 和 EM一般指高斯混合模型 GMM。K-means 是迭代算法里最适合手写的因为只有两步分配类和更新中心。import numpy as np def kmeans(X, k, max_iter100, random_state42): rng np.random.default_rng(random_state) centers X[rng.choice(len(X), sizek, replaceFalse)] for _ in range(max_iter): # 所有样本到所有中心的距离形状 (n_samples, k) dists np.linalg.norm(X[:, None, :] - centers, axis2) labels dists.argmin(axis1) new_centers [] for i in range(k): cluster X[labels i] # 空簇保留旧中心避免某个中心被彻底丢弃 new_centers.append(cluster.mean(axis0) if len(cluster) 0 else centers[i]) new_centers np.array(new_centers) if np.allclose(new_centers, centers): break centers new_centers return centers, labelsX[:, None, :] - centers的广播机制把距离计算一次完成argmin(axis1)给每个样本分配最近的簇。空簇处理是手写版最容易翻车的地方如果某个中心离所有样本都太远它可能一直没有成员cluster.mean直接报错。保留旧中心只是兜底更好的做法是把它重新初始化到方差最大的簇附近但那样代码复杂度会上一个台阶演示场景下保留旧中心已经够用。GMM 不建议从头写。EM 的 E 步要算每个样本对每个高斯分量的后验M 步要更新均值、协方差和权重数值上稍有不慎协方差矩阵就奇异。sklearn 的GaussianMixture把初始化、协方差正则和收敛判断都做好了源码里的正确用法是from sklearn.mixture import GaussianMixture gmm GaussianMixture( n_components3, covariance_typefull, init_paramskmeans, reg_covar1e-6, random_state42 ) gmm.fit(X_std) labels gmm.predict(X_std)init_paramskmeans用 K-means 结果做 EM 的起点比随机初始化稳定得多reg_covar1e-6在协方差矩阵对角线加正则防止奇异。小数据集上尤其明显——直接random初始化可能让某个高斯分量退化成一个点然后那一个簇吃掉 90% 的样本。后面第 4 章会细讲这个现象。2.3 关联与图Apriori 和 PageRank最值得保留源码的两段Apriori 和 PageRank 都不依赖梯度优化逻辑直白但非常容易写错。先看 Apriori。下面这种用frozenset做支持度计数的写法是可读性和性能折中最舒服的版本from collections import defaultdict from itertools import combinations def apriori(transactions, min_support0.4): n len(transactions) item_counts defaultdict(int) for t in transactions: for item in t: item_counts[item] 1 freq {frozenset([i]): c / n for i, c in item_counts.items() if c / n min_support} all_freq dict(freq) k 2 while freq: # 用频繁 k-1 项集连接生成候选 k 项集 candidates set() items_list list(freq) for a, b in combinations(items_list, 2): cand frozenset(a | b) if len(cand) k and len(a b) k - 2: candidates.add(cand) # 一次扫描统计候选支持度 counts defaultdict(int) for t in transactions: ts frozenset(t) for cand in candidates: if cand ts: counts[cand] 1 freq {items: c / n for items, c in counts.items() if c / n min_support} all_freq.update(freq) k 1 return all_freq关键点有两个候选生成只用“频繁 k-1 项集”之间做连接而不是枚举所有子集这是 Apriori 剪枝的核心支持度判断用cand tsfrozenset的集合包含操作比列表遍历快得多。连接时用len(a b) k - 2保证两个项集只有一个元素不同能减少重复候选。这段代码没有做候选连接后的二次剪枝真实大数据集建议直接用 mlxtend 的apriori做对照但课程设计和小规模数据上这段足够读懂原理。PageRank 要注意悬空节点。一个没有出链的网页会把权重“吞掉”不处理的话分数总和越迭代越小。常用实现如下import numpy as np def pagerank(adj, d0.85, max_iter100, tol1e-6): n adj.shape[0] adj adj.astype(float) # 悬空节点出度为 0 的行改成均匀跳转到所有节点 dangling adj.sum(axis1) 0 adj[dangling] 1.0 out_deg adj.sum(axis1, keepdimsTrue) M (adj / out_deg).T # 概率转移矩阵 r np.ones(n) / n for _ in range(max_iter): r_new (1 - d) / n d * (M r) if np.linalg.norm(r_new - r, 1) tol: break r r_new return rdangling行在归一化之前被替换成 1.0再除以出度后等于均匀分布模拟“随机跳转”。阻尼系数 d 不能省它保证图即便有循环也能收敛到唯一解。判断收敛用 L1 范数比检查单个节点变化更安全。这段代码在开源包里被改坏的频率很高建议拿到手先存个备份。2.4 读源码包的顺序先看接口再看评估入口下载“数据挖掘十大算法源代码”这类免费 Python 源码大全时我发现不少版本名不副实一半文件只是 sklearn 脚本真正有实现细节的只有 kNN 和 K-means。我的读包顺序固定三步。第一步看每个文件有没有稳定的主函数签名比如fit(X, y)和predict(X)如果十个算法有十种签名先写适配层不要改原代码。第二步看数据入口是 numpy 数组还是文件路径统一转成X, y两个变量。第三步看输出里有没有统一的评估指标没有的话自己补否则十个算法根本没法横向比。这一步决定后面整个最小管线能不能搭起来。3. 跑通十个算法的最小管线环境、数据、出口统一起来第一个目标只是让每个文件单独能跑第二个目标才是真正给你可复现的东西——一个文件跑完十个算法输出一张能对比的表。这章按我实际搭管线的方式展开。3.1 环境隔离与依赖安装先把 Python 版本和库锁死很多所谓免费源码大全里的旧脚本在 Python 3.10 以上会遇到np.int不存在、sklearn参数改名这类问题。因此我建项目的第一件事是创建独立虚拟环境而不是往系统 Python 里装包。python3 -m venv .data_mining_venv source .data_mining_venv/bin/activate # Windows 用 .data_mining_venv\Scripts\activate python -m pip install --upgrade pip pip install numpy pandas scikit-learn scipy mlxtend matplotlibvenv保证后续装包不污染其他项目python -m pip比直接pip更保险遇到多版本 Python 时不会装错地方。这个管线里真正必要的只有 numpy、pandas、scikit-learnmlxtend 用于和手写 Apriori 做对照matplotlib 在画轮廓系数和混淆矩阵时用。装完跑一句python -c import sklearn; print(sklearn.__version__)把输出版本记到 README这是后面排查“换台机器结果不一样”的后悔药。3.2 数据准备一份鸢尾花、一份购物篮、一张小图分类和聚类共用鸢尾花因为 150 条样本、4 个特征、3 类标签大小适中任何算法跑完都不超过几秒。关联规则用一个 10 条左右的交易模拟数据集尺寸小而能展示频繁项集的完整过程。PageRank 用手写邻接矩阵。import numpy as np import pandas as pd from sklearn.datasets import load_iris iris load_iris() X iris.data y iris.target # 关联规则每笔交易是商品的 frozenset transactions [ {牛奶, 面包, 鸡蛋}, {面包, 尿布, 啤酒, 鸡蛋}, {牛奶, 尿布, 啤酒, 可乐}, {面包, 牛奶, 尿布, 啤酒}, {面包, 鸡蛋, 牛奶}, {牛奶, 面包, 尿布}, {尿布, 啤酒, 可乐}, {牛奶, 面包, 鸡蛋, 啤酒}, {面包, 鸡蛋, 可乐}, {牛奶, 面包, 尿布, 鸡蛋}, ] transactions [frozenset(t) for t in transactions] # PageRank 的简单有向图 adj np.array([ [0, 1, 1, 0], [0, 0, 1, 1], [1, 0, 0, 1], [0, 0, 1, 0], ])鸢尾花直接用 sklearn 自带接口不需要下载外部文件购物篮用手工列表可控可审计。这里我想强调的落地原则是数据集引入越少越好先用一个已知答案的数据把代码逻辑验对再做真实数据。很多项目一上来就灌百万行业务数据结果算法有没有写错都不知道。3.3 统一评估入口分类、聚类、关联、图四个出口现在把十套算法包进同一个run_all.py。分类出口用交叉验证输出平均准确率from sklearn.model_selection import cross_val_score from sklearn.tree import DecisionTreeClassifier from sklearn.svm import SVC from sklearn.ensemble import AdaBoostClassifier from sklearn.neighbors import KNeighborsClassifier from sklearn.naive_bayes import GaussianNB models { CART: DecisionTreeClassifier(random_state42), SVM: SVC(C1.0, kernelrbf, gammascale, random_state42), AdaBoost: AdaBoostClassifier(random_state42), kNN: KNeighborsClassifier(n_neighbors5), NaiveBayes: GaussianNB(), } for name, model in models.items(): scores cross_val_score(model, X, y, cv5, scoringaccuracy) print(f{name}: {scores.mean():.4f} /- {scores.std():.4f})cv5默认分层采样每一折的类别比例和整体保持一致这对 iris 的意义不大在不平衡数据上是必须的。若要加入手写 kNN 或朴素贝叶斯需要把它们包装成 sklearn 风格类提供fit和predict方法才能进cross_val_score。C4.5 没有 sklearn 原生实现这里用 CART 暂代手写 C4.5 的验证单独写另一个脚本避免把不兼容代码塞进同一套评估。聚类出口轮不到准确率因为聚类不知道真实类标用轮廓系数from sklearn.preprocessing import StandardScaler from sklearn.cluster import KMeans from sklearn.mixture import GaussianMixture from sklearn.metrics import silhouette_score X_std StandardScaler().fit_transform(X) for name, model in [ (KMeans, KMeans(n_clusters3, initk-means, n_init10, random_state42)), (GMM, GaussianMixture(n_components3, covariance_typefull, init_paramskmeans, reg_covar1e-6, random_state42)), ]: labels model.fit_predict(X_std) sil silhouette_score(X_std, labels) print(f{name}: silhouette{sil:.4f})StandardScaler对 K-means 是必要条件不然花萼长度的大数值会压过花瓣宽度。GMM 加上reg_covar避免小数据集协方差奇异。轮廓系数区间是 -1 到 1数值越高表示簇内紧凑、簇间分离明显它不需要真实标签所以也适合交叉验证没有意义的情况。关联出口直接打印频繁项集和对应支持度freq_items apriori(transactions, min_support0.3) for items, sup in sorted(freq_items.items(), keylambda kv: kv[1], reverseTrue): print(set(items), round(sup, 3))PageRank 出口打印排序后的分数pr pagerank(adj, d0.85) rank_order np.argsort(pr)[::-1] print(PageRank:, np.round(pr, 4), order:, rank_order)四个出口写完后run_all.py就是你的回归基线。以后改任何一段源码先跑这个脚本看分数有没有变化分数变了先怀疑自己改坏了而不是数据集变了。3.4 运行顺序先快后慢先小后大这十条算法跑 iris 都是秒级但换到真实数据就不一样。我的习惯是先跑 kNN、朴素贝叶斯、CART再跑 SVM、AdaBoost、GMM最后跑 Apriori 和 PageRank。Apriori 的复杂度随 min_support 下降呈指数上升第一次跑不要把阈值设太低PageRank 的迭代在稀疏大图上每次矩阵乘法都可能拖慢先用小图验证收敛再上全量。把运行顺序写进 README 的“先看这里”比写完代码就丢给后人更负责任。4. 复现十大算法源码时最常见的 5 类翻车现象、原因、处理这章全是我自己的血泪经验。每条按“现象 → 原因 → 处理”三个环节写。前两类最频繁后三类比较隐蔽但遇到一次就能折腾一下午。4.1 K-means 结果每次跑都不一样现象同一个kmeans函数在同一份数据上跑三次三次的聚类标签和中心点都不一致有时连簇的顺序都会换。用准确率对比时就发现“昨天 0.9今天 0.6”。原因手写版用np.random.choice选初始中心每次起点不同旧版 sklearn 的KMeans如果没设置random_state初始化同样随机。K-means 是非凸问题初始点不同就可能落在不同局部最优。处理手写版显式传入random_state并且把初始中心保存进返回值方便复现。调 sklearn 版时设置initk-means、n_init10、random_state42from sklearn.cluster import KMeans km KMeans(n_clusters3, initk-means, n_init10, random_state42) labels km.fit_predict(X_std)不要相信“多跑几次取最好”是稳定做法那是拿随机性碰运气。回归测试必须固定种子如果你在手写版本里每次都要做多次初始化对比至少把每次初始中心打印出来否则你连“到底哪次跑得好”都说不清。4.2 Apriori 一到稍微大的数据就卡死现象交易数据只有几百行apriori函数却跑了好几分钟内存占用持续上涨最后进程被系统杀掉。原因很多源码把项集的全集枚举出来再逐条判断是否频繁。项集种类随商品数指数增长没有 Apriori 剪枝候选集合直接爆炸。处理候选生成只允许“两个频繁 k-1 项集连接出一个 k 项集”这就是先验剪枝支持度计数用frozenset的集合包含而不是in遍历列表。看下面这个反面写法很多翻车代码都是从这开始的# 反面写法直接枚举交易的全子集候选规模爆炸 from itertools import combinations def bad_apriori(transactions, min_support0.4): all_items set().union(*transactions) candidates [] for r in range(1, len(all_items) 1): candidates.extend(combinations(all_items, r)) # 然后逐条统计支持度交易稍多就彻底卡死正确做法是每一轮只保留频繁项集作为下一轮种子。第一次运行时min_support先设 0.5确认频繁 1 项集规模很小以后再往下调。我见过有人为了演示把阈值压到 0.01结果就是无限等待。4.3 手写 C4.5 在连续特征上准确率反而很低现象在 iris 上手写 C4.5 的分类准确率明显低于 sklearn 的 CART甚至低于随机猜测信息增益率的公式明明没错。原因连续特征如花萼长度几乎每个样本取值不同离散取值太多固有值很大信息增益率被压得偏低源码没有对连续特征做预处理树就不知道该怎么分裂。处理先用分位数分箱或者直接改用 CART 的二分策略import pandas as pd df pd.DataFrame(X, columnsiris.feature_names) df[sepal_length_bin] pd.qcut(df[sepal length (cm)], q4, labelsFalse) df[sepal_width_bin] pd.qcut(df[sepal width (cm)], q4, labelsFalse)C4.5 的源码设计初衷是处理离散属性数据挖掘教材里的例子也以离散属性为主。真实数据里连续特征占大多数所以生产项目里更常见的是让决策树统一走 CART 实现。如果你在课设里必须展示 C4.5也要在 README 写明“连续列先分箱”不然答辩时会被追问到怀疑人生。4.4 GMM 聚类退化成一个巨大的簇现象n_components3预测出来的标签里某一个类占了 95% 以上剩下两类几乎没人。原因EM 迭代中某个高斯分量的协方差矩阵变得非常小集中到一个点附近那个分量的似然特别大把大部分样本都拉了过去。小样本高维环境下full协方差的估计很不稳。处理covariance_typediag忽略维度间协方差或者reg_covar1e-6给对角线加正则from sklearn.mixture import GaussianMixture gmm GaussianMixture( n_components3, covariance_typediag, init_paramskmeans, reg_covar1e-6, random_state42 )手写 EM 的话每次 M 步之后检查协方差矩阵的最小特征值低于阈值就重置或加正则。多数情况下改成diag后聚类轮廓会立刻正常。这个坑在二维数据上几乎不会出现维度一旦到 10 以上就成了高发问题。4.5 PageRank 分数出现 NAN或者总和越来越小现象前几次迭代正常后面突然变成nan另一种情况是分数一直在减小总和远小于 1。原因图里有出度为 0 的节点按行归一化时除以 0 得到nan不处理悬空节点时权重进入死节点后无法流出总和不守恒。处理先把出度为 0 的行替换为均匀分布再做归一化保留阻尼系数d0.85它让每个节点都保留一部分随机跳转权重。判断收敛时顺手检查一下print(sum:, r.sum(), has_nan:, np.isnan(r).any())收敛判断用 L1 范数并且限制max_iter两边都能防止无限循环。如果你自己写 PageRank至少把这几行打印留在函数里不然出了问题只能从头查矩阵。这五条不是全部陷阱但覆盖了分类、聚类、关联、图四类算法里最容易被“下载一个源码包”带翻车的部分。下一章的三个参数是这些坑之外最值得先调的开关。5. 三个必调参数kNN 的 k、SVM 的 C、Apriori 的 min_support十大算法各有参数但从复现源码的经验看最先值得花时间的三个参数是 kNN 的 k、SVM 的 C 与 RBF 的 gamma、Apriori 的 min_support。一个控制邻居范围一个控制过拟合一个控制规则数量调好这三个后续 grid search 才不会漫无目的。5.1 kNN 的 k从“邻居范围”到“决策边界平滑度”k 太小决策边界很碎一个噪声点就能改变整片区域k 太大跨类样本混进来边界被磨平。iris 上常见做法是[1, 3, 5, 7, 15]扫一遍from sklearn.model_selection import cross_val_score from sklearn.neighbors import KNeighborsClassifier from sklearn.preprocessing import StandardScaler from sklearn.pipeline import make_pipeline for k in [1, 3, 5, 7, 15]: model make_pipeline(StandardScaler(), KNeighborsClassifier(n_neighborsk)) acc cross_val_score(model, X, y, cv5).mean() print(fk{k}: {acc:.4f})注意这里把StandardScaler放进 pipeline而不是在外面 fit 完再转换因为交叉验证要求每一折都独立做标准化防止数据泄露。k 取偶数时需要额外的平票处理规则所以一般直接扫奇数。真实项目中 k 的范围取决于样本量样本量小的时候 k 超过 15 就不合理了。5.2 SVM 的 C 和 gamma惩罚系数与 RBF 的影响半径C 控制对误分类的惩罚。C 太大margin 变小训练集准确率高但容易过拟合C 太小模型会接受大量误分类。RBF 的 gamma 控制单个样本的影响半径gamma 越大决策边界越弯曲。from sklearn.model_selection import GridSearchCV from sklearn.pipeline import make_pipeline from sklearn.preprocessing import StandardScaler from sklearn.svm import SVC pipe make_pipeline(StandardScaler(), SVC(kernelrbf, random_state42)) grid { svc__C: [0.1, 1, 10], svc__gamma: [scale, 0.1, 0.5], } gs GridSearchCV(pipe, grid, cv5, scoringaccuracy) gs.fit(X, y) print(gs.best_params_, gs.best_score_)svc__C的命名来自 pipeline 中步骤的名字这是新手最容易卡住的地方。另一个经验是先固定gammascale只扫 C扫到合适范围后再一起扫 gamma否则网格组合太多。真实数据上这一步可能要跑很久iris 只是演示不代表所有场景。5.3 Apriori 的 min_support规则数量从温和到爆炸Apriori 的输出规模由 min_support 决定。阈值设 0.5只有高频项集留下把阈值降到 0.05频繁项集数量可能翻几十倍而且大部分是长尾组合没有业务含义。我的处理方法是先打印“频繁 1 项集数量”freq1 {items: sup for items, sup in freq_items.items() if len(items) 1} print(len(freq1), 个频繁1项集)如果频繁 1 项集已经超过几十个min_support 就需要提高否则后续候选集会指数增长。关联分析还要区分支持度、置信度和提升度min_support 只过滤“是否常见”判断规则有没有意义要看第 6 章说的置信度和提升度。5.4 联调顺序先用小网格试方向再扩大范围三个参数可以合成一张速查表按需改起点参数常见起点先扫范围主要风险kNN 的 k51、3、5、7、15过小边界碎过大被跨类样本污染SVM 的 C1.00.1、1、10过大过拟合过小欠拟合SVM 的 gammascale0.1、0.5过大边界扭曲过小边界过平滑Apriori 的 min_support0.50.3、0.4、0.5过低候选爆炸过高规则太少我的经验是先跑小网格看分数是单调上升还是出现尖峰。如果 k1 反而最好说明数据本身很干净、也说明模型很脆如果 C 到了 10 还在涨先加数据而不是继续加压惩罚。参数调整必须配合第 3 章的回归脚本否则你连“参数变好了还是随机种子变好了”都分不清。6. 从“能跑”到“能信”验证与回归的四个技巧十个算法都能跑通、参数也调过一轮之后真正把源码包变成自己东西的是验证方式。四种算法类型对应四种验证出口我分别说。6.1 分类交叉验证永远优先于单次测试集单次train_test_split的结果受随机划分影响很大尤其在样本量小的时候。正确做法是用cross_val_score或StratifiedKFold重复评估from sklearn.model_selection import StratifiedKFold, cross_val_score from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay model KNeighborsClassifier(n_neighbors5) cv StratifiedKFold(n_splits5, shuffleTrue, random_state42) scores cross_val_score(model, X, y, cvcv, scoringaccuracy) print(scores.mean(), scores.std())shuffleTrue让每折不按原始顺序切random_state42保证可复现。交叉验证分数稳定后再用全部训练数据拟合在保留集上打印混淆矩阵看哪些类互相混淆。只看 accuracy 会漏掉“模型把某一类全部认成另一类”的问题。6.2 聚类轮廓系数比“距离平方和”更可靠K-means 惯用 inertia簇内平方和评估但它是单调递减的k 越大分数越低不能用来选 k。轮廓系数不依赖真实标签更适合聚类场景from sklearn.metrics import silhouette_score for k in [2, 3, 4, 5]: labels KMeans(n_clustersk, n_init10, random_state42).fit_predict(X_std) print(k, silhouette_score(X_std, labels))轮廓系数在 k3 附近出现峰值说明这个 k 与数据中的天然分团匹配。它也有缺陷对高维数据会偏差所以一般先 PCA 降维到 2~3 维再算。手写 K-means 后用同样的逻辑验证能很快发现空簇问题。6.3 关联只看置信度很容易被高频商品骗到置信度只说明“买了 A 的人有多少买 B”但 B 本身很常见时置信度也会虚高。提升度的定义是confidence(A→B) / support(B)大于 1 才算正相关def lift(conf, support_b): return conf / support_b if support_b 0 else float(inf) for (a, b), conf in rules.items(): lift_value lift(conf, support_b) if lift_value 1.2: print(a, -, b, lift:, round(lift_value, 3))这段代码不完整因为还要从频繁项集推断规则但它想表达的是关联分析里支持度低不一定没价值提升度高才是“买了 A 又买 B”不是巧合的证据。购物篮分析里我通常先用支持度和置信度粗筛再用提升度排序。6.4 回归基线与结果落盘让每次改动有据可查最后一个技巧也是我坚持最久的习惯把每个版本的运行结果落盘成 JSON固定随机种子记录环境和参数再提交代码。这样别人复现结果不对时不用猜是数据变了还是环境变了import json, platform, sklearn result { model: KMeans, n_clusters: 3, silhouette: round(sil, 4), random_state: 42, python: platform.python_version(), sklearn: sklearn.__version__, } with open(result_log.json, a) as f: f.write(json.dumps(result) \n)我自己的翻车经历里有一半以上不是算法写错而是随机种子没固定、库版本变了、特征忘了标准化。把每次实验输出落盘成一行 JSON回退起来非常快不需要从黑匣子一样的日志里翻找。这比“再跑一次看看”可靠得多。希望这一套从选型、最小管线到验证回归的方法能帮你在“数据挖掘十大算法源代码Python”上少走点冤枉路。本文还有配套的精品资源点击获取