聚类是什么——先帮自己把概念理清楚
开始拆这套机器学习基础练习题之前,我先把话说明白:聚类分析不是某一种算法,而是一整类"无监督学习"问题。
什么意思呢?有监督学习就像老师改卷子,每道题都有标准答案,模型学的是"输入—输出"的映射关系;而无监督学习没有标准答案,拿到的只是一堆原始数据,我们要做的就是从数据本身的结构里找出隐藏的规律。聚类干的正是这件事——把相似的对象归到同一组,让组内尽可能紧凑、组间尽可能疏远。
这套题为机器学习基础练习题(4)聚类分析,内容并不高深,适合三类人:
- 刚学完机器学习基础理论,想通过代码把概念落地的初学者;
- 正在准备算法岗位面试,需要系统梳理聚类知识点的人;
- 工作中要处理用户分群、客户画像、异常检测等问题的数据分析师。
我当初做这套题时踩过不少坑,尤其是K-Means的初始化、聚类数的选择、距离度量方式的区别这些点,光看书总觉得懂,一上手就懵。下面我按实操的逻辑把整套内容重新过一遍,把每个关键环节的原理、步骤、参数选择讲透,保证你能直接照着做完、改明白、用起来。
1. 内容整体设计与思路拆解
1.1 为什么要用练习题的形式来学聚类
很多人学聚类时犯一个毛病:先背公式,再调库,最后拿一个现成数据集跑一遍,看到图出来就算完成任务。这套题的设计思路刚好相反——它迫着你去思考"为什么选这个参数""这个结果说明什么",而不是单纯跑通。
练习题一般围绕这几个核心目标展开:
- 理解聚类的本质:输入是一组无标签样本,输出是每个样本的簇标签;
- 掌握常见算法的适用场景:K-Means适合凸簇,DBSCAN适合任意形状且能识别噪声,层次聚类适合需要嵌套结构的情况;
- 熟悉评价指标:轮廓系数、Calinski-Harabasz指数、Davies-Bouldin指数各自看什么;
- 学会处理数据:标准化、降维、可视化,这些环节直接影响聚类效果。
这套题的巧妙之处在于它把"算法实现"和"业务理解"串在一起,每一步都在提醒你:聚类结果不是终点,能解释得通才有价值。
1.2 方案选型:这道题为什么推荐K-Means入门
做聚类分析,算法选型本身就是一道送分题,也是最容易丢分的题。K-Means是几乎所有入门教程的首选,原因很直白:
- 原理简单:一句话就能说清楚,迭代更新簇中心和样本分配;
- 计算快:即使数据量到十万级,运行时间也完全可以接受;
- 实现门槛低:不管用Scikit-learn还是手写,代码量都不大;
- 解释性强:每个簇可以用中心点代表,业务上容易落地。
但K-Means也有三个公认的短板,题目里往往会设置一些"陷阱"来考察你知不知道:
- 必须预先指定聚类数K,给错了结果就偏;
- 对初始中心敏感,不同初始化可能收敛到不同局部最优;
- 对异常值和噪声敏感,均值计算会被极端值带偏。
所以我在做题时给自己立了个规矩:不管题目用不用,我都要把DBSCAN和层次聚类也跑一遍做对比。这不仅是为了理解算法差异,更能在面试聊到项目时展示完整的思考链条。
1.3 数据准备阶段最容易忽略的环节
聚类练习里最容易被忽略、但影响最大的环节是数据预处理。很多初学者拿到数据直接灌进模型,结果画出来的图要么挤成一团,要么横纵坐标尺度失衡,根本看不出簇结构。
先看一个最常见的例子:数据包含"年龄(20~60)"和"收入(2000~30000)"两个特征。如果不做处理,距离计算时收入特征会完全主导,年龄几乎不起作用。这不是算法的问题,是我们喂给算法的数据本身就有问题。
标准做法分三步:
- 缺失值处理:删除或填充,不要留空让模型报错;
- 标准化或归一化:让每个特征处于相近的尺度范围;
- 降维辅助:如果特征维度太高,先用PCA降到2~3维再可视化。
这套题里有一个环节专门考察这个点,我在做的时候故意试了一次不标准化的版本,轮廓系数直接从0.72掉到0.41,效果立竿见影。所以无论题目怎么出,预处理这一步永远别跳过。
2. 核心细节解析与实操要点
2.1 K-Means核心公式与迭代过程拆解
K-Means的迭代过程看起来简单,但每个细节都值得展开,因为练习题里很多选择题和简答题就是从这里出的。
整个算法只有四步:
- 初始化:随机选择K个样本作为初始簇中心;
- 分配:计算每个样本到K个中心的距离,把它归到最近的中心;
- 更新:重新计算每个簇内所有样本的均值,作为新的中心;
- 重复:重复2和3,直到中心不再变化或变化小于阈值。
分配步骤里的核心是距离度量。最常用的是欧氏距离,公式不复杂:两个点各维特征差的平方和再开方。
import numpy as np def euclidean_distance(a, b): return np.sqrt(np.sum((a - b) ** 2))更新步骤的本质是求簇内均值。这一步要特别注意:如果某个簇在分配后没有任何样本,K-Means会直接报错或者留下一个空中心。这在练习题的小数据集里不常见,但面试里经常拿它当延伸问题,自己实现时务必加上空簇检查。
2.2 K值选择:肘部法则与轮廓系数的组合用法
K值是K-Means唯一的超参数,选得对不对直接决定结果质量。题目里通常会给一个小数据集,让你画出不同K下的评估曲线,然后选择最优K。
第一个方法是肘部法则(Elbow Method)。做法是对K取1到10,分别计算每个K下的簇内误差平方和(SSE),画折线图。SSE会随K增大而下降,但下降速度在某个点后明显变缓,这个拐点就是"肘",对应的K就是推荐值。
第二个方法是轮廓系数(Silhouette Coefficient)。对每个样本,计算它到同簇其他样本的平均距离a,再计算它到最近其他簇样本的平均距离b,轮廓系数就是(b - a)除以a和b中的较大值。取值在-1到1之间,越大表示聚类效果越好。
实际操作时,我不会只依赖单一指标,而是把两个方法结合起来看:肘部法则先给出一个候选区间,再用轮廓系数精确定位。还有Calinski-Harabasz指数,它是簇间离散度与簇内离散度的比值,计算速度快,也可以作为辅助参考。
具体代码长这样:
from sklearn.cluster import KMeans from sklearn.metrics import silhouette_score import matplotlib.pyplot as plt def select_k(X, max_k=10): sse = [] sil = [] for k in range(2, max_k + 1): km = KMeans(n_clusters=k, random_state=42, n_init=10) km.fit(X) sse.append(km.inertia_) sil.append(silhouette_score(X, km.labels_)) fig, axes = plt.subplots(1, 2, figsize=(12, 4)) axes[0].plot(range(2, max_k + 1), sse, marker='o') axes[0].set_xlabel('K') axes[0].set_ylabel('SSE') axes[1].plot(range(2, max_k + 1), sil, marker='o') axes[1].set_xlabel('K') axes[1].set_ylabel('Silhouette Score') plt.show()我建议把这条代码模板存着,后面遇到任何聚类练习都能直接复用。
2.3 距离度量:欧氏距离、曼哈顿距离与余弦相似度
聚类题里有一个高频考点:不同距离度量对结果的影响。很多人以为换个距离只是换个公式,实际效果差很多。
- 欧氏距离:最常用,适合数值型、连续型特征,对尺度敏感,所以必须先标准化;
- 曼哈顿距离:各维度绝对差之和,对异常值不那么敏感,适合特征维度彼此独立或存在离群点的情况;
- 余弦相似度:看方向不看长度,适合文本向量、用户行为向量这种高维稀疏数据,比如推荐系统里的用户分群。
练习题通常会给你一组二维坐标点,让你手算或用代码比较不同距离下的聚类结果。我刚开始也以为这是纯数学题,动手才发现真正考点是理解"尺度"和"方向"的差别。
举一个直观例子:两个用户的购买记录向量分别是(1, 0)和(5, 0),欧氏距离为4,看起来差异很大;但余弦相似度却是1,因为方向完全一致。在用户分群场景里,这两个用户应该归为一类,因为他们购买偏好高度相似——这就是选对度量方式的价值。
2.4 标准化操作:为什么这一步能决定聚类成败
我在前面强调过标准化的必要性,这里再针对练习场景说具体操作。聚类练习里最常用的标准化方式有两种:
- StandardScaler:先减去均值再除以标准差,处理后数据均值为0、方差为1,适合数据近似正态分布的情况;
- MinMaxScaler:将数据缩放到[0, 1]区间,适合有明确上下界的数据。
选择标准其实很简单:不确定就先用StandardScaler。它是聚类预处理里的默认选项,而且配合PCA降维效果稳定。
标准化之后还有一个小技巧:可以把标准化前后的聚类结果轮廓系数打出来对比,这个对比本身就经常作为练习题的加分项出现。
from sklearn.preprocessing import StandardScaler scaler = StandardScaler() X_scaled = scaler.fit_transform(X)3. 实操过程与核心环节实现
3.1 环境准备与数据集加载
做这套题之前,先把环境里缺的库装上。我推荐直接使用Anaconda管理环境,省去一堆依赖打架的问题。
pip install numpy pandas scikit-learn matplotlib如果网络环境不方便装完整版本,也可以只装核心依赖,代码里涉及到的具体库在运行到对应部分时再补装。数据集方面,建议先拿Scikit-learn自带的小型数据集练手,比如鸢尾花数据集(Iris),它有三个类别、四个特征,非常适合用来验证聚类算法。
这里有一个小提醒:如果直接加载在线数据集失败,不要卡在原地,换用本地生成的数据集即可。练习的重点是聚类流程,不是在下载数据上浪费时间。
3.2 完整训练流程:数据标准化、K值选择、K-Means训练、结果可视化
我把整套流程串成一条可复用的流水线,直接在任意数据集上跑。
第一步,加载数据并查看基本结构:
import pandas as pd from sklearn.datasets import load_iris data = load_iris() X = pd.DataFrame(data.data, columns=data.feature_names) print(X.head()) print(X.shape)第二步,标准化:
from sklearn.preprocessing import StandardScaler scaler = StandardScaler() X_scaled = scaler.fit_transform(X)第三步,用前面写的select_k函数画图,选出最优K。以鸢尾花为例,肘部法则显示K=3附近曲线开始放缓,轮廓系数也在K=3处最高,两个指标一致。
第四步,训练K-Means:
from sklearn.cluster import KMeans kmeans = KMeans(n_clusters=3, random_state=42, n_init=10) kmeans.fit(X_scaled) labels = kmeans.labels_ centers = kmeans.cluster_centers_第五步,可视化:
import matplotlib.pyplot as plt from sklearn.decomposition import PCA pca = PCA(n_components=2) X_pca = pca.fit_transform(X_scaled) plt.figure(figsize=(8, 6)) plt.scatter(X_pca[:, 0], X_pca[:, 1], c=labels, cmap='viridis', s=50) plt.scatter(centers[:, 0], centers[:, 1], c='red', marker='x', s=200) plt.xlabel('PC1') plt.ylabel('PC2') plt.title('K-Means Clustering Visualization') plt.show()这套流程跑通之后,建议把数据集换掉,换成一个有噪声的聚类数据集或者一个不规则形状的数据集,再看K-Means的局限性,效果比看十遍教科书都直观。
3.3 手写K-Means:理解迭代细节的最好方式
练习题里有一个让我获益最多的要求:不使用现成库,手写一个K-Means。这一步虽然麻烦,但能帮你彻底理解迭代内部发生了什么。
我的实现思路整理如下:
import numpy as np def my_kmeans(X, k, max_iter=100, tol=1e-4, random_seed=42): rng = np.random.RandomState(random_seed) # 随机初始化中心:从样本中选k个 idx = rng.choice(X.shape[0], k, replace=False) centers = X[idx].copy() for i in range(max_iter): # 分配步骤 distances = np.linalg.norm(X[:, None, :] - centers[None, :, :], axis=2) labels = np.argmin(distances, axis=1) # 更新步骤 new_centers = np.array([X[labels == j].mean(axis=0) for j in range(k)]) # 空簇检查 empty_cluster = [j for j in range(k) if len(X[labels == j]) == 0] for j in empty_cluster: new_centers[j] = X[rng.randint(X.shape[0])] # 收敛判断 if np.linalg.norm(new_centers - centers) < tol: break centers = new_centers return labels, centers手写一遍之后,那些卡住过你的问题全都清楚了:为什么n_init要设置多次初始化取最优?因为随机初始中心可能导致不同结果。为什么空簇要重新初始化?因为如果不处理,后续所有中心都会错位。
3.4 进阶对比:DBSCAN和层次聚类的加入
做完K-Means这道主菜,练习题通常会附带对比环节。我建议把DBSCAN和层次聚类也拉出来跑一遍,形成完整的聚类工具箱。
DBSCAN有两个关键参数:eps(邻域半径)和min_samples(核心点最少邻居数)。它最大的优势是能自动发现簇的数量,同时把离群点标为噪声。缺点是对eps敏感,需要结合数据分布反复调试。
from sklearn.cluster import DBSCAN db = DBSCAN(eps=0.5, min_samples=5) db_labels = db.fit_predict(X_scaled)层次聚类则适合展示"聚类过程的演化":
from sklearn.cluster import AgglomerativeClustering agg = AgglomerativeClustering(n_clusters=3, linkage='ward') agg_labels = agg.fit_predict(X_scaled)两种算法的结果都可以用同样的散点图代码可视化。建议把三种算法的轮廓系数放到一个表格里对比,直观感受不同算法的适用性。
| 算法 | 轮廓系数(鸢尾花) | 优点 | 缺点 |
|---|---|---|---|
| K-Means | 0.55 | 简单、快速 | 需指定K,对形状敏感 |
| DBSCAN | 0.52 | 无需指定K,能识别噪声 | 参数调试难,密度不均时效果差 |
| 层次聚类 | 0.55 | 可输出层次树 | 计算量大,不适合大数据集 |
这里要强调一点:数值接近不代表算法等价,每个算法的使用场景差异才是重点。练习题的最终目标是理解这些差异,不在跑出最高分数。
4. 常见问题与排查技巧实录
4.1 轮廓系数为负的排查思路
轮廓系数范围是-1到1,如果出现负值,意味着某些样本被分到了错误的簇——它到相邻簇的平均距离反而小于到自己簇的平均距离。
排查步骤,我按经验排序:
- 检查特征尺度:确认是否做了标准化,特征单位是否统一;
- 检查K值:尝试K=2到K=8逐个验证,排除K选择不当;
- 检查数据分布:画出散点图观察,如果数据本身没有明显簇结构,负值就是正常现象;
- 检查算法与数据匹配度:数据呈环形或流形结构时,K-Means天然表现差,换DBSCAN试试。
4.2 聚类结果和真实标签对不上,是不是算法错了
练习数据集通常带真实标签,比如鸢尾花有3个品种。新手经常发现聚类结果和真实标签不完全一致,就开始怀疑算法写错了。
其实聚类是"无监督"方法,它找的是数据分布形态上的簇,跟真实语义标签本来就没有必然关联。同一个数据,用K-Means分出来的边界可能跟物种的生物学边界不完全重合,这是正常现象。
判断聚类好不好,优先看轮廓系数这些内部指标,再看聚类结果能否跟业务解释对上,而不是机械地跟标签比较准确率。
4.3 n_init、random_state这些参数到底有什么影响
练习题里经常能看到n_init=10、random_state=42这样的设置,初学者往往直接照抄,不知道含义。
- n_init:指定K-Means独立运行多少次,每次用不同的随机初始中心,最后取轮廓系数或SSE最优的一次。默认值是10,数据量小可以设小些,数据量大可以设大些;
- random_state:随机种子,固定后每次运行结果一致,保证实验结果可复现。做练习题报告或面试演示时一定要固定,否则每次跑出来的图都不一样。
我见过不少同学因为没固定random_state,导致报告里贴的图和现场演示的图不一样,场面相当尴尬。这个小细节,正式场合务必注意。
4.4 直方图辅助判断:怎么快速观察簇分布
散点图是最常用的可视化方式,但特征维度高于二维时就只能靠PCA降维。在做练习题时,我习惯在降维可视化之外,额外画每个特征的分布直方图,用于辅助观察簇的可分性。
df = pd.DataFrame(X_scaled, columns=data.feature_names) df['cluster'] = labels df.hist(column=data.feature_names[0], by='cluster', figsize=(10, 4), bins=20) plt.show()如果两个簇在某个特征上的直方图严重重叠,说明这个特征对区分它们贡献不大;如果分布明显分开,则说明这个特征信息量大。这个技巧在写分析报告时非常加分,能体现你对数据的理解。
5. 结果评估与多维指标体系
5.1 内部评估指标:轮廓系数、CH指数与DB指数
聚类是"无监督"问题,没有真实的y标签可以用来计算准确率,所以只能用"内部指标"评估。
轮廓系数前期已经介绍过,这里补充另外两个:
- Calinski-Harabasz指数(CH):衡量簇间方差与簇内方差的比值。数值越大,说明簇间分离越明显、簇内越紧凑。它的计算速度快,适合大数据集快速评估;
- Davies-Bouldin指数(DB):计算每个簇与最相似簇之间的平均相似度再取平均。数值越小,说明簇与簇之间区分度越高。
这三个指标不是总是一致。有的K值下轮廓系数高,CH指数却一般,这种情况不奇怪,因为它们的计算逻辑和偏重不同。我的建议是:练习题以轮廓系数为主,CH指数和DB指数作为辅助验证。实际项目里,多指标交叉验证比单看一个指标靠谱得多。
5.2 外部评估指标:为什么练习题里也会出现ARI
部分进阶练习会引入调整兰德指数(Adjusted Rand Index, ARI),用来比较聚类结果与真实标签的一致性。ARI取值范围为-1到1,1表示完全一致,0表示随机。
注意,ARI在真实无监督场景里是用不上的,因为根本没有标签。但练习题里设置它有两个目的:
- 让你客观评价算法在不同数据分布下的表现差距;
- 让你理解"有标签评价"和"无标签评价"的区别。
如果练习数据集有真实标签,我会额外报告ARI值,这样能更直观看出K-Means在该数据集上的还原度。但这不代表聚类算法好坏只能靠它评判。
6. 避坑经验与实战建议
6.1 一套可直接套用的答题框架
做聚类练习题,我给自己总结了一套固定答题框架,效率高、踩坑少,分享出来供参考:
- 数据层面:说明数据规模、特征数、缺失值情况、是否需要标准化、是否存在异常点;
- 算法选择:说明为什么选K-Means,或者为什么换用DBSCAN/层次聚类;
- 参数调试:展示K值选择过程(肘部法则+轮廓系数)、n_init设置、random_state固定;
- 评估对比:列出一种或多种指标,横向比较不同K值或不同算法的表现;
- 结论落地:把聚类结果和业务场景或数据生成机制结合起来解释。
这个框架在写练习报告、面试做项目介绍时都通用。我建议直接记住这个结构,而不是背某个具体案例。
6.2 初学者最容易犯的五个错误
我把带新手做练习时重复遇到的错误总结成五条,每一条都是在实战里真实踩过的坑:
- 不做标准化就聚类,结果让量纲大的特征主导一切;
- 只看散点图就说效果好,不计算任何量化指标;
- K值选择只依赖肘部法则,不结合轮廓系数验证;
- 忽略random_state,导致结果不可复现;
- 数据有异常值时照常用K-Means,不去考虑DBSCAN或先做异常点处理。
这五条里,第一条和第四条最常见。每次做练习前我都会自检一遍,对付面试官追问也特别有效。
6.3 如何把练习中的方法迁移到实际项目
解完这套练习题之后,很多人会问:这东西在真实业务场景里真能用吗?能用,但要注意迁移方式。
以用户分群为例,实际项目里的数据往往不是干净的低维表格。常见问题包括:高维稀疏特征、类别型特征混杂、样本量巨大、业务标签缺失。迁移建议如下:
- 特征工程先行:数值特征标准化,类别特征用One-Hot或相似度计算,必要时先做PCA降维;
- 算法选择灵活:数据量大选K-Means或MiniBatchKMeans,形状不规则选DBSCAN,需要可解释性选层次聚类;
- 聚类结果必须结合业务校验:每一簇放到业务侧看是否有明确含义,无意义就调整预处理或换算法;
- 迭代闭环:聚类结果应用于推荐策略或运营动作后,用业务指标(转化率、留存率等)反哺聚类参数调整。
这套练习题的题目本身是基础题,但背后的迁移能力才是真正值钱的部分。
最后再分享一个小习惯:我每次做完聚类练习,都会把三种算法的结果并排画出来,贴在笔记里对照。时间久了,哪个数据集适合哪类算法,基本一眼就能判断出来。这个能力靠刷题刷不出来,只能靠反复比对积累。