🧮 第3章 无监督学习
📄 一句话概括

混合高斯假设数据是几个高斯分布混出来的:每个成分有自己的中心、自己的形状、自己的份量。 它用 EM 算法交替算「每个点属于各成分的概率」和「按概率重算参数」。 Demo 里一团竖长条加一个小圆球: k-means 纯度只有 0.6925,混合高斯 0.9975——因为它认出了「一个细长、一个圆胖」, y 方向标准差差了 5.2 倍。

📝 本节和前面几节的关系

上一节末尾留了三个坑:k-means 只会直线切、假设簇大小相近、给硬编号。 这一节用一套概率语言把三个坑一起填了。 它和 k-means 的关系很紧: k-means 其实是混合高斯的一个极端特例——所有成分的协方差都相同且趋近于 0 时, EM 就退化成 k-means 的两步循环。 理解了这一节,等于同时理解了「软聚类」「EM 算法」「生成模型」三件事。

一、从「一个高斯」到「几个高斯」

单个高斯分布很好理解:一个中心 μ、一个协方差 Σ,数据以 μ 为中心、按 Σ 的形状散开。 它的等高线是椭圆。 混合高斯就是把几个这样的椭圆按不同份量叠在一起。 每个成分有三个参数: 权重 π(这个成分占多大比例)、中心 μ、协方差 Σ。所有成分的权重加起来等于 1。 它的表达能力比单个高斯强得多: 两个扁椭圆斜着叠,能拼出弯月形;三个椭圆能拼出一个圈。 而 k-means 只能拼出圆—— 这就是它在长条数据上失败的原因。 这里要说清一件事:混合高斯是「生成模型」。 它不只描述数据长什么样,还能「造」数据: 先从权重里抽一个成分,再从那个成分的高斯里抽一个点。 这个视角很重要,因为训练它的方法(最大似然)就是从这个假设推出来的。

二、EM 算法:先猜归属,再改参数

要是知道每个点属于哪个成分,问题就简单了——直接按成分分组、算均值和协方差。 要是知道参数,问题也简单——算每个点属于各成分的概率就行了。难的是两样都不知道。 EM 算法的办法是先随便猜一样,再交替。 E 步(Expectation):固定参数,算每个点属于每个成分的概率,这个概率叫责任度。 M 步(Maximization):固定责任度,按责任度加权重算每个成分的中心、协方差、权重。 关键在「加权」。 k-means 里一个点要么属于 A 要么属于 B,非黑即白; 混合高斯里一个点可能「七成属于 A、三成属于 B」,于是它在更新参数时, 同时以 0.7 的份量影响 A、以 0.3 的份量影响 B。 这就是「软分配」的实际含义。 EM 有个漂亮的性质:每一轮迭代,对数似然只增不减。 因为 E 步和 M 步各自都在最大化同一个下界, 交替进行下界只会往上走。Demo 里对数似然从 −1262.40 一路爬到 −1030.35,然后就平了。 和 k-means 一样, EM 也只保证局部最优。 而且它比 k-means 更挑初值——如果一开始就有个成分被塞在没人的角落, 它可能永远分不到点(权重趋近 0)。所以代码里拿 k-means 的结果打底,这是常见做法。

三、责任度:它到底在算什么

责任度的正式名字是「后验概率」: 在看到这个点之后,它属于成分 c 的概率。 用贝叶斯公式算——先验是权重 π, 似然是这个点在高斯 c 下的密度,两者相乘再归一化。 Demo 里挑了六个「最说不清」的点,责任度从 0.6962 / 0.3038 到 0.8834 / 0.1166。 注意最模糊的也到了 0.70 对 0.30,说明这份数据的两团其实分得很干脆。 换成边界犬牙交错的数据,你会看到成片的 0.5 对 0.5。 那时软分配的价值才真正显出来: k-means 会把这类点硬塞进某一类,一个点的归属就能让边界大幅摆动; 混合高斯则让它们「两边都算一点」,边界稳定得多。 责任度还有个实际用途: 它就是异常检测的分数。 一个点如果对每个成分的责任度都很低(也就是在所有高斯下密度都极小), 那它就是个离群点。这比「到中心的距离」稳健得多。

四、协方差矩阵:每个簇有自己的形状

这是混合高斯比 k-means 强的根本原因。 每个成分有自己的 Σ,而 Σ 是一个完整的协方差矩阵——它同时描述了「多散」和「朝哪个方向散」。 Demo 里两个成分的 y 方向标准差是 2.531 和 0.489, 差了 5.2 倍。 算法完全靠自己发现了「一个细长、一个圆胖」。 k-means 里没有这个概念,它只能假设所有簇的散度相同。 协方差的自由度是 d(d+1)/2,d 是维度。二维是 3 个参数,十维就是 55 个。 维度一高,参数就爆炸,而且很容易过拟合(某些成分的 Σ 会退化成一条线)。 所以工程上有几种简化,值得知道:球形(Σ = σ²I,等于 k-means 的软版本)、对角(各维独立)、 全协方差(最灵活也最贵)。 scikit-learn 里这个参数叫 covariance_type,数据量大、维度高时用对角更稳。

五、它和 k-means 的关系

把混合高斯的每个 Σ 都固定成同一个 σ²I,然后让 σ 趋近 0。 这时每个点的责任度会变成「一个接近 1、 其余接近 0」——软分配退化成硬分配。 E 步变成「找最近的中心」,M 步变成「算均值」。 这就是 k-means。 所以两者的关系是: k-means 是混合高斯的一个极端特例。 这解释了为什么 k-means 那么快——它少估了一大堆参数。 也解释了为什么混合高斯更慢但更准——它多花的钱买的是「形状」。 实践中的选择很直接:簇明显是圆的、数据量大、要快要稳,用 k-means; 簇形状各不相同、簇的大小差别大、需要「概率」这个输出,用混合高斯。 还有一个判据: 混合高斯能算「这个点有多异常」,k-means 只能算「离中心多远」。

六、动手 Demo:一团长条加一个圆球,k-means 切不动

两团点各 200 个:一团是竖着的长条(x 抖 0.25、y 抖 2.6), 另一团是小圆球(中心在 (1.7, 0)、两个方向都抖 0.5)。 人眼一看就分得开,但 k-means 分不开——它只会用直线切,而长条的重心在中段, 一刀下去必然把它劈成两半。 代码是一套完整的 EM:E 步算责任度,M 步按责任度加权重算中心、协方差、权重。 初值用 k-means 的结果打底,避免 EM 卡在奇怪的地方。

import numpy as np

for it in range(iters):
    # E 步:每个点属于各成分的「责任度」(后验概率)
    for c in range(k):
        diff = X - mu[c]
        R[:, c] = w[c] * np.exp(-0.5 * ((diff @ inv[c]) * diff).sum(axis=1)) / np.sqrt(det[c])
    ll = np.log(R.sum(axis=1)).sum()            # 对数似然,只增不减
    R = R / R.sum(axis=1, keepdims=True)

    # M 步:按责任度加权重算 中心 / 协方差 / 权重
    nk = R.sum(axis=0)
    mu = (R.T @ X) / nk[:, None]
    for c in range(k):
        diff = X - mu[c]
        cov[c] = ((R[:, c][:, None] * diff).T @ diff) / nk[c]
    w = nk / len(X)

实跑输出:

--------------------------------------------------------------------
混合高斯分布:用「软分配」补上 k-means 的短板
--------------------------------------------------------------------

  造两团点,各 200 个。**一团是竖着的长条(x 只抖 0.25,y 抖 2.6),另一团是个小圆球。**
   这两团人眼一看就分得开,但 k-means 分不开——因为它只会「用直线切」,而长条的重心在中段,
  切一刀必然把它劈成两半。

  【一】先看 k-means 栽在哪
  方法        两个中心                          纯度
  --------------------------------------------------------
  k-means     (-0.04, -2.56) 与 (+1.04, +0.50)  0.6925
  一个中心落在长条的下半截,另一个挤在长条中段和小球之间——
  **小球被并进了长条,长条被拦腰劈成两段**。
  纯度只有 0.69,比瞎猜(0.5)好一点,但两团根本没分开。

  【二】混合高斯怎么想这件事
  它假设数据是**几个高斯分布混在一起生成的**:
  每个成分有自己的中心、自己的形状(协方差矩阵)、自己的份量(权重)。
  长条的协方差在 y 方向大、x 方向小,**一个「竖着的高斯」就能罩住它,不必切成两半。**
  成分    权重      中心            x 方向标准差      y 方向标准差
  --------------------------------------------------------------------
  1       0.499     (-0.01, -0.20)  0.265             2.531
  2       0.501     (+1.68, +0.02)  0.472             0.489
  两个成分的 y 方向标准差差了 5.2 倍——**算法自己认出了「一个细长、一个圆胖」。**
   纯度 0.9975。这就是「让每个簇有自己的形状」带来的差别。

  【三】软分配:每个点拿到的是「概率」,不是「编号」
  点          属于成分 1    属于成分 2
  --------------------------------------
  第 33 个    0.6962        0.3038
  第 220 个   0.7270        0.2730
  第 178 个   0.8042        0.1958
  第 137 个   0.8211        0.1789
  第 28 个    0.8559        0.1441
  第 74 个    0.8834        0.1166
  这几个点是「最说不清」的:责任度最接近 0.5。
  **k-means 会硬给它一个编号,混合高斯会告诉你「七成是这堆、三成是那堆」。**
   这个责任度就是 E 步算出来的东西,它是下一轮更新参数的权重——
  **边界上的点同时影响两个成分,这才叫「软」。**
  顺带说一句:这次最模糊的点也到了 0.70 对 0.30,说明这份数据的两团确实分得干脆。
  换成边界犬牙交错的数据,你会看到成片的 0.5 对 0.5——**那时「软分配」的价值才真正显出来。**

  【四】EM 在最大化什么:对数似然一路往上
  迭代    对数似然
  ----------------------
  1       -1262.40
  2       -1238.93
  3       -1208.79
  5       -1097.59
  10      -1030.38
  20      -1030.35
  40      -1030.35
  60      -1030.35
  每一轮 E 步算责任度、M 步更新参数,对数似然只增不减——
  **和 k-means 的惯性单调下降是同一件事的两面**:
  都在保证「这一轮不会比上一轮差」。代价也一样:**只保证爬到某个局部最优,不保证是全局最优**
  ,所以初值仍然重要,代码里才拿 k-means 的结果打底。

先看 k-means 的两个中心:(−0.04, −2.56) 和 (+1.04, +0.50)。 一个在长条下半截, 另一个挤在长条中段和小球之间。结果是小球被并进长条、长条被拦腰劈开,纯度 0.6925。 混合高斯的参数表才叫「认出了形状」。成分 1 的中心 (−0.01, −0.20)、x 标准差 0.265、 y 标准差 2.531——这就是那条竖长条。成分 2 的中心 (1.68, 0.02)、 两个标准差 0.472 和 0.489——这就是那个小球。纯度 0.9975。 责任度表里最模糊的点是 0.6962 对 0.3038。换句话说,连最说不清的点都有七成把握。 这份数据太干净了,软分配的优势没充分体现,但机制是一样的。 对数似然那张表:−1262.40 → −1238.93 → −1208.79 → … → −1030.35。第 10 轮就到 −1030.38, 后面 50 轮只动了 0.03。EM 和梯度下降一样:前几步跑得快,越靠近谷底越磨蹭。

30 秒实验

  1. 把小球挪到 (1.2, 0)(更靠近长条),两团开始重叠,纯度会掉到多少?
  2. 把成分数改成 3(数据里只有两团),多出来的那个成分会分到什么?
  3. 把初值从「k-means 打底」改成纯随机,连跑五次,对数似然的终点一样吗?

常见误区

  1. 把混合高斯当成「更高级的 k-means」直接替换。它参数多得多,维度高时容易过拟合,而且更慢。 先确认簇真不是圆的,再换。
  2. 用全协方差处理高维数据。自由度是 d(d+1)/2,十维就 55 个参数, 数据不够时协方差会退化成奇异矩阵。高维改用对角协方差。
  3. 以为 EM 一定收敛到全局最优。它只保证对数似然单调不降,停在哪取决于初值。 多起几个初值取最好的,或者先用 k-means 打底。
自测三问(点开对答案) 1. E 步和 M 步分别在算什么? —— E 步固定参数,算每个点属于各成分的后验概率(责任度); M 步固定责任度,按它加权重算每个成分的权重、中心、协方差。 2. 为什么混合高斯能处理长条形的簇,k-means 不能? —— 因为每个成分有自己的完整协方差矩阵,能描述「朝哪个方向散」; k-means 隐含假设所有簇散度相同且各向同性,只能切出接近圆的区域。 3. 混合高斯和 k-means 是什么关系? —— k-means 是混合高斯在「所有协方差相同且趋于 0」时的极限特例,此时软分配退化成硬分配。 所以 k-means 更快,但表达能力更弱。

到 3.6 节为止,所有算法都假设数据躺在「平面」上(哪怕是斜的平面)。 3.7 节要处理一张卷起来的曲面。

相关笔记