跳转至

03 - 无监督学习

无监督学习图

🎯 无监督学习概述

核心思想

数据没有标签,算法自动发现数据内在结构和模式

Text Only
监督学习 vs 无监督学习:

监督学习:
[有标签数据] → [模型] → [预测]

无监督学习:
[无标签数据] → [模型] → [发现模式]

三大任务类型

Text Only
1. 聚类 (Clustering)
   将相似样本分组
   例: 客户分群、新闻聚合

2. 降维 (Dimensionality Reduction)
   压缩数据维度,保留主要信息
   例: 可视化、特征提取

3. 密度估计 (Density Estimation)
   估计数据分布
   例: 异常检测、生成模型

📊 聚类算法

K-Means 聚类

核心思想

将数据分成K个簇,使簇内样本相似,簇间样本差异大

Text Only
直观理解:
给定一堆点,找到K个中心点,
使每个点到最近中心的距离最小

示例: 客户分群
👤👤👤  👥👥  👤👤👤👤
 价格敏感  中立  品质追求

算法步骤

初始化:

Text Only
1. 随机选择K个点作为初始中心
   或使用K-Means++优化初始化

迭代:

Text Only
重复直到收敛:
    1. 分配: 每个样本分配到最近中心
       中心1: ●●●●●
       中心2: ○○○
       中心3: ▲▲▲▲

    2. 更新: 重新计算每个簇的中心
       新中心 = 簇内所有点的均值

    3. 检查: 中心不再变化则停止

目标函数 (损失函数)

\[J = \sum_{i=1}^{K}\sum_{x \in C_i} \|x - \mu_i\|^2\]
  • \(\mu_i\): 第i个簇的中心
  • 目标: 最小化所有点到其所属中心的距离平方和

K值选择

肘部法则 (Elbow Method):

Python
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans

# 计算不同K值的损失
losses = []
K_range = range(1, 11)
for k in K_range:
    kmeans = KMeans(n_clusters=k, random_state=42)
    kmeans.fit(X)
    losses.append(kmeans.inertia_)  # 损失函数值

# 绘制肘部图
plt.plot(K_range, losses, 'bo-')
plt.xlabel('K值')
plt.ylabel('损失 (SSE)')
plt.show()

# 找到"肘部" (下降速度明显变缓的点)

轮廓系数 (Silhouette Score):

Text Only
轮廓系数 = (b - a) / max(a, b)

a: 样本到同簇其他点的平均距离 (簇内相似度)
b: 样本到最近异簇点的平均距离 (簇间分离度)

范围: [-1, 1]
- 接近1: 聚类效果好
- 接近0: 样本在簇边界
- 接近-1: 聚类效果差

代码实现

Python
from sklearn.cluster import KMeans
import numpy as np

# 示例数据
X = np.array([
    [1, 2], [1, 4], [1, 0],
    [10, 2], [10, 4], [10, 0]
])

# 训练
kmeans = KMeans(
    n_clusters=2,      # 簇的数量
    init='k-means++',  # 初始化方法
    max_iter=300,      # 最大迭代次数
    random_state=42
)
kmeans.fit(X)

# 预测
labels = kmeans.labels_        # 每个样本的簇标签
centers = kmeans.cluster_centers_  # 簇中心

print(f"簇标签: {labels}")
print(f"簇中心:\n{centers}")

# 预测新样本
new_samples = [[1, 1], [10, 3]]
predictions = kmeans.predict(new_samples)
print(f"预测: {predictions}")

优缺点

优点: - 简单易实现,速度快 - 可解释性强 - 适合凸形簇

缺点: - 需要预先指定K值 - 对初始中心敏感 (可用K-Means++缓解) - 对异常值敏感 - 只能发现球形簇 (不适合非凸形状)


层次聚类 (Hierarchical Clustering)

核心思想

通过逐步合并或分裂簇,构建聚类层次树

