机器学习算法全解析:从KNN到集成与聚类 📅 发布时间:2026/9/20 8:02:17 👁 浏览次数: 简介这是适合机器学习入门与复习的系统课件合集以436页PDF呈现覆盖K近邻、线性回归、逻辑回归、决策树、集成学习、聚类等常用算法并穿插距离度量、特征工程、交叉验证、网格搜索等关键知识点。整包为单个PDF文件体积约57.19MB无需解压即可按章节翻阅目录层级清晰目前已有183人学习下载适合在校学生、算法初学者或准备算法岗位面试的开发者系统查阅。内容不是简单罗列公式而是以案例驱动讲解包含鸢尾花种类预测、波士顿房价预测、癌症分类预测、泰坦尼克号生存预测、Facebook签到位置预测等经典实战场景同时把算法原理与Scikit-learn工具使用结合起来。课件还专门总结了模型保存与加载、欠拟合过拟合处理、正则化线性模型、特征降维与聚类优化等进阶话题并对聚类评估指标和降维方法做了梳理可作为复习提纲或教学备课素材持续参考。1. KNN 是理解机器学习算法的最佳入口也是被低估最多的起点KNN 是我在面试里最常被追问的模型也是这份 436 页课件里最容易被低估的一章。很多人看到“根据邻居判断类别”就觉得它简单真到用的时候才发现距离公式选哪个、K 值调多大、数据量大了怎么加速每个问题都能展开成一道硬核面试题。这份课件从 KNN 一路讲到聚类覆盖了机器学习入门到大半的主流算法每一节都配了 sklearn 代码和真实案例包括鸢尾花分类、波士顿房价、乳腺癌预测、泰坦尼克号生存预测。适合三类人准备算法岗面试的、拿它做课程设计参考的、以及想系统补齐机器学习应用细节的工程师。接下来我按课件主线把每个算法在工程里最容易踩坑的地方单独拎出来讲。2. KNN 三要素距离度量、K 值与 kd 树2.1 距离度量欧氏距离只是闵可夫斯基距离的特例KNN 的定义一句话就能说完如果一个样本在特征空间中的 k 个最相似样本大多属于某个类别那这个样本也属于该类别。但“相似”怎么量化这就落到距离公式上。课件里给出了距离公式必须满足的四个性质非负性、同一性、对称性、直递性三角不等式。不满足三角不等式的度量严格来说不能称为距离这决定了后续能否用树结构加速检索。距离名称公式特点欧氏距离(\sqrt{\sum (x_i - y_i)^2})最直观所有维度同等对待曼哈顿距离(\sumx_i - y_i切比雪夫距离(\maxx_i - y_i闵可夫斯基距离((\sumx_i - y_i闵可夫斯基距离是这一族的统一表达实际调参时不需要手写公式sklearn 的KNeighborsClassifier里通过p参数直接控制。先看一段用 scipy 验证距离矩阵的代码import numpy as np from scipy.spatial.distance import pdist, squareform X np.array([[1, 1], [2, 2], [3, 3], [4, 4]]) # p2对应欧氏距离 print(euclidean:) print(squareform(pdist(X, metriceuclidean))) # p1对应曼哈顿距离 print(cityblock:) print(squareform(pdist(X, metriccityblock))) # p3闵可夫斯基距离的一个中间形态 print(minkowski p3:) print(squareform(pdist(X, metricminkowski, p3)))pdist返回的是压缩后的距离数组squareform把它还原成方阵方便对照。metric参数支持字符串形式也可传自定义函数但自定义函数在数据量大时性能损耗明显生产环境建议直接用内置度量。一个容易被忽略的坑是量纲问题。课件里举的例子很典型二维样本 (身高cm, 体重kg)a(180,50)、b(190,50)、c(180,60)a 与 b 的距离等于 a 与 c 的距离但身高差 10cm 和体重差 10kg 在物理意义上完全不对等。闵可夫斯基距离把各个分量“同等看待”没有考虑分布差异。所以 KNN 之前做标准化是必须的一般用StandardScaler把每个特征变成均值 0、方差 1。这一点在很多课程里只提一句但真实项目里不做标准化KNN 的准确率能差出十几个点。2.2 K 值选择近似误差与估计误差的权衡K 值选多少是 KNN 唯一真正需要手工调的超参数。课件引用了李航《统计学习方法》的结论K 值过小只有很近的样本才对结果起作用近似误差减小但估计误差增大模型整体变复杂容易过拟合K 值过大远处不相似的样本也参与投票近似误差增大模型过于简单容易欠拟合K 等于训练样本数时模型退化成“永远预测多数类”完全没有信息量。实操里 K 值一般取一个较小的奇数但具体多大不能拍脑袋。常见做法是用交叉验证扫一遍from sklearn.datasets import load_iris from sklearn.model_selection import cross_val_score from sklearn.neighbors import KNeighborsClassifier X, y load_iris(return_X_yTrue) for k in range(1, 21): knn KNeighborsClassifier(n_neighborsk) # cv5 表示 5 折交叉验证返回每折的准确率 scores cross_val_score(knn, X, y, cv5) print(fk{k:2d}, acc{scores.mean():.4f})cross_val_score会自动完成“切分-训练-评估”的循环cv5把数据分成 5 份轮流用 4 份训练、1 份验证。注意这里没有单独留测试集交叉验证的分数只用来选 K选完之后还要在真正没见过的测试集上做最终评估否则会有信息泄漏。选择标准是“估计误差小”也就是验证分数高且方差小而不是训练分数高——训练分数高恰恰可能是过拟合的信号。2.3 kd 树当数据集变大时 KNN 的加速方案KNN 最朴素的实现是线性扫描每个预测点都要算一遍与全部训练样本的距离复杂度是 O(DN²)D 是特征数N 是样本数。数据量到几万条时每次预测都变成一次小型计算任务。kd 树的核心思路是用树结构提前存储距离信息如果 A 和 B 距离很远、B 和 C 距离很近那 A 和 C 大概率也远搜索时可以直接跳过那些不可能进入最近邻集合的分支。构建时按方差最大的维度递归切分搜索时通过剪枝减少距离计算次数。优化后复杂度能降到 O(DN log N)。课件里提到 1989 年的 Ball Tree 在这基础上进一步优化sklearn 的KNeighborsClassifier默认algorithmauto会按数据规模和特征维度自动选 kd 树或 Ball Tree。特征维度超过 20 左右时kd 树的效率会明显下降这时候更该考虑降维或换模型而不是硬调树结构参数。3. 从线性回归到逻辑回归损失函数与梯度下降3.1 最小二乘与梯度下降两种求解路径线性回归是理解“损失 优化”这套框架最简单的载体。模型预测值 (\hat{y} w^T x b)损失函数用均方误差 MSE目标是最小化 (\frac{1}{n}\sum (y_i - \hat{y_i})^2)。求解有两条路正规方程直接求闭式解适合特征维度低万级以下、样本量不大的情况梯度下降迭代逼近适合特征多、数据量大的场景。梯度下降的更新规则是 (w : w - \eta \frac{\partial L}{\partial w})(\eta) 是学习率。学习率太大会震荡不收敛太小则收敛极慢。sklearn 里SGDRegressor封装了随机梯度下降。原课件用的是波士顿房价数据集但这个数据集在新版 sklearn 里已被标记为 deprecated 并可能从远程下载中移除实际练习时我一般用加州房价替代接口完全一致from sklearn.datasets import fetch_california_housing from sklearn.linear_model import SGDRegressor, LinearRegression from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler X, y fetch_california_housing(return_X_yTrue) X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42 ) # 梯度下降对特征尺度敏感必须先标准化 scaler StandardScaler() X_train scaler.fit_transform(X_train) X_test scaler.transform(X_test) sgd SGDRegressor(max_iter1000, eta00.01, penaltyl2, random_state42) sgd.fit(X_train, y_train) print(SGD R2:, sgd.score(X_test, y_test)) lr LinearRegression() lr.fit(X_train, y_train) print(OLS R2:, lr.score(X_test, y_test))max_iter1000是最大迭代轮数eta00.01是初始学习率penaltyl2对应岭回归的正则化项。有一点容易被忽略fit_transform只在训练集上调用测试集只调用transform理由是标准化用的均值和方差来自训练集测试集不能参与计算否则就是数据泄漏。3.2 欠拟合、过拟合与正则化线性模型模型在训练集上误差小、测试集上误差大这是过拟合两边误差都大是欠拟合。线性回归的过拟合通常表现为权重系数过大对噪声过于敏感。正则化的思路是在损失函数后面加惩罚项。Ridge 加 L2 范数Lasso 加 L1 范数前者让权重整体变小后者让部分权重直接变成 0起特征选择作用。模型惩罚项效果适用场景LinearRegression无可能过拟合特征少且独立Ridge(\alpha |w|_2^2)权重整体收缩特征间存在相关性Lasso(\alpha |w|_1)部分权重归零特征多、想自动筛选alpha 是正则化强度越大惩罚越重模型越简单。实践中 alpha 跨数量级取值比如 [0.01, 0.1, 1, 10]配合交叉验证选。Ridge 的alpha1.0只是默认值不代表最优。特征量纲不一致时Lasso 的结果会偏向量纲大的特征所以用之前同样要标准化。3.3 逻辑回归从线性到分类的桥梁逻辑回归名字里有“回归”实际做的是分类。它在线性输出上套了一层 sigmoid 函数将结果压缩到 0~1 之间作为正类概率。损失函数不用 MSE而用交叉熵——MSE 在 sigmoid 上是非凸函数梯度下降容易陷进局部最优交叉熵在逻辑回归上是凸函数理论上能找到全局最优。课件里的案例是乳腺癌良恶性预测sklearn 自带load_breast_cancer数据集直接可以复现from sklearn.datasets import load_breast_cancer from sklearn.linear_model import LogisticRegression from sklearn.model_selection import train_test_split from sklearn.metrics import roc_auc_score data load_breast_cancer() X, y data.data, data.target X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42 ) lr LogisticRegression(max_iter5000, C1.0) lr.fit(X_train, y_train) # predict_proba 返回 [负类概率, 正类概率]取正类 y_pred_proba lr.predict_proba(X_test)[:, 1] print(AUC:, roc_auc_score(y_test, y_pred_proba))C是正则化强度的倒数C越小正则化越强默认 1.0。max_iter5000是因为这个数据集特征维数高默认的 100 次迭代可能还没收敛sklearn 会报ConvergenceWarning看到这个警告第一反应是调大max_iter而不是加正则化。只报准确率不报 AUC 是不够的——当正负样本不平衡时AUC 比准确率更能反映模型排序能力。ROC 曲线下的面积就是 AUC画 ROC 曲线可以用sklearn.metrics.roc_curve取不同阈值算出 TPR 和 FPR 后连线。4. 决策树到集成学习从 CART 剪枝到 GBDT4.1 CART 树的分裂与剪枝决策树的核心问题是每个节点选哪个特征、用什么阈值切分。分类树用基尼系数衡量不纯度基尼系数越小样本越纯。回归树用 MSE。CART 树是二叉树每次只做一个“是/否”的切分和 ID3、C4.5 的多叉分裂不同所以 sklearn 里的DecisionTreeClassifier默认就是 CART。剪枝是决策树最容易出问题的环节。不剪枝的树能完美拟合训练集但测试集效果很差。sklearn 里最常用的剪枝手段就是预剪枝参数max_depth限制树深min_samples_split限制内部节点再分裂所需的最少样本数min_samples_leaf限制叶子节点最少样本数。看一个在鸢尾花上的简单验证from sklearn.datasets import load_iris from sklearn.model_selection import cross_val_score from sklearn.tree import DecisionTreeClassifier X, y load_iris(return_X_yTrue) for depth in [None, 3, 5, 10]: clf DecisionTreeClassifier( max_depthdepth, min_samples_split5, random_state42 ) scores cross_val_score(clf, X, y, cv5) print(fdepth{depth}, acc{scores.mean():.4f})max_depthNone表示不限制深度这时树会长到所有叶子都纯为止训练分数接近 1但交叉验证分数通常会下降。min_samples_split5的意思是节点样本数少于 5 就不再分裂这是最简单的后剪枝替代方案。注意决策树对特征尺度不敏感不需要标准化这是它相比 KNN 和线性模型的一个优势。4.2 特征提取与泰坦尼克号生存预测课件里在决策树章节插入了特征提取顺序安排有讲究。决策树对数值特征直接切分但真实数据里大量是类别特征比如性别、船舱等级。常见做法是把类别特征做编码有序类别映射成数值无序类别用 one-hot。泰坦尼克号案例是决策树章节的标配练习。这个数据集有缺失值、有类别特征、有数值特征非常适合演示完整流程。一个可以跑的简化版本import pandas as pd import seaborn as sns from sklearn.model_selection import train_test_split from sklearn.tree import DecisionTreeClassifier from sklearn.metrics import accuracy_score df sns.load_dataset(titanic) # 只保留有效特征缺失值直接丢弃真实项目要单独处理 df df[[survived, pclass, sex, age, fare]].dropna() df[sex] df[sex].map({male: 0, female: 1}) X df.drop(survived, axis1) y df[survived] X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42 ) clf DecisionTreeClassifier(max_depth5, min_samples_split5, random_state42) clf.fit(X_train, y_train) print(acc:, accuracy_score(y_test, clf.predict(X_test)))dropna()在这里是为了演示方便真实项目里更该做的是用中位数或众数填充。sex列映射成 0/1男性 0、女性 1。pclass本身是 1/2/3存在序关系可以不 one-hot。max_depth5在这个数据集上通常比不限制深度的效果更好因为样本量只有几百树太深完全是记住噪声。训练完之后建议打印一下clf.feature_importances_可以看出性别和票价通常贡献了绝大部分重要性这跟历史认知一致。4.3 Bagging 与 Boosting随机森林和 GBDT 为什么有效单棵决策树方差大换一组数据结果可能完全不同。集成学习的思路是训练多棵树再综合结果。Bagging 是并行训练多棵树、每棵独立随机采样最后投票Boosting 是串行训练、每棵树都在拟合前一棵的残差。随机森林是 Bagging 的典型代表GBDT 是 Boosting 的代表。方向Bagging随机森林BoostingGBDT训练方式并行样本自助采样串行拟合残差目标降低方差降低偏差关键参数n_estimators, max_featuresn_estimators, learning_rate过拟合风险相对低learning_rate 过大容易过拟合随机森林里除了样本采样还有特征采样——每个节点分裂时只随机挑一部分特征这样能进一步降低树之间的相关性。GBDT 的重要参数是learning_rate控制每棵树贡献的权重调小学习率通常要调大n_estimators两者是一对需要一起调的组合。一个常见误用是GBDT 里树太深时几乎必然过拟合因为每棵树都在拟合残差深度一般控制在 3~6而不是像随机森林那样可以放到 10 以上。5. 聚类算法与特征降维K-Means 的工程细节5.1 K-Means 四步流程与轮廓系数聚类是无监督学习没有标签可对评估方式完全不同。K-Means 的流程课件里写得很清楚随机选 K 个中心点计算每个样本到中心的距离并分配最近的中心然后重新计算每类的均值作为新中心重复直到中心点不再变化或达到最大迭代次数。这个算法对初始中心敏感所以 sklearn 默认n_init10跑 10 次取最优避免陷入局部最优。模型评估用轮廓系数同时考虑类内距离和类间距离范围在 -1 到 1 之间越大说明聚类越合理。看一段直接可跑的代码from sklearn.datasets import make_blobs from sklearn.cluster import KMeans from sklearn.metrics import silhouette_score X, _ make_blobs(n_samples500, centers4, cluster_std1.2, random_state42) for k in range(2, 7): km KMeans(n_clustersk, n_init10, random_state42) labels km.fit_predict(X) score silhouette_score(X, labels) print(fk{k}, silhouette{score:.4f}, inertia{km.inertia_:.1f})silhouette_score直接吃特征矩阵和聚类标签不需要真实标签。inertia是样本到最近中心的距离平方和K-Means 的目标就是最小化它但它会随着 K 增大单调下降不能单独用来选 K。正确做法是看“肘部”画 K 与 inertia 的曲线找拐点。轮廓系数不受这个影响但它的缺点是计算成本高样本量过万时建议先抽样再算。5.2 PCA 降维从高维到可解释的低维投影聚类之前通常先做降维原因有两个一是高维空间下距离度量会退化所有点都趋向等距聚类效果变差二是可视化需要把数据压到二维或三维。PCA 是线性降维的默认选择。它找的是数据方差最大的投影方向把原始特征线性组合成新特征。课件里的案例是“探究用户对物品类别的喜好细分”典型场景是用户-商品评分矩阵特征维度等于商品数几百上千维直接聚类既慢效果又差。常见做法是先 PCA 再 K-Means。关键参数是n_components可以指定维度数也可以传 0~1 之间的小数表示保留多少方差from sklearn.decomposition import PCA from sklearn.cluster import KMeans from sklearn.datasets import load_digits X, _ load_digits(return_X_yTrue) print(原始维度:, X.shape[1]) pca PCA(n_components0.95) X_pca pca.fit_transform(X) print(保留95%方差后维度:, X_pca.shape[1]) km KMeans(n_clusters10, n_init10, random_state42) labels km.fit_predict(X_pca)n_components0.95表示保留 95% 的累计方差贡献率算法会自动选择所需维数。手写数字数据集本身是 64 维通常压到 30 维左右就能保留 95% 方差。注意 PCA 之前必须做标准化否则方差大的特征会主导主成分方向这一点和 KNN 的要求一致。降维后再聚类轮廓系数通常会略微下降但聚类结果更稳定、计算更快这是信息损失换来的属于正常现象。5.3 算法选型思路与常见误用KNN 适合低维、小样本、类别边界清晰的场景线性模型适合高维稀疏数据比如文本分类决策树和集成方法适合特征间有非线性关系、且特征量纲不统一的表格数据数据没有任何标签时只能用聚类。很多新手在拿到一份数据后第一反应直接上随机森林但如果特征数量几千、样本量只有几百线性模型加正则化往往更稳。聚类常见误用是拿 K-Means 处理类别特征——K-Means 的均值计算在类别特征上没有意义这种情况应该先编码或用 K-Modes。特征降维也不是万能药PCA 是线性投影对非线性流形结构的数据效果反而不如直接用原始特征加正则化模型。6. 网格搜索与交叉验证把调参收在验证上6.1 交叉验证的切分细节交叉验证最常见的是 K 折cv5把数据切成 5 份。但分类问题有一个隐藏坑如果原始数据类别不平衡随机切分可能导致某一折里全是某一类。解决办法是用StratifiedKFold做分层采样每一折里各类别比例和整体一致。训练集和验证集的划分只能做一次交叉验证分数是用来选参数的最终评估必须用独立测试集这两步的混淆是新手最容易犯的数据泄漏错误。6.2 GridSearchCV 参数网格与模型保存网格搜索配合交叉验证是 sklearn 调参的标准组合。设计param_grid时先粗后细先固定一个参数扫另一个不要一开始就排列组合所有候选值。以 KNN 为例from sklearn.model_selection import GridSearchCV, StratifiedKFold from sklearn.neighbors import KNeighborsClassifier from sklearn.datasets import load_iris import joblib X, y load_iris(return_X_yTrue) param_grid { n_neighbors: [3, 5, 7, 9], weights: [uniform, distance], p: [1, 2] } knn KNeighborsClassifier() cv StratifiedKFold(n_splits5, shuffleTrue, random_state42) grid GridSearchCV(knn, param_grid, cvcv, scoringaccuracy, n_jobs-1) grid.fit(X, y) print(best params:, grid.best_params_) print(best score:, grid.best_score_) # 保存最优模型避免重新训练 joblib.dump(grid.best_estimator_, best_knn.pkl) loaded joblib.load(best_knn.pkl) print(loaded pred:, loaded.predict([[5.1, 3.5, 1.4, 0.2]]))param_grid传的是参数字典Cartesian 乘积生成全部组合。scoringaccuracy决定用什么指标选最优模型分类问题也可以用roc_auc回归用neg_mean_squared_error。n_jobs-1用满所有 CPU 核心。一次搜索跑完后grid.best_estimator_是已经用全量训练数据重训好的最优模型不用再手动 fit 一遍。保存模型用joblib而不是pickle因为 joblib 对 numpy 数组的序列化效率更高。加载后直接predict就行。weightsdistance表示按距离反比加权投票距离越近权重越大在数据量小时往往比uniform效果好但也会放大小样本中的噪声。本文还有配套的精品资源点击获取