机器学习从入门到精通 - 聚类算法大比拼:K-Means、DBSCAN实战与评估陷阱

开场白:推开无监督学习的大门

朋友们,不知道你们有没有对着堆积如山、没有标签的数据发过愁?想从里面找出点规律,分组什么的,结果发现根本无处下手 —— 这就是聚类算法大显身手的时候了!它属于无监督学习的核心领域,目标就是:在不知道正确答案的前提下,把相似的数据点自动归到一堆儿里。听起来像魔法对吧?今天咱们就深入两个明星选手:K-Means 和 DBSCAN。我会手把手带你们实战,更重要的是 —— 把那些评估环节里暗藏玄机的陷阱,一个个揪出来。相信我,踩过这些坑,你对聚类的理解绝对能上一个大台阶。准备好笔记本,咱们出发!


一、 为什么要聚类?数据的内在秩序

想象一下,你有一大堆顾客的购买记录(没有分类标签)。你想搞促销,但不可能给每个人都推一样的商品吧?聚类帮你把顾客分成不同群体:可能是“母婴用品爱好者”、“数码发烧友”、“周末大采购党”。没有聚类,你就是大海捞针;有了它,你就能精准投放资源。

或者,天文望远镜拍下海量星空图像,聚类能自动识别出哪些光点属于同一个星团。聚类的核心价值,就是揭示数据内部隐藏的结构和模式,这是探索性数据分析(EDA)和许多后续任务的基石。多说一句,很多特征工程也依赖它。


二、 K-Means:经典的中心引力战法

原理核心:物以类聚,人以群分。 K-Means 的想法特别直观:

  1. 先拍脑袋(或用肘部法则)决定要分成 K 个组(簇)。
  2. 随机扔 K 个点作为簇中心(Centroid)。
  3. 让每个数据点都奔向离它最近的中心点,形成初始分组。
  4. 重新计算每个簇的中心点(取所有点的平均值)。
  5. 重复步骤3和4,直到中心点不怎么动了(收敛)。

数学本质:最小化平方误差

K-Means 的目标函数是最小化簇内平方和(Within-Cluster Sum of Squares, WCSS)

J=∑i=1K∑x∈Ci∣∣x−μi∣∣2J = \sum_{i=1}^{K} \sum_{\mathbf{x} \in C_i} ||\mathbf{x} - \mathbf{\mu}_i||^2J=i=1KxCi∣∣xμi2

  • K: 我们指定的簇数量。
  • C_i: 第 i 个簇包含的所有数据点的集合。
  • \mathbf{x}: 数据点向量。
  • \mathbf{\mu}_i: 第 i 个簇的中心点(质心)向量。
  • ||\mathbf{x} - \mathbf{\mu}_i||^2: 数据点 \mathbf{x} 到其所在簇中心 \mathbf{\mu}_i 的欧几里得距离的平方。

目标函数推导(为啥最小化 WCSS 有效?)

  1. 定义距离: 我们使用欧氏距离衡量点到中心的差异:d(\mathbf{x}, \mathbf{\mu}_i) = ||\mathbf{x} - \mathbf{\mu}_i|| = \sqrt{(x_1 - \mu_{i1})^2 + ... + (x_n - \mu_{in})^2}。平方是为了方便计算(可导、凸性更优)并放大较大误差的影响。
  2. 簇内求和: \sum_{\mathbf{x} \in C_i} ||\mathbf{x} - \mathbf{\mu}_i||^2 计算了簇 C_i 中所有点到其中心的距离平方和。这个值越小,说明这个簇的点越紧密围绕在中心周围,簇内相似度越高。
  3. 全局求和: \sum_{i=1}^{K} 把 K 个簇的 WCSS 都加起来,得到全局损失 J
  4. 优化目标: K-Means 的迭代过程(重新分配点 + 重新计算中心)就是在不断尝试降低 J。当中心点不再显著变化(即 J 的下降小于某个阈值)时,算法认为找到了一个(局部)最优解,让所有簇都尽可能“紧凑”。

