【老计带你懂AI算法】07:聚类,没有标准答案时,让机器自己把数据分堆
开头:从有答案到没答案
前面五篇讲的模型,有个共同的前提,你得先给数据打好标签。这是垃圾邮件那不是、这套房卖了多少钱、这个肿瘤是良性还是恶性。机器是照着你给的标准答案学的,这叫监督学习。
可现实里,大量数据根本没有标签。你有一百万个用户的行为数据,没人事先告诉你谁是谁一类;你有一堆商品,也没人标好哪些该归一起。这时候你想让机器帮你自动把相似的东西归到一堆,怎么办?
这就是无监督学习要干的事,其中最典型、最常用的就是聚类。聚类的目标很朴素:在没有标准答案的情况下,让机器根据数据本身的相似程度,自动把它们分成若干堆(术语叫簇)。同一堆里的尽量像,不同堆之间尽量不像。
打个比方:你走进一个陌生的大聚会,没人给你介绍谁是谁。但你观察一会儿就会发现,那边一群人聊投资、这边一群人聊带娃、角落几个人聊游戏,你自然而然就把人分成了几堆。你靠的不是谁贴了标签,而是他们表现出来的相似性。聚类就是让机器干这件事。
K-Means:找几个中心点,让数据抱团
最经典、最常用的聚类算法叫K-Means(K均值),思路特别直观。
它的核心想法是:假设数据能分成K堆,那每一堆应该都有一个中心,堆里的点都围着自己的中心抱团。于是它要做的,就是找到这K个最合适的中心位置,让每个点都离它所属的那个中心尽可能近。
它怎么找?是个特别巧妙的反复迭代过程,我用大白话描述:
- 第一步,随机撒K个中心点(比如你想分3堆,就先随便撒3个点当临时中心)。
- 第二步,每个数据点,看自己离哪个中心最近,就先归到那一堆。
- 第三步,每一堆归好后,重新计算这堆的真正中心(把堆里所有点的位置平均一下,得到新中心,这就是"均值"的由来)。
- 第四步,中心挪动了,那每个点离哪个中心最近可能就变了,回到第二步重新归堆,再算新中心……
这个"归堆、算中心、再归堆、再算中心"的循环反复进行,直到中心不再明显移动、分堆稳定下来,聚类就完成了。你可以想象成一群人围着几个临时召集人站队,召集人根据身边站了谁不断挪到人群正中间,人们又根据召集人的新位置重新站队,来回几轮,就自然形成了几个稳定的圈子。
K-Means的软肋:K要你自己定
K-Means有个绕不开的问题,那个K(分几堆),得你自己事先指定。可现实里你往往并不知道数据该分几堆,这就尴尬了。
有个常用的办法帮你挑K,叫手肘法。思路是:把K从小到大试一遍(分2堆、3堆、4堆……),每次算一下"堆内的点离中心有多紧凑"这个指标。K越大,堆分得越细、点离中心自然越近,这个指标一路下降。但你会发现,降到某个K之后,再增加堆数,紧凑度的提升就不明显了,曲线出现一个像手肘一样的拐点。那个拐点对应的K,通常就是比较合适的分堆数。它背后的直觉是,拐点之前每多分一堆都带来明显收益,拐点之后收益骤减,说明再细分意义不大了。
除了K要指定,K-Means还有几个脾气得知道:它假设每堆大致是圆形、大小差不多的,遇到形状怪异(比如弯月形)的数据堆就会分错;它对初始中心的随机位置敏感,撒得不好可能收敛到不太好的结果(实践中会多撒几次取最好的,sklearn默认就这么做);它还对离群点敏感,一个极端的outlier能把中心拽偏。
DBSCAN:按密度圈人群,还能揪出离群点
针对K-Means的软肋,另一个经典算法DBSCAN换了个完全不同的思路,它不找中心点,而是看密度。
DBSCAN的想法很符合直觉:一堆数据,如果某个区域点挤得密密麻麻,那这片就是一个簇;点和点之间稀稀拉拉的地方,就是簇的边界;而那些孤零零、周围没几个邻居的点,就是噪声(离群点)。它顺着密集的区域一点点蔓延,把连成一片的稠密点圈成一个簇。
这带来几个K-Means没有的好处:
- 不用事先指定分几堆,它自己根据密度算出来有几个簇。
- 能发现任意形状的簇(弯月形、环形都行),因为它是顺着密度蔓延的,不假设是圆的。
- 天生能识别离群点,那些不属于任何稠密区域的点,会被直接标记为噪声,这在异常检测里很有用(下一篇孤立森林会专门讲异常检测)。
当然它也有自己的脾气:它靠两个参数控制"多密才算密"(一个是邻域半径,一个是成簇的最少点数),这俩参数得调;而且当数据里不同簇的疏密程度差异很大时,用一套统一的密度标准就不好使了。
还有一类:层次聚类,像画家谱
除了K-Means和DBSCAN,还有一类值得知道的思路,叫层次聚类,它的画风又不一样,像在给数据画一棵家谱树。
它有两种走法。一种是自底向上:一开始把每个点都当成一个独立的小簇,然后每一步把最相近的两个簇合并成一个,就像亲戚关系里先合并最亲的,再一层层往上合,最后所有点合成一大家子。另一种是自顶向下,反过来,先把所有点当一大堆,再逐步往下拆分。
层次聚类最迷人的产出,是一棵叫树状图的东西,它记录了"谁先和谁合并、在多相似的程度上合并"的完整过程。好处是你不用像K-Means那样事先定死分几堆,而是可以事后看着这棵树,在你想要的相似程度上横切一刀,切出几堆就是几堆,非常灵活。打个比方,这就像看家谱,你想按"直系亲属"分就切浅一点、想按"整个家族"分就切深一点,一棵树满足不同粒度的需求。
它的代价是计算量大,数据一多就慢,所以更适合中小规模、且你想看清数据层层嵌套结构的场景(比如生物学里给物种分类,天生就是层层嵌套的)。记住聚类不止一种玩法,K-Means求快、DBSCAN看密度识异形、层次聚类给你一棵可任意切分的关系树,各有各的用武之地。
输入和输出长什么样
- 输入:一批没有标签的样本,每个样本若干数值特征。因为聚类基本都靠算距离,所以和上一篇的KNN、SVM一样,特征通常要先标准化。
- 输出:每个样本被分到的簇编号(0号堆、1号堆……)。DBSCAN还会把离群点单独标记出来(通常标为-1)。注意这些编号只是分组标识,没有大小和好坏含义,聚类只告诉你"谁和谁一伙",至于每伙代表什么,要你自己去解读。
上代码:K-Means和DBSCAN对比
用sklearn,在同一份数据上跑两种聚类。输入:二维坐标点。输出:每个点的簇编号。
# 依赖:pip install scikit-learnfromsklearn.datasetsimportmake_moonsfromsklearn.clusterimportKMeans,DBSCANfromsklearn.preprocessingimportStandardScalerimportnumpyasnp# 造一份"两个弯月形"的数据,专门难为假设圆形的K-MeansX,_=make_moons(n_samples=300,noise=0.06,random_state=0)X=StandardScaler().fit_transform(X)# 聚类前先标准化# K-Means:硬指定分2堆km=KMeans(n_clusters=2,n_init=10,random_state=0)km_labels=km.fit_predict(X)print("K-Means 分出的簇:",np.unique(km_labels))# DBSCAN:按密度自动成簇,还能标离群点db=DBSCAN(eps=0.3,min_samples=5)db_labels=db.fit_predict(X)print("DBSCAN 分出的簇(含-1噪声):",np.unique(db_labels))print("DBSCAN 识别出的离群点数量:",int(np.sum(db_labels==-1)))# 简单看一下两者对弯月形的处理差异(不画图,看每个簇的样本数)forname,labelsin[("K-Means",km_labels),("DBSCAN",db_labels)]:vals,counts=np.unique(labels,return_counts=True)print(f"{name}各簇样本数:",dict(zip(vals.tolist(),counts.tolist())))运行输出(示例):
K-Means 分出的簇: [0 1] DBSCAN 分出的簇(含-1噪声): [-1 0 1] DBSCAN 识别出的离群点数量: 4 K-Means 各簇样本数: {0: 150, 1: 150} DBSCAN 各簇样本数: {-1: 4, 0: 148, 1: 148}运行你会体会到差异:面对弯月形数据,K-Means因为假设圆形,往往会把两个月牙从中间硬切开、分得不自然;而DBSCAN顺着密度蔓延,能漂亮地把两个弯月各自圈成一簇,还顺手标出零星的噪声点。这直观展示了两个算法适用的数据形状不同。
关键参数
- K-Means的n_clusters:分几堆,最关键、要你定,可用手肘法辅助。
- K-Means的n_init:多撒几次初始中心取最好,缓解对初始值敏感的问题。
- DBSCAN的eps(邻域半径)和min_samples(成簇最少点数):这两个共同定义"多密才算一簇",是DBSCAN调参的核心。
优缺点与适用场景
K-Means:简单、快、易懂,适合数据量大、各簇大致圆形且大小相近的场景(如用户分群、图像颜色量化)。软肋是要指定K、只认圆形、怕离群点。
DBSCAN:不用指定簇数、能识别任意形状和离群点,适合形状不规则的数据和需要顺带做异常检测的场景。软肋是参数要调、对疏密差异大的数据不友好。
聚类整体适合:探索性分析(先看看数据能自然分成几类)、用户或商品分群、异常检测、给数据打初步标签。不适合:你其实已经有明确标签、该用监督学习的场景,那样用聚类是舍近求远。
这里还得点破一个新手常纠结的问题:聚类的结果,到底怎么判断好不好?监督学习有标准答案,对了几个一目了然;聚类没有答案,怎么评?有两个角度。一是看内部指标,比如轮廓系数,衡量"同一堆内部够不够紧凑、不同堆之间够不够分得开",值越高说明分得越利落,这不需要标签。二是看业务解不解释得通,这往往更重要,机器把用户分成了五群,你得去看每群的实际特征,是不是真对应了"高价值活跃用户"“沉睡用户"这种有业务意义的群体。聚类给出的分组只是数学上的相似,最终有没有价值,要靠人结合业务去解读和验证,这一步机器替代不了。记住这点,你用聚类时就不会盲目相信机器分出的堆,而会多问一句"这么分,业务上讲得通吗”。
小结与承上启下
- 聚类:无监督学习,没标准答案时让机器按相似度自动把数据分堆。
- K-Means:找K个中心让数据抱团,反复归堆算中心,要指定K、认圆形。
- DBSCAN:按密度圈簇,不用指定簇数、能识别任意形状和离群点。
- 共同点:靠距离吃饭,要先标准化;输出只是分组编号,含义靠人解读。
聚类里DBSCAN已经露了一手识别离群点的本事。可专门用来抓异常、抓那些"和大家都不一样"的点,还有更专门更强的模型。下一篇我们讲一个异常检测的利器,孤立森林,看它怎么用一个反常识的思路快速揪出异常。
我们下一篇见。
延伸阅读
- scikit-learn 官方文档:聚类(含K-Means、DBSCAN、层次聚类):https://scikit-learn.org/stable/modules/clustering.html
(说明:以上为官方公开文档地址,可能随版本调整,如打不开可用标题搜索。)