Text Only
层次树 (Dendrogram):

        ┌─────┴─────┐
      样本A      样本B
        │         │
      合并成簇   样本C
        │         │
        └────┬────┘
          最终簇

高度: 合并时的距离

两种策略

凝聚 (自底向上):

Text Only
1. 初始: 每个样本是一个簇
2. 迭代: 合并最相似的两个簇
3. 终止: 所有样本合并为一个簇

示例:
步骤1: 🔴🔴🔴🔴🔴 (5个簇)
步骤2: 🔴🔴🔴 🔴🔴  (4个簇)
步骤3: 🔴🔴🔴🔴   (3个簇)
步骤4: 🔴🔴 🔴🔴   (2个簇)
步骤5: 🔴🔴🔴🔴🔴  (1个簇)

分裂 (自顶向下):

Text Only
1. 初始: 所有样本是一个簇
2. 迭代: 分裂一个簇为两个
3. 终止: 每个样本是一个簇

链接方式 (Linkage)

如何计算簇间距离?

Text Only
单链接 (Single Linkage):
两个簇中最近样本的距离
优点: 能发现非凸形状
缺点: 链式效应 (可能将不应该连接的点连起来)

完全链接 (Complete Linkage):
两个簇中最远样本的距离
优点: 避免链式效应
缺点: 对异常值敏感

平均链接 (Average Linkage):
两个簇所有样本对的平均距离
优点: 折中方案
缺点: 计算量大

Ward链接:
合并后簇内方差增加最小的两个簇
优点: 效果通常最好
缺点: 只适合欧式距离

代码实现

Python
from sklearn.cluster import AgglomerativeClustering
from scipy.cluster.hierarchy import dendrogram, linkage
import matplotlib.pyplot as plt

# 凝聚层次聚类
agg = AgglomerativeClustering(
    n_clusters=3,          # 簇的数量
    linkage='ward'         # 链接方式
)
labels = agg.fit_predict(X)

# 绘制层次树
linkage_matrix = linkage(X, method='ward')
plt.figure(figsize=(10, 7))
dendrogram(linkage_matrix)
plt.title('层次聚类树')
plt.xlabel('样本索引')
plt.ylabel('距离')
plt.show()

优缺点

优点: - 不需要指定K值 (可通过层次树确定) - 可视化层次结构 - 能发现不同尺度的簇

缺点: - 计算复杂度高 O(n³) 或 O(n²) - 一旦合并/分裂不能撤销 - 对噪声敏感


📉 降维算法

PCA (主成分分析, Principal Component Analysis)

核心思想

找到数据方差最大的方向,将高维数据投影到低维空间

Text Only
直观理解:
二维数据投影到一维:

原始数据 (二维):    投影后 (一维):
  🔴🔴                  🔴🔴
    🔴🔴              🔴🔴
🔴🔴                    🔴🔴

保留信息最多的方向 = 方差最大的方向

数学原理

