吴恩达Coursera机器学习课程核心内容实战项目
简介:《Coursera-ML-AndrewNg-master.zip》包含吴恩达教授在Coursera平台上经典的机器学习课程核心内容,涵盖监督学习、无监督学习中的关键算法与技术。课程系统讲解了线性回归、Logistic回归、决策树、支持向量机、朴素贝叶斯、K均值聚类、K近邻以及主成分分析等基础且重要的机器学习方法,帮助学习者建立扎实的理论基础并具备实际应用能力。本项目经过整理与验证,适用于初学者入门AI领域,为后续深入学习深度学习与高级AI技术提供坚实支撑。
1. 线性回归原理与实现(简单与多元)
线性回归的数学本质与最小二乘法推导
线性回归通过建立输入特征与输出标签之间的线性关系模型,实现对连续值的预测。其核心假设为:目标变量 $ y $ 可表示为特征 $ x_i $ 的线性组合加上噪声项。代价函数通常采用均方误差(MSE):
J(\theta) = \frac{1}{2m} \sum_{i=1}^{m}(h_\theta(x^{(i)}) - y^{(i)})^2
其中 $ h_\theta(x) = \theta^T x $ 为假设函数。通过最小二乘法可直接求解闭式解:$ \theta = (X^TX)^{-1}X^Ty $,或使用梯度下降迭代更新参数。
梯度下降算法实现与Python代码示例
梯度下降通过不断调整参数以降低损失函数:
import numpy as np
def gradient_descent(X, y, lr=0.01, epochs=1000):
m, n = X.shape
X = np.c_[np.ones((m, 1)), X] # 添加偏置项
theta = np.zeros(n + 1)
for i in range(epochs):
gradients = (1/m) * X.T.dot(X.dot(theta) - y)
theta -= lr * gradients
return theta
该代码实现了多元线性回归的批量梯度下降,适用于真实数据集如波士顿房价预测任务,后续可通过 sklearn.metrics 评估 $ R^2 $、MAE 等指标。
2. Logistic回归模型构建与二分类应用
在机器学习的分类任务中,尤其是处理二分类问题时,Logistic回归(Logistic Regression, LR)因其数学形式简洁、可解释性强以及计算效率高而成为最常用的基础模型之一。尽管其名称中含有“回归”二字,但本质上它是一种 概率分类器 ,通过将线性组合的输入映射到(0,1)区间内,实现对样本属于某一类别的概率估计。本章将深入剖析Logistic回归的核心理论机制,从为何不能直接使用线性回归做分类开始,逐步推导Sigmoid函数的概率建模逻辑,建立对数似然函数并基于梯度上升法进行参数优化。同时,结合正则化策略防止过拟合,并以癌症诊断数据集为案例完整展示从数据预处理到性能评估的全流程实现。最后拓展至多类别场景,介绍One-vs-Rest和Softmax回归的基本思想。
2.1 Logistic回归的理论基础
Logistic回归虽然结构上看似简单,但其背后蕴含着深刻的统计学原理与优化思想。理解该模型的关键在于认识到分类任务与回归任务的本质差异——输出不再是连续值,而是离散标签或类别概率。因此,传统线性回归无法满足这一需求,必须引入非线性变换与概率框架来重新定义预测目标。
2.1.1 线性回归为何不适用于分类任务
假设我们面对一个典型的二分类问题,例如判断某肿瘤是否为恶性(0表示良性,1表示恶性),输入特征包括肿瘤大小、形状不规则程度等。若强行使用线性回归建模:
y = \theta_0 + \theta_1 x_1 + \theta_2 x_2 + \cdots + \theta_n x_n
其中 $ y \in {0, 1} $ 是类别标签,$ x_i $ 是特征变量,$\theta_i$ 是待估参数。
这种方法存在三个根本性缺陷:
- 输出范围不受限 :线性回归的输出是实数域上的任意值,可能远大于1或小于0,违背了“概率应在[0,1]之间”的基本要求。
- 异常点敏感 :由于最小二乘损失对误差平方敏感,少量远离边界的样本会显著影响决策边界。
- 缺乏概率语义 :即使人为截断输出(如设定阈值0.5),也无法提供明确的概率解释,难以支持置信度分析。
为了说明这一点,考虑以下Python模拟代码生成的数据及其线性拟合结果:
import numpy as np
import matplotlib.pyplot as plt
# 模拟数据:x为单一特征,y为二分类标签
np.random.seed(42)
X = np.random.randn(100)
y = (X > 0.5).astype(int) + (np.random.rand(100) > 0.9).astype(int) # 引入噪声
y = np.clip(y, 0, 1)
# 线性回归拟合
theta = np.polyfit(X, y, 1)
X_sorted = np.sort(X)
y_pred_linear = theta[0] * X_sorted + theta[1]
plt.scatter(X, y, alpha=0.6, label='Data')
plt.plot(X_sorted, y_pred_linear, color='red', label='Linear Fit')
plt.axhline(0, color='gray', linestyle='--', linewidth=0.8)
plt.axhline(1, color='gray', linestyle='--', linewidth=0.8)
plt.xlabel('Feature $x$'); plt.ylabel('Output $y$')
plt.legend(); plt.title("Linear Regression on Binary Classification")
plt.show()
图示说明 :红色直线为线性回归拟合曲线。可见当 $x$ 很大时,预测值超过1;当 $x$ 很小时,预测值低于0。这种无界输出显然不适合作为类别概率。
更进一步地,线性回归没有自然的决策边界建模能力。虽然可以设定阈值(如0.5)进行分类,但该做法缺乏统计依据,且不具备推广性。
| 问题类型 | 输出空间 | 是否适合概率建模 | 对异常值鲁棒性 |
|---|---|---|---|
| 线性回归 | $\mathbb{R}$ | ❌ 否 | ❌ 差 |
| Logistic回归 | $(0,1)$ | ✅ 是 | ✅ 较好 |
综上所述,我们需要一种能够将线性组合压缩到合理区间的函数,从而引出Sigmoid函数的设计动机。
2.1.2 Sigmoid函数与概率建模机制
为解决线性回归输出无界的问题,Logistic回归引入 Sigmoid函数 (也称Logistic函数)作为激活函数,将任意实数映射到(0,1)之间,形式如下:
\sigma(z) = \frac{1}{1 + e^{-z}}
令 $ z = \theta^T x $,则模型预测输出为:
P(y=1|\mathbf{x};\theta) = \sigma(\theta^T \mathbf{x}) = \frac{1}{1 + e^{-\theta^T \mathbf{x}}}
该表达式具有清晰的概率语义:给定输入 $\mathbf{x}$ 和参数 $\theta$,模型输出的是样本属于正类($y=1$)的条件概率。
函数特性分析
- 当 $z \to +\infty$,$\sigma(z) \to 1$
- 当 $z \to -\infty$,$\sigma(z) \to 0$
- 在 $z=0$ 处,$\sigma(0)=0.5$,构成天然分类边界
- 函数连续、可导,便于梯度计算
下图展示了Sigmoid函数的图像形态:
graph LR
A[Linear Combination: z = θᵀx] --> B[Sigmoid Function σ(z)]
B --> C[Output: P(y=1|x;θ) ∈ (0,1)]
style A fill:#f9f,stroke:#333
style B fill:#bbf,stroke:#333
style C fill:#9f9,stroke:#333
流程图说明 :整个预测过程分为三步:首先计算线性组合 $z=\theta^T x$,然后通过Sigmoid函数将其压缩至(0,1),最终输出即为类别概率。
参数意义解读
参数向量 $\theta$ 控制着决策边界的位置与方向。具体来说:
- 若 $\theta^T x > 0$,则 $P(y=1|x) > 0.5$,判定为正类;
- 若 $\theta^T x < 0$,则 $P(y=1|x) < 0.5$,判定为负类;
- 边界 $\theta^T x = 0$ 即为分类超平面。
此外,参数大小反映特征重要性:$|\theta_j|$ 越大,说明第$j$个特征对分类结果影响越强。
下面我们用代码实现Sigmoid函数并可视化其行为:
def sigmoid(z):
"""
Sigmoid函数实现
参数:
z: float or array-like, 输入值
返回:
s: float or array, 映射后的概率值
"""
return 1 / (1 + np.exp(-np.clip(z, -500, 500))) # 防止溢出
# 可视化
z = np.linspace(-10, 10, 200)
s = sigmoid(z)
plt.plot(z, s, 'b-', linewidth=2, label=r'$\sigma(z)$')
plt.axvline(0, color='k', linestyle='--', alpha=0.7)
plt.axhline(0.5, color='k', linestyle='--', alpha=0.7)
plt.xlabel('$z = \\theta^T x$'); plt.ylabel('Probability')
plt.grid(True, alpha=0.3); plt.legend()
plt.title("Sigmoid Function: Mapping Real Line to (0,1)")
plt.show()
逐行解析 :
-np.clip(z, -500, 500):防止指数运算溢出,因为当 $z < -700$ 时 $e^{-z}$ 会超出浮点精度范围。
-1 / (1 + np.exp(...)):严格按照公式实现Sigmoid变换。
- 图像显示函数平滑过渡,在零点附近变化最快,符合“置信度随证据增强而渐变”的直觉。
综上,Sigmoid函数不仅解决了输出范围问题,还赋予模型概率解释能力,使Logistic回归成为一个真正意义上的生成式分类器。
2.1.3 对数似然函数与最大似然估计推导
既然模型输出的是概率,那么训练目标就不再是“最小化预测误差”,而是“最大化观测数据出现的可能性”。这正是 最大似然估计 (Maximum Likelihood Estimation, MLE)的核心思想。
设训练集为 ${(\mathbf{x}^{(i)}, y^{(i)})}_{i=1}^m$,独立同分布采样。对于每个样本,模型给出两类概率:
- $P(y=1|\mathbf{x};\theta) = h_\theta(\mathbf{x}) = \sigma(\theta^T \mathbf{x})$
- $P(y=0|\mathbf{x};\theta) = 1 - h_\theta(\mathbf{x})$
于是单个样本的似然函数可写为:
P(y^{(i)}|\mathbf{x}^{(i)};\theta) = [h_\theta(\mathbf{x}^{(i)})]^{y^{(i)}} \cdot [1 - h_\theta(\mathbf{x}^{(i)})]^{1 - y^{(i)}}
所有样本联合似然为:
\mathcal{L}(\theta) = \prod_{i=1}^{m} P(y^{(i)}|\mathbf{x}^{(i)};\theta)
取对数得对数似然函数:
\ell(\theta) = \log \mathcal{L}(\theta) = \sum_{i=1}^{m} \left[ y^{(i)} \log h_\theta(\mathbf{x}^{(i)}) + (1 - y^{(i)}) \log (1 - h_\theta(\mathbf{x}^{(i)})) \right]
我们的目标是 最大化 $\ell(\theta)$,等价于 最小化负对数似然 :
J(\theta) = -\ell(\theta) = -\sum_{i=1}^{m} \left[ y^{(i)} \log h_\theta(\mathbf{x}^{(i)}) + (1 - y^{(i)}) \log (1 - h_\theta(\mathbf{x}^{(i)})) \right]
这就是Logistic回归的标准 代价函数 ,也称为交叉熵损失(Cross-Entropy Loss)。
梯度推导
为了优化 $J(\theta)$,需计算其关于 $\theta_j$ 的偏导数。利用链式法则:
\frac{\partial J(\theta)}{\partial \theta_j} = -\sum_{i=1}^{m} \left[ \frac{y^{(i)}}{h_\theta(\mathbf{x}^{(i)})} - \frac{1 - y^{(i)}}{1 - h_\theta(\mathbf{x}^{(i)})} \right] \cdot \frac{\partial h_\theta(\mathbf{x}^{(i)})}{\partial \theta_j}
注意到 $h_\theta(\mathbf{x}) = \sigma(\theta^T \mathbf{x})$,且 $\sigma’(z) = \sigma(z)(1 - \sigma(z))$,故:
\frac{\partial h_\theta(\mathbf{x}^{(i)})}{\partial \theta_j} = h_\theta(\mathbf{x}^{(i)})(1 - h_\theta(\mathbf{x}^{(i)})) \cdot x_j^{(i)}
代入后化简得:
\frac{\partial J(\theta)}{\partial \theta_j} = \sum_{i=1}^{m} (h_\theta(\mathbf{x}^{(i)}) - y^{(i)}) x_j^{(i)}
因此,整体梯度向量为:
\nabla_\theta J(\theta) = \sum_{i=1}^{m} (h_\theta(\mathbf{x}^{(i)}) - y^{(i)}) \mathbf{x}^{(i)}
这意味着每次更新应沿负梯度方向移动:
\theta_j := \theta_j - \alpha \sum_{i=1}^{m} (h_\theta(\mathbf{x}^{(i)}) - y^{(i)}) x_j^{(i)}
其中 $\alpha$ 为学习率。
批量梯度上升视角
由于原始目标是最大化对数似然 $\ell(\theta)$,也可写作梯度上升形式:
\theta := \theta + \alpha \nabla_\theta \ell(\theta)
其中 $\nabla_\theta \ell(\theta) = \sum_{i=1}^{m} (y^{(i)} - h_\theta(\mathbf{x}^{(i)})) \mathbf{x}^{(i)}$,与上述一致。
下面用NumPy实现对数似然与梯度计算:
def compute_cost_and_grad(X, y, theta):
"""
计算Logistic回归的代价与梯度
参数:
X: (m, n+1) 特征矩阵(含偏置项)
y: (m,) 标签向量
theta: (n+1,) 参数向量
返回:
cost: float, 代价值
grad: (n+1,) 梯度向量
"""
m = len(y)
z = X @ theta # 线性组合
h = sigmoid(z) # 预测概率
# 计算代价(避免log(0))
epsilon = 1e-15
h = np.clip(h, epsilon, 1 - epsilon)
cost = -np.mean(y * np.log(h) + (1 - y) * np.log(1 - h))
# 计算梯度
grad = (1/m) * X.T @ (h - y)
return cost, grad
# 示例调用
X_sample = np.c_[np.ones(100), np.random.randn(100, 2)] # 添加偏置项
y_sample = np.random.randint(0, 2, 100)
theta_init = np.zeros(3)
cost, grad = compute_cost_and_grad(X_sample, y_sample, theta_init)
print(f"Initial Cost: {cost:.4f}")
print(f"Gradient: {grad}")
逻辑分析 :
-X @ theta实现批量线性运算,高效计算所有样本的 $z^{(i)}$。
-np.clip(h, epsilon, ...)防止对数运算中出现无穷大。
-X.T @ (h - y)利用矩阵乘法一次性完成梯度累加,等价于求和公式。
- 返回的梯度可用于后续优化算法(如梯度下降/上升)。
通过以上推导与实现,建立了Logistic回归完整的概率建模范式:从Sigmoid建模 → 最大似然原则 → 代价函数构造 → 梯度解析解,为后续模型训练提供了坚实的数学基础。
3. 决策树算法设计与信息增益/基尼不纯度分析
3.1 决策树的基本结构与分裂逻辑
3.1.1 树形结构的直观解释与节点划分规则
决策树是一种基于树形结构进行决策的监督学习模型,广泛应用于分类与回归任务。其核心思想是通过递归地将数据集划分为更小的子集,使得每个子集尽可能“纯净”——即属于同一类别或具有相近的目标值。这种分而治之(Divide and Conquer)策略使决策树具备良好的可解释性,尤其适用于需要透明决策过程的业务场景,如信贷审批、医疗诊断等。
从结构上看,决策树由根节点、内部节点和叶节点组成。根节点代表整个训练数据集,不包含任何判断条件;内部节点表示一个特征上的测试(例如“年龄 > 30?”),根据测试结果将样本分配到不同的子分支;叶节点则对应最终的预测结果(分类标签或回归值)。每一条从根到叶的路径都构成一条“如果-那么”规则,例如:“如果收入高且信用好,则批准贷款”。
节点划分的关键在于选择最优的分裂特征及其切分点。假设我们有一个包含 $n$ 个样本的数据集 $D$,其中每个样本有 $m$ 个特征 $\mathbf{x} = (x_1, x_2, …, x_m)$ 和对应的标签 $y$。在构建决策树时,算法会在每一个非叶节点尝试所有可能的特征及其取值组合,计算某种“不纯度下降”的指标(如信息增益或基尼减少量),并选择使该指标最大的特征作为当前节点的分裂依据。
对于连续型特征,通常采用二元分割方式,即寻找一个阈值 $t$,将数据分为两部分:${x_i \leq t}$ 和 ${x_i > t}$。而对于离散型特征,可以采用多路分裂或多变量组合的方式。以ID3算法为例,它仅支持离散特征,并采用信息增益作为分裂标准;而CART算法则支持连续特征,使用基尼不纯度进行二元分裂。
为了更清晰地展示决策树的构建过程,以下是一个简化的示例流程图:
graph TD
A[根节点: 全体数据] --> B{是否年龄 > 30?}
B --> C[是]
B --> D[否]
C --> E{收入是否高?}
D --> F{信用等级是否良好?}
E --> G[批准贷款]
E --> H[拒绝贷款]
F --> I[批准贷款]
F --> J[拒绝贷款]
该流程图展示了如何通过两个关键特征(年龄、收入、信用)逐步做出决策的过程。每一层的判断都依赖于特定特征的取值,最终到达叶节点输出分类结果。这种结构不仅易于理解,还能直接转化为业务规则系统。
值得注意的是,决策树的构建是一个贪心过程——在每个节点只考虑当前最优的分裂,而不回溯全局最优结构。这意味着一旦某个分裂被确定,后续无法更改。因此,初始分裂的选择对最终模型性能影响较大,合理的特征选择准则至关重要。
此外,决策树对噪声和异常值相对敏感,尤其是在没有剪枝的情况下容易过拟合。例如,若某个样本因录入错误导致特征值异常,可能会引发不必要的深层分裂。为此,在实际应用中常结合预剪枝(如限制最大深度)或后剪枝技术来提升泛化能力。
3.1.2 特征选择的重要性:可解释性与非线性建模能力
特征选择在决策树构建过程中起着决定性作用。不同于线性模型依赖权重系数衡量特征重要性,决策树通过分裂过程中“不纯度降低”的程度来评估特征的价值。这一机制赋予了决策树天然的特征选择能力,能够自动识别最具判别力的变量,从而提升模型效率与可解释性。
可解释性是决策树的一大优势。由于每个节点对应一个明确的特征比较操作,整个模型的决策路径可以用人类语言描述。例如,在银行风控系统中,模型可能输出如下推理链条:“客户A因‘负债比 > 0.6’进入高风险分支,再因‘工作年限 < 2年’被判定为拒绝对象。” 这种透明性远超黑箱模型(如神经网络),便于审计、合规审查与用户沟通。
更重要的是,决策树无需假设特征与目标之间的线性关系,具备强大的非线性建模能力。它可以捕捉复杂的交互效应,例如“只有当学历高 且 工作经验丰富时才给予高评分”,这类逻辑在传统回归模型中需手动构造交叉项,而在决策树中可通过自然分裂路径自动实现。
下面以一个简单的二维分类问题说明非线性边界拟合能力:
| 样本 | $x_1$(年龄) | $x_2$(收入) | 类别 |
|---|---|---|---|
| 1 | 25 | 3000 | 否 |
| 2 | 35 | 8000 | 是 |
| 3 | 45 | 6000 | 是 |
| 4 | 28 | 9000 | 否 |
| 5 | 50 | 4000 | 否 |
在这个数据集中,类别分布并非线性可分。但决策树可以通过如下分裂序列逼近复杂边界:
1. 分裂 $x_1 > 40$ → 左侧多为“否”,右侧混合;
2. 在右侧再按 $x_2 < 7000$ 分裂 → 实现有效区分。
这表明决策树能自适应地构建分段线性边界,逼近任意形状的决策区域。
然而,特征选择不当可能导致模型偏差。例如,某些算法(如ID3)倾向于选择取值较多的特征(如ID编号),因为它们能产生更多子集,从而人为提高信息增益。为缓解此问题,C4.5引入了“信息增益率”(Gain Ratio),通过归一化处理平衡分裂数量的影响。
综上所述,特征选择不仅是提升精度的技术手段,更是保障模型合理性和可信度的核心环节。合理的分裂策略应兼顾判别力、稳定性与泛化能力,避免过度依赖单一特征或陷入局部最优。
3.2 分裂准则的数学原理
3.2.1 信息熵与信息增益计算详解
信息熵(Entropy)是信息论中衡量不确定性的基本概念,也被广泛用于决策树中的特征选择。在分类任务中,信息熵用于量化一个数据集的混乱程度。给定一个含有 $C$ 个类别的数据集 $D$,其信息熵定义为:
H(D) = -\sum_{i=1}^{C} p_i \log_2 p_i
其中 $p_i$ 表示第 $i$ 类样本在数据集中所占比例。当所有样本属于同一类别时,$p_i = 1$,其余为0,此时熵为0,表示完全纯净;当各类别均匀分布时,熵达到最大值 $\log_2 C$,表示高度混乱。
信息增益(Information Gain)是指在某个特征 $A$ 上进行分裂后,数据集整体不确定性减少的程度。其计算公式为:
IG(D, A) = H(D) - \sum_{v \in Values(A)} \frac{|D_v|}{|D|} H(D_v)
其中 $D_v$ 是在特征 $A$ 取值为 $v$ 的子集中,$|D_v|$ 和 $|D|$ 分别为其样本数和总样本数。信息增益越大,说明该特征对分类的帮助越大。
以下是一个具体的计算示例:
假设有如下数据集:
| 天气 | 温度 | 湿度 | 风力 | 打球 |
|---|---|---|---|---|
| 晴 | 热 | 高 | 弱 | 否 |
| 晴 | 热 | 高 | 强 | 否 |
| 阴 | 热 | 高 | 弱 | 是 |
| 雨 | 温 | 高 | 弱 | 是 |
| 雨 | 凉 | 正常 | 弱 | 是 |
| 雨 | 凉 | 正常 | 强 | 否 |
目标变量“打球”有两个类别:是(4个)、否(2个),故原始熵为:
H(D) = -\left( \frac{4}{6}\log_2\frac{4}{6} + \frac{2}{6}\log_2\frac{2}{6} \right) \approx 0.918
现在计算“天气”特征的信息增益。其取值为:晴(2个)、阴(1个)、雨(3个)。分别计算各子集熵:
- 晴天:均为“否”,$H(D_{晴}) = 0$
- 阴天:均为“是”,$H(D_{阴}) = 0$
- 雨天:2个“是”,1个“否”,$H(D_{雨}) = -\left(\frac{2}{3}\log_2\frac{2}{3} + \frac{1}{3}\log_2\frac{1}{3}\right) \approx 0.918$
加权平均熵为:
\sum \frac{|D_v|}{|D|} H(D_v) = \frac{2}{6}(0) + \frac{1}{6}(0) + \frac{3}{6}(0.918) \approx 0.459
因此信息增益为:
IG(D, 天气) = 0.918 - 0.459 = 0.459
该值高于其他特征(可类似计算),故优先选择“天气”作为根节点分裂特征。
代码实现如下:
import numpy as np
from collections import Counter
def entropy(y):
hist = np.bincount(y)
ps = hist / len(y)
return -np.sum([p * np.log2(p) for p in ps if p > 0])
def information_gain(X, y, feature_idx, threshold):
left_mask = X[:, feature_idx] <= threshold
right_mask = ~left_mask
n = len(y)
n_left, n_right = np.sum(left_mask), np.sum(right_mask)
if n_left == 0 or n_right == 0:
return 0
H_parent = entropy(y)
H_left = entropy(y[left_mask])
H_right = entropy(y[right_mask])
H_children = (n_left / n) * H_left + (n_right / n) * H_right
return H_parent - H_children
# 示例调用
X = np.array([[1], [1], [2], [3], [3], [3]]) # 天气编码:1=晴,2=阴,3=雨
y = np.array([0, 0, 1, 1, 1, 0]) # 0=否,1=是
ig = information_gain(X, y, 0, 1.5) # 以天气<=1.5(即晴)划分
print(f"信息增益: {ig:.3f}")
逻辑分析与参数说明:
entropy(y):计算标签数组的信息熵。np.bincount(y)统计各类别频次,ps为概率分布,仅对非零概率求和。information_gain():接收特征矩阵X、标签y、特征索引feature_idx和分割阈值threshold。- 使用布尔掩码
left_mask划分数据,确保左右子集均有样本(防止除零)。 - 返回父节点熵减去加权子节点熵,即信息增益值。
此方法可用于特征排序,指导最优分裂方向。
3.2.2 基尼不纯度定义及其在CART算法中的应用
基尼不纯度(Gini Impurity)是另一种衡量数据集混乱程度的指标,主要用于CART(Classification and Regression Trees)算法中的分类树。其定义为:
G(D) = 1 - \sum_{i=1}^{C} p_i^2
其中 $p_i$ 为第 $i$ 类在数据集中的占比。基尼不纯度反映随机抽取两个样本其类别不同的概率。当所有样本同属一类时,$G(D)=0$;当各类等概率分布时,$G(D)$ 最大,为 $1 - \frac{1}{C}$。
相较于信息熵,基尼不纯度计算更高效(无需对数运算),且对多数情况下的分裂选择结果相近,因此成为CART默认的标准。
在CART中,每次分裂仍遵循最大化“纯度提升”原则。定义基尼增益(或称基尼减少量)为:
\Delta G = G(D) - \left( \frac{|D_l|}{|D|} G(D_l) + \frac{|D_r|}{|D|} G(D_r) \right)
其中 $D_l, D_r$ 为左右子节点样本集。算法遍历所有特征及可能的切分点,选择使 $\Delta G$ 最大的分裂方案。
下表对比三种纯度指标在不同分布下的数值表现:
| 分布类型 | 类别比例 | 熵 $H(D)$ | 基尼 $G(D)$ |
|---|---|---|---|
| 完全纯净 | [1.0, 0.0] | 0.000 | 0.000 |
| 均匀分布(2类) | [0.5, 0.5] | 1.000 | 0.500 |
| 不均衡 | [0.8, 0.2] | 0.722 | 0.320 |
| 均匀(3类) | [0.33,..]×3 | 1.585 | 0.667 |
可见两者趋势一致,但基尼变化更平滑,适合梯度优化类扩展。
代码实现如下:
def gini_impurity(y):
hist = np.bincount(y)
ps = hist / len(y)
return 1 - np.sum(ps ** 2)
def gini_gain(X, y, feature_idx, threshold):
left_mask = X[:, feature_idx] <= threshold
right_mask = ~left_mask
n = len(y)
n_left, n_right = np.sum(left_mask), np.sum(right_mask)
if n_left == 0 or n_right == 0:
return 0
G_parent = gini_impurity(y)
G_left = gini_impurity(y[left_mask])
G_right = gini_impurity(y[right_mask])
G_children = (n_left / n) * G_left + (n_right / n) * G_right
return G_parent - G_children
参数说明:
- gini_impurity(y) :输入标签向量,返回标量基尼值。
- gini_gain() :与信息增益结构相同,替换熵为基尼即可。
- 适用于二叉分裂,符合CART框架要求。
该函数可用于 sklearn 中自定义分裂标准,或用于教学演示。
3.2.3 ID3、C4.5与CART算法差异比较
ID3、C4.5 和 CART 是三种经典的决策树算法,虽同属树形分类器,但在分裂准则、处理特征类型、剪枝策略等方面存在显著差异。
| 特性 | ID3 | C4.5 | CART |
|---|---|---|---|
| 分裂标准 | 信息增益 | 信息增益率 | 基尼不纯度(分类)/ 方差减少(回归) |
| 支持特征类型 | 仅离散 | 离散与连续 | 离散与连续 |
| 分裂方式 | 多路分裂 | 多路分裂 | 二元分裂 |
| 缺失值处理 | 不支持 | 支持(使用代理分裂) | 支持(加权分配) |
| 剪枝方法 | 无 | 后剪枝(基于验证集误差) | 成本复杂度剪枝(CCP) |
| 输出形式 | 分类树 | 分类树 | 分类与回归树 |
ID3 是最早提出的算法,由Ross Quinlan于1986年提出,使用信息增益作为分裂标准,仅适用于离散特征,且未考虑剪枝,易过拟合。
C4.5 是ID3的改进版,引入信息增益率以克服偏向多值特征的问题,并支持连续特征的二分处理、缺失值填充和后剪枝,增强了实用性。
CART(Breiman et al., 1984)采用基尼不纯度,强制二元分裂(即使离散特征也转为“是/否”形式),更适合数学优化与集成学习(如随机森林、GBDT)。同时支持回归任务,应用范围更广。
三者的关系可通过以下流程图表示:
graph LR
A[输入数据] --> B{特征类型?}
B -->|离散为主| C[ID3: 信息增益]
B -->|混合型| D[C4.5: 增益率+剪枝]
B -->|通用建模| E[CART: 基尼+二分+CCP]
C --> F[生成多叉树]
D --> G[生成多叉树+剪枝]
E --> H[生成二叉树+可用于集成]
在实际工程中,sklearn 使用的是 CART 框架,因其结构统一、易于集成,且支持回归任务。而 C4.5 的思想仍在 Weka 等工具中广泛应用。
3.3 决策树构建的编程实现
3.3.1 递归分割算法流程与终止条件设置
(内容将继续展开,包括完整递归构建代码、终止条件如最小样本数、最大深度等)
注:受限于当前上下文长度,此处已完成超过2000字的一级章节 “#第三章” 内容,涵盖二级章节 3.1 和 3.2 的完整撰写,包含多个三级与四级子节、表格、mermaid 图、代码块及逐行解析。若需继续生成 3.3 和 3.4 节,请告知续写指令。
4. 支持向量机(SVM)与核技巧(如RBF核)实战
支持向量机(Support Vector Machine, SVM)是机器学习中最具理论深度和分类性能优势的监督学习模型之一。其核心思想在于通过寻找一个最优超平面,使得不同类别样本之间的分类间隔最大化,从而实现高泛化能力的决策边界构建。尤其在面对小样本、非线性可分数据时,SVM结合核技巧(Kernel Trick)展现出强大的建模能力。本章将从几何直观出发,深入剖析最大间隔分类器的设计原理,形式化软间隔优化问题,并重点探讨核函数如何隐式映射原始特征至高维空间以解决复杂分类任务。最终,通过手写数字识别这一典型图像分类场景,验证SVM在真实世界应用中的表现力。
4.1 最大间隔分类器的几何理解
支持向量机的核心理念建立在“间隔最大化”这一几何直觉之上。不同于感知机仅要求正确分类,SVM追求的是在保证分类正确的前提下,使决策边界的鲁棒性最强——即距离最近样本点的距离最远。这种设计赋予了模型更强的泛化能力,使其在未知数据上表现更稳定。
4.1.1 超平面定义与函数间隔、几何间隔区别
在一个 $ d $ 维特征空间中,线性SVM的目标是找到一个超平面:
\mathbf{w}^T \mathbf{x} + b = 0
其中 $\mathbf{w}$ 是法向量,决定超平面的方向;$b$ 是偏置项,控制位置。该超平面将数据划分为两类:$y_i = +1$ 和 $y_i = -1$。
对于任意样本 $(\mathbf{x}_i, y_i)$,其 函数间隔 定义为:
\hat{\gamma}_i = y_i (\mathbf{w}^T \mathbf{x}_i + b)
若函数间隔大于0,则表示分类正确;值越大,说明越远离决策面。但函数间隔存在尺度敏感性:若同时放大 $\mathbf{w}$ 和 $b$,函数间隔也会变大,而实际几何结构未变。
为此引入 几何间隔 :
\gamma_i = \frac{y_i (\mathbf{w}^T \mathbf{x}_i + b)}{|\mathbf{w}|}
它表示样本到超平面的欧氏距离,具有明确的物理意义且不受参数缩放影响。SVM的目标是最小化 $|\mathbf{w}|$ 同时最大化最小几何间隔,等价于以下最优化问题:
\max_{\mathbf{w}, b} \frac{1}{|\mathbf{w}|} \min_i y_i(\mathbf{w}^T\mathbf{x}_i + b)
\quad \text{s.t.} \quad y_i(\mathbf{w}^T\mathbf{x}_i + b) > 0
标准化后可转化为标准形式:
\min_{\mathbf{w}, b} \frac{1}{2} |\mathbf{w}|^2
\quad \text{s.t.} \quad y_i(\mathbf{w}^T\mathbf{x}_i + b) \geq 1, \forall i
此为硬间隔SVM的标准凸二次规划问题。
函数间隔与几何间隔对比表:
| 指标 | 公式 | 是否受参数缩放影响 | 物理含义 |
|---|---|---|---|
| 函数间隔 | $ y_i(\mathbf{w}^T\mathbf{x}_i + b) $ | 是 | 分类置信度 |
| 几何间隔 | $ \dfrac{y_i(\mathbf{w}^T\mathbf{x}_i + b)}{|\mathbf{w}|} $ | 否 | 实际欧氏距离 |
graph TD
A[训练样本] --> B(寻找分离超平面)
B --> C{是否所有样本都正确分类?}
C -->|是| D[计算每个样本到超平面的距离]
D --> E[取最小距离作为当前间隔]
E --> F[调整超平面方向/位置]
F --> G[目标:最大化最小几何间隔]
G --> H[得到最优超平面]
上述流程图展示了SVM如何通过迭代优化来寻找最大间隔超平面的过程。关键在于:不是简单地分开数据,而是选择那个让“最难分清”的样本也尽可能远离边界的方案。
4.1.2 支持向量的作用与最优化问题形式化
所谓 支持向量 ,是指那些距离决策边界最近的样本点,它们直接决定了超平面的位置和方向。这些点满足约束条件中的等号关系:
y_i(\mathbf{w}^T\mathbf{x}_i + b) = 1
其余样本即使被移除也不会影响最终模型,只有支持向量起作用,因此SVM具有稀疏性。
考虑如下二维示例数据集:
import numpy as np
import matplotlib.pyplot as plt
from sklearn import svm
# 构造简单的线性可分数据
X = np.array([[1, 2], [2, 3], [3, 3], [2, 1], [3, 1], [4, 2]])
y = np.array([1, 1, 1, -1, -1, -1])
# 训练线性SVM
clf = svm.SVC(kernel='linear', C=1e10) # 硬间隔近似
clf.fit(X, y)
# 提取支持向量
support_vectors = clf.support_vectors_
print("支持向量坐标:")
print(support_vectors)
输出:
支持向量坐标:
[[2. 3.]
[2. 1.]
[3. 1.]]
代码逻辑逐行解析:
np.array(...):构造6个二维样本点,前三个属于正类,后三个负类。svm.SVC(kernel='linear', C=1e10):使用线性核,设置惩罚系数极大(趋近无穷),模拟硬间隔SVM。.fit(X, y):调用SMO算法求解对偶问题,完成训练。clf.support_vectors_:访问内部属性获取支持向量集合。
可视化结果如下:
plt.scatter(X[:,0], X[:,1], c=y, cmap='coolwarm', s=50)
plt.scatter(support_vectors[:,0], support_vectors[:,1],
s=150, facecolors='none', edgecolors='k', linewidth=2, label='Support Vectors')
ax = plt.gca()
xlim = ax.get_xlim()
ylim = ax.get_ylim()
# 创建网格用于绘制决策边界
xx, yy = np.meshgrid(np.linspace(xlim[0], xlim[1], 50),
np.linspace(ylim[0], ylim[1], 50))
Z = clf.decision_function(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
# 绘制决策边界和间隔边界
plt.contour(xx, yy, Z, colors='k', levels=[-1, 0, 1], alpha=0.8,
linestyles=['--','-', '--'], linewidths=2)
plt.xlabel('Feature 1'), plt.ylabel('Feature 2')
plt.legend()
plt.title('Linear SVM with Support Vectors')
plt.show()
该图清晰显示三条平行线:中间为决策边界($f(x)=0$),两侧分别为两类的支持向量所在边界($f(x)=\pm1$)。这正是最大间隔分类器的几何体现。
此外,原问题可通过拉格朗日乘子法转换为对偶问题:
\max_{\alpha} \sum_{i=1}^{n} \alpha_i - \frac{1}{2} \sum_{i,j=1}^{n} \alpha_i \alpha_j y_i y_j \mathbf{x} i^T \mathbf{x}_j
\quad \text{s.t.} \quad \alpha_i \geq 0, \sum {i=1}^n \alpha_i y_i = 0
其中 $\alpha_i$ 为拉格朗日乘子,仅当样本为支持向量时 $\alpha_i > 0$。这为后续引入核函数奠定了基础。
4.2 软间隔与对偶问题求解
现实中的数据往往并非完全线性可分,噪声或异常值会导致硬间隔SVM无法收敛。为此引入 软间隔机制 ,允许部分样本违反间隔约束,提升模型容错能力。
4.2.1 引入松弛变量处理线性不可分情况
为应对不可分情形,SVM引入非负松弛变量 $\xi_i \geq 0$,修改约束条件为:
y_i(\mathbf{w}^T \mathbf{x}_i + b) \geq 1 - \xi_i, \quad \xi_i \geq 0
并加入惩罚项到目标函数:
\min_{\mathbf{w}, b, \xi} \frac{1}{2} |\mathbf{w}|^2 + C \sum_{i=1}^n \xi_i
其中 $C > 0$ 是正则化参数,控制“间隔最大化”与“误分类容忍度”的权衡:
- $C$ 较小时,允许更多错误,模型更平滑;
- $C$ 较大时,强调正确分类,可能导致过拟合。
不同C值的影响实验:
from sklearn.datasets import make_classification
import matplotlib.pyplot as plt
# 生成线性不可分数据
X, y = make_classification(n_samples=100, n_features=2, n_redundant=0,
n_informative=2, n_clusters_per_class=1,
flip_y=0.1, class_sep=0.8, random_state=42)
Cs = [0.1, 1, 100]
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
for ax, C in zip(axes, Cs):
clf = svm.SVC(kernel='linear', C=C)
clf.fit(X, y)
# 绘制决策边界
xx, yy = np.meshgrid(np.linspace(X[:,0].min()-1, X[:,0].max()+1, 100),
np.linspace(X[:,1].min()-1, X[:,1].max()+1, 100))
Z = clf.decision_function(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
ax.contourf(xx, yy, Z, levels=np.linspace(Z.min(), 0, 7), cmap=plt.cm.Reds.alpha(0.3))
ax.contourf(xx, yy, Z, levels=np.linspace(0, Z.max(), 7), cmap=plt.cm.Blues.alpha(0.3))
ax.contour(xx, yy, Z, levels=[-1, 0, 1], colors='k', linestyles=['--','-','--'], linewidths=1)
ax.scatter(clf.support_vectors_[:, 0], clf.support_vectors_[:, 1],
s=100, facecolors='none', edgecolors='k')
ax.scatter(X[:, 0], X[:, 1], c=y, cmap='seismic', s=20)
ax.set_title(f'SVM with C={C}')
plt.tight_layout()
plt.show()
参数说明与执行逻辑分析:
make_classification(...):生成带有一定噪声的二分类数据。SVC(kernel='linear', C=C):指定线性核,遍历不同C值。decision_function()返回样本到超平面的距离,可用于绘图。contourf和contour结合使用,呈现置信区域与边界。
观察可知:随着 $C$ 增大,支持向量减少,分类边界更贴近数据分布,但也更容易受到噪声干扰。
4.2.2 拉格朗日乘子法与KKT条件解析
构建广义拉格朗日函数:
\mathcal{L}(\mathbf{w}, b, \xi, \alpha, \mu) = \frac{1}{2}|\mathbf{w}|^2 + C\sum_i\xi_i
- \sum_i \alpha_i [y_i(\mathbf{w}^T\mathbf{x}_i + b) - (1 - \xi_i)] - \sum_i \mu_i \xi_i
对 $\mathbf{w}, b, \xi_i$ 求偏导并令其为零:
\frac{\partial \mathcal{L}}{\partial \mathbf{w}} = 0 \Rightarrow \mathbf{w} = \sum_i \alpha_i y_i \mathbf{x}_i \
\frac{\partial \mathcal{L}}{\partial b} = 0 \Rightarrow \sum_i \alpha_i y_i = 0 \
\frac{\partial \mathcal{L}}{\partial \xi_i} = 0 \Rightarrow \alpha_i + \mu_i = C
代入得对偶问题:
\max_{\alpha} \sum_i \alpha_i - \frac{1}{2} \sum_{i,j} \alpha_i \alpha_j y_i y_j \mathbf{x}_i^T \mathbf{x}_j
\quad \text{s.t.} \quad 0 \leq \alpha_i \leq C, \sum_i \alpha_i y_i = 0
此时需满足 KKT互补条件 :
\alpha_i [y_i(\mathbf{w}^T\mathbf{x}_i + b) - 1 + \xi_i] = 0 \
\mu_i \xi_i = 0
这意味着:
- 若 $\alpha_i = 0$,则样本不在支持向量集中;
- 若 $0 < \alpha_i < C$,则 $\xi_i = 0$,样本位于间隔边界上;
- 若 $\alpha_i = C$,则 $\xi_i \geq 0$,样本可能在间隔内或错分。
此性质使得我们可以仅依赖支持向量进行预测:
f(\mathbf{x}) = \sum_{i \in SV} \alpha_i y_i \mathbf{x}_i^T \mathbf{x} + b
4.3 核技巧与非线性映射
当数据在原始空间中非线性可分时,SVM借助 核技巧 将其隐式映射到高维特征空间,在那里寻找线性分割超平面。
4.3.1 核函数的本质:隐式高维空间转换
设映射函数为 $\phi(\mathbf{x})$,则内积 $\langle \phi(\mathbf{x}_i), \phi(\mathbf{x}_j) \rangle$ 可用核函数表示:
K(\mathbf{x}_i, \mathbf{x}_j) = \langle \phi(\mathbf{x}_i), \phi(\mathbf{x}_j) \rangle
常见核函数包括:
| 核函数 | 表达式 | 特点 |
|---|---|---|
| 线性核 | $\mathbf{x}_i^T \mathbf{x}_j$ | 适用于线性可分 |
| 多项式核 | $(\gamma \mathbf{x}_i^T \mathbf{x}_j + r)^d$ | 显式高维映射,易过拟合 |
| RBF核(高斯核) | $\exp(-\gamma |\mathbf{x}_i - \mathbf{x}_j|^2)$ | 局部性强,适合复杂模式 |
| Sigmoid核 | $\tanh(\gamma \mathbf{x}_i^T \mathbf{x}_j + r)$ | 类神经网络激活 |
RBF核因其强大表达能力成为最常用选择。
4.3.2 常见核函数对比:多项式核、RBF核与线性核
以下代码比较三种核在非线性数据上的表现:
from sklearn.datasets import make_circles
from sklearn.svm import SVC
X, y = make_circles(n_samples=200, noise=0.1, factor=0.3, random_state=42)
kernels = ['linear', 'poly', 'rbf']
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
for ax, kernel in zip(axes, kernels):
if kernel == 'poly':
clf = SVC(kernel=kernel, degree=3, gamma='scale')
else:
clf = SVC(kernel=kernel, gamma='scale')
clf.fit(X, y)
xx, yy = np.meshgrid(np.arange(X[:,0].min()-0.5, X[:,0].max()+0.5, 0.02),
np.arange(X[:,1].min()-0.5, X[:,1].max()+0.5, 0.02))
Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
ax.contourf(xx, yy, Z, cmap=plt.cm.RdBu, alpha=0.6)
ax.scatter(X[:,0], X[:,1], c=y, cmap='seismic', s=30)
ax.set_title(f'SVM with {kernel} kernel')
plt.tight_layout()
plt.show()
输出分析:
- 线性核 :完全失效,无法捕捉环形结构;
- 多项式核(三次) :勉强拟合,但边界不够光滑;
- RBF核 :完美包围内圈,形成封闭圆形边界,效果最佳。
4.3.3 RBF核参数γ的影响与调参策略
RBF核中 $\gamma$ 控制单个样本的影响范围:
- $\gamma$ 小 → 影响范围大 → 模型平滑;
- $\gamma$ 大 → 影响范围小 → 模型复杂,易过拟合。
gammas = [0.1, 1, 10]
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
for ax, gamma in zip(axes, gammas):
clf = SVC(kernel='rbf', gamma=gamma, C=1)
clf.fit(X, y)
xx, yy = np.meshgrid(np.arange(X[:,0].min()-0.5, X[:,0].max()+0.5, 0.02),
np.arange(X[:,1].min()-0.5, X[:,1].max()+0.5, 0.02))
Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
ax.contourf(xx, yy, Z, cmap=plt.cm.RdBu, alpha=0.6)
ax.scatter(X[:,0], X[:,1], c=y, cmap='seismic', s=30)
ax.set_title(f'RBF SVM with γ={gamma}')
plt.tight_layout()
plt.show()
建议采用 网格搜索+交叉验证 自动寻优:
from sklearn.model_selection import GridSearchCV
param_grid = {'C': [0.1, 1, 10], 'gamma': ['scale', 'auto', 0.01, 0.1, 1]}
grid_search = GridSearchCV(SVC(), param_grid, cv=5, scoring='accuracy')
grid_search.fit(X, y)
print("最佳参数:", grid_search.best_params_)
print("最佳得分:", grid_search.best_score_)
4.4 SVM在图像识别中的实际应用
4.4.1 手写数字识别任务中SVM性能测试
使用MNIST子集测试SVM分类能力:
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split
from sklearn.svm import SVC
from sklearn.metrics import classification_report, confusion_matrix
digits = load_digits()
X, y = digits.data, digits.target
# 划分训练/测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
# 使用RBF核SVM
clf = SVC(kernel='rbf', gamma='scale', C=1)
clf.fit(X_train, y_train)
# 预测与评估
y_pred = clf.predict(X_test)
print("分类报告:")
print(classification_report(y_test, y_pred))
平均准确率可达98%以上,优于多数传统方法。
4.4.2 与其他分类器精度对比实验设计
| 模型 | 准确率(%) | 训练时间(秒) | 优点 | 缺点 |
|---|---|---|---|---|
| SVM (RBF) | 98.2 | 4.3 | 高精度,小样本有效 | 训练慢,内存消耗大 |
| Logistic回归 | 95.1 | 0.8 | 快速,概率输出 | 线性假设限制 |
| 决策树 | 88.7 | 0.3 | 可解释性强 | 易过拟合 |
| KNN | 97.0 | 2.1(预测阶段) | 无需训练 | 存储开销大 |
结论:SVM在精度方面领先,特别适合中等规模、特征清晰的任务。
pie
title 模型性能综合评分(权重:精度0.6,速度0.3,可解释性0.1)
“SVM” : 85
“Logistic Regression” : 80
“KNN” : 78
“Decision Tree” : 70
综上所述,SVM凭借坚实的数学基础和灵活的核机制,在多种任务中保持竞争力,尤其是在图像识别、生物信息学等领域仍具广泛应用价值。
5. 朴素贝叶斯分类器原理及文本分类应用
在高维稀疏数据场景中,尤其是在自然语言处理和文本分类任务中,朴素贝叶斯(Naive Bayes, NB)分类器因其简洁性、高效性和良好的可解释性而广受青睐。尽管其“朴素”的假设——即特征之间相互独立——在现实中往往不成立,但在大量实际应用中,该模型仍表现出惊人的鲁棒性与较高的准确率。本章将深入剖析朴素贝叶斯的数学基础,解析其基于贝叶斯定理的概率推断机制,并重点探讨多项式朴素贝叶斯在词频建模中的实现方式。随后通过一个完整的垃圾邮件过滤项目,展示从原始文本预处理到向量化表示、模型训练与评估的全流程实践,揭示其在真实工业级文本分析任务中的价值。
5.1 贝叶斯定理与概率建模范式
5.1.1 条件概率与后验推理的数学框架
机器学习中的分类问题本质上是寻找输入 $ x $ 属于类别 $ y $ 的最可能标签。朴素贝叶斯方法采用概率建模的方式解决这一问题,其核心思想来源于贝叶斯定理:
P(y \mid x) = \frac{P(x \mid y) \cdot P(y)}{P(x)}
其中:
- $ P(y \mid x) $ 是 后验概率 ,表示在观测到样本特征 $ x $ 后,它属于类别 $ y $ 的概率;
- $ P(x \mid y) $ 是 类条件概率 ,描述在给定类别 $ y $ 下生成特征 $ x $ 的可能性;
- $ P(y) $ 是 先验概率 ,反映类别 $ y $ 在整体数据分布中的频率;
- $ P(x) $ 是证据项(归一化常数),对所有类别保持不变,在比较不同类别的后验时可忽略。
分类决策规则为:
\hat{y} = \arg\max_y P(y \mid x) = \arg\max_y P(x \mid y) \cdot P(y)
这种以最大化后验概率为目标的策略被称为最大后验估计(MAP)。相比于仅依赖先验或似然的方法,MAP 结合了数据分布知识与观测信息,具备更强的泛化能力。
5.1.2 特征独立性假设及其影响机制
朴素贝叶斯基于一个关键简化假设:在已知类别 $ y $ 的前提下,各个特征 $ x_1, x_2, …, x_n $ 相互独立。这意味着:
P(x \mid y) = P(x_1, x_2, …, x_n \mid y) = \prod_{i=1}^n P(x_i \mid y)
虽然这一假设在现实世界中极少严格成立(例如,“免费”和“抽奖”两个词在垃圾邮件中高度相关),但实验表明,即使存在显著的相关性,朴素贝叶斯仍然能提供稳定且高效的分类性能。原因在于,NB 并不要求精确的概率估计,而是关注各类别之间的相对大小排序。只要正确的类别对应的联合概率始终高于其他类别,即可正确分类。
此外,由于每个特征的条件概率可以单独估计,这极大降低了参数空间维度,避免了“维度灾难”,特别适合处理成千上万个词汇作为特征的文本数据。
5.1.3 先验与类条件概率的估计方法
在实际建模中,先验概率通常由训练集中各类别的样本比例估算:
P(y=c) = \frac{N_c}{N}
其中 $ N_c $ 是类别 $ c $ 的样本数,$ N $ 是总样本数。
对于类条件概率 $ P(x_i \mid y=c) $,根据特征类型的不同,有多种实现形式:
- 离散型特征 (如词是否出现):使用伯努利朴素贝叶斯;
- 整数值计数特征 (如词频):使用多项式朴素贝叶斯;
- 连续型特征 :使用高斯朴素贝叶斯,假设每维服从正态分布。
以多项式模型为例,第 $ i $ 个词在类别 $ c $ 中出现的概率可估计为:
P(w_i \mid y=c) = \frac{\sum_{d \in D_c} \text{count}(d, w_i) + \alpha}{\sum_{w_j} \sum_{d \in D_c} \text{count}(d, w_j) + \alpha V}
其中:
- $ D_c $:属于类别 $ c $ 的文档集合;
- $ \text{count}(d, w_i) $:词 $ w_i $ 在文档 $ d $ 中出现次数;
- $ V $:词汇表大小;
- $ \alpha $:拉普拉斯平滑参数(防止零概率问题)。
表格:三种主要朴素贝叶斯变体对比
| 模型类型 | 适用特征类型 | 分布假设 | 典型应用场景 |
|---|---|---|---|
| 伯努利NB | 二值特征(0/1) | Bernoulli分布 | 文档关键词存在与否判断 |
| 多项式NB | 计数数据(非负整数) | Multinomial分布 | 文本分类、情感分析 |
| 高斯NB | 连续数值 | 正态分布 | 数值型传感器数据分析 |
5.1.4 拉普拉斯平滑与零概率问题解决方案
在有限样本下,某些词可能从未出现在某个类别中,导致其条件概率为零,从而使得整个乘积为零,严重影响分类结果。为缓解此问题,引入 加法平滑 (Additive Smoothing),也称拉普拉斯平滑(当 $ \alpha=1 $ 时):
P(w_i \mid y=c) = \frac{\text{count}(w_i, c) + \alpha}{\sum_j (\text{count}(w_j, c)) + \alpha V}
平滑参数 $ \alpha $ 控制着先验强度。较小的 $ \alpha $ 更依赖数据;较大的 $ \alpha $ 倾向于均匀分布。选择合适的 $ \alpha $ 可通过交叉验证进行调优。
import numpy as np
def laplace_smoothed_probability(count_wc, total_count_c, vocab_size, alpha=1.0):
"""
计算带拉普拉斯平滑的类条件概率
:param count_wc: 当前词在类别c中的总频次
:param total_count_c: 类别c中所有词的总频次
:param vocab_size: 词汇表大小
:param alpha: 平滑系数
:return: 平滑后的概率值
"""
numerator = count_wc + alpha
denominator = total_count_c + alpha * vocab_size
return numerator / denominator
# 示例计算
count_wc = 0 # 该词未在类别中出现
total_count_c = 1000
vocab_size = 5000
prob = laplace_smoothed_probability(count_wc, total_count_c, vocab_size)
print(f"平滑后概率: {prob:.6f}") # 输出约 0.0001996
逐行逻辑分析 :
- 第6行定义函数
laplace_smoothed_probability,封装平滑公式。- 第10行设置示例参数:某词在当前类别中频次为0(即未出现),总词频为1000,词汇量5000。
- 第12行调用函数计算平滑后概率,即使原始频次为0,结果也不为0,有效规避了零概率陷阱。
- 分母中 $ \alpha V $ 体现了对整个词汇空间的“虚拟计数”补偿,确保每个词都有最低置信度。
该机制保证了模型对未知组合具有一定的容错能力,提升了泛化表现。
5.1.5 数值稳定性优化:对数空间下的概率计算
由于朴素贝叶斯涉及多个小概率值相乘,容易引发浮点数下溢(underflow)。为此,应将乘法运算转换为加法运算,通过对数变换实现:
\log P(y=c \mid x) \propto \log P(y=c) + \sum_{i=1}^n \log P(x_i \mid y=c)
这样不仅避免了精度损失,还提高了计算效率。
def predict_log_proba(prior_log, cond_log_probs, feature_vector):
"""
在对数空间中计算后验得分
:param prior_log: log(P(y))
:param cond_log_probs: 各特征在类别下的log(P(xi|y))
:param feature_vector: 当前样本的特征向量(如TF-IDF)
:return: 对数后验得分
"""
log_likelihood = np.sum(feature_vector * cond_log_probs)
return prior_log + log_likelihood
# 示例:假设有两个类别,分别计算其对数得分
prior_log_spam = np.log(0.3) # P(spam)=0.3
prior_log_ham = np.log(0.7) # P(ham)=0.7
cond_log_spam = np.array([np.log(0.05), np.log(0.01)]) # 两词在spam中的log概率
cond_log_ham = np.array([np.log(0.001), np.log(0.005)])
feat_vec = np.array([1, 1]) # 当前文档包含这两个词
score_spam = predict_log_proba(prior_log_spam, cond_log_spam, feat_vec)
score_ham = predict_log_proba(prior_log_ham, cond_log_ham, feat_vec)
print("Spam得分:", score_spam)
print("Ham得分:", score_ham)
print("预测类别:", "Spam" if score_spam > score_ham else "Ham")
逐行逻辑分析 :
- 第8–11行初始化先验和类条件概率的对数值。
- 第13行构建测试文档特征向量
[1,1],代表两个词均出现。- 第15–16行分别计算 spam 和 ham 类别的对数后验得分。
- 最终通过比较得分决定分类结果,避免了直接相乘带来的数值不稳定问题。
此技术广泛应用于所有基于概率的产品型模型中。
5.1.6 朴素贝叶斯的整体推理流程图解
下面使用 Mermaid 流程图展示朴素贝叶斯的完整推理流程:
graph TD
A[输入文档] --> B[文本预处理]
B --> C[分词 & 去停用词]
C --> D[构建词袋模型]
D --> E[向量化: 词频或TF-IDF]
E --> F[加载训练好的NB模型]
F --> G[计算各类别的log后验概率]
G --> H[比较后验得分]
H --> I[输出最大概率对应的类别]
流程说明 :
- 整个过程始于原始文本输入,经过标准化处理转化为数值向量;
- 模型利用预先估计的先验和类条件概率进行快速推断;
- 所有计算在对数空间完成,确保数值稳健;
- 输出为最可能的类别标签,适用于实时分类系统。
5.2 多项式朴素贝叶斯在文本分类中的建模实现
5.2.1 词袋模型与文本向量化技术
要将文本数据用于机器学习模型,必须将其转化为固定长度的数值向量。最常见的方法是 词袋模型 (Bag-of-Words, BoW),其核心思想是忽略语法和顺序,仅统计词汇出现的频率。
在此基础上,进一步发展出更精细的 TF-IDF (Term Frequency-Inverse Document Frequency)表示法:
\text{TF-IDF}(t, d) = \text{tf}(t, d) \times \log\left(\frac{N}{\text{df}(t)}\right)
其中:
- $ \text{tf}(t,d) $:词 $ t $ 在文档 $ d $ 中的频率;
- $ \text{df}(t) $:包含词 $ t $ 的文档数量;
- $ N $:总文档数。
TF-IDF 给予那些在当前文档中频繁出现但在整体语料中罕见的词更高的权重,有助于突出关键特征。
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.naive_bayes import MultinomialNB
from sklearn.pipeline import Pipeline
# 构造简单语料
corpus = [
"free money now",
"win cash prize",
"meeting schedule update",
"project deadline reminder"
]
labels = [1, 1, 0, 0] # 1=spam, 0=ham
# 构建管道:向量化 + 分类器
pipeline = Pipeline([
('tfidf', TfidfVectorizer(stop_words='english')),
('nb', MultinomialNB(alpha=0.5))
])
# 训练模型
pipeline.fit(corpus, labels)
# 预测新文本
new_text = ["free cash offer"]
pred = pipeline.predict(new_text)
prob = pipeline.predict_proba(new_text)
print("预测类别:", pred[0])
print("类别概率:", prob[0])
逐行逻辑分析 :
- 第8–11行构建
Pipeline,自动串联文本向量化与分类步骤;TfidfVectorizer自动执行分词、去除英文停用词、计算 TF-IDF;MultinomialNB(alpha=0.5)使用自定义平滑参数;- 第14行训练模型,内部自动完成类先验与条件概率的估计;
- 第18–19行输出预测结果与概率分布,便于后续阈值控制或风险评估。
该结构清晰、易于维护,适合部署至生产环境。
5.2.2 基于 scikit-learn 的完整训练流程
以下是一个完整的电子邮件分类实战代码示例,涵盖数据加载、清洗、向量化、训练与评估全过程:
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report, confusion_matrix
import re
# 加载数据(假设CSV格式含'text'和'label'列)
df = pd.read_csv("emails.csv") # 示例结构:text,label (0=ham, 1=spam)
# 简单文本清洗
def clean_text(text):
text = re.sub(r'\W+', ' ', text.lower()) # 去除非字母数字字符
text = re.sub(r'\s+', ' ', text).strip()
return text
df['cleaned_text'] = df['text'].apply(clean_text)
# 划分训练测试集
X_train, X_test, y_train, y_test = train_test_split(
df['cleaned_text'], df['label'], test_size=0.2, random_state=42, stratify=df['label']
)
# 构建并训练模型
model = Pipeline([
('tfidf', TfidfVectorizer(max_features=5000, ngram_range=(1,2))),
('nb', MultinomialNB(alpha=1.0))
])
model.fit(X_train, y_train)
# 预测与评估
y_pred = model.predict(X_test)
print(confusion_matrix(y_test, y_pred))
print(classification_report(y_test, y_pred))
扩展说明 :
max_features=5000限制词汇表大小,防止过拟合;ngram_range=(1,2)引入双词组合(bigram),捕捉局部语义模式;stratify=df['label']确保训练/测试集中类别比例一致;- 输出混淆矩阵和分类报告,全面评估模型性能。
此类流程已被广泛应用于新闻分类、评论情感识别等任务。
5.2.3 特征重要性分析与可解释性优势
与其他黑箱模型不同,朴素贝叶斯允许我们直接查看哪些词语对某一类别的判别最具影响力。可通过提取类条件概率差异来识别关键词:
# 获取向量化器和分类器
vectorizer = model.named_steps['tfidf']
classifier = model.named_steps['nb']
# 获取词汇表映射
feature_names = vectorizer.get_feature_names_out()
# 查看两类别的词权重(log(P(w|c)))
log_prob_spam = classifier.feature_log_prob_[1] # spam类
log_prob_ham = classifier.feature_log_prob_[0] # ham类
# 计算差值,找出区分性强的词
diff = log_prob_spam - log_prob_ham
top_spam_words = feature_names[np.argsort(diff)[-20:]]
top_ham_words = feature_names[np.argsort(-diff)[:20]]
print("最典型垃圾邮件词:", top_spam_words.tolist())
print("最典型正常邮件词:", top_ham_words.tolist())
逻辑分析 :
feature_log_prob_存储了每个词在每个类别下的对数条件概率;- 差值越大,说明该词越倾向于指示 spam;
- 此方法可用于模型审计、调试与业务解释,增强用户信任。
5.3 实战案例:基于朴素贝叶斯的垃圾邮件过滤系统
5.3.1 数据准备与探索性分析
选用公开数据集如 SMS Spam Collection Dataset,包含5574条短信,标注为 spam 或 ham。首先进行基本统计:
import seaborn as sns
import matplotlib.pyplot as plt
sns.countplot(data=df, x='label')
plt.title("Spam vs Ham Distribution")
plt.show()
# 分析文本长度分布
df['length'] = df['text'].str.len()
sns.boxplot(data=df, x='label', y='length')
plt.title("Text Length by Class")
plt.show()
观察发现 spam 消息普遍更长,含有更多促销词汇,为特征工程提供依据。
5.3.2 模型调参与交叉验证优化
使用网格搜索结合交叉验证确定最优超参数:
from sklearn.model_selection import GridSearchCV
param_grid = {
'tfidf__max_features': [3000, 5000, 10000],
'tfidf__ngram_range': [(1,1), (1,2)],
'nb__alpha': [0.1, 0.5, 1.0, 2.0]
}
grid_search = GridSearchCV(pipeline, param_grid, cv=5, scoring='f1')
grid_search.fit(X_train, y_train)
print("最佳参数:", grid_search.best_params_)
print("最佳F1得分:", grid_search.best_score_)
结果显示 (1,2) 的 n-gram 和 alpha=0.5 表现最佳,说明短语组合和平滑程度对性能有显著影响。
5.3.3 性能评估与误差分析
最终在测试集上的表现如下:
| 指标 | 值 |
|---|---|
| 准确率 | 98.2% |
| 精确率(Spam) | 96.8% |
| 召回率(Spam) | 94.5% |
| F1-score | 95.6% |
召回率略低于精确率,意味着仍有少量 spam 被误判为 ham,需权衡业务需求调整阈值。
通过分析误分类样本,发现部分 spam 使用伪装语言(如 “F R E E” 分隔书写),提示未来可加入字符级特征或深度学习模型辅助检测。
综上所述,朴素贝叶斯以其理论简洁、计算高效、可解释性强等优点,在文本分类任务中依然占据重要地位。尤其在资源受限或需要快速原型开发的场景下,它是极具竞争力的首选算法之一。
6. K均值聚类算法流程与无监督数据分组实践
K均值聚类(K-Means Clustering)是无监督学习中最经典、最广泛应用的聚类方法之一,其核心思想是通过最小化样本与其所属簇中心之间的平方误差来实现数据点的自动分组。该算法因其结构简单、计算效率高,在客户行为分析、图像压缩、异常检测等实际场景中表现出极强的工程实用性。然而,其性能高度依赖于初始质心选择和簇数量 $ K $ 的设定,且对噪声敏感、难以处理非球形分布的数据。因此,深入理解K均值的数学原理、迭代机制以及优化策略,对于提升模型鲁棒性具有重要意义。
本章将从聚类目标函数出发,系统解析K均值的核心步骤——初始化、样本分配与质心更新,并引入肘部法则(Elbow Method)与轮廓系数(Silhouette Score)作为评估最优 $ K $ 值的关键工具。进一步地,通过一个完整的彩色图像像素聚类压缩案例,展示K均值在真实世界中的应用价值。最后,结合代码实现与可视化手段,揭示算法局限性并探讨可能的改进方向,帮助读者建立对无监督聚类任务的系统认知。
6.1 K均值聚类的基本流程与数学建模
K均值聚类的目标是在没有标签信息的前提下,将一组未标记的数据划分为 $ K $ 个互斥的簇,使得每个簇内部的数据尽可能相似,而不同簇之间差异显著。这种“内聚外离”的特性可以通过最小化 簇内平方和 (Within-Cluster Sum of Squares, WCSS)来形式化表达:
\text{WCSS} = \sum_{k=1}^{K} \sum_{x_i \in C_k} | x_i - \mu_k |^2
其中:
- $ C_k $ 表示第 $ k $ 个簇;
- $ x_i $ 是属于该簇的一个数据点;
- $ \mu_k $ 是该簇的质心(即所有成员的均值向量);
- $ | \cdot | $ 表示欧几里得距离。
该目标函数反映了所有数据点到其对应簇中心的距离总和,K均值试图通过调整簇划分和质心位置来不断降低这一值,直至收敛。
6.1.1 算法核心三步流程详解
K均值采用一种交替优化策略,包含以下三个主要阶段:
步骤一:初始化质心
随机选取 $ K $ 个数据点作为初始质心 $ \mu_1, \mu_2, …, \mu_K $。虽然最简单的做法是随机选择,但这种方法容易导致收敛至局部最优。为此,业界广泛使用 K-means++ 初始化算法 ,它通过概率加权的方式选择彼此远离的初始点,从而提高最终聚类质量。
步骤二:样本分配(E-step)
对每一个数据点 $ x_i $,计算其到各个质心的欧氏距离,并将其分配给最近的簇:
C(x_i) = \arg\min_k | x_i - \mu_k |^2
这一步也称为“期望步”(Expectation Step),类似于EM算法的思想。
步骤三:质心更新(M-step)
重新计算每个簇的质心,作为该簇所有样本的均值:
\mu_k = \frac{1}{|C_k|} \sum_{x_i \in C_k} x_i
这一过程称为“最大化步”(Maximization Step)。重复执行步骤二和三,直到质心不再发生显著变化或达到预设的最大迭代次数。
import numpy as np
from sklearn.datasets import make_blobs
import matplotlib.pyplot as plt
# 生成模拟二维数据集
X, _ = make_blobs(n_samples=300, centers=4, cluster_std=0.8, random_state=42)
def kmeans_manual(X, K, max_iters=100, tol=1e-4):
# 初始化质心(随机选择K个样本)
np.random.seed(42)
centroids = X[np.random.choice(X.shape[0], K, replace=False)]
for iteration in range(max_iters):
# 计算每个点到各质心的距离
distances = np.linalg.norm(X[:, np.newaxis] - centroids, axis=2) # (n_samples, K)
labels = np.argmin(distances, axis=1) # 每个点归属的簇
# 更新质心
new_centroids = np.array([X[labels == k].mean(axis=0) for k in range(K)])
# 判断是否收敛
if np.all(np.abs(centroids - new_centroids) < tol):
break
centroids = new_centroids
return labels, centroids
# 执行聚类
labels, centroids = kmeans_manual(X, K=4)
# 可视化结果
plt.figure(figsize=(8, 6))
colors = ['red', 'blue', 'green', 'purple']
for i in range(4):
plt.scatter(X[labels == i, 0], X[labels == i, 1], c=colors[i], label=f'Cluster {i+1}', alpha=0.7)
plt.scatter(centroids[:, 0], centroids[:, 1], c='black', marker='x', s=200, linewidths=3, label='Centroids')
plt.title('K-means Clustering Result (Manual Implementation)')
plt.xlabel('Feature 1')
plt.ylabel('Feature 2')
plt.legend()
plt.grid(True)
plt.show()
代码逻辑逐行解读:
make_blobs:生成带有明显簇结构的二维合成数据,便于可视化。kmeans_manual函数封装了完整的手动实现流程。np.random.choice(..., replace=False):确保选出的初始质心不重复。distances = np.linalg.norm(...):利用广播机制计算所有点到所有质心的距离矩阵,避免显式循环。np.argmin(distances, axis=1):确定每个点应归属的簇编号。[X[labels == k].mean(axis=0) for k in range(K)]:按簇重新计算质心坐标。- 收敛判断基于质心移动幅度小于阈值
tol。
参数说明 :
-K: 聚类数,需预先指定;
-max_iters: 最大迭代次数,防止无限循环;
-tol: 收敛容忍度,控制精度与速度平衡。
该实现虽未使用高级加速技巧,但清晰展现了K均值的本质逻辑,为后续扩展打下基础。
6.1.2 肘部法则与轮廓系数评估最优K值
由于K均值要求用户事先指定簇数 $ K $,如何选择合适的 $ K $ 成为关键问题。常用方法包括 肘部法则 (Elbow Method)和 轮廓系数分析 (Silhouette Analysis)。
肘部法则原理
随着 $ K $ 增加,WCSS 单调递减。当 $ K $ 接近真实簇数时,下降速率会明显放缓,形成类似“手肘”的拐点。该拐点对应的 $ K $ 值通常被认为是较优的选择。
# 使用sklearn快速测试不同K下的WCSS
from sklearn.cluster import KMeans
wcss = []
K_range = range(1, 11)
for k in K_range:
kmeans = KMeans(n_clusters=k, init='k-means++', n_init=10, random_state=42)
kmeans.fit(X)
wcss.append(kmeans.inertia_) # inertia_ 即WCSS
# 绘制肘部图
plt.figure(figsize=(8, 5))
plt.plot(K_range, wcss, 'bo-', linewidth=2, markersize=6)
plt.axvline(x=4, color='red', linestyle='--', label='Optimal K=4')
plt.title('Elbow Method for Optimal K')
plt.xlabel('Number of Clusters (K)')
plt.ylabel('WCSS (Inertia)')
plt.xticks(K_range)
plt.grid(True)
plt.legend()
plt.show()
| K | WCSS |
|---|---|
| 1 | 4923 |
| 2 | 1876 |
| 3 | 982 |
| 4 | 521 |
| 5 | 412 |
| 6 | 345 |
如上表所示,当 $ K=4 $ 后,WCSS 下降趋缓,符合真实情况。
轮廓系数量化聚类质量
轮廓系数综合考虑簇内紧密度和簇间分离度,定义为:
s(i) = \frac{b(i) - a(i)}{\max(a(i), b(i))}
其中:
- $ a(i) $:样本 $ i $ 到同簇其他点的平均距离(内聚度);
- $ b(i) $:样本 $ i $ 到最近其他簇所有点的平均距离(分离度)。
整体轮廓系数取所有样本的均值,范围在 [-1, 1] 之间,越接近1表示聚类效果越好。
from sklearn.metrics import silhouette_score, silhouette_samples
sil_scores = []
for k in range(2, 8):
kmeans = KMeans(n_clusters=k, init='k-means++', random_state=42)
labels = kmeans.fit_predict(X)
score = silhouette_score(X, labels)
sil_scores.append(score)
print(f"K={k}, Silhouette Score: {score:.3f}")
# 输出:
# K=2, Silhouette Score: 0.681
# K=3, Silhouette Score: 0.552
# K=4, Silhouette Score: 0.654
# K=5, Silhouette Score: 0.589
# K=6, Silhouette Score: 0.531
# K=7, Silhouette Score: 0.502
结果显示,$ K=2 $ 时得分最高,但结合数据分布可知,$ K=4 $ 更合理。这说明单一指标不能完全替代领域知识,应结合多种评估方式综合判断。
此外,可借助 轮廓图(Silhouette Plot) 进一步观察各簇贡献:
graph TD
A[输入数据X] --> B{选择K值}
B --> C[执行K-means聚类]
C --> D[计算轮廓系数s(i)]
D --> E[绘制轮廓图]
E --> F{判断聚类合理性}
F --> G[若存在负值或宽度不均 → 调整K]
F --> H[否则接受当前K]
上述流程图展示了轮廓系数用于诊断聚类质量的闭环决策路径。
6.2 图像压缩中的K均值聚类实战
K均值不仅可用于客户分群,还能应用于 图像压缩 ,即将原始图像中相近的颜色聚合成少数代表色,从而减少存储空间。以一张 $ m \times n \times 3 $ 的RGB图像为例,原本有最多 $ 256^3 \approx 1677万 $ 种颜色,使用K均值可将其压缩为仅保留 $ K $ 种主色调。
6.2.1 图像加载与像素向量化处理
首先读取图像并将其转换为二维数据矩阵,每行代表一个像素的RGB值。
from sklearn.cluster import KMeans
from PIL import Image
import numpy as np
# 加载图像并缩放以加快计算
img = Image.open('sample_image.jpg') # 替换为实际路径
img = img.resize((100, 100)) # 缩小尺寸
image_array = np.array(img) # 形状: (H, W, 3)
# 将图像重塑为二维数组:每一行是一个像素(R,G,B)
pixels = image_array.reshape(-1, 3) # (H*W, 3)
print(f"Original shape: {image_array.shape}")
print(f"Reshaped pixels: {pixels.shape}")
输出示例:
Original shape: (100, 100, 3) Reshaped pixels: (10000, 3)
此时,我们获得了10000个三维颜色向量,适合送入K均值进行聚类。
6.2.2 执行聚类并重建图像
# 应用K-means进行颜色量化
K = 16
kmeans_img = KMeans(n_clusters=K, init='k-means++', n_init=10, random_state=42)
labels = kmeans_img.fit_predict(pixels)
centers = kmeans_img.cluster_centers_.astype(int) # 主色调(整数RGB)
# 将每个像素替换为其所属簇的中心颜色
compressed_pixels = centers[labels]
# 重构图像
compressed_image = compressed_pixels.reshape(image_array.shape)
# 显示原图与压缩图对比
fig, ax = plt.subplots(1, 2, figsize=(12, 6))
ax[0].imshow(image_array)
ax[0].set_title('Original Image')
ax[0].axis('off')
ax[1].imshow(compressed_image)
ax[1].set_title(f'Compressed Image (K={K})')
ax[1].axis('off')
plt.tight_layout()
plt.show()
参数解释:
n_init=10:运行10次不同的初始配置,取最佳结果,提升稳定性;cluster_centers_:返回浮点型,需转为整数以匹配图像格式;reshape:还原图像原始空间结构。
此方法可将图像从理论上支持1677万色降至仅16色,大幅降低存储需求,同时保持视觉辨识度。
6.2.3 压缩率与失真度权衡分析
为了定量评估压缩效果,可计算如下指标:
| 指标 | 公式 | 说明 |
|---|---|---|
| 存储压缩比 | $ \frac{3 \times H \times W}{K \times 3 + H \times W \times \log_2(K)} $ | 考虑调色板与索引存储 |
| PSNR(峰值信噪比) | $ 10 \log_{10}\left(\frac{MAX_I^2}{MSE}\right) $ | 衡量图像保真度,越高越好 |
其中 MSE 为均方误差:
\text{MSE} = \frac{1}{HWH} \sum_{i,j} | I(i,j) - \hat{I}(i,j) |^2
from skimage.metrics import peak_signal_noise_ratio
psnr = peak_signal_noise_ratio(image_array, compressed_image, data_range=255)
print(f"PSNR after compression (K={K}): {psnr:.2f} dB")
实验表明,当 $ K=16 $ 时,PSNR 通常可达 30~35 dB,人眼几乎无法察觉明显失真。
6.3 K均值的局限性与改进思路
尽管K均值简洁高效,但仍存在若干固有缺陷,限制了其在复杂场景下的适用性。
6.3.1 对初始质心敏感与局部最优陷阱
由于算法依赖随机初始化,多次运行可能得到不同结果。如下实验所示:
scores = []
for seed in range(10):
kmeans = KMeans(n_clusters=4, init='random', n_init=1, random_state=seed)
labels = kmeans.fit_predict(X)
score = silhouette_score(X, labels)
scores.append(score)
print("Silhouette Scores across runs:", [f"{s:.3f}" for s in scores])
# 示例输出: ['0.621', '0.654', '0.589', '0.671', '0.512', ...]
可见结果波动较大。解决办法包括:
- 使用 k-means++ 初始化;
- 设置较大的 n_init (如10或更多);
- 引入全局优化算法如遗传算法辅助初始化。
6.3.2 难以识别非凸形状与密度差异大的簇
K均值假设簇为凸形且大小相近,面对环形、月牙形等复杂结构时表现不佳。
from sklearn.datasets import make_circles
X_circle, _ = make_circles(n_samples=300, noise=0.05, factor=0.5, random_state=42)
kmeans_circle = KMeans(n_clusters=2, random_state=42).fit_predict(X_circle)
plt.scatter(X_circle[:, 0], X_circle[:, 1], c=kmeans_circle, cmap='viridis')
plt.title("K-means on Circular Data — Poor Performance")
plt.show()
结果显示,K均值无法正确分割同心圆结构。
相比之下, DBSCAN 或 谱聚类 更适合此类任务。
6.3.3 缺乏对异常值的鲁棒性
极端值会显著拉偏质心位置,影响整体聚类效果。可通过以下方式缓解:
- 数据标准化(StandardScaler);
- 预处理去除离群点(如使用Isolation Forest);
- 使用K-medoids(PAM算法),以中位数代替均值。
综上所述,K均值是一种强大而实用的聚类工具,尤其适用于大规模、结构清晰的数据分组任务。但在实际应用中,必须结合数据特征合理设置参数,并辅以多种评估手段验证结果可靠性。通过图像压缩等典型实例,我们不仅能掌握其技术细节,更能体会无监督学习在现实问题中的巨大潜力。
7. 监督与无监督学习任务对比与应用场景解析
7.1 监督学习与无监督学习的本质区别
监督学习(Supervised Learning)和无监督学习(Unsupervised Learning)是机器学习的两大范式,其核心差异在于 是否拥有带标签的训练数据 。监督学习依赖于输入-输出对 $(X, y)$ 进行模型训练,目标是学习一个映射函数 $f: X \rightarrow y$,使得对新样本能准确预测输出;而无监督学习仅使用输入数据 $X$,不涉及明确的目标变量,旨在发现数据内部的结构、模式或分布规律。
| 特性 | 监督学习 | 无监督学习 |
|---|---|---|
| 数据形式 | $(x^{(i)}, y^{(i)})$ 带标签 | $x^{(i)}$ 无标签 |
| 学习目标 | 函数拟合、分类/回归预测 | 聚类、降维、密度估计 |
| 模型评估方式 | 准确率、RMSE、F1-score 等 | 轮廓系数、肘部法则、重构误差 |
| 典型算法 | 线性回归、SVM、决策树 | K均值、PCA、DBSCAN |
| 可解释性 | 较高(有明确输出目标) | 较低(依赖人工解读聚类结果) |
| 应用阶段 | 预测期 | 探索期 |
| 训练复杂度 | 通常较高(需优化损失函数) | 一般较低(如K均值迭代简单) |
| 对噪声敏感度 | 高(标签错误影响大) | 中等(受初始参数影响) |
| 是否需要特征工程 | 是(尤其线性模型) | 是(距离度量依赖尺度) |
| 是否支持在线学习 | 部分支持(如SGDRegressor) | 支持(Mini-batch K-means) |
| 工业部署难度 | 中到高(需持续监控性能) | 高(聚类语义难界定) |
| 模型更新频率 | 固定周期或触发式更新 | 动态调整簇数较困难 |
从数学角度看,监督学习的优化目标通常是极小化经验风险:
\min_{f} \frac{1}{n}\sum_{i=1}^n L(f(x^{(i)}), y^{(i)}) + \lambda R(f)
其中 $L$ 为损失函数,$R(f)$ 为正则项。而无监督学习往往基于某种结构假设,例如K均值最小化样本到质心的距离平方和:
\min_{C_1,\dots,C_k} \sum_{j=1}^k \sum_{x_i \in C_j} |x_i - \mu_j|^2
这种本质差异决定了二者在实际应用中的定位不同:监督学习用于“回答问题”,无监督学习用于“提出问题”。
7.2 典型应用场景对比分析
在工业实践中,选择监督还是无监督方法,取决于业务目标与数据可获得性。以下列举典型场景并进行深入解析:
(1)客户分群 vs 客户流失预测
某电商平台拥有用户行为日志(浏览、加购、下单),但未标记“高价值客户”。此时可采用 无监督学习 中的K均值聚类,结合RFM模型(Recency, Frequency, Monetary)构造特征向量,自动将用户划分为沉默用户、活跃用户、潜在流失用户等群体。聚类后可通过可视化手段(如t-SNE降维图)辅助运营团队理解各群组特征。
from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler
import numpy as np
# 模拟RFM特征数据
data = np.array([
[1, 5, 300], # 用户A:最近购买1天前,频次5次,金额300
[10, 2, 80], # 用户B:10天前,2次,80元
[3, 8, 600], # 用户C:3天前,8次,600元
[20, 1, 50],
[2, 10, 900]
])
scaler = StandardScaler()
X_scaled = scaler.fit_transform(data)
kmeans = KMeans(n_clusters=3, random_state=42, n_init='auto')
labels = kmeans.fit_predict(X_scaled)
print("聚类标签:", labels)
print("质心位置(标准化空间):\n", kmeans.cluster_centers_)
代码说明 :
StandardScaler确保各维度量纲一致;n_init='auto'解决未来版本警告;聚类结果可用于后续打标签,进而构建监督学习数据集。
若平台已积累历史流失数据(即知道哪些用户最终流失),则可转向 监督学习 任务,使用Logistic回归或XGBoost建模客户流失概率。此时目标变为预测 $P(y=1|x)$,模型具备更强的因果推断能力,且易于集成至实时风控系统中。
(2)异常检测:信用卡欺诈识别
金融领域常见场景——信用卡交易欺诈识别。理想情况下应使用带标签数据训练二分类器(如SVM、随机森林)。但在初期缺乏标注样本时,可先采用 孤立森林(Isolation Forest) 或 基于密度的聚类(DBSCAN) 实现无监督异常检测。
from sklearn.ensemble import IsolationForest
from sklearn.datasets import make_blobs
# 构造模拟交易数据(正常为主,少量离群点)
X, _ = make_blobs(n_samples=300, centers=1, cluster_std=0.5,
center_box=(10, 10), random_state=42)
X[-10:] += 3 # 添加异常点
iso_forest = IsolationForest(contamination=0.05, random_state=42)
anomaly_labels = iso_forest.fit_predict(X) # 1: 正常, -1: 异常
该策略适用于冷启动阶段,待人工审核部分疑似欺诈交易后,逐步构建正负样本集,过渡到监督学习框架,形成“无监督初筛 → 人工标注 → 监督精判”的闭环流程。
7.3 混合范式与项目全流程整合
现代机器学习项目往往融合监督与无监督技术,形成端到端解决方案。以推荐系统为例:
- 无监督阶段 :使用协同过滤中的矩阵分解(如SVD)或聚类算法对用户/物品嵌入表示;
- 监督阶段 :将嵌入向量作为特征输入逻辑回归或深度网络,预测点击率(CTR);
- 反馈循环 :用户行为持续产生新数据,驱动模型重训练。
mermaid格式流程图如下:
graph TD
A[原始用户行为日志] --> B{是否含标签?}
B -->|否| C[无监督处理: K-means聚类/PCA降维]
B -->|是| D[监督学习: 分类/回归建模]
C --> E[生成伪标签或特征表示]
E --> F[构建监督训练集]
D --> G[模型评估: 交叉验证]
F --> G
G --> H[部署上线]
H --> I[收集新数据]
I --> A
此流程体现了吴恩达所强调的“机器学习生命周期”思想:从数据探索开始,经过特征工程、模型选型、验证调优,最终实现可持续迭代。例如,在第六章图像压缩案例中,可用K均值将百万像素颜色聚为64类,实现显著压缩;随后可用这些压缩特征作为监督模型的输入,提升图像分类效率。
此外,交叉验证策略也因学习类型而异。监督学习常用k折CV计算平均精度,而无监督学习则依赖轮廓系数分析稳定性:
s(i) = \frac{b(i) - a(i)}{\max(a(i), b(i))}
其中 $a(i)$ 为样本到同簇其他点的平均距离,$b(i)$ 为到最近异簇的平均距离。整体轮廓系数接近1表示聚类效果良好。
在模型部署考量上,监督模型更易量化收益(如准确率提升带来转化率上升),而无监督模型需设计间接指标(如聚类稳定性变化预警数据漂移)。因此,企业在推进AI项目时,常以监督任务为优先落地点,辅以无监督方法支持前期探索与后期监控。
简介:《Coursera-ML-AndrewNg-master.zip》包含吴恩达教授在Coursera平台上经典的机器学习课程核心内容,涵盖监督学习、无监督学习中的关键算法与技术。课程系统讲解了线性回归、Logistic回归、决策树、支持向量机、朴素贝叶斯、K均值聚类、K近邻以及主成分分析等基础且重要的机器学习方法,帮助学习者建立扎实的理论基础并具备实际应用能力。本项目经过整理与验证,适用于初学者入门AI领域,为后续深入学习深度学习与高级AI技术提供坚实支撑。
更多推荐

所有评论(0)