流程可视化 (Mermaid)

Yes
No
Start: Choose K
Randomly Initialize K Centroids
Assign Points to Nearest Centroid
Update Centroids: Mean of Points
Converged?
Stop

Python 实战 & 踩坑记录

import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_blobs
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score# 1. 生成模拟数据 - 3个清晰簇
X, y_true = make_blobs(n_samples=300, centers=3, cluster_std=0.60, random_state=0)
plt.scatter(X[:, 0], X[:, 1], s=50)
plt.title('Raw Data (Ground Truth Clusters Visible)')
plt.show()# 2. 应用 K-Means - 指定 K=3
kmeans = KMeans(n_clusters=3, init='k-means++', n_init=10, random_state=42)
kmeans.fit(X)
y_kmeans = kmeans.labels_# 3. 可视化结果
plt.scatter(X[:, 0], X[:, 1], c=y_kmeans, s=50, cmap='viridis')
centers = kmeans.cluster_centers_
plt.scatter(centers[:, 0], centers[:, 1], c='red', s=200, alpha=0.75, marker='X')
plt.title('K-Means Clustering (K=3)')
plt.show()# 4. 评估 - 轮廓系数 (越大越好,范围[-1, 1])
silhouette_avg = silhouette_score(X, y_kmeans)
print(f"Silhouette Score for K=3: {silhouette_avg:.4f}")# 5. 经典大坑:K 值选错!
# 尝试 K=2 和 K=4
for k in [2, 4]:kmeans_wrong = KMeans(n_clusters=k, random_state=42).fit(X)y_wrong = kmeans_wrong.labels_plt.scatter(X[:, 0], X[:, 1], c=y_wrong, s=50, cmap='viridis')plt.title(f'K-Means with K={k} (Probably Wrong)')plt.show()wrong_score = silhouette_score(X, y_wrong)print(f"Silhouette Score for K={k}: {wrong_score:.4f}") # 注意看分数变化!

K-Means 关键踩坑点:

  1. K 值选择: 这是最大的坑!你拍脑袋定的 K 可能完全错误。肉眼看着数据猜?不靠谱!用“肘部法则”(Elbow Method)看 WCSS 下降拐点?有时拐点模糊不清。轮廓系数(Silhouette Score)?也不总是可靠(后面评估陷阱会细说)。实践建议:结合业务理解 + 多种方法尝试 + 可视化验证
  2. 初始中心点敏感: 随机初始化可能导致:
    • 收敛到局部最优解(效果差)。
    • 每次运行结果不同(缺乏稳定性)。
    • 规避策略:使用 k-means++ 初始化(sklearn 默认),它能更聪明地选择初始中心点,显著降低随机性影响。设置 n_init > 1(比如 10)让算法多次运行选最好结果。
  3. 球形簇假设: K-Means 基于距离(尤其是欧氏距离),它天然偏好发现凸的、球形分布、大小相似的簇。遇到长条形、环形、嵌套形、密度差异大的数据,它就抓瞎了!看图最直观:
# 生成非球形数据 (月牙形)
from sklearn.datasets import make_moons
X_moons, _ = make_moons(n_samples=200, noise=0.05, random_state=0)kmeans_moons = KMeans(n_clusters=2, random_state=42).fit(X_moons)
y_kmeans_moons = kmeans_moons.labels_plt.scatter(X_moons[:, 0], X_moons[:, 1], c=y_kmeans_moons, s=50, cmap='viridis')
plt.title('K-Means Fails on Moons Data')
plt.show()
  1. 噪声点影响: 一个离群点(Noise)能显著把中心点“拉歪”。K-Means 没有显式的噪声处理机制。

三、 DBSCAN:基于密度的空间探索者

当数据奇形怪状、有噪音、簇密度还不一样时,K-Means 就力不从心了。DBSCAN(Density-Based Spatial Clustering of Applications with Noise)登场!它的想法很酷:簇是数据空间中密集的区域,被低密度区域分割开。