步骤1: 数据标准化 $\(x_i' = \frac{x_i - \mu}{\sigma}\)$

步骤2: 计算协方差矩阵 $\(C = \frac{1}{n}X^TX\)$

步骤3: 特征值分解 $\(C \cdot v = \lambda \cdot v\)$ - \(v\): 特征向量 (主成分方向) - \(\lambda\): 特征值 (该方向的方差)

步骤4: 选择前k个主成分 选择特征值最大的k个特征向量

步骤5: 投影 $\(X_{new} = X \cdot V_k\)$

解释方差 (Explained Variance)

Text Only
每个主成分解释的方差比例:
方差比例 = 特征值 / 所有特征值之和

累计解释方差:
前k个主成分的累计方差比例

示例:
主成分1: 50% 方差
主成分2: 30% 方差
主成分3: 10% 方差

前2个累计: 80% (保留80%信息)

代码实现

Python
from sklearn.decomposition import PCA
import matplotlib.pyplot as plt

# 标准化 (重要!)
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

# PCA降维
pca = PCA(n_components=2)  # 降到2维
X_pca = pca.fit_transform(X_scaled)

# 查看解释方差
print(f"各主成分解释方差: {pca.explained_variance_ratio_}")
print(f"累计解释方差: {pca.explained_variance_ratio_.cumsum()}")

# 可视化
plt.scatter(X_pca[:, 0], X_pca[:, 1], c=labels)
plt.xlabel(f'主成分1 ({pca.explained_variance_ratio_[0]:.2%})')
plt.ylabel(f'主成分2 ({pca.explained_variance_ratio_[1]:.2%})')
plt.title('PCA降维可视化')
plt.show()

# 选择保留95%方差
pca_95 = PCA(n_components=0.95)
X_pca_95 = pca_95.fit_transform(X_scaled)
print(f"保留95%方差需要 {pca_95.n_components_} 个主成分")

应用场景

  1. 可视化: 高维数据投影到2D/3D
  2. 特征压缩: 减少特征数量,加速训练
  3. 噪声过滤: 丢弃方差小的主成分 (通常是噪声)
  4. 图像压缩: 人脸识别 (Eigenfaces)

优缺点

优点: - 数学原理清晰 - 无参数需要调优 - 降维效果好

缺点: - 只能捕获线性关系 - 对数据尺度敏感 (必须标准化) - 主成分可解释性差


t-SNE (t-Distributed Stochastic Neighbor Embedding)

核心思想

保持数据间的局部相似性,适合可视化高维数据

Text Only
PCA vs t-SNE:

PCA: 保持全局结构
    线性投影
    适合降维到很多维度

t-SNE: 保持局部结构
     非线性映射
     只适合降维到2D/3D可视化

算法原理

步骤1: 在高维空间计算样本间相似度

Text Only
对点i和j,计算条件概率 p(j|i)
表示在i的邻域中,j出现的概率

使用高斯分布:
距离近 → 概率大
距离远 → 概率小

步骤2: 在低维空间也计算相似度

Text Only
使用t分布 (比高斯分布更重尾)

步骤3: 最小化两个分布的差异

Text Only
使用KL散度 (Kullback-Leibler divergence)
目标: 使低维分布尽可能接近高维分布

代码实现

Python
from sklearn.manifold import TSNE

# t-SNE降维到2维
tsne = TSNE(
    n_components=2,      # 只支持2或3维
    perplexity=30,       # 类似"最近邻数量" (5-50)
    n_iter=1000,         # 迭代次数
    random_state=42
)
X_tsne = tsne.fit_transform(X)

# 可视化
plt.scatter(X_tsne[:, 0], X_tsne[:, 1], c=labels, cmap='viridis')
plt.colorbar()
plt.title('t-SNE可视化')
plt.show()

超参数: Perplexity

Text Only
perplexity ≈ 有效的最近邻数

太小 (如5): 只关注极近邻,可能过度碎片化
太大 (如50): 关注全局,类似MDS

建议: 5-50之间,通常30是好的默认值

优缺点

优点: - 能发现复杂的非线性结构 - 可视化效果好 - 保持局部邻域关系

缺点: - 计算慢 (不适合大数据) - 结果不稳定 (每次运行不同) - 不能用于新数据 (需要重新训练) - 全局结构可能失真


🎲 EM算法与高斯混合模型

EM算法 (Expectation-Maximization)

核心思想

用于含有隐变量的概率模型参数估计

Text Only
问题:
如果数据有缺失值或隐变量,
如何估计模型参数?

示例:
身高数据,但不知道性别 (隐变量),
如何估计男生和女生的身高分布?

EM算法:
E步: 估计隐变量的期望
M步: 基于期望更新参数
迭代直到收敛

算法流程

Text Only
初始化: 随机初始化参数 θ

重复:
    E步 (Expectation):
        计算隐变量的后验概率
        Q(θ|θ⁽ᵗ⁾) = E[log P(X,Z|θ) | X, θ⁽ᵗ⁾]

    M步 (Maximization):
        最大化Q函数,更新参数
        θ⁽ᵗ⁺¹⁾ = argmax Q(θ|θ⁽ᵗ⁾)

直到收敛 (参数变化很小)

直观示例: 抛硬币

Text Only
问题:
有两枚硬币A和B,随机选一个抛10次,
重复5次实验,得到如下结果 (但不知道每次是A还是B):

实验1: 正正反正反正正正正反
实验2: 反正正反正反反正正
...

EM算法:
E步: 根据当前参数,估计每次实验是A还是B的概率
M步: 更新A和B的正面概率

迭代:
初始: P(A正面)=0.6, P(B正面)=0.5
E步: 实验1有0.7概率是A, 0.3概率是B
M步: 更新P(A正面)=0.65, P(B正面)=0.48
...
收敛: 得到最终估计

高斯混合模型 (Gaussian Mixture Model, GMM)

核心思想

用多个高斯分布的加权和来建模数据分布

Text Only
单高斯 vs 高斯混合:

单高斯分布:
    🔔
   🔔🔔
  🔔🔔🔔

高斯混合模型 (2个高斯):
    🔔      🔔
   🔔🔔    🔔🔔
  🔔🔔🔔  🔔🔔🔔

每个高斯: 一个簇
权重: 该簇的样本比例

数学模型

\[P(x) = \sum_{k=1}^{K} \pi_k \mathcal{N}(x | \mu_k, \Sigma_k)\]
  • \(\pi_k\): 第k个高斯的混合系数 (权重)
  • \(\mu_k\): 第k个高斯的均值
  • \(\Sigma_k\): 第k个高斯的协方差矩阵

约束: $\(\sum_{k=1}^{K} \pi_k = 1\)$

用EM算法求解GMM

参数: \(\theta = \{\pi_k, \mu_k, \Sigma_k\}_{k=1}^{K}\)

E步(Expectation):计算每个样本 \(x_n\) 属于第 \(k\) 个高斯的后验概率(责任度):

\[\gamma(z_{nk}) = \frac{\pi_k \mathcal{N}(x_n | \mu_k, \Sigma_k)}{\sum_{j=1}^{K} \pi_j \mathcal{N}(x_n | \mu_j, \Sigma_j)}\]

其中 \(\mathcal{N}(x | \mu, \Sigma) = \frac{1}{(2\pi)^{d/2}|\Sigma|^{1/2}} \exp\left(-\frac{1}{2}(x-\mu)^T\Sigma^{-1}(x-\mu)\right)\)

M步(Maximization):利用责任度更新参数。

完整推导:M 步的目标是最大化 Q 函数:

\[Q(\theta | \theta^{\text{old}}) = \sum_{n=1}^{N} \sum_{k=1}^{K} \gamma(z_{nk}) \left[\ln \pi_k + \ln \mathcal{N}(x_n | \mu_k, \Sigma_k)\right]\]

\(\mu_k\) 求导并令其为零

\[\frac{\partial Q}{\partial \mu_k} = \sum_{n=1}^{N} \gamma(z_{nk}) \Sigma_k^{-1}(x_n - \mu_k) = 0\]

解出:

\[\mu_k^{\text{new}} = \frac{\sum_{n=1}^{N} \gamma(z_{nk}) x_n}{\sum_{n=1}^{N} \gamma(z_{nk})}\]

\(\Sigma_k\) 求导并令其为零(利用 \(\frac{\partial}{\partial \Sigma^{-1}} \left[-\frac{1}{2}(x-\mu)^T \Sigma^{-1}(x-\mu) + \frac{1}{2}\ln|\Sigma^{-1}|\right] = 0\)):

\[\Sigma_k^{\text{new}} = \frac{\sum_{n=1}^{N} \gamma(z_{nk})(x_n - \mu_k^{\text{new}})(x_n - \mu_k^{\text{new}})^T}{\sum_{n=1}^{N} \gamma(z_{nk})}\]

\(\pi_k\) 采用拉格朗日乘子法(约束 \(\sum_k \pi_k = 1\)):

\[\frac{\partial}{\partial \pi_k}\left[Q + \lambda(1 - \sum_k \pi_k)\right] = \frac{\sum_n \gamma(z_{nk})}{\pi_k} - \lambda = 0 \quad \Rightarrow \quad \pi_k = \frac{\sum_n \gamma(z_{nk})}{N}\]

\(N_k = \sum_{n=1}^{N} \gamma(z_{nk})\)(第 \(k\) 个高斯的有效样本数),以上结果即:

\[\mu_k^{\text{new}} = \frac{1}{N_k}\sum_{n=1}^{N} \gamma(z_{nk}) x_n\]
\[\Sigma_k^{\text{new}} = \frac{1}{N_k}\sum_{n=1}^{N} \gamma(z_{nk})(x_n - \mu_k^{\text{new}})(x_n - \mu_k^{\text{new}})^T\]
\[\pi_k^{\text{new}} = \frac{N_k}{N}\]

反复迭代 E 步和 M 步,直到对数似然 \(\log P(X|\theta)\) 收敛。

代码实现

Python
from sklearn.mixture import GaussianMixture

# 拟合GMM
gmm = GaussianMixture(
    n_components=3,     # 高斯数量 (簇数)
    covariance_type='full',  # 协方差类型
    max_iter=100,
    random_state=42
)
gmm.fit(X)

# 预测簇标签
labels = gmm.predict(X)

# 预测概率 (软聚类)
proba = gmm.predict_proba(X)
# proba[i, k] = 样本i属于簇k的概率

# 查看参数
means = gmm.means_              # 各高斯的均值
covariances = gmm.covariances_  # 各高斯的协方差
weights = gmm.weights_          # 混合系数

print(f"各高斯权重: {weights}")
print(f"各高斯均值:\n{means}")

GMM vs K-Means

Text Only
K-Means:
- 硬聚类 (每个点只属于一个簇)
- 假设簇是球形的
- 基于距离 (几何方法)
- 快速简单

GMM:
- 软聚类 (每个点有属于各簇的概率)
- 假设簇是椭球形的 (通过协方差)
- 基于概率 (统计方法)
- 更灵活但更慢

优缺点

优点: - 软聚类 (提供概率) - 能建模不同形状的簇 (通过协方差) - 能生成新样本 (从分布采样)

缺点: - 需要指定簇数量 - 对初始化敏感 - 假设数据符合高斯分布 (可能不成立) - 计算复杂度高


📊 算法对比

聚类算法对比

算法 簇形状 簇数量 速度 可扩展性 适用场景
K-Means 球形 需指定 ⭐⭐⭐⭐⭐ 凸形簇,大数据
层次聚类 任意 可选 ⭐⭐ 小数据,需层次
GMM 椭球形 需指定 ⭐⭐⭐ 软聚类,密度估计

降维算法对比

算法 类型 保持结构 速度 用途
PCA 线性 全局方差 ⭐⭐⭐⭐⭐ 降维,特征压缩
t-SNE 非线性 局部邻域 ⭐⭐ 可视化
LDA 线性 类别可分性 ⭐⭐⭐⭐ 监督降维

🎯 实践建议

聚类评估指标

Python
from sklearn.metrics import silhouette_score, calinski_harabasz_score

# 轮廓系数 (-1到1,越大越好)
score = silhouette_score(X, labels)

# Calinski-Harabasz指数 (越大越好)
score_ch = calinski_harabasz_score(X, labels)

降维实践流程

Text Only
1. 标准化数据 (必须!)
2. 先用PCA降到中等维度 (如50维)
3. 再用t-SNE降到2维可视化

下一步

继续阅读 04-时序模型.md,学习时间序列分析!