Python Machine Learning 第3章实战:使用 Scikit-Learn 完成分类算法全景巡礼
【免费下载链接】python-machine-learning-bookThe "Python Machine Learning (1st edition)" book code repository and info resource项目地址: https://gitcode.com/gh_mirrors/py/python-machine-learning-book
导读
本文基于开源仓库python-machine-learning-book中第 3 章的代码与文档(code/ch03/README.md、code/ch03/ch03.ipynb)编写。本章围绕"用 scikit-learn 训练机器学习分类器"这一核心主题,从感知机出发,依次讲解逻辑回归、线性 SVM、核 SVM、决策树、随机森林与 K 近邻六类经典分类算法。读完本文,你将掌握一套完整可复用的 Iris 分类实验流程:数据加载、训练/测试集划分、特征标准化、决策区域可视化,以及每种算法的核心参数调优与过拟合控制思路,并能直接对照仓库中的 Jupyter Notebook 与可选 Python 脚本动手复现。
本文所有代码均来源于 code/ch03/ch03.ipynb,其可运行的合并脚本位于 code/optional-py-scripts/ch03.py;决策树可视化产物见 code/ch03/tree.dot。
1. 如何选择分类算法:经验法则与本章路线图
「Choosing a classification algorithm」是本章的起点:不存在对所有问题都最优的万能分类器,选择哪种算法取决于训练样本数量、特征维度、数据是否线性可分、对模型可解释性的要求,以及训练/预测的时延预算。
从源码结构看,code/ch03/ch03.ipynb 的 91 个单元格(54 个 Markdown + 37 个代码单元格)按以下路线循序渐进,这也是本仓库第 3 章目录(code/ch03/README.md)给出的正式大纲:
| 算法 | 核心思想 | 适用场景 |
|---|---|---|
| 感知机(Perceptron) | 线性决策边界 + 错误驱动学习 | 线性可分数据的入门基线 |
| 逻辑回归(Logistic Regression) | 用 sigmoid 建模类别概率 | 需要概率输出、可解释性强的分类 |
| 线性 SVM | 最大间隔超平面 | 线性可分/近似可分数据 |
| 核 SVM | 核技巧映射到高维空间 | 非线性决策边界 |
| 决策树 | 递归划分、信息增益最大化 | 可解释性优先、特征含义明确 |
| 随机森林 | 多棵决策树集成投票 | 降低单树过拟合、提升鲁棒性 |
| K 近邻 | 惰性学习、基于距离投票 | 低维小样本、决策边界复杂 |
本章全部实验统一使用Iris(鸢尾花)数据集,它包含 3 个类别(Iris-Setosa、Iris-Versicolor、Iris-Virginica)与 4 个数值特征,是 scikit-learn 内置的标准多分类基准数据,便于不同算法在同一数据上横向对比。
2. scikit-learn 第一步:Iris 数据加载、划分与标准化
本节对应源码中的 "First steps with scikit-learn" 部分,是整个实验的公共数据管线,后续所有算法都复用这套数据。
2.1 加载数据集并选取特征
仓库代码只取iris.data的第 3 列(花瓣长度)与第 4 列(花瓣宽度)作为特征X,类别标签已由 scikit-learn 转换为整数:0=Iris-Setosa,1=Iris-Versicolor,2=Iris-Virginica:
from sklearn import datasets import numpy as np iris = datasets.load_iris() X = iris.data[:, [2, 3]] # petal length, petal width y = iris.target print('Class labels:', np.unique(y)) # 输出: Class labels: [0 1 2]选取两个特征是为了能够在二维平面上可视化决策区域——这是本章贯穿始终的直观验证手段。
2.2 划分训练集与测试集(含 sklearn 0.18 兼容处理)
仓库代码用一个版本判断来兼容新旧版 scikit-learn(0.18起train_test_split迁移到sklearn.model_selection):
from distutils.version import LooseVersion as Version from sklearn import __version__ as sklearn_version if Version(sklearn_version) < '0.18': from sklearn.cross_validation import train_test_split else: from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.3, random_state=0)test_size=0.3:30% 的数据(45 个样本,见源码输出y_test.shape→(45,))留作测试集,70% 用于训练;random_state=0:固定随机种子,保证每次运行得到相同划分,便于复现书中结果。
2.3 特征标准化:为什么训练集和测试集必须用同一套参数
标准化的目的,是让不同量纲的特征统一到均值为 0、方差为 1 的尺度,避免大数值特征在距离计算与梯度下降中主导学习。关键操作顺序是:先用StandardScaler在训练集上fit(仅计算均值与标准差),再用同一套参数去transform训练集和测试集:
from sklearn.preprocessing import StandardScaler sc = StandardScaler() sc.fit(X_train) # 只在训练集上估计 μ 和 σ X_train_std = sc.transform(X_train) # 应用同一 μ、σ X_test_std = sc.transform(X_test)从源码结构看,后续感知机、逻辑回归、线性 SVM、核 SVM、KNN 全部使用标准化后的X_train_std / X_test_std;而决策树与随机森林因为不依赖距离/梯度,直接使用原始特征(见 code/ch03/ch03.ipynb 的决策树、随机森林单元格)。这一点提示了实用经验:距离类与梯度类模型必须标准化,树模型通常不必。
3. 决策区域可视化辅助函数
本章多次复用第二章定义的plot_decision_regions函数,它把整个特征平面网格化后交给分类器预测,用等高线填充色块表示决策区域,同时叠加训练样本与测试样本:
from matplotlib.colors import ListedColormap import matplotlib.pyplot as plt import warnings def versiontuple(v): return tuple(map(int, (v.split(".")))) def plot_decision_regions(X, y, classifier, test_idx=None, resolution=0.02): # setup marker generator and color map markers = ('s', 'x', 'o', '^', 'v') colors = ('red', 'blue', 'lightgreen', 'gray', 'cyan') cmap = ListedColormap(colors[:len(np.unique(y))]) # plot the decision surface x1_min, x1_max = X[:, 0].min() - 1, X[:, 0].max() + 1 x2_min, x2_max = X[:, 1].min() - 1, X[:, 1].max() + 1 xx1, xx2 = np.meshgrid(np.arange(x1_min, x1_max, resolution), np.arange(x2_min, x2_max, resolution)) Z = classifier.predict(np.array([xx1.ravel(), xx2.ravel()]).T) Z = Z.reshape(xx1.shape) plt.contourf(xx1, xx2, Z, alpha=0.4, cmap=cmap) plt.xlim(xx1.min(), xx1.max()) plt.ylim(xx2.min(), xx2.max()) for idx, cl in enumerate(np.unique(y)): plt.scatter(x=X[y == cl, 0], y=X[y == cl, 1], alpha=0.6, c=cmap(idx), edgecolor='black', marker=markers[idx], label=cl) # highlight test samples if test_idx: if not versiontuple(np.__version__) >= versiontuple('1.9.0'): X_test, y_test = X[list(test_idx), :], y[list(test_idx)] warnings.warn('Please update to NumPy 1.9.0 or newer') else: X_test, y_test = X[test_idx, :], y[test_idx] plt.scatter(X_test[:, 0], X_test[:, 1], c='', alpha=1.0, edgecolor='black', linewidths=1, marker='o', s=55, label='test set')调用时通常先拼接训练与测试数据,并用test_idx=range(105, 150)高亮测试样本:
X_combined_std = np.vstack((X_train_std, X_test_std)) y_combined = np.hstack((y_train, y_test))可视化函数是贯穿全章的"仪表盘":同一张图上,决策区域的形状、边界的光滑程度、测试样本是否落入正确区域,能直观反映每种算法的偏差-方差特性。
4. 用 scikit-learn 训练感知机
4.1 模型训练
Perceptron是 scikit-learn 对第二章手写感知机的现成封装,训练过程与手写版一致——逐样本错误驱动更新权重:
from sklearn.linear_model import Perceptron ppn = Perceptron(n_iter=40, eta0=0.1, random_state=0) ppn.fit(X_train_std, y_train)n_iter=40:遍历训练集的轮数(epochs);eta0=0.1:学习率;random_state=0:固定随机种子(配合内部shuffle=True保证可复现)。
4.2 评估
仓库代码同时给出误分类样本数与准确率两种评估口径:
y_pred = ppn.predict(X_test_std) print('Misclassified samples: %d' % (y_test != y_pred).sum()) # 输出: Misclassified samples: 4 from sklearn.metrics import accuracy_score print('Accuracy: %.2f' % accuracy_score(y_test, y_pred)) # 输出: Accuracy: 0.91在 45 个测试样本上仅错 4 个(准确率 91%),对线性可分数据而言已经是不错的基线。绘制决策区域后可以看到,感知机在 Iris 两个特征上的决策边界呈直线,且收敛依赖数据严格线性可分——这是感知机的天然局限,也自然引出下一节具备概率建模能力的逻辑回归。
5. 用逻辑回归建模类别概率
5.1 直觉与条件概率:从净输入到 sigmoid
逻辑回归的起点与 Adaline 相同:对特征做加权求和得到净输入z = w0 + w1·x1 + ... + wm·xm,但不再直接输出实数值,而是将其送入sigmoid 函数φ(z) = 1 / (1 + e^(-z)),把任意实数压缩到 (0, 1) 区间,解释为类别 1 的条件概率 P(y=1|x):
import matplotlib.pyplot as plt import numpy as np def sigmoid(z): return 1.0 / (1.0 + np.exp(-z)) z = np.arange(-7, 7, 0.1) phi_z = sigmoid(z) plt.plot(z, phi_z) plt.axvline(0.0, color='k') plt.ylim(-0.1, 1.1) plt.xlabel('z') plt.ylabel('$\phi (z)$') plt.yticks([0.0, 0.5, 1.0]) ax = plt.gca() ax.yaxis.grid(True) plt.show()从源码结构看,sigmoid 曲线是逻辑回归的原理基石:当z=0时φ(z)=0.5,对应决策边界;z>0时概率超过 0.5,预测类别 1。上图中的 sigmoid 曲线即 code/ch03/ch03.ipynb 单元格 35 的运行结果,与该章书籍配图03_03(逻辑回归模型工作流程图:净输入 → sigmoid 激活 → 量化器输出)相互印证,完整说明了逻辑回归将"线性模型 + 概率输出"结合的结构。
5.2 逻辑代价函数:为什么用对数似然而非平方误差
逻辑回归不再使用平方误差,而是采用基于最大似然估计的对数代价函数。仓库代码用两张代价曲线直观解释其动机:
def cost_1(z): return - np.log(sigmoid(z)) def cost_0(z): return - np.log(1 - sigmoid(z)) z = np.arange(-10, 10, 0.1) phi_z = sigmoid(z) c1 = [cost_1(x) for x in z] plt.plot(phi_z, c1, label='J(w) if y=1') c0 = [cost_0(x) for x in z] plt.plot(phi_z, c0, linestyle='--', label='J(w) if y=0') plt.ylim(0.0, 5.1) plt.xlim([0, 1]) plt.xlabel('$\phi$(z)') plt.ylabel('J(w)') plt.legend(loc='best') plt.show()两张曲线都是凸的且单调:当真实标签y=1时,若φ(z)→1(预测正确且自信),代价趋近 0;若φ(z)→0(严重误判),代价趋近无穷,从而对错误预测施加强烈惩罚。整个训练过程即通过梯度下降最小化这一代价函数来学习权重。
5.3 用 scikit-learn 训练逻辑回归并预测概率
from sklearn.linear_model import LogisticRegression lr = LogisticRegression(C=1000.0, random_state=0) lr.fit(X_train_std, y_train) plot_decision_regions(X_combined_std, y_combined, classifier=lr, test_idx=range(105, 150)) plt.xlabel('petal length [standardized]') plt.ylabel('petal width [standardized]') plt.legend(loc='upper left') plt.show()训练完成后,可以用predict_proba查看每个测试样本的类别概率:
if Version(sklearn_version) < '0.17': lr.predict_proba(X_test_std[0, :]) else: lr.predict_proba(X_test_std[0, :].reshape(1, -1))这段代码同样包含版本兼容分支:旧版允许直接传入一维样本,新版要求显式reshape(1, -1)成二维。C=1000.0在这里取值较大,意味着正则化强度较弱,先观察"几乎不约束"的拟合效果,为下一节的过拟合讨论做铺垫。
5.4 用正则化缓解过拟合:C 参数的权重路径实验
过拟合的本质是模型对训练数据的噪声过度敏感、泛化能力下降。逻辑回归通过给代价函数添加 L2 惩罚项λ/2 · ‖w‖²来控制模型复杂度,scikit-learn 用正则化强度的倒数C = 1/λ暴露该控制旋钮:C 越小,正则化越强,权重被压缩得越厉害。
仓库代码用一个"权重路径"实验直观展示 C 的影响——遍历C = 10^-5 ... 10^4,记录每个 C 下类别 1(Versicolor)的两个特征系数:
weights, params = [], [] for c in np.arange(-5., 5.): lr = LogisticRegression(C=10.**c, random_state=0) lr.fit(X_train_std, y_train) weights.append(lr.coef_[1]) params.append(10**c) weights = np.array(weights) plt.plot(params, weights[:, 0], label='petal length') plt.plot(params, weights[:, 1], linestyle='--', label='petal width') plt.ylabel('weight coefficient') plt.xlabel('C') plt.legend(loc='upper left') plt.xscale('log') plt.show()实验结果(对应书籍配图03_06的"欠拟合 / 良好拟合 / 过拟合"对比)可以总结为:
| C 值 | 正则化强度 | 权重幅值 | 模型状态 |
|---|---|---|---|
| C 很小(如 10⁻⁵) | 极强 | 趋近 0 | 欠拟合(高偏差),边界过于简单 |
| C 适中(如 10⁰) | 适中 | 适中 | 良好拟合 |
| C 很大(如 10³) | 极弱 | 较大 | 过拟合(高方差),边界过度复杂 |
调参经验:C 是逻辑回归最重要的超参数,实践中应结合交叉验证在10^-3 ~ 10^3的对数尺度上网格搜索,而非直接取极值。本章在 code/ch06 的模型评估章节还会系统讲解交叉验证与网格搜索。
6. 最大间隔分类:支持向量机(SVM)
6.1 最大间隔直觉:不只要分对,还要分得"稳"
与逻辑回归关注概率不同,SVM 的目标是找到一条离最近训练样本(支持向量)距离最大的决策边界(最大间隔超平面)。间隔越大,边界对轻微扰动的鲁棒性越强,泛化能力通常越好。仓库代码用SVC(kernel='linear')在标准化 Iris 数据上训练线性 SVM:
from sklearn.svm import SVC svm = SVC(kernel='linear', C=1.0, random_state=0) svm.fit(X_train_std, y_train) plot_decision_regions(X_combined_std, y_combined, classifier=svm, test_idx=range(105, 150)) plt.xlabel('petal length [standardized]') plt.ylabel('petal width [standardized]') plt.legend(loc='upper left') plt.show()kernel='linear'表示使用线性核,即原始特征空间中的直线/超平面决策边界。
6.2 用松弛变量处理近似线性可分数据
现实中数据往往并非严格线性可分。SVM 为此引入松弛变量(slack variables, ξ),允许少量样本落在间隔之内甚至错误一侧,同时通过惩罚参数C在"间隔最大化"与"误分类惩罚"之间权衡:
- C 大:对误分类惩罚重,间隔变窄,倾向完美拟合训练集(易过拟合);
- C 小:容忍更多误分类,间隔更宽(易欠拟合)。
从源码结构看,C=1.0是仓库示例的默认取值,也是实践中最常用的起点。惩罚项的思想与上一节逻辑回归的 L2 正则化殊途同归——都是通过"约束复杂度"来平衡偏差与方差。
6.3 scikit-learn 中的替代实现
除SVC外,scikit-learn 针对不同场景还提供线性 SVM 的替代实现,例如LinearSVC(线性核专用、对大数据集更快)与SGDClassifier(随机梯度下降、支持在线学习)。仓库本章主体仍以SVC为主,因为它的kernel参数可以无缝切换到下一节的非线性核。
7. 用核 SVM 解决非线性问题
7.1 为什么线性模型失效:XOR 数据集
线性分类器在"线性不可分"的数据上会彻底失效。仓库代码构造了一个经典的XOR 数据集来演示:200 个随机二维样本,类别由异或逻辑生成,正负类呈交叉分布:
np.random.seed(0) X_xor = np.random.randn(200, 2) y_xor = np.logical_xor(X_xor[:, 0] > 0, X_xor[:, 1] > 0) y_xor = np.where(y_xor, 1, -1) plt.scatter(X_xor[y_xor == 1, 0], X_xor[y_xor == 1, 1], c='b', marker='x', label='1') plt.scatter(X_xor[y_xor == -1, 0], X_xor[y_xor == -1, 1], c='r', marker='s', label='-1') plt.xlim([-3, 3]); plt.ylim([-3, 3]) plt.legend(loc='best') plt.show()没有任何一条直线能分开这类数据,必须先做特征映射再分类。
7.2 核技巧:在隐式高维空间找超平面
核技巧的精妙之处在于:不必显式计算高维映射φ(x),只需用核函数在原始空间中计算样本对的高维内积。常用的RBF 径向基核为K(x, x') = exp(-γ·‖x - x'‖²),其中γ(gamma)控制单个训练样本的影响半径。
先在 XOR 数据上验证 RBF 核 SVM 能学出非线性边界:
svm = SVC(kernel='rbf', random_state=0, gamma=0.10, C=10.0) svm.fit(X_xor, y_xor) plot_decision_regions(X_xor, y_xor, classifier=svm) plt.legend(loc='upper left') plt.show()再将 RBF 核应用到 Iris 数据,仓库代码特意对比了两个 gamma 值的效果:
# gamma=0.2:决策边界较平滑 svm = SVC(kernel='rbf', random_state=0, gamma=0.2, C=1.0) svm.fit(X_train_std, y_train) plot_decision_regions(X_combined_std, y_combined, classifier=svm, test_idx=range(105, 150)) plt.xlabel('petal length [standardized]') plt.ylabel('petal width [standardized]') plt.legend(loc='upper left') plt.show() # gamma=100.0:每个训练样本的影响范围极小,边界剧烈扭曲 svm = SVC(kernel='rbf', random_state=0, gamma=100.0, C=1.0) svm.fit(X_train_std, y_train) plot_decision_regions(X_combined_std, y_combined, classifier=svm, test_idx=range(105, 150)) plt.xlabel('petal length [standardized]') plt.ylabel('petal width [standardized]') plt.legend(loc='upper left') plt.show()gamma 与 C 是两个核心旋钮:
| 参数 | 含义 | 取值过大 | 取值过小 |
|---|---|---|---|
gamma | 单个样本的影响半径 | 每个样本只影响极小邻域,边界严重过拟合 | 决策边界过于平滑,近似线性 |
C | 误分类惩罚 | 过拟合训练噪声 | 边界过宽、欠拟合 |
gamma=0.2得到平滑合理的非线性边界;gamma=100.0时每个训练点周围都"圈出"自己的小区域,决策区域碎裂——这是过拟合的可视化典型样例,也是理解"高方差"最直观的教材。
8. 决策树学习:递归划分与信息增益
8.1 最大化信息增益:熵、基尼不纯度与误分类率
决策树通过递归二分特征空间构建,每次分裂都试图最大化信息增益(分裂前后不纯度的降低)。仓库代码对比了三种常用的不纯度度量,画出它们随类别概率p(i=1)变化的曲线:
def gini(p): return p * (1 - p) + (1 - p) * (1 - (1 - p)) def entropy(p): return - p * np.log2(p) - (1 - p) * np.log2((1 - p)) def error(p): return 1 - np.max([p, 1 - p]) x = np.arange(0.0, 1.0, 0.01) ent = [entropy(p) if p != 0 else None for p in x] sc_ent = [e * 0.5 if e else None for e in ent] err = [error(i) for i in x] fig = plt.figure() ax = plt.subplot(111) for i, lab, ls, c, in zip([ent, sc_ent, gini(x), err], ['Entropy', 'Entropy (scaled)', 'Gini Impurity', 'Misclassification Error'], ['-', '-', '--', '-.'], ['black', 'lightgray', 'red', 'green', 'cyan']): line = ax.plot(x, i, label=lab, linestyle=ls, lw=2, color=c) ax.legend(loc='upper center', bbox_to_anchor=(0.5, 1.15), ncol=3, fancybox=True, shadow=False) ax.axhline(y=0.5, linewidth=1, color='k', linestyle='--') ax.axhline(y=1.0, linewidth=1, color='k', linestyle='--') plt.ylim([0, 1.1]) plt.xlabel('p(i=1)') plt.ylabel('Impurity Index') plt.show()三种度量在p=0.5(最不纯)时都取最大值、在p=0或p=1(完全纯)时归零,因此在实践中选择哪一种对结果影响通常不大;熵与基尼不纯度在类别极不平衡时比误分类率更敏感。
8.2 构建决策树:criterion 与 max_depth
from sklearn.tree import DecisionTreeClassifier tree = DecisionTreeClassifier(criterion='entropy', max_depth=3, random_state=0) tree.fit(X_train, y_train) X_combined = np.vstack((X_train, X_test)) y_combined = np.hstack((y_train, y_test)) plot_decision_regions(X_combined, y_combined, classifier=tree, test_idx=range(105, 150)) plt.xlabel('petal length [cm]') plt.ylabel('petal width [cm]') plt.legend(loc='upper left') plt.show()注意这里直接使用原始特征(未标准化)——决策树按特征值阈值分裂,对特征尺度不敏感。
criterion='entropy':用信息熵作为分裂度量;max_depth=3:限制树的深度为 3,是防止决策树无限生长、过拟合训练数据的最直接手段;random_state=0:固定分裂评估的随机性。
8.3 用 Graphviz 导出并可视化决策树
仓库代码把训练好的树导出为 DOT 文件(code/ch03/tree.dot),这是决策树"可解释性"的直接体现:
from sklearn.tree import export_graphviz export_graphviz(tree, out_file='tree.dot', feature_names=['petal length', 'petal width'])code/ch03/tree.dot 中保存了完整的树结构,根节点即为petal width <= 0.75(熵 1.5799、105 个样本、类别分布 [34, 32, 39]),随后逐层分裂:左子树在petal width <= 1.65处继续划分 Versicolor,右子树在petal length <= 4.95处继续区分 Virginica,最终得到熵为 0 的纯叶子节点。整棵树的每个节点都标注了分裂特征、阈值、熵、样本数与类别计数,可以逐节点核对分类逻辑。
如果安装了pydotplus(pip install pydotplus)且 scikit-learn 版本 ≥ 0.18,还可以在 Notebook 内直接渲染带类别名、填充色与圆角样式的树图(利用filled=True, rounded=True, class_names=['setosa', 'versicolor', 'virginica']等新参数),无需先生成 dot 文件;未安装时仓库代码会打印pydotplus is not installed.的提示。
8.4 随机森林:从弱学习器到强学习器
单棵决策树容易过拟合,随机森林通过"装袋 + 特征随机化"把多棵弱树集成为强分类器:每棵树在训练集的有放回抽样(bootstrap)子集上训练,且每次分裂只考虑随机选取的部分特征,最后以多数投票决定类别(对应书籍配图03_20的多数投票示意)。
from sklearn.ensemble import RandomForestClassifier forest = RandomForestClassifier(criterion='entropy', n_estimators=10, random_state=1, n_jobs=2) forest.fit(X_train, y_train) plot_decision_regions(X_combined, y_combined, classifier=forest, test_idx=range(105, 150)) plt.xlabel('petal length [cm]') plt.ylabel('petal width [cm]') plt.legend(loc='upper left') plt.show()n_estimators=10:森林中的决策树数量,越多通常越稳健(代价是训练时间);criterion='entropy':单棵树的熵分裂准则;n_jobs=2:并行使用 2 个 CPU 核训练,体现集成方法的计算开销与并行化收益;- 与单棵树相同,随机森林也直接使用原始特征。
相比单棵决策树,随机森林的决策边界明显更平滑、更稳健,且对噪声与轻微过拟合有更强的抵抗力,是"从弱到强"集成思想的经典示范。
9. K 近邻:一种惰性学习算法
KNN 是本章收尾算法,与前面所有"急切学习"(训练阶段学出模型参数)方法不同,它属于惰性学习——训练阶段只是把样本"记住",真正的计算发生在预测时:对新样本,找出训练集中距离最近的 K 个邻居,按多数投票决定类别。
from sklearn.neighbors import KNeighborsClassifier knn = KNeighborsClassifier(n_neighbors=5, p=2, metric='minkowski') knn.fit(X_train_std, y_train) plot_decision_regions(X_combined_std, y_combined, classifier=knn, test_idx=range(105, 150)) plt.xlabel('petal length [standardized]') plt.ylabel('petal width [standardized]') plt.legend(loc='upper left') plt.show()参数含义:
| 参数 | 取值 | 说明 |
|---|---|---|
n_neighbors | 5 | 投票邻居数 K;K 越小边界越复杂(易过拟合),K 越大边界越平滑 |
metric | 'minkowski' | 闵可夫斯基距离族,是欧氏距离与曼哈顿距离的推广 |
p | 2 | 闵可夫斯基距离的阶数,p=2即欧氏距离,p=1为曼哈顿距离 |
KNN 必须使用标准化后的特征(X_train_std / X_test_std),因为距离计算对特征量纲高度敏感;这也是它与树模型在数据预处理上的关键差异。它的优势是实现简单、无需训练,但预测时需要与全部训练样本计算距离,样本量大时开销显著,且高维空间下距离区分度会下降("维度灾难")。
10. 本章小结
第 3 章用同一份 Iris 数据串起了 scikit-learn 的六类经典分类算法,形成了一条完整的实战主线:
- 数据管线先行:加载 →
train_test_split划分(test_size=0.3, random_state=0)→StandardScaler只在训练集上fit再统一transform; - 线性模型三连:感知机(
Perceptron,91% 准确率基线)→ 逻辑回归(sigmoid 概率输出 +C正则化权重路径实验)→ 线性 SVM(最大间隔 + 松弛变量); - 非线性进阶:核 SVM 用 RBF 核解决 XOR 等线性不可分问题,
gamma与C联合控制偏差-方差权衡; - 树模型与集成:决策树(熵/基尼不纯度、
max_depth防过拟合、Graphviz 导出)→ 随机森林(bootstrap + 特征随机化 + 多数投票); - 惰性学习收尾:KNN(
n_neighbors、p、metric)展示"零训练"的另一种建模范式。
贯穿始终的方法论是偏差-方差权衡:从逻辑回归的C、SVM 的gamma/C,到决策树的max_depth、KNN 的K,每个超参数都在"拟合训练数据"与"保持泛化能力"之间寻找平衡点,而plot_decision_regions让这种权衡变得肉眼可见。
本文所有代码均可直接在 code/ch03/ch03.ipynb 中逐单元格运行,或运行合并脚本 code/optional-py-scripts/ch03.py 一次跑完;决策树导出文件 code/ch03/tree.dot 可用 Graphviz 渲染成树形图。如果你需要先搭建 Python 与 Jupyter Notebook 环境,可参考 code/ch01/README.md 中的环境搭建说明;第 4、5 章(数据预处理、降维)与第 6 章(模型评估与超参数优化)会在此基础上继续深入,把本文中提到的交叉验证、网格搜索等方法系统化。
后续章节的进阶路线:若想了解模型评估与超参数调优的系统方法(交叉验证、网格搜索),可继续学习 code/ch06/ch06.ipynb;若关注特征工程与降维对分类器的影响,可阅读 code/ch04/ch04.ipynb 与 code/ch05/ch05.ipynb。
【免费下载链接】python-machine-learning-bookThe "Python Machine Learning (1st edition)" book code repository and info resource项目地址: https://gitcode.com/gh_mirrors/py/python-machine-learning-book
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考