核心概念:

  • ε (eps): 邻域的半径。想象以每个点为中心画个圆圈。
  • MinPts: 定义一个点为核心点(Core Point)所需的最小邻居数(包括自己)。
  • 核心点 (Core Point): 在自身 ε-邻域内包含至少 MinPts 个点(包括自身)。
  • 边界点 (Border Point): 自身邻居数小于 MinPts,但落在某个核心点的 ε-邻域内。
  • 噪声点 (Noise Point): 既不是核心点也不是边界点的点。
  • 直接密度可达 (Directly Density-Reachable):q 在核心点 p 的 ε-邻域内。
  • 密度可达 (Density-Reachable): 存在一串点 p1, p2, ..., pn,其中 p1 = p, pn = q,且每个 pi+1 都直接密度可达于 pi(所有 pi 都是核心点)。
  • 密度相连 (Density-Connected): 存在一个点 o,使得点 pq 都密度可达于 o

原理流程:

  1. 随机选一个未访问点 p
  2. 检查 p 的 ε-邻域:
    • 如果邻居数 >= MinPtsp 是核心点,创建一个新簇 C,把 p 和它邻域内所有点(包括边界点)都加入 C
    • 如果邻居数 < MinPts暂时标记 p 为噪声(后续可能被其他核心点“拯救”成边界点)。
  3. 对于刚加入 C每一个未访问的核心点 q,递归地访问它的 ε-邻域,把邻域内所有点也加入 C(这是实现“密度可达”的关键)。
  4. 重复 1-3 直到所有点都被访问。
  5. 最终,所有被标记为噪声的点就是离群点。

流程可视化 (Mermaid)

Yes
Yes
No
No
Yes
Start
Pick Unvisited Point p
Neighbors of p >= MinPts?
Mark p as Core. Create Cluster C
Add p's Neighbors to C
For each new Core q in C, visit q's neighborhood Recursively
Add q's Neighbors to C
All points in C processed?
Mark all in C as Visited
Mark p as Noise?*
All points visited?
Reclassify Noise: Border if in some Core's Neighbor, else True Noise
End

Python 实战 & 踩坑记录

from sklearn.cluster import DBSCAN
from sklearn.preprocessing import StandardScaler # 重要!# 1. 用之前的月牙形数据
plt.scatter(X_moons[:, 0], X_moons[:, 1], s=50)
plt.title('Moons Data - Ground Truth')
plt.show()# 2. DBSCAN 大展身手
# 参数调优是关键坑! eps=0.3, min_samples=5 是常用起点
dbscan = DBSCAN(eps=0.3, min_samples=5)
y_dbscan = dbscan.fit_predict(X_moons)# 3. 可视化 - DBSCAN 完美分离月牙
plt.scatter(X_moons[:, 0], X_moons[:, 1], c=y_dbscan, s=50, cmap='viridis')
plt.title('DBSCAN Clustering on Moons')
plt.show()# 4. 处理噪声点 (看看标签 y_dbscan,-1 代表噪声)
print(f"Number of clusters found: {len(set(y_dbscan)) - (1 if -1 in y_dbscan else 0)}")
print(f"Number of noise points: {list(y_dbscan).count(-1)}")# 5. 另一个坑:数据尺度差异!
# 生成尺度假数据 (一个维度范围 0-1,另一个 0-100)
X_scale = np.array([[0.1, 10], [0.2, 20], [0.15, 15], [0.9, 90], [0.85, 85], [0.8, 800]]) # 注意最后一个点[0.8, 800] 是噪声# 错误做法:直接扔给 DBSCAN
dbscan_bad = DBSCAN(eps=1, min_samples=2).fit(X_scale)
print("Bad Clustering (without scaling):", dbscan_bad.labels_)# 正确做法:必须标准化!
scaler = StandardScaler()
X_scale_scaled = scaler.fit_transform(X_scale)
dbscan_good = DBSCAN(eps=0.5, min_samples=2).fit(X_scale_scaled) # eps 需要重新调整
print("Good Clustering (with scaling):", dbscan_good.labels_)

