
Hidden Markov Model (HMM) —— 隐马尔可夫模型
系统讲解隐马尔可夫模型(HMM)的完整数学原理与三大核心算法:从马尔可夫链到双重随机过程的演进出发,定义HMM的五元组参数(Q, V, π, A, B);详细推导估值问题、解码问题和学习问题;并通过词性标注、语音识别等经典应用场景展示HMM的实践价值。
阅读文章ZHY's Blog
A UNIVERSE OF IDEAS · BY ZHANG HAOYI
让好奇心 点亮知识宇宙
在代码、模型与思想之间自由漫游。这里持续记录人工智能、机器学习、软件工程与成长实践,让每次阅读都成为一次新的发现。
ARTICLE NOTE
经过第二阶段:树模型与集成学习的完整旅程,我们完成了监督学习核心范式的全部拼图 —— 从单棵决策树的生长与剪枝,到随机森林的并行降方差,再到 AdaBoost、GBM、XGBoost、LightGBM 的串行降偏差迭代。这些算法的共同前提是:训练数据带有明确的标签 y,模型的目标是学习从 x 到 y 的映射关系。
现在,我们将进入一个全新的领域——无监督学习。这里的挑战不再是“如何预测”,而是 “如何从无标签数据中发现结构” 。主成分分析(PCA) 正是这个领域最经典、最基础的算法,由 Karl Pearson 于 1901 年提出,迄今已逾百年,却依然是数据预处理和探索性分析中最常用的工具之一。
PCA 的核心思想极为简洁:找到数据中方差最大的几个正交方向,将数据投影到这些方向上,用更少的维度保留尽可能多的信息。这一直觉背后有着完整的数学支撑 —— 从方差最大化与最小化重构误差两个等价视角出发,通过拉格朗日乘子法可以严格推导出:主成分方向即为数据协方差矩阵的特征向量,主成分方差即为对应的特征值。在实现层面,PCA 既可以通过对协方差矩阵做特征值分解(EVD) 完成,也可以通过奇异值分解(SVD) 更高效地实现——后者在“样本数远小于特征数”的高维场景下优势尤为明显。
本文还将讨论累计方差贡献率、碎石图肘部法则等选择主成分数量的实用方法,并明确指出 PCA 的适用边界 —— 它对特征尺度敏感(标准化是必修课)、假设数据关系是线性的、对离群点敏感,这些局限决定了它并非万能。
Note读完本文,你将掌握 PCA 从几何直觉到数学推导再到工程实现的完整链路。作为第三阶段:无监督与概率模型的起点,PCA 将为我们建立“从数据自身结构出发”的思维方式。
下一篇文章,我们将转向聚类问题——K-Means 与 PCA 在目标上不同(聚类 vs 降维),但二者在“利用数据协方差结构”这一底层逻辑上有着深刻的联系。
假设我们有一堆二维数据点,它们大致沿着某个方向拉伸(比如一条斜线)。如果我们只能用一个数来描述每个点,应该选哪个方向?
直觉上,我们应该选数据变化最大的那个方向 —— 也就是数据点投影后方差最大的方向。因为方差越大,说明数据在这个方向上越“分散”,保留的信息就越多。如果选了一个方差很小的方向,所有点投影后都挤在一起,信息就丢失了。
主成分(Principal Component)就是数据中方差最大的方向。第一个主成分是方差最大的方向,第二个主成分是与第一个正交的、方差次大的方向,依此类推。
一个简单的实例想象二维平面上的数据点呈椭圆形分布(长轴沿45°方向)。PCA会找到两个主成分:
- 第一主成分(PC1) :沿椭圆长轴方向——数据在这个方向上变化最大
- 第二主成分(PC2) :沿椭圆短轴方向——与 PC1 正交,变化次大
如果把数据从2维降到1维,就保留 PC1,丢掉 PC2。虽然丢掉了一些信息(短轴方向的变化),但损失最小 —— 因为在所有可能的1维投影中,PC1 方向的投影保留了最多的方差。
PCA可以从两个完全等价的角度来理解,两者都指向同一个数学结论。
视角一:方差最大化
1. 核心思想 —— 将数据投影到某个方向上,使得投影后的数据方差最大。
方差最大就代表信息保留最多 —— 方差越大,数据点在这个方向上的散布越广,区分度越高。如果一个方向上所有数据点的投影都挤在一起(方差小),那么这个方向就几乎没有区分能力。
2. 数学表述 —— 寻找一个单位向量 u1(∥u1∥=1),使得数据 X 在 u1 方向上的投影方差最大。
数据点 xi 在 u 方向上的投影为 uTxi(假设数据已中心化,均值为0)。投影的方差为:
Var(uTX)=n1i=1∑n(uTxi)2=uT(n1i=1∑nxixiT)u=uTΣu其中 Σ=n1∑i=1nxixiT 是数据的协方差矩阵。
于是问题转化为:
umaxuTΣu,s.t.uTu=1视角二:投影距离最小化
1. 核心思想 —— 将数据投影到某个低维子空间上,使得原始数据点到投影点的距离平方和最小。
这个视角更接近“有损压缩”的直觉 —— 用一个低维的“近似”来代表高维的原始数据,让“近似误差”尽可能小。
2. 数学表述 —— 寻找一个 d 维子空间(d<p),使得所有数据点到该子空间的垂直距离平方和最小。
两个视角的等价性
- 方差最大化:投影后的点散布得越开越好 → 信息保留越多
- 距离最小化:投影后的点离原始点越近越好 → 信息损失越少
毕达哥拉斯定理告诉我们:对于每个数据点,原始点到原点的距离平方 = 投影点到原点的距离平方 + 原始点到投影点的垂直距离平方。
当数据已中心化时,“投影点到原点的距离平方”就是投影的方差,“原始点到投影点的垂直距离平方”就是重构误差。两者之和为常数,因此最大化方差等价于最小化重构误差。
设我们有 n 个样本,每个样本有 p 个特征。数据矩阵 X 为 n×p 的矩阵,每一行是一个样本,每一列是一个特征。
PCA对数据的绝对位置不敏感,只关心相对变化。因此,首先对每个特征进行中心化 —— 减去该特征的均值:X~=X−Xˉ,其中 Xˉ 是 p 维均值向量。
中心化后,数据的均值为零。如果不中心化,第一主成分会被数据的均值方向“带偏”,无法正确反映数据的真实变化方向。
假设要找一个单位向量 w1(∥w1∥=1),将数据点投影到 w1 方向上。投影后的值为:zi=x~iTw1
所有样本投影后的方差为:
Var(z)=n1i=1∑n(x~iTw1)2=n1w1T(i=1∑nx~ix~iT)w1注意到 ∑i=1nx~ix~iT 正是协方差矩阵(乘以 n):
Σ=n1i=1∑nx~ix~iT=n1X~TX~因此,投影方差为:
Var(z)=w1TΣw1目标是在 ∥w1∥=1 的约束下,最大化 w1TΣw1。这是一个带约束的优化问题。构造拉格朗日函数:
L(w1,λ)=w1TΣw1−λ(w1Tw1−1)对 w1 求偏导并令其为零:
∂w1∂L=2Σw1−2λw1=0得到:
Σw1=λw1这正是特征方程。其中 λ 是特征值,w1 是特征向量。由此可以得到结论:
推导第二个主成分:在 w2⊥w1 的约束下最大化 w2TΣw2。同样用拉格朗日乘子法,可以得到:Σw2=λ2w2
也就是说,第 k 个主成分就是协方差矩阵的第 k 大特征值对应的特征向量。
如果我们对协方差矩阵 Σ 做特征值分解:
Σ=WΛWT其中:
那么有如下结论:
一个重要且优美的性质∑k=1pλk=∑j=1pVar(xj)
也就是说,所有特征值之和等于原始数据所有特征的方差之和。
这意味着特征值衡量了每个主成分捕获了多少总方差;且第 k 个主成分的方差贡献率 = λk/∑j=1pλj
步骤1:数据标准化(中心化 + 缩放)
将每个特征减去其均值(中心化),再除以标准差(缩放到单位方差):
xij(标准化)=σjxij−μj标准化的原因:如果特征的量纲不同,量纲大的特征会主导协方差矩阵,导致PCA结果偏向于量纲大的特征。标准化让所有特征在同一尺度上公平竞争。
步骤2:计算协方差矩阵
使用 n−1 是样本协方差的无偏估计;也有实现使用 n,差异在常数倍上,不影响特征向量的方向。
Σ=n−11XTX步骤3:特征分解
求解特征方程 Σu=λu,得到特征值 λ1≥λ2≥...≥λp 和对应的特征向量 u1,u2,...,up。
步骤4:选择主成分
计算累计方差贡献率:
累计贡献率(k)=∑i=1pλi∑i=1kλi选择最小的 k 使得累计贡献率达到某个阈值(通常为85%或95%)。
步骤5:投影降维
取前 k 个特征向量组成投影矩阵 Uk=[u1,u2,...,uk],将原始数据投影到主成分空间:
Y=UkTX其中 Y 是 k×n 的降维后数据。
代码实现:
1import numpy as np2import matplotlib.pyplot as plt3from sklearn.datasets import make_blobs4
5# 生成二维数据(椭圆形分布)6np.random.seed(42)7X, _ = make_blobs(n_samples=300, centers=1, cluster_std=[[3, 1.5]], random_state=42)8X = X @ np.array([[np.cos(np.pi/4), -np.sin(np.pi/4)],9 [np.sin(np.pi/4), np.cos(np.pi/4)]])10
11# 中心化12X_centered = X - np.mean(X, axis=0)13
14# 计算协方差矩阵15cov_matrix = np.cov(X_centered, rowvar=False)16
17# 特征值分解18eigenvalues, eigenvectors = np.linalg.eig(cov_matrix)19# 按特征值降序排列20idx = np.argsort(eigenvalues)[::-1]21eigenvalues = eigenvalues[idx]22eigenvectors = eigenvectors[:, idx]23
24print(f"特征值: {eigenvalues}")25print(f"特征向量:\n{eigenvectors}")26print(f"总方差 = {np.sum(eigenvalues):.4f}")27print(f"PC1方差贡献率 = {eigenvalues[0] / np.sum(eigenvalues):.4f}")28
29# 可视化30plt.figure(figsize=(8, 8))31plt.scatter(X[:, 0], X[:, 1], alpha=0.6)32# 绘制主成分方向(箭头)33for i in range(2):34 # 箭头长度 = 2 * sqrt(特征值)35 length = 2 * np.sqrt(eigenvalues[i])36 plt.arrow(0, 0, eigenvectors[0, i] * length, eigenvectors[1, i] * length,37 head_width=0.2, head_length=0.2,38 color='red' if i == 0 else 'blue',39 label=f'PC{i+1} (λ={eigenvalues[i]:.2f})')40plt.axhline(0, color='black', linewidth=0.5)41plt.axvline(0, color='black', linewidth=0.5)42plt.axis('equal')43plt.legend()44plt.title('PCA主成分方向(椭圆数据)')45plt.show()第3节通过特征值分解(Eigenvalue Decomposition, EVD) 实现了PCA:先计算协方差矩阵 Σ=n1XTX,再对 Σ 做特征值分解。
但当特征维度 p 很大时(比如图像数据,p=10000),协方差矩阵 Σ 是 10000×10000 的矩阵,计算特征值分解的计算量巨大。此时,奇异值分解(Singular Value Decomposition, SVD) 提供了一个更高效的替代方案。
对中心化后的数据矩阵 X~(n×p)做SVD:
X~=UΣsvdVT其中:
现在计算协方差矩阵:
X~TX~=(UΣsvdVT)T(UΣsvdVT)=VΣsvdTΣsvdVT由于 ΣsvdTΣsvd 是对角矩阵,其对角元素为奇异值的平方 σi2。
因此:
X~TX~=V⋅diag(σ12,σ22,...,σp2)⋅VT关键结论
- PCA的特征向量(主成分方向)就是SVD的右奇异向量 V 的列向量。
- PCA的特征值等于SVD奇异值的平方 σi2 除以 n。
即主成分方向 = V 的列;特征值 λi=σi2/n
| 对比维度 | EVD方法 | SVD方法 |
|---|---|---|
| 需要计算的矩阵 | 协方差矩阵 Σ(p×p) | 直接分解数据矩阵 X(n×p) |
| 计算复杂度 | O(p3) | O(np2)(当 n<p 时更优) |
| 数值稳定性 | 一般 | 更好(避免显式计算协方差矩阵) |
| 适用场景 | p 较小时 | p 很大时优势明显 |
当 样本数 n 远小于特征数 p 时(如基因数据:n=200,p=20000),SVD的优势尤其明显——直接分解 200×20000 的矩阵,远比先算 20000×20000 的协方差矩阵再分解要高效得多。
代码实现:
1def pca_via_evd(X, n_components=None):2 """通过特征值分解实现PCA"""3 # 中心化4 X_centered = X - np.mean(X, axis=0)5 # 协方差矩阵6 cov = np.cov(X_centered, rowvar=False)7 # 特征值分解8 eigvals, eigvecs = np.linalg.eigh(cov)9 # 按特征值降序10 idx = np.argsort(eigvals)[::-1]11 eigvals = eigvals[idx]12 eigvecs = eigvecs[:, idx]13 if n_components:14 eigvecs = eigvecs[:, :n_components]15 # 投影16 X_pca = X_centered @ eigvecs17 return X_pca, eigvals, eigvecs18
19def pca_via_svd(X, n_components=None):20 """通过奇异值分解实现PCA(更高效)"""21 # 中心化22 X_centered = X - np.mean(X, axis=0)23 # SVD24 U, S, Vt = np.linalg.svd(X_centered, full_matrices=False)25 # Vt 的每一行是一个右奇异向量(即主成分方向)26 components = Vt[:n_components].T if n_components else Vt.T27 # 投影28 X_pca = X_centered @ components29 # 特征值 = 奇异值^2 / (n-1)30 eigvals = (S[:n_components] ** 2) / (X.shape[0] - 1) if n_components else (S ** 2) / (X.shape[0] - 1)31 return X_pca, eigvals, components32
33# 验证两种方法等价34from sklearn.datasets import load_iris35iris = load_iris()36X = iris.data37
38X_pca_evd, evals_evd, evecs_evd = pca_via_evd(X, n_components=2)39X_pca_svd, evals_svd, evecs_svd = pca_via_svd(X, n_components=2)40
41print("EVD vs SVD 投影结果是否一致:", np.allclose(np.abs(X_pca_evd), np.abs(X_pca_svd), atol=1e-10))42print("EVD特征值:", evals_evd)43print("SVD特征值:", evals_svd)PCA降维需要决定保留多少个主成分。这个问题没有唯一的答案,但有几种常用的启发式方法。
这是最常用的方法。计算前 k 个主成分的累计方差贡献率:
Cumulative Ratio(k)=∑i=1pλi∑i=1kλi通常选择一个阈值(如 80%、85%或95% ),选择使累计贡献率达到该阈值的最小 k。
绘制特征值从大到小的分布图(碎石图),寻找“肘部(Elbow)” —— 即特征值开始急剧下降后趋于平缓的位置。肘部之前的成分包含了大部分信息,之后的成分贡献很小可以舍弃。
肘部法则的局限:与K-Means中的肘部法则类似,这种方法主观性较强,且在高维噪声数据中肘部可能不明显。
只保留特征值大于1的主成分。
这个准则基于一个直觉:每个主成分至少应该解释一个原始变量的方差(原始变量方差为1,前提是数据已经标准化)。
在实践中,累计方差贡献率法最常用。对于探索性数据分析,80%-90%通常就足够了;对于后续的机器学习任务,建议用交叉验证来评估不同 k 对下游任务的影响。
代码实现:
1from sklearn.decomposition import PCA2from sklearn.datasets import load_digits3
4# 加载手写数字数据(64维)5digits = load_digits()6X = digits.data7
8# 使用sklearn的PCA9pca = PCA()10pca.fit(X)11
12# 计算累计方差贡献率13cumulative_variance = np.cumsum(pca.explained_variance_ratio_)14
15# 碎石图16plt.figure(figsize=(12, 4))17
18plt.subplot(1, 2, 1)19plt.bar(range(1, len(pca.explained_variance_ratio_) + 1),20 pca.explained_variance_ratio_)21plt.xlabel('主成分编号')22plt.ylabel('方差贡献率')23plt.title('碎石图(Scree Plot)')24
25plt.subplot(1, 2, 2)26plt.plot(range(1, len(cumulative_variance) + 1), cumulative_variance, 'ro-')27plt.axhline(y=0.85, color='g', linestyle='--', label='85%阈值')28plt.axhline(y=0.90, color='orange', linestyle='--', label='90%阈值')29plt.axhline(y=0.95, color='r', linestyle='--', label='95%阈值')30plt.xlabel('主成分数量')31plt.ylabel('累计方差贡献率')32plt.legend()33plt.title('累计方差贡献率')34
35plt.tight_layout()36plt.show()37
38# 计算达到不同阈值所需的主成分数39for threshold in [0.80, 0.85, 0.90, 0.95]:40 n = np.argmax(cumulative_variance >= threshold) + 141 print(f"达到 {threshold*100:.0f}% 方差需要 {n} 个主成分")假设一:线性关系。 PCA寻找的是线性的主成分方向。如果数据中存在复杂的非线性结构(如环形、流形),PCA无法有效捕捉。
假设二:方差代表信息。 PCA认为方差越大的方向越重要。但如果数据中存在噪声,方差大的方向可能只是噪声的方向,而不是信号的方向。
假设三:特征相互独立(在降维后)。 PCA的目标是找到一组线性无关的主成分,这意味着它假设原始特征之间存在线性相关性(这正是PCA想要去除的)。
局限一:对特征尺度敏感。 PCA是基于协方差矩阵的,如果不同特征的量纲差异很大,量纲大的特征会主导主成分方向。解决方案即为在PCA之前对数据进行标准化(Standardization) ——使每个特征均值为0、方差为1。
局限二:主成分难以解释。 主成分是原始特征的线性组合,往往不具有直接的物理或业务含义。
局限三:对离群点敏感。 方差对离群点非常敏感,一个极端值可能会严重扭曲主成分方向。
局限四:假设数据服从高斯分布(在协方差矩阵的意义上)。 PCA只利用了数据的二阶统计量(均值和协方差),忽略了更高阶的统计信息。
1import numpy as np2import matplotlib.pyplot as plt3from sklearn.datasets import load_iris4from sklearn.preprocessing import StandardScaler5from sklearn.decomposition import PCA6
7# 1. 加载数据8iris = load_iris()9X, y = iris.data, iris.target10feature_names = iris.feature_names11
12print(f"原始数据维度: {X.shape}") # (150, 4)13
14# 2. 标准化(对PCA至关重要)15scaler = StandardScaler()16X_scaled = scaler.fit_transform(X)17
18# 3. 使用sklearn的PCA(使用SVD实现)19pca = PCA(n_components=2)20X_pca = pca.fit_transform(X_scaled)21
22print(f"降维后维度: {X_pca.shape}") # (150, 2)23print(f"PC1方差贡献率: {pca.explained_variance_ratio_[0]:.4f}")24print(f"PC2方差贡献率: {pca.explained_variance_ratio_[1]:.4f}")25print(f"累计方差贡献率: {np.sum(pca.explained_variance_ratio_):.4f}")26
27# 4. 可视化降维结果28plt.figure(figsize=(10, 6))29colors = ['red', 'green', 'blue']30target_names = iris.target_names31for i, color, name in zip(range(3), colors, target_names):32 plt.scatter(X_pca[y == i, 0], X_pca[y == i, 1],33 color=color, label=name, alpha=0.7)34
35plt.xlabel(f'第一主成分 ({pca.explained_variance_ratio_[0]*100:.1f}%)')36plt.ylabel(f'第二主成分 ({pca.explained_variance_ratio_[1]*100:.1f}%)')37plt.title('鸢尾花数据 PCA 降维可视化')38plt.legend()39plt.grid(True, alpha=0.3)40plt.show()41
42# 5. 查看主成分的"含义"(载荷)43print("\n主成分载荷(特征向量):")44for i, comp in enumerate(pca.components_):45 print(f"PC{i+1}:")46 for j, name in enumerate(feature_names):47 print(f" {name}: {comp[j]:.4f}")总结
概念 核心内容 PCA的目标 找到数据中方差最大的几个正交方向,用更少的维度保留尽可能多的信息 数学核心 对协方差矩阵 Σ=n1XTX 做特征值分解,特征向量 = 主成分方向,特征值 = 主成分方差 SVD实现 对中心化数据矩阵做SVD,右奇异向量 = 主成分方向,奇异值²/n = 特征值,更高效、更稳定 主成分选择 累计方差贡献率法(80%-95%)、碎石图肘部法则、Kaiser准则(特征值>1) 关键假设 数据中的关系是线性的,方差代表信息 重要提醒 PCA对特征尺度敏感,使用前必须标准化 核心要点回顾
- PCA的本质是找到数据中方差最大的方向。第一主成分是协方差矩阵最大特征值对应的特征向量,其方差等于该特征值。所有主成分两两正交。
- EVD vs SVD:EVD方法先算协方差矩阵再分解,适合特征数较少的情况;SVD方法直接分解数据矩阵,当样本数远小于特征数时效率更高、数值更稳定。
- 主成分的数量通常通过累计方差贡献率来选择——达到80%-95%即可。**碎石图的“肘部”**也是一个直观的参考。
- 标准化是PCA的必修课。PCA基于协方差矩阵,量纲不同的特征会扭曲主成分方向。使用PCA前,务必对每个特征进行标准化(均值为0、方差为1)。
- PCA的局限:它是线性的,无法捕捉非线性结构;对离群点敏感;主成分往往难以解释。在这些场景下,可以考虑核PCA(Kernel PCA) 等非线性降维方法。
按顺序完成这组文章,循序渐进地掌握主题
发现错误、内容过时或有改进想法?欢迎告诉我
根据本文分类与标签,为你推荐可能感兴趣的内容

系统讲解隐马尔可夫模型(HMM)的完整数学原理与三大核心算法:从马尔可夫链到双重随机过程的演进出发,定义HMM的五元组参数(Q, V, π, A, B);详细推导估值问题、解码问题和学习问题;并通过词性标注、语音识别等经典应用场景展示HMM的实践价值。
阅读文章
系统讲解期望最大化(EM)算法的完整数学原理:从极大似然估计在隐变量存在时的困境出发,推导E步与M步的迭代框架;基于Jensen不等式证明ELBO证据下界与收敛性;通过二硬币模型与高斯混合模型(GMM)两个完整实例展示EM的具体计算流程;揭示K-Means是EM在硬分配下的特例这一深层联系。
阅读文章
系统讲解K-Means聚类的核心原理与算法细节,涵盖Lloyd交替优化算法的收敛性分析、K-Means作为EM算法特例的理论联系(硬分配 vs 软分配)、K-Means++初始化策略的D²采样机制与O(log K)近似保证,以及肘部法则与轮廓系数的选择K值方法及其局限。
阅读文章请使用微信扫描二维码分享
当前文章会保持在原页面