这批练习卷子我批到一半的时候发现,最有意思的不是那些能直接套公式的题目,而是看似没坑、实则处处扣分的聚类分析题。作为这个系列的第四期,这次题目覆盖了 K-means、层次聚类和 DBSCAN 三种主流算法,既有概念辨析,也有手算推导,还埋了不少容易踩的坑。如果你正在准备机器学习相关的考试,或者在自学路上想检验一下聚类这章到底吃透没有,这组题值得认真做一遍,尤其是做完之后对照这篇复盘来看,会比单纯对答案有用得多。
这组题整体难度不算高,但它有个特点:所有题都是从“实际分析中会遇到的真实问题”出发来出的,而不是让你背算法步骤。比如有一道题让写 K-means 的一次迭代手算过程,看似笨拙,实际上是在考察你是否真正理解“样本归属更新”和“聚类中心更新”这两个核心步骤是怎么交替进行、又是如何收敛的。还有一道层次聚类题,给的是几个样本点之间的距离矩阵,要求画出聚类树状图并解释为什么在某个高度切割可以得到合理的簇数,这其实就是在模拟你拿到一沓数据后怎么决定到底分几类。
1.1 从题目结构看训练目标
把整套题过一遍以后会发现,聚类的练习基本跑不出四类:第一类是概念辨析题,比如“聚类和分类有什么不同”“无监督学习和有监督学习怎么界定”,这类题考的是底层认知,概念不清后面全乱。第二类是相似度计算题,给几个向量,让算欧氏距离、余弦相似度,考的是对距离度量适用场景的敏感度。第三类是算法流程模拟题,像刚才说的手写 K-means 迭代,考的是对算法内部机制的熟悉度。第四类是调参与评估题,给一个含噪音的数据集,让分析为什么某算法效果差,并给出改进建议,考的是真实项目里的诊断能力。
这道练习题最有价值的地方就在第四类。很多人在入门时容易把聚类当成“调一个函数就完事”的工具,实际上聚类的难点从来不在调用,而在你知道什么时候不能用它、结果跑出来以后到底怎么判断好坏。这组题里有几道是专门冲着这个目的去的,它会在题干里给出一些很微妙的信号,比如某个数据集中各个簇的方差差别很大,或者数据里有明显的离群点,又或者特征的量纲差异悬殊。你能不能把这些信号翻译成“应该选什么算法、预处理要怎么做、参数要怎么调”,才是这组题真正想考的东西。
1.2 每道题背后的设计意图
出题人显然不希望你只是记住“K-means 是基于距离的迭代聚类算法,层次聚类是自底向上或自顶向下的聚类算法”这样的定义。定义谁都会背,但实际做项目时有几个人会因为 K-means 里带了个 K 就去思考最优 K 值到底是多少?这套题里有一道就是让你用肘部法则判断 K 值,而不是直接告诉你“数据有三类”。肘部法则的本质是衡量簇内误差平方和 SSE 随 K 增大的下降幅度,当 K 接近真实簇数时,SSE 的下降会明显变缓,这个“拐点”所在的位置就是合理的 K 值。但陷阱在于,有时候肘部并不明显,线条是平滑下降的,这时候你还要结合业务场景去判断,而不是死等一个完美拐点。
层次聚类那道题的设计也有讲究。距离矩阵一给出来,其实整个聚类过程就已经确定了,难点是链接准则的选取。single linkage 容易产生带状簇,complete linkage 对离群点敏感,average linkage 介于两者之间,Ward 方法倾向于产生大小相近的球状簇。题目里只问“选择什么链接准则得到的结果更合理”,看似是选择题,实则是在检验你能不能预判不同准则在特定数据分布下的表现差异。DBSCAN 那道题则聚焦在 eps 和 min_samples 这两个参数上,尤其是 eps 的选择。传统的方法是画 k-distance 图找拐点,但真实场景中这个拐点经常是模糊的,于是很多人就开始瞎猜 eps。这套题没有直接问“eps 要设为多少”,而是换了个角度问“当密度差异较大时,为什么一个全局 eps 无法同时识别所有簇”,这个问题的杀伤力远比直接问你参数取值大得多,因为它考的是对算法局限性的理解。
2. 做题前必须先理清的概念:聚类并不是分类
这套题里最容易翻车的第一道坎,其实是概念题。很多人写到“聚类和分类都是把数据分组”就把两个概念划等号了,这在考试里能拿一半分,但在实际分析里会出大问题。聚类是无监督学习的一个代表分支,它的训练数据没有任何标签,算法只能根据样本之间的相似性去自动发现群体的自然结构。分类则是有监督学习的内容,它需要事先有标注好的样本作为训练集,模型学到的是特征到标签的映射函数。
一个是帮你在没有正确答案的情况下发现答案,另一个是根据已知答案去预测新样本的答案,本质方向完全不同。做题的时候如果把这个底层认知搞错了,后面几道关于评估指标的选择也会连锁出错,因为聚类不能使用准确率、精确率这些依赖真实标签的指标(除非是外部评估,用带有标签的数据去校验聚类结果),当题目中出现“用准确率评估聚类效果”这种表述时,基本就可以判定为张冠李戴。
2.1 为什么距离度量选择会直接改变聚类结果
聚类的本质是“物以类聚”,而“类”的判定标准就是样本之间的距离。这套题里专门有一道考察距离公式适用场景的题目,数据给的是用户行为向量,有的维度是点击次数这种有实际物理意义的数值,有的维度是类别型取值,你如果直接套欧氏距离,效果会非常不稳定。
欧氏距离在大多数教科书场景下是最常用的选择,但它有一个隐含假设:各个维度在数值上的差异直接反映真实差异。一旦特征里有“用户月消费金额”和“用户访问天数”这种量纲差了几十倍的变量,欧氏距离就会几乎完全被“消费金额”主导,访问天数形成的模式就完全被淹没了。所以题目里如果给了这种数据,你先要意识到标准化是必须的。余弦相似度针对的是向量方向而非模长,适合文本 TF-IDF 向量、用户兴趣方向这种“只要比例不要大小”的场景。曼哈顿距离在高维稀疏数据里有时比欧氏距离更稳定,因为它没有平方项,不会让大数值变量的影响力继续放大。
做题时有一个快速判断技巧:如果题干强调“不同特征取值范围差异巨大”,第一反应就应该是标准化;如果强调的是“关注用户行为偏好方向而非活跃程度”,第一反应是余弦相似度;如果数据是稀疏的 0/1 矩阵,也别一上来就欧氏距离,先看看曼哈顿距离或者 Jaccard 相似度是否更合适。这套题没有直接让你背这些公式,而是把公式埋在具体的业务情境里,考验你能不能抽取出该用哪种度量的信号。
2.2 三种经典聚类算法的性格差异
做题做到后面我发现一个规律:凡是能把下面这张表格里三个算法的性格差异理解透的人,后面的应用题基本没有失分的。我带过了不少入门阶段的朋友,十有八九的困惑不在于不知道怎么调库,而在于不知道什么情况下该选择哪种算法,以及选择之后会遇到什么新的问题。
| 特性 | K-means | 层次聚类 | DBSCAN |
|---|---|---|---|
| 簇形状假设 | 倾向于球状簇 | 取决于链接准则 | 任意形状簇 |
| 能否处理离群点 | 敏感,离群点会拉偏中心 | 离群点影响较大 | 天然识别噪音点 |
| 簇数量 K | 必须预先指定 | 通过树状图切割决定 | 由 eps 和 min_samples 决定 |
| 计算复杂度 | 低,适合大规模数据 | 高,不适合海量样本 | 中,适合中等规模数据 |
| 输入参数敏感度 | 对初始中心敏感 | 对链接准则敏感 | 对密度参数敏感 |
题目中有一道让根据给定数据特征选择合适算法:数据是 8000 条带有明显噪音的 GPS 轨迹点,要求发现任意形状的停留区域。很多人一看“8000 条”就直接选 K-means,理由是数据量适中使用 K-means 效率高,但忽略了两个关键信号:“噪音”和“任意形状”,这两点恰恰是 K-means 的致命短板,而 DBSCAN 的密度连通机制就是专门为这种场景设计的。做题如果只盯着计算量,把算法当纯工程工具去选,就很难选对最优解。
2.3 一个项目视角下的聚类评估逻辑
这组题的最后一道设计得比较综合,它没有直接问“轮廓系数怎么计算”,而是给出两个聚类方案,一个是 K=2 的 K-means 结果,一个是 DBSCAN 的结果,让你分析哪个更好,并且给出评估依据。这种开放性题目如果平时只背公式就会卡壳。
做这类题的基本思路是先搞清楚评估的层次。内部评估指标只有数据本身参与,常用的有 SSE(簇内误差平方和)、轮廓系数(Silhouette Coefficient)。SSE 的问题在于它一定是随着 K 增大而下降的,所以不能单独用来比较不同 K 的聚类结果,需要配合肘部法则或者业务直觉使用。轮廓系数则综合了簇内紧密度和簇间分离度,取值区间在 -1 到 1 之间,越接近 1 代表聚类效果越好,但它对簇形状的假设同样是偏球状的,如果把任意形状的簇用轮廓系数去衡量,结果会非常差,这并不一定是算法不好,而是评估指标选错了。
如果数据集本身带有标签,可以使用外部评估指标,比如调整兰德指数 ARI 和标准化互信息 NMI,这两个指标可以比较聚类结果和真实标签的一致性。做题的时候要注意题干里是否提到“已知真实类别”,如果提到了而你还用轮廓系数去回答,就会被判定为没有建立起“有标签时优先使用外部指标”的意识。这道综合题真正的考察点是:你能不能根据数据情况和已知信息,选择正确的评估方式,然后基于这个评估方式去评价不同聚类的结果,而不是抛开数据条件只看分数高低。
3. 实操复盘:三道典型题的完整推导
现在进入这套题里最有操作价值的部分:手算推导和参数选择。我直接拿题目原型来做复盘,把每一步是怎么算的、在哪里容易算错、算完之后怎么看结果都过一遍。如果你还没来得及做题,建议先自己拿纸笔推一遍,再对照这部分查漏补缺。
3.1 手写一遍 K-means 迭代全过程
题目的原型是这样的:给定 6 个二维样本点,要求用 K=2 做 K-means 聚类,初始两个聚类中心分别为 A1(2,10) 和 B1(5,8),写出第一轮迭代后的聚类中心。
样本点分别为: P1 = (2, 10) P2 = (2, 5) P3 = (3, 3) P4 = (8, 4) P5 = (5, 8) P6 = (1, 2)第一步是“样本归属更新”。分别计算每个点到两个中心的欧氏距离,看离哪个更近,就归入哪个簇。
P1 到 A1 的距离: sqrt((2-2)^2 + (10-10)^2) = 0 P1 到 B1 的距离: sqrt((2-5)^2 + (10-8)^2) = sqrt(9+4) ≈ 3.61 P1 明显离 A1 更近,所以 P1 划入簇 A。
P2 到 A1 的距离: sqrt((2-2)^2 + (5-10)^2) = 5 P2 到 B1 的距离: sqrt((2-5)^2 + (5-8)^2) = sqrt(9+9) ≈ 4.24 P2 这一次划入簇 B,注意 P2 其实离两个中心都不算近,但两害相权取其轻,还是归属 B。
P3 到 A1 的距离: sqrt((3-2)^2 + (3-10)^2) = sqrt(1+49) ≈ 7.07 P3 到 B1 的距离: sqrt((3-5)^2 + (3-8)^2) = sqrt(4+25) ≈ 5.39 P3 归入簇 B。
P4 到 A1 的距离: sqrt((8-2)^2 + (4-10)^2) = sqrt(36+36) ≈ 8.49 P4 到 B1 的距离: sqrt((8-5)^2 + (4-8)^2) = sqrt(9+16) = 5 P4 归入簇 B。
P5 到 A1 的距离: sqrt((5-2)^2 + (8-10)^2) = sqrt(9+4) ≈ 3.61 P5 到 B1 的距离: sqrt((5-5)^2 + (8-8)^2) = 0 P5 归入簇 B。
P6 到 A1 的距离: sqrt((1-2)^2 + (2-10)^2) = sqrt(1+64) ≈ 8.06 P6 到 B1 的距离: sqrt((1-5)^2 + (2-8)^2) = sqrt(16+36) ≈ 7.21 P6 归入簇 B。
第一轮归属结果为:簇 A 只有 P1,簇 B 包含 P2、P3、P4、P5、P6。
第二步是“聚类中心更新”。新的中心取各自簇内所有点的均值。
簇 A 的新中心 A2: ((2)/1, (10)/1) = (2, 10)
簇 B 的新中心 B2: x = (2+3+8+5+1)/5 = 19/5 = 3.8 y = (5+3+4+8+2)/5 = 22/5 = 4.4 所以 B2 = (3.8, 4.4)。
到这里第一轮迭代就算完成了。答案是 A(2,10),B(3.8,4.4)。
这道题最容易出错的点有三个。第一个是算距离的时候忘了开根号,导致排序结果整体偏差;第二个是更新聚类中心时粗心大意把旧中心也当成样本点算进去;第三个是不知道迭代终止条件,一直死算。实际上 K-means 的终止条件有三个:聚类中心不再变化、样本归属不再变化、或者达到预设的最大迭代次数。做题时如果发现第二轮归属结果和第一轮完全一致,就可以停笔了。还有一个做题时不太会用到、但真实项目中很值得注意的点是:如果你用的是 sklearn 里的 K-means,默认的 init 方式是 K-means++,也就是说初始中心不是随机盲选,而是刻意让初始中心离得远一些,来减少收敛到局部最优的可能。手算题给你指定的初始中心,其实是为了保证计算过程可控,真实项目里你基本不会手动指定初始值。
3.2 层次聚类树状图的切割点选择套路
层次聚类这题给的是一个 5 个样本的距离矩阵,要求用凝聚式层次聚类合并样本,并画出树状图。这种题型的核心不是算距离矩阵,而是理解“每次合并最近的两个簇”这个原则。
距离矩阵呈现为: 样本 a 与 b 距离 2 样本 b 与 c 距离 3 样本 c 与 d 距离 5 样本 d 与 e 距离 1 样本 a 与 e 距离 6 其他组合距离略第一步:找到全局最小距离,是最小的那对样本,比如 d 和 e 距离 1,所以先把 d、e 合并成一个簇,记为 (d,e)。第二步:重新计算新簇与其他样本之间的距离,这里就看题目采用的链接准则是什么。如果采用 single linkage,那么簇 (d,e) 到样本 c 的距离是 min(dist(d,c), dist(e,c)),也就是 min(5, 某个值),取较小值;如果采用 complete linkage,则取较大值。第三步:重复“找最小距离、合并簇、更新距离”这三步,直到所有样本合并成一个簇为止,树状图也就画出来了。
做题时最容易忽略的地方是:链接准则不同,合并顺序可能完全不一样。很多人在第一步找最小距离时还能做对,但到了第二步重新计算距离时容易自己发明规则,把 average linkage 当成了 complete linkage。这类题丢分不是因为不会,而是因为不够仔细。现实中层次聚类的一个常见问题是大样本量下计算量很大,所以一般建议样本量在几千以内时优先考虑层次聚类,数据量更大时直接上 K-means 或者 DBSCAN 更实际。
判断应该在树状图的哪个高度切一刀,也有一个经验性套路:观察切割线上下两侧的跨度差异是否明显。如果树状图中存在一条很长的竖线连接两个大的分支,说明这两个分支之间的合并高度远大于分支内部的合并高度,那么在这条长竖线的中部切下去,一般能得到稳定且合理的簇划分。哪怕你对业务一无所知,这种基于合并高度差异的切割方式也往往能得到说得通的结果。
3.3 DBSCAN 参数选择实操:拒当盲猜选手
DBSCAN 这一题没直接让你设 eps,但问到了为什么局部密度差异太大时全局参数会失效。为了把背后的逻辑讲透,这里补充一个真实项目中选参的操作流程。
DBSCAN 的定义里有几个关键角色:核心点(在半径 eps 内至少包含 min_samples 个点)、边界点(在某个核心点的半径内但自身不满足核心点条件)、噪音点(不属于任何核心点的邻域)。从核心点出发,不断吸收与之密度相连的点,最终形成一个簇。
这个算法最大的魔力是它能自动发现“任意形状”的簇,并且能直接把离群点单独标出来。它的短板也在这里:eps 只有一个全局值,如果数据里存在不同密度的簇,你是没有办法用一个统一的 eps 同时覆盖紧凑簇和稀疏簇的。
选择一个合理的 eps 有一个常用实操办法,就是把所有样本到其第 k 个最近邻的距离按从大到小排序,然后画一条曲线,曲线里急剧上升的位置对应一个比较合理的 eps 取值。由于排序后距离变化最剧烈的地方可以去理解为一个拐点,紧挨着拐点左边的距离值往往就是合适的 eps。用 Python 参考实现如下:
from sklearn.neighbors import NearestNeighbors import numpy as np # X 为已经标准化后的样本矩阵 neigh = NearestNeighbors(n_neighbors=5) neigh.fit(X) distances, _ = neigh.kneighbors(X) # 取每个样本到其第 5 个近邻的距离,排序后可视化 k_dist = np.sort(distances[:, -1]) # 找拐点处对应的距离值作为 eps 初选值 # 可视化排序后的 k_dist,观察曲线拐点 for eps in np.arange(0.1, 1.0, 0.05): # 快速尝试不同 eps 下的簇数量 passmin_samples 的经验值一般取特征维度的两倍,或者取 2 倍到 3 倍之间。比如特征是三维数据,那么 min_samples 可以先从 6 开始试。如果你的数据本身比较大,可以适当把 min_samples 调大一些,这样能过滤掉更多零散噪声,但也会让稀疏簇更容易被拆散,所以是时候做一个权衡了。这套题想提醒你的就是:eps 不能拍脑袋,哪怕拐点不好找,也要用 k-distance 图或者项目业务预期去约束选择,否则聚类结果即使能跑出来,解释起来也没有底气。
代码这一段的定位是用来说明“参数选择是有方法支撑的”,而不是让你交作业时贴代码。做题的时候你只需要能答清楚“密度大的簇应该用较小的 eps 来区分,密度小的簇需要较大的 eps 才能内部连通”,以及“当两类簇密度差异过大的时候,单靠 DBSCAN 一个全局 eps 并不够”这两点就算抓住核心了。
4. 交作业之后我见过的翻车现场:常见问题与排查
这一部分什么都不讲,专门复盘我在批改类似练习和看朋友项目时见到的各种翻车现场。不看不知道,看了才发现很多失分或做错数据的原因完全一致,几乎可以整理成一张速查表。
4.1 你以为是聚类算法问题,其实是数据预处理问题
有一道类似的练习,数据集是两个特征,一个特征的取值范围在 0 到 1 之间,另一个特征的取值范围在 0 到 10000 之间,当时不少人的结论是“K-means 在这个数据上聚类效果很差,簇完全挤在一起”。实际上问题不出在 K-means 上,而是样本根本没有做标准化。大数值特征在欧氏距离计算中近乎完全主导了结果,小数值特征直接被淹没,所谓“挤在一起”的簇不过是大数值特征单维度上的分割线。
这种情况的典型症状是聚类可视化图上几乎只能看到一个方向的分裂。排查方式也很简单:直接打印每个维度的方差,如果方差差异超过一个数量级,先做标准化再说。做题时如果题目给的距离矩阵不是从标准化后的数据算出来的,你在回答“为什么聚类效果不好”时,首先就应该怀疑预处理环节,而不是急着换算法。
4.2 K-means 结果不稳定:初始化与空簇的坑
大家还容易遇到的一个问题是 K-means 每次跑出来的结果都不一样。这不是算法本身“出错了”,而是 K-means 对初始聚类中心敏感,不同的初始中心可能收敛到不同的局部最优解。做题时题目往往已经把初始中心给定,你感受不到这个问题,但到了真实项目里第一次跑 K-means,基本都会遇到。
解决办法不复杂:正式建模前多用几个不同的 random_state 跑一遍,观察聚类中心是否变化巨大。如果变化很大,说明数据本身的簇结构没有你想象的那么清晰,或者 K 值选得不是很合理。另外 K-means 还有一个小概率翻车情况是出现空簇,也就是在迭代过程中某个簇沦落到一个样本都没有。此时不能直接报错然后不处理,常见的处理方式是重新初始化这个空簇的中心。做题时如果手算过程中发现某个簇已经空了,这意味着初始中心选得不好,可以启动新的初始中心重新迭代。
4.3 评估指标乱用导致结论全反
做题时我还见过有同学用 SSE 最小的那个 K 去选择最优 K 值,理由是 SSE 越小说明簇内部越紧凑。听着挺合理,但 SSE 在 K 增大的过程中永远是下降的,你一路比下去肯定会选出一个特别大的 K,分类细碎到没有业务解释力。用肘部法则的话,得看 SSE 曲线的拐点,而不是全程的最小值。
还有个更隐蔽的问题是用轮廓系数去评价 DBSCAN 的结果。DBSCAN 聚类出的簇可能是任意形状,而轮廓系数本质上是基于距离来评估紧密度和分离度的,它对球形簇比较友好,碰上任意形状的簇时会给出偏低的分数,这个分数并不代表 DBSCAN 的结果“差”,只能说明评估工具选得不对。正确做法是:如果真的需要对比不同算法的效果,而且数据有真实标签,就用 ARI 或 NMI;没有标签时,结合业务本身给簇做定性分析往往比单一指标对项目更有用。
4.4 常见错误速查表
| 现象 | 可能原因 | 排查思路 | 解决办法 |
|---|---|---|---|
| 聚类结果被单个特征主导 | 特征未标准化 | 检查各维度方差 | 做 StandardScaler 或 MinMaxScaler |
| K-means 每次结果不同 | 初始中心敏感 | 比较多次运行结果 | 固定 random_state,或改用 K-means++ |
| 簇数量怎么选都不合理 | K 值设定没有依据 | 画 SSE 曲线找肘部 | 使用肘部法则结合业务预期 |
| 树状图切割后簇太碎 | 切割高度选得太低 | 观察纵轴跨度差异 | 在长竖线中部附近切割 |
| DBSCAN 一个簇都分不出来 | eps 设置过小 | 检查 k-distance 图拐点 | 调大 eps 或调小 min_samples |
| DBSCAN 全被分成一个簇 | eps 设置过大 | 检查 k-distance 图拐点 | 调小 eps 或调大 min_samples |
| 聚类评估指标显示很差 | 指标与数据不匹配 | 确认是否球状簇假设 | 换用 ARI/NMI 或做定性分析 |
最后
说实话,以前我也觉得手算 K-means 迭代、画层次聚类树状图这种东西透着一种“复古感”,直到后来真正做了项目才承认,正是这些基本功训练让你在拿到一个陌生数据集时不慌:你心里清楚这个结果可能受什么因素影响、哪个参数一碰就会导致结果崩掉、指标应该在哪个环节参与决策。这一组聚类练习题算是把该覆盖的地方都覆盖了,概念、距离、三个核心算法、评估指标,全都有对应的坑。如果你打算进一步巩固,我建议你之后找一个人工构造的数据集,分别用 K-means、层次聚类和 DBSCAN 去跑一遍,故意把参数调坏再调好,感受一下每个参数拧起来时结果会怎么变化,这种手感比多刷十道题更能帮你建立对聚类的直觉。