DBSCAN 关键踩坑点:

  1. 参数调优 (epsMinPts): 这是 DBSCAN 最棘手的地方。选小了,把什么都当噪声;选大了,所有点挤成一团。没有银弹规则!强烈建议:
    • 用 KNN 距离图:计算每个点到其第 MinPts 个最近邻的距离,排序后画图。找拐点(陡升点)作为 eps 的参考值。MinPts 一般从 2 * 维度 开始试。
    • 可视化辅助判断:在不同参数下可视化结果。
    • 理解数据密度分布。
  2. 数据尺度陷阱: DBSCAN 极度依赖距离!不同特征量纲差异巨大(比如年龄 vs 年薪),必须先标准化(StandardScaler)或归一化(MinMaxScaler),否则数值大的特征完全主导距离计算,小特征被忽略。这是我见过最常犯的错误!
  3. 变密度问题: 如果数据中不同簇的密度差异很大,DBSCAN 可能无法用一个统一的 epsMinPts 同时捕捉到所有簇。可能漏掉低密度簇或把高密度簇切碎。后续的 OPTICS 算法能缓解这个问题。
  4. 高维诅咒: 维度太高时,“距离”概念变得模糊,所有点都显得差不多远,DBSCAN 效果会下降。这时可能需要降维(PCA,t-SNE)或尝试其他算法。

四、 算法大对决:同一数据集,不同表现

用合成数据直观感受两者的适用场景:

# 生成多形状数据:球形 + 环形 + 长条 + 噪声
from sklearn.datasets import make_circles, make_blobs
import matplotlib.pyplot as plt# 球形簇
centers = [[0, 0], [1.5, 1.5]]
X1, y1 = make_blobs(n_samples=100, centers=centers, cluster_std=0.2, random_state=0)# 环形簇
X2, y2 = make_circles(n_samples=100, factor=0.5, noise=0.05, random_state=0)
X2 = X2 * 0.5 + [0.5, 2]  # 平移缩放# 长条形簇 (手动构造)
np.random.seed(0)
angle = np.pi / 4
rot = np.array([[np.cos(angle), -np.sin(angle)], [np.sin(angle), np.cos(angle)]])
X3 = np.dot(np.random.rand(50, 2) * [5, 0.3], rot) + [3, 1]  # 长条# 噪声点
noise = np.random.rand(20, 2) * [4, 3]  # 大致在 [0,4]x[0,3] 范围# 合并数据
X_all = np.vstack([X1, X2, X3, noise])
y_true_all = np.hstack([y1, y2 + 2, np.ones(50) * 4, np.ones(20) * -1])  # 生成真实标签(-1噪声)# 可视化真值
plt.figure(figsize=(12, 4))
plt.subplot(1, 3, 1)
plt.scatter(X_all[:, 0], X_all[:, 1], c=y_true_all, s=50, cmap='viridis')
plt.title('Ground Truth (with Noise)')# K-Means 尝试 (K=4,大致对应球形+环形+长条?)
kmeans_all = KMeans(n_clusters=4, random_state=42).

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/97897.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/97897.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/97897.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

AI 重构内容创作:从文案生成到视频剪辑,创作者该如何与 AI 协同共生?

一、引言&#xff1a;AI 掀起内容创作的 “重构浪潮”​行业现象引入&#xff1a;列举 AI 在内容创作领域的爆发式应用案例&#xff08;如某平台 AI 文案工具日生成量破百万、AI 视频剪辑软件用户增长超 300%&#xff09;​创作者需求变化&#xff1a;通过调研数据说明创作者对…

后端一次性返回十万条数据时,前端需要采用多种性能优化策略来避免页面卡顿

