三个球状高斯团上,两个完全不同的判据给出同一个答案:间隙统计量选 K=3(gap 最高 0.7261), 混合模型 BIC 也选 K=3(最小 3332.1)。真值是 3。 换到同心圆,两个判据全错:gap 选 K=1(等于说不用分),BIC 一路切到上限 6,而真值是 2。 更关键的是:就算 K=2 选对了,K 均值给出的划分 ARI 也只有 0.0047。 判据只能在模型族内部挑参数,管不了模型族本身对不对。
前三节都留了一个尾巴:K 要人来定,带宽要人来定,邻居数要人来定。 这一节正面回答「没人给答案的时候,怎么自动定」。 要讲清一个容易混淆的点: 自动选参数(model selection)和自动学参数(parameter estimation)是两件事。 中心坐标、簇的划分,这些是「学」出来的;K、带宽这些是「选」出来的。 前者是优化问题,后者没有目标函数可用——因为目标函数对模型复杂度单调(14.1 节已实测)。 所以「选」必须额外引入一个原则,这一节讲两个常见的:间隙统计量和 BIC。
一、为什么不能拿目标函数选
先把错误的路堵死。 14.1 节已经实测过:簇内平方和从 K=1 的 2356.92 一路降到 K=7 的 201.70,永远不回升。 拿它当标准选 K,永远选最大的那个。 这不是 K 均值的问题,是所有「拟合得越紧越好」的目标函数的通病。 回归里加特征、决策树加深、混合模型加分量,全都是同一个毛病。 第 4 章的正则化、第 5 章的 ℓ1 约束,治的也是这个病——只是那里罚的是参数大小, 这里要罚的是「簇的个数」。 所以「选」的通用配方是:拟合优度 加上(或减去)复杂度惩罚。 BIC 就是这个配方的标准形式: 负二倍对数似然(拟合越好越小)加上参数个数乘样本数的对数(参数越多越大)。 两项一个往下压、一个往上抬,最小值就在中间某个地方。 听起来很完美,但它有一个致命前提:似然得算得对。 而似然要算得对,就得先假定一个概率模型——BIC 假定的是「每簇是一个高斯」。 这个假定就是这一节后面要出问题的地方。
二、判据一:间隙统计量
间隙统计量的思路很妙:不问「拟合得好不好」,而问「比随机数据紧多少」。 具体做法:对每个 K,算出真实数据的簇内离散度(簇内两两距离之和除以两倍簇大小),取对数; 然后生成若干份「同样大小、在同样范围里均匀分布」的随机数据,各算一遍,取平均。 两者之差就是 gap。gap 越大,说明「真实数据比随机数据紧得多」,也就是「这个 K 抓到了结构」。 它的好处是不用假定任何概率模型——只比较「紧不紧」。 它的好处也是它的局限:正因为不假定模型,它衡量的是「紧」, 所以它默认「一簇 = 一团紧的东西」,本质上还是球状假设。 选择规则是:取第一个满足「gap(k) 不小于 gap(k+1) 减去一个标准差」的 k。 为什么要减一个标准差?因为 gap 本身是估出来的,有抽样波动,不减的话会因为噪声多切一块。 这个「减一个标准差」是原论文的写法,实际实现里也有用「取 gap 最大」的简化版; 本节的代码两个都算,但按前者决策。
三、判据二:混合模型的 BIC
BIC 走的是另一条路:假定每簇是一个高斯,用 EM 算法把混合模型拟合出来, 然后算「负二倍对数似然 加 参数个数乘 log(n)」。 参数个数是 K 乘以(维数加 2)再减 1:每簇一个均值向量(d 个参数)、一个方差、一个混合权重。 这个惩罚项的意思很直白:每多一个簇,就要多付 log(n) 的代价。 BIC 的优点是它有明确的理论依据(大样本下近似贝叶斯证据),而且它给出的是「整个模型」的比较, 不只是「紧不紧」。 缺点是它那个「高斯」假定是硬的:数据不是高斯,BIC 就可能在错的方向上优化。 实现上有两个坑,我第一次写全踩了: 第一个,方差不能用一个「所有簇合并」的方差。 那样的话 K 越大方差越小,似然无限增长,BIC 一定选最大 K——这和直接用簇内平方和没区别。 第二个,每个簇要各自一个方差,而且要按「每个坐标」算,不能按「所有坐标加总」算, 否则量纲差一个维数倍,BIC 的绝对值全错。 这两条不是理论细节,是能不能用的分界。 修好之后,这份数据上 BIC 的最小值确实落在 K=3。
四、球状数据上:两个判据都对
三个高斯团,K 从 1 试到 8:
| K | gap | 标准差 | ARI | BIC |
|---|---|---|---|---|
| 1 | 0.2649 | 0.0123 | 0.0000 | 4234.7 |
| 2 | 0.3752 | 0.0155 | 0.5608 | 3987.4 |
| 3 | 0.7261 | 0.0147 | 0.9850 | 3332.1 |
| 4 | 0.5960 | 0.0119 | 0.8533 | 3356.9 |
| 5 | 0.5521 | 0.0137 | 0.8097 | 3379.5 |
| 6 | 0.5552 | 0.0156 | 0.6636 | 3398.2 |
| 7 | 0.5588 | 0.0181 | 0.5090 | 3421.6 |
| 8 | 0.5397 | 0.0157 | 0.4584 | 3440.5 |
| gap 在 K=3 处最高(0.7261),之后掉到 0.54 到 0.60 之间;BIC 也在 K=3 处最小(3332.1)。 | ||||
| 两个判据、两套完全不同的道理,给出同一个答案,而且和真相一致。 | ||||
| 它们为什么能对?因为「球状高斯团」这个假设,正好就是两个判据都默认的模型。 | ||||
| gap 默认「一簇是一团紧的东西」,BIC 默认「一簇是一个高斯」——这份数据两条都满足, | ||||
| 所以判据当然准。 | ||||
| 注意 ARI 那一列:它也在 K=3 处最高。但 ARI 是拿真标签算的,真实场景里没有。 | ||||
| 所以这一列只是用来「验算判据准不准」,不是可用手段。 |
五、非球状数据上:两个判据全错
换到同心圆(真值 K=2):
| K | gap | 标准差 | ARI | BIC |
|---|---|---|---|---|
| 1 | 0.1038 | 0.0133 | 0.0000 | 3018.7 |
| 2 | 0.0414 | 0.0147 | 0.0047 | 3041.0 |
| 3 | 0.0378 | 0.0161 | 0.0004 | 3058.9 |
| 4 | -0.0438 | 0.0155 | 0.0616 | 3042.3 |
| 5 | 0.0369 | 0.0120 | 0.3763 | 3033.5 |
| 6 | 0.0646 | 0.0171 | 0.3691 | 3015.5 |
| gap 选 K=1——等于说「这堆数据根本不用分」。BIC 选 K=6——一路切到上限。 | ||||
| 两个答案完全不同,而且都不是 2。 | ||||
| 原因不难想:gap 和 BIC 都默认「一个簇是一个球」,而同心圆的两个簇,一个实心盘、 | ||||
| 一个圆环——用球去套,套不上。 | ||||
| 数据在球状假设下显得「不紧」,gap 就看不出结构(K=1 反而最高); | ||||
| 而 BIC 发现用小圆球去拼圆环能拟合得更准,就一直加分量。 | ||||
| 注意 gap 在 K=4 处出现了负数(负 0.0438)——这说明「真实数据比同范围的均匀分布还松」。 | ||||
| 这本身就是个信号:假设已经崩了。可惜判据不会自己喊停,它只会给一个错的答案。 | ||||
| 还有一件更要注意的事:就算 K 选对了,划分也不对。 | ||||
| K=2 是正确答案,而 K 均值给出的划分 ARI 只有 0.0047。 | ||||
| 选参数和分簇是两道独立的关,过了第一道不代表第二道能过。 | ||||
| 所以「自动选参数」这四个字有一个隐含前提:模型族已经选对了。 | ||||
| 判据能在你给的模型族里挑一个最好的参数,但它没法告诉你模型族本身错没错。 | ||||
| 先确认形状,再谈参数——顺序反了,越精细的判据越会把错的事做得更认真。 |
六、这套思路的通用形状
两个判据看起来差别很大,其实是同一个配方: 「拟合优度」减去「复杂度惩罚」,然后取极值。 gap 的拟合优度是「比随机紧多少」,惩罚藏在「取第一个满足条件的 k」这个规则里; BIC 的拟合优度是似然,惩罚是显式的参数个数乘 log(n)。 这个配方在哪都能用,但每一处都要重新回答两个问题: 第一,拟合优度怎么算? 第二,复杂度怎么罚? 答错第一个,判据会偏;答错第二个,判据会一直选最大或一直选最小。 回到这一章的三个方法,各自的「自动选取」分别是: K 均值——选 K,用 gap 或 BIC; 核 K 均值——选带宽,理论上也能用 gap(在核空间里算离散度), 但更常见的做法是看「核矩阵的有效秩」或者直接交叉验证; 谱聚类——选邻居数和 K,前者看谱的第二特征值是否够小,后者看谱有几个跳。 最后必须说清一件事:这些判据都只是「建议」,不是「答案」。 聚类这件事没有客观正确答案——同一份数据,按「形状」分和按「用途」分,簇数可以不一样, 而且都对。 判据能帮你排除明显不合理的选项,但最后拍板的一定是「这个划分在业务上有没有意义」。 这句话不是废话:很多项目里 K 是业务定的(比如「我要把客户分成四档」), 这时候判据的作用是「检查四档是否勉强合理」,而不是「替你决定几档」。
七、动手 Demo:自动选 K:球状数据上两个判据都对,同心圆上全错
这段代码实现两个判据:间隙统计量(拿同范围的均匀分布当参照)和混合模型 BIC(EM 拟合)。 然后在三团和同心圆上各跑一遍 K 从 1 到 8。 重点看两张表的对照:三团上 gap 最高点和 BIC 最低点都在 K=3; 同心圆上 gap 选 1、BIC 选 6,而真值是 2。
import numpy as np
def d2(A, B):
return np.maximum(((A[:, None, :] - B[None, :, :]) ** 2).sum(-1), 0.0)
def dmat(A, B):
return np.sqrt(d2(A, B))
def kmeans(X, K, seed=0, iters=100):
rg = np.random.default_rng(seed)
n = len(X)
C = [X[rg.integers(n)]]
for _ in range(K - 1): # k-means++ 初始化
d = d2(X, np.array(C)).min(axis=1)
C.append(X[rg.choice(n, p=d / d.sum())])
C = np.array(C, dtype=float)
for _ in range(iters):
lab = np.argmin(d2(X, C), axis=1)
newC = np.array([X[lab == k].mean(0) if (lab == k).any() else C[k]
for k in range(K)])
if np.allclose(newC, C):
return newC, lab
C = newC
return C, np.argmin(d2(X, C), axis=1)
def within(X, lab, K): # gap 里的 W_k
tot = 0.0
for k in range(K):
m = lab == k
if m.sum() < 2:
continue
tot += dmat(X[m], X[m]).sum() / (2.0 * m.sum())
return tot
def gap(X, K, B=20, seed=0):
rg = np.random.default_rng(seed)
lo, hi = X.min(axis=0), X.max(axis=0)
ref = np.array([np.log(within(R, kmeans(R, K)[1], K))
for R in (rg.uniform(lo, hi, X.shape) for _ in range(B))])
lw = np.log(within(X, kmeans(X, K)[1], K))
return ref.mean() - lw, ref.std()
def bic(X, K, seed=0, iters=80):
n, d = X.shape
C, lab = kmeans(X, K, seed=seed)
vr = np.array([max(((X[lab == k] - C[k]) ** 2).sum()
/ max((lab == k).sum() * d, 1), 1e-3) for k in range(K)])
pi = np.array([max((lab == k).sum(), 1) / float(n) for k in range(K)])
ll = -1e18
for _ in range(iters):
logp = np.column_stack([
np.log(pi[k]) - 0.5 * d * np.log(2 * np.pi * vr[k])
- 0.5 * ((X - C[k]) ** 2).sum(1) / vr[k] for k in range(K)])
mx = logp.max(axis=1, keepdims=True)
r = np.exp(logp - mx)
ss = r.sum(axis=1, keepdims=True)
r = r / ss
new = float((mx[:, 0] + np.log(ss[:, 0])).sum())
nk = r.sum(axis=0) + 1e-12
pi = nk / n
C = (r.T @ X) / nk[:, None]
for k in range(K):
vr[k] = max((r[:, k] * ((X - C[k]) ** 2).sum(1)).sum()
/ (nk[k] * d), 1e-3)
if new - ll < 1e-6:
ll = new
break
ll = new
return -2.0 * ll + (K * (d + 2) - 1) * np.log(n)
def ari(a, b):
ua, ub = np.unique(a), np.unique(b)
tab = np.zeros((len(ua), len(ub)))
for i, x in enumerate(ua):
for j, y in enumerate(ub):
tab[i, j] = np.sum((a == x) & (b == y))
c2 = lambda z: z * (z - 1.0) / 2.0
sij, sa, sb = c2(tab).sum(), c2(tab.sum(1)).sum(), c2(tab.sum(0)).sum()
exp, mx2 = sa * sb / c2(float(len(a))), 0.5 * (sa + sb)
return float((sij - exp) / (mx2 - exp))
for name, X, y in (('三团', XB, yB), ('同心圆', XC, yC)):
print(name)
for K in range(1, 8):
g, sd = gap(X, K, B=20, seed=0)
_, lab = kmeans(X, K)
print(' K=%d gap %.4f sd %.4f ARI %.4f BIC %.1f'
% (K, g, sd, ari(lab, y), bic(X, K)))
实跑输出:
--------------------------------------------------------------------
自动选 K:三个团上 gap 和 BIC 都选 3,同心圆上全错
--------------------------------------------------------------------
判据一 间隙统计量 gap(k):拿「同样大小的均匀分布」当参照,看实际数据比它紧多少
判据二 BIC:混合高斯模型,拟合越好减分越多,参数越多罚分越多
先看能用的场合——三个球状高斯团:
K gap 标准差 ARI BIC
--------------------------------------------
1 0.2649 0.0123 0.0000 4234.7
2 0.3752 0.0155 0.5608 3987.4
3 0.7261 0.0147 0.9850 3332.1 <-最小
4 0.5960 0.0119 0.8533 3356.9
5 0.5521 0.0137 0.8097 3379.5
6 0.5552 0.0156 0.6636 3398.2
7 0.5588 0.0181 0.5090 3421.6
8 0.5397 0.0157 0.4584 3440.5
gap 选出的 K 3
**gap 在 K=3 处最高(0.7261),之后掉下来;BIC 也在 K=3 处最小。**
**两个判据、两套完全不同的道理,给出同一个答案,而且和真相一致。**
但注意它们为什么能对:
**因为「球状高斯团」这个假设,正好就是两个判据都默认的模型。数据长在假设上,判据当然准。**
现在换一份数据,形状完全对不上假设——同心圆:
K gap 标准差 ARI BIC
---------------------------------------------
1 0.1038 0.0133 0.0000 3018.7
2 0.0414 0.0147 0.0047 3041.0
3 0.0378 0.0161 0.0004 3058.9
4 -0.0438 0.0155 0.0616 3042.3
5 0.0369 0.0120 0.3763 3033.5
6 0.0646 0.0171 0.3691 3015.5 <-最小
gap 选出的 K 1(真值是 2)
**两种判据给出两个完全不同的答案,而且都不是 2。**
**gap 说 K=1,等于「这堆数据根本不用分」;BIC 一路切到上限 6,等于「能切多细就切多细」。**
**原因不难想:gap 和 BIC 都默认「一个簇是一个球」,而同心圆的两个簇,一个是实心盘、一个是圆环——用球去套,套不上。**
**数据在球状假设下显得「不紧」,gap 就看不出结构;而 BIC 发现用小球拼圆环能拟合得更准,就一直加分量。**
更要注意的是:就算 K 选对了,划分也不对。
同心圆 K=2 的 ARI 0.0047
**K=2 是正确答案,可 K 均值给出的划分 ARI 只有 0.0047。**
**所以「自动选参数」这四个字有一个隐含前提:模型族已经选对了。**
**判据能在你给的模型族里挑一个最好的参数,但它没法告诉你模型族本身错没错。**
**先确认形状,再谈参数——顺序反了,越精细的判据越会把错的事做得更认真。**
三团的八行里,gap 在 K=3 冲到 0.7261,之后掉到 0.54 到 0.60; BIC 在 K=3 落到 3332.1,K=4 又回到 3356.9。 两个判据都精确地指在 3 上,和真值一致。 同心圆的六行就完全不同了:gap 在 K=1 最高(0.1038),BIC 一路降到 K=6(3015.5)。 一个说「不用分」,一个说「切到最细」。 注意 gap 在 K=4 出现负数(负 0.0438)。 这个负数是一个警报:它说明真实数据比同范围的均匀分布还松——球状假设已经崩了。 但判据不会自己喊停,它只会继续给出一个错的答案。 最关键的一行是 K=2 时的 ARI:0.0047。 就算你绕过判据、直接用正确答案 K=2,K 均值也分不出同心圆。 所以判据和算法是两道独立的关:判据决定「分几堆」,算法决定「怎么分」。 两个都得对,缺一个就全错。 BIC 那两处实现细节值得再看一眼代码:方差是每个簇各自一个、 而且按「每个坐标」除(除以 nk 乘 d)。 这两条错了,BIC 就会退化成「和簇内平方和一样单调」,永远选最大 K。 第一次写这段的时候两种错都犯了,K=1 到 8 的 BIC 一直在涨。
30 秒实验
- 把
gap的B从 20 改成 100。 gap 的标准差会变小,选择结果更稳,但耗时是五倍。想想: 为什么标准差的估计精度影响的是「切几块」这个决策? - 把
bic里的vr改成所有簇共用一个方差。 BIC 会变成单调下降,永远选最大 K——亲手复现一下这个 bug,比看十遍解释管用。 - 把同心圆换成
blobs(三团),但把std调到 1.5 让三个团重叠。 看 gap 和 BIC 还准不准——重叠意味着「球状假设仍然成立,但边界模糊」,这时判据通常还凑合, 只是会倾向少分。
常见误区
- 「用簇内平方和的最小值选 K」。 它对 K 单调下降,永远选最大。实测从 K=1 的 2356.92 降到 K=7 的 201.70。 必须加复杂度惩罚,或者换成带惩罚的判据(BIC、gap)。
- 「BIC 会自动给出正确的模型」。 BIC 只在「模型族正确、样本足够大」时才有理论保证。 同心圆上它选 K=6 而不是 2,就是因为「高斯」这个模型族本身错了。判据能挑参数, 不能纠正模型族。
- 「gap 选出来的 K 一定比 BIC 可靠,因为它不需要概率模型」。 不需要概率模型不等于没有假设——gap 的假设是「一簇是一团紧的东西」,依然是球状假设。 同心圆上它给出的 K=1 错得更离谱。
- 「自动选完参数就可以直接交付了」。 聚类没有客观正确答案。 同一份数据按形状分和按用途分,簇数可以不同而且都对。 判据的作用是排除明显不合理的选项,最后拍板的是业务含义。 很多项目里 K 本来就是业务定的,判据只用来检查「这个 K 勉强合理吗」。
自测三问(点开对答案)
1. 为什么不能直接用簇内平方和选 K? —— 因为它对 K 单调下降,永远不回升:K 每加一,模型多一个中心可用,极端情况是每点一簇、 目标值归零。实测从 K=1 的 2356.92 一路降到 K=7 的 201.70。选 K 必须引入复杂度惩罚。 2. 两个判据在三团上都选 3,在同心圆上都错。差别在哪? —— 三团是球状高斯团,正好符合两个判据都默认的「一簇是一团紧的东西/一个高斯」假设; 同心圆是一盘一环,两个簇都不是球,假设崩了:gap 认为「不够紧」所以选 K=1, BIC 发现用小圆球拼圆环能拟合得更好所以一路加到上限 6。 3. 为什么说「判据和算法是两道独立的关」? —— 判据决定分几堆(选 K),算法决定怎么分(划分本身)。 同心圆上 K=2 是正确答案,但 K 均值给出的划分 ARI 只有 0.0047。 选对了 K 不代表分得对,两个都得过。无监督学习这三章到此结束:第 12 章判断「像不像」、第 13 章「换个坐标系」、 第 14 章「分成几堆」。三章的共同点是——都没有正确答案,所以评价方式全得重新发明。 下一章换到「在线学习」:数据一批一批地来,模型得边用边改。
- ☐ (本章完,下一节 15.1 15.1)