当后端一次性返回十万条数据时&#xff0c;前端需要采用多种性能优化策略来避免页面卡顿。以下是主要的优化方案&#xff1a; 分页加载 - 将数据分批次加载显示虚拟滚动 - 只渲染可视区域内的数据数据懒加载 - 按需加载数据Web Workers - 在后台线程处理数据时间切片 - 分散渲染…

基于-轻量级文档搜索系统的测试报告

文章目录一、项目背景二、项目功能三、测试计划&#xff08;一&#xff09;测试用例设计&#xff08;二&#xff09;测试用例实现1.功能测试2.界面测试3.兼容性测试4.易用性测试5.安全性测试一、项目背景 1.基于轻量级文档检索系统采用C技术栈来实现&#xff0c;同时使用了本地…

编辑器vim(Linux)

Linux下开发工具是独立的写代码——编辑器 vim编译代码——gcc/g调试——gdb、cgdb构建工具——makefile、make、cmakevim只用来写代码注意&#xff1a;直接用vim打开一个不存在的文件并保存退出&#xff0c;就会自动生成该文件vim有多种模式命令模式&#xff08;Normal Mode&a…

GitLab,2025最新如何配置中的SSH key步骤

电脑右键先检查&#xff0c;是否有公钥 git cat ~/.ssh/id_rsa.pub下面是有&#xff0c;不用生成公钥&#xff0c;没有就要生成生成本地电脑公钥, 建议用第二种 //第一种ssh-keygen -t rsa//第二种------- 1.打开git bash,输入&#xff1a;ssh-keygen -t rsa -C “你的邮箱”ss…

华为HCIE证书多久续一次费?费用多少?

根据华为官方政策&#xff0c;华为认证HCIE的有效期为3年&#xff0c;有效期自证书正式发放之日起计算&#xff0c;考生可通过华为人才在线官网登录个人账号&#xff0c;在“我的证书”栏目中查询具体有效期起止时间。一、HCIE证书到期后的续证方式 1.重考对应HCIE的认证考试&a…

提升文本到图像强化学习稳定性:Pref - GRPO算法如何革新图像生成?

提升文本到图像强化学习稳定性&#xff1a;Pref - GRPO算法如何革新图像生成&#xff1f; 在文本到图像生成领域&#xff0c;强化学习正重塑着模型与人类偏好的对齐方式。本文聚焦于一种创新的基于成对偏好奖励的GRPO方法&#xff08;Pref - GRPO&#xff09;&#xff0c;它通…

Linux UDisks守护进程曝本地提权漏洞CVE-2025-8067,PoC已发布

漏洞概述安全研究人员在Linux环境中广泛使用的磁盘管理组件UDisks守护进程中&#xff0c;发现了一个严重漏洞&#xff08;编号CVE-2025-8067&#xff0c;CVSS评分8.5&#xff09;。该漏洞已报告给红帽产品安全团队&#xff0c;并在UDisks更新版本中得到修复。技术细节该漏洞存在…

uniapp 开发上架 iOS App全流程

操作文档网址&#xff1a;https://ask.dcloud.net.cn/article/152 操作学习视频地址&#xff1a;uniapp打包上线微信小程序、安卓、IOS流程_哔哩哔哩_bilibili 第一步&#xff1a;注册苹果 iOS 个人开发者账号 费用说明 ‌个人开发者账号‌&#xff1a;适用于独立开发者或小…

Sqlsugar补充自定义模板

DBFirst默认创建所有实体CreateClassFile()的第二个参数为生成实体类命名空间//.net6以下 db.DbFirst.IsCreateAttribute().CreateClassFile("c:\\Demo\\1", "Models"); //.net6以上 string加? db.DbFirst.IsCreateAttribute().StringNullable().CreateCl…

LeetCode 392.判断子序列

给定字符串 s 和 t &#xff0c;判断 s 是否为 t 的子序列。 字符串的一个子序列是原始字符串删除一些&#xff08;也可以不删除&#xff09;字符而不改变剩余字符相对位置形成的新字符串。&#xff08;例如&#xff0c;"ace"是"abcde"的一个子序列&#x…

逻辑回归:从原理到实战的完整指南

在机器学习中&#xff0c;分类任务是最常见的应用场景之一。而逻辑回归&#xff08;Logistic Regression&#xff09;&#xff0c;尽管名字中有“回归”&#xff0c;实际上是一种非常强大且广泛应用的二分类模型。它简单、高效、可解释性强&#xff0c;是数据科学初学者入门分类…

鸿蒙搭配前端开发:应用端与WEB端交互

鸿蒙系统&#xff08;HarmonyOS&#xff09;是华为开发的一款面向全场景的分布式操作系统&#xff0c;其设计初衷是为了适应物联网时代的需求&#xff0c;旨在构建一个统一的操作系统&#xff0c;支持多种设备的无缝协同工作。其分布式开发的一些主要优势&#xff1a; 跨设备协…

配置sscms时被sql server处处刁难

今天要记下来的是一个小例子。接前面&#xff0c;当我终于完成sql server的安装时&#xff0c;才发现要填写sscms的两个空是有多么艰难。首先安装sql server2016出现了太多环境不兼容的问题&#xff0c;让我只好退而安装sql server2012。安装sql server2012时其实是可以避坑的&…

【Flink】DataStream API:源算子、数据类型

目录源算子&#xff08;Source&#xff09;从集合中读取数据从文件读取数据从Socket读取数据从Kafka读取数据从数据生成器读取数据Flink支持的数据类型Flink的类型系统Flink支持的数据类型类型提示&#xff08;Type Hints&#xff09;源算子&#xff08;Source&#xff09; Fli…

Linux 安装docker-compose安装方法(安装docker compose安装)

文章目录**方法一&#xff1a;通过 curl 下载二进制文件&#xff08;推荐&#xff09;**1. 安装前准备- **确保已安装 Docker**- **检查 Docker 是否安装成功**2. 下载并安装 Docker Compose- **下载最新版本的 Docker Compose 二进制文件**- **国内加速下载&#xff08;解决 G…

OCR 发票识别与验真接口:助力电子化发票新时代

自 2025 年 10 月 1 日起&#xff0c;纸质火车票彻底告别历史舞台&#xff0c;全面数字化的电子发票取而代之&#xff0c;这一变革标志着票务领域的重大革新&#xff0c;也让电子化发票处理的需求呈井喷式增长。在此背景下&#xff0c;OCR 发票识别和发票验真接口技术挺身而出&…

设计模式:抽象工厂模式(Abstract Factory Pattern)

文章目录一、概念二、实例分析三、完整示例一、概念 抽象工厂模式是一种创建型设计模式。 提供一个接口用于创建一系列相关或相互依赖的对象&#xff0c;而无需指定它们的具体类。 相比于工厂方法模式&#xff0c;抽象工厂模式不仅仅是创建单一产品&#xff0c;而是一族产品&am…

轻量级注意力模型HOTSPOT-YOLO:无人机光伏热异常检测新SOTA,mAP高达90.8%

【导读】 无人机光伏巡检如何更智能、更高效&#xff1f;HOTSPOT-YOLO模型给出了亮眼答案&#xff01;给AI装上“热成像鹰眼”&#xff0c;能精准锁定光伏板上的细微热斑缺陷。它不仅将检测精度&#xff08;mAP&#xff09;提升至90.8%&#xff0c;更在保持实时性的前提下大幅…

CHT共轭传热: 导热系数差异如何影响矩阵系数

文章目录 一、导热系数差异如何影响矩阵系数&#xff1f;二、如何处理系数差异以加速收敛&#xff1f;1. **变量重缩放&#xff08;Scaling of Variables&#xff09;**2. **使用物理型预条件子&#xff08;Physics-based Preconditioning&#xff09;**3. **区域分解法&#x…