目录
[第一步:随便选3个"标杆学生" 📍](#第一步:随便选3个“标杆学生” 📍)
[第二步:学生们找"最近标杆" 👥](#第二步:学生们找“最近标杆” 👥)
[第三步:重新选"更好的标杆" 🔄](#第三步:重新选“更好的标杆” 🔄)
[第四步:重复排队 🔁](#第四步:重复排队 🔁)
[第五步:直到没人换组 ✅](#第五步:直到没人换组 ✅)
[用买菜来比喻 🥦](#用买菜来比喻 🥦)
[1. 当你需要"自动分组"时 📊](#1. 当你需要“自动分组”时 📊)
[2. 当你知道要分几组时 🔢](#2. 当你知道要分几组时 🔢)
[3. 当数据量比较大时 ⚡](#3. 当数据量比较大时 ⚡)
[4. 当你的数据是"球形分布"时 ⚽](#4. 当你的数据是“球形分布”时 ⚽)
情景:你带了100个学生去操场,要按身高把他们分成3组做游戏。
k-means人话解释!
第一步:随便选3个"标杆学生" 📍
你随便挑了3个学生(A、B、C),对他们说:
"你们仨站开一点,其他人看自己离谁最近,就站到谁后面去!"
第二步:学生们找"最近标杆" 👥
学生们开始比较:
-
"我离A最近!" → 站A后面
-
"我离B最近!" → 站B后面
-
以此类推...
最后形成了3个人堆。
第三步:重新选"更好的标杆" 🔄
你发现这3个标杆位置可能不合适:
-
A后面全是高个子
-
B后面全是矮个子
-
C后面全是不高不矮的
于是你说:"现在每组里,找个最中间的人当新标杆 !"
(这个"最中间"就是计算这组人的平均身高)
第四步:重复排队 🔁
新标杆选好后,你对全班喊:
"重新排队!再看离哪个新标杆最近,就站谁后面!"
第五步:直到没人换组 ✅
这样反复几次后,大家发现:
"我站在这个组,再怎么换标杆,我还是离这个标杆最近。"
这时候分组就完成了!
用买菜来比喻 🥦
任务:把一堆大小不同的土豆分成3堆
-
随便拿3个土豆放桌上当"代表"
-
每个土豆对比:我离哪个代表最近?
-
形成3堆土豆
-
每堆里重新选个"平均大小"的土豆当新代表
-
重新分堆,再选新代表...
-
直到土豆不再换堆为止
为什么叫k-means?
-
k = 你想分几组(比如3组)
-
means = 每组的"平均值"(中心点)
核心思想简单说:
"物以类聚,人以群分"
通过不断调整"组长"位置,让组员离自己的组长最近。
实际例子:
给客户分组:
-
想分3类客户(高价值、中价值、低价值)
-
随便选3个客户当"代表客户"
-
其他客户根据消费习惯,找最像的代表
-
每类客户里重新选个"典型客户"当新代表
-
重复直到稳定
一句话总结:
"找几个中心点,让大家跟最近的中心点抱团,然后中心点挪到团中央,重新抱团,直到团不再变。"
什么情况下会使用k-means?
1. 当你需要"自动分组"时 📊
常见场景:
-
客户细分:把客户分成几类(高价值、普通、潜在流失)
-
市场分析:找出相似购买习惯的人群
-
图片压缩:把相似颜色归为一类,减少颜色种类
-
文档归类:自动给新闻分主题(体育、财经、娱乐)
比如 :电商平台有100万用户,你不知道他们有什么特点,让k-means帮你自动发现几种典型的用户类型。
2. 当你知道要分几组时 🔢
前提条件:你心里大概有数要分多少类
-
产品分3个档次(低、中、高端)
-
员工按绩效分4档(A、B、C、D)
-
城市按发展水平分5类
注意 :如果你完全不知道该分几组,要先探索(用肘部法则找最佳k值)。
3. 当数据量比较大时 ⚡
k-means计算快,适合大数据:
-
10万条以上的客户数据
-
社交媒体的用户行为数据
-
传感器采集的海量数据
4. 当你的数据是"球形分布"时 ⚽
什么样的数据?
-
各类数据点像"一团一团的"
-
每团数据比较紧密
-
团与团之间分得比较开
不适合的情况:数据像月亮、环形、交叉分布时效果不好。
//随机生成三组坐标
//使用k-means对其进行聚类
import numpy as np
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans
from sklearn.datasets import make_blobs
# 设置中文字体(可选)
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号
# 1. 随机生成三组坐标数据
np.random.seed(42) # 设置随机种子,保证每次运行结果相同
# 生成300个点,分为3簇
n_samples = 300
n_clusters = 3
# 使用make_blobs生成聚类数据(比纯随机更好展示效果)
# 参数说明:
# n_samples: 总样本数
# centers: 中心点数量(即簇的数量)
# cluster_std: 簇的标准差(控制簇的紧密程度)
# random_state: 随机种子
X, y_true = make_blobs(n_samples=n_samples,
centers=n_clusters,
cluster_std=0.8, # 簇的紧密程度,值越小越紧密
random_state=42)
print(f"生成数据形状: {X.shape}")
print(f"前5个点的坐标:")
for i in range(5):
print(f" 点{i+1}: ({X[i, 0]:.2f}, {X[i, 1]:.2f})")
# 2. 可视化原始数据
plt.figure(figsize=(15, 5))
# 子图1:原始数据(带真实标签)
plt.subplot(1, 3, 1)
plt.scatter(X[:, 0], X[:, 1], c=y_true, cmap='viridis', s=50, alpha=0.7, edgecolors='k')
plt.title('原始数据 (3个自然簇)')
plt.xlabel('X 坐标')
plt.ylabel('Y 坐标')
plt.grid(True, alpha=0.3)
# 3. 应用K-means聚类
print("\n" + "="*50)
print("开始K-means聚类...")
# 创建K-means模型
kmeans = KMeans(n_clusters=3, # 指定分为3簇
init='k-means++', # 智能初始化,避免随机性带来的问题
n_init=10, # 用不同初始中心运行10次,取最好结果
max_iter=300, # 最大迭代次数
random_state=42) # 随机种子
# 训练模型
kmeans.fit(X)
# 获取聚类结果
labels = kmeans.labels_ # 每个点所属的簇标签 (0, 1, 2)
centers = kmeans.cluster_centers_ # 三个簇的中心点坐标
inertia = kmeans.inertia_ # 所有点到其所属簇中心的距离平方和(越小越好)
print(f"聚类完成!")
print(f"簇中心点坐标:")
for i, center in enumerate(centers):
print(f" 簇{i}中心: ({center[0]:.2f}, {center[1]:.2f})")
print(f"总距离平方和 (inertia): {inertia:.2f}")
# 4. 可视化聚类结果
# 子图2:聚类结果
plt.subplot(1, 3, 2)
colors = ['red', 'blue', 'green'] # 为每个簇指定颜色
# 按簇绘制点
for i in range(n_clusters):
cluster_points = X[labels == i]
plt.scatter(cluster_points[:, 0], cluster_points[:, 1],
c=colors[i], label=f'簇 {i}', s=50, alpha=0.7, edgecolors='k')
# 绘制簇中心点
plt.scatter(centers[:, 0], centers[:, 1],
c='black', marker='X', s=200, label='簇中心', linewidths=2)
plt.title('K-means聚类结果')
plt.xlabel('X 坐标')
plt.ylabel('Y 坐标')
plt.legend()
plt.grid(True, alpha=0.3)
# 5. 对比真实标签和预测标签
# 子图3:对比图(真实 vs 预测)
plt.subplot(1, 3, 3)
# 左侧:真实标签
plt.scatter(X[:, 0], X[:, 1], c=y_true, cmap='viridis', s=50, alpha=0.7, edgecolors='k')
plt.title('真实标签 vs 聚类标签\n(左侧: 真实, 右侧: 聚类)')
plt.xlabel('X 坐标')
plt.ylabel('Y 坐标')
# 在右侧添加聚类结果的边界示意
# 计算数据的范围
x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
# 生成网格点
xx, yy = np.meshgrid(np.linspace(x_min, x_max, 100),
np.linspace(y_min, y_max, 100))
# 预测网格点的标签
Z = kmeans.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
# 绘制决策边界
plt.contourf(xx, yy, Z, alpha=0.1, cmap='viridis')
plt.scatter(X[:, 0], X[:, 1], c=labels, cmap='viridis', s=30, alpha=0.7, edgecolors='k')
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
# 6. 打印详细统计信息
print("\n" + "="*50)
print("聚类统计信息:")
# 计算每个簇的大小
unique, counts = np.unique(labels, return_counts=True)
for cluster, count in zip(unique, counts):
cluster_points = X[labels == cluster]
print(f"\n簇 {cluster}:")
print(f" 样本数量: {count} ({count/n_samples*100:.1f}%)")
print(f" 中心点: ({centers[cluster, 0]:.2f}, {centers[cluster, 1]:.2f})")
# 计算簇内点到中心的平均距离
distances = np.linalg.norm(cluster_points - centers[cluster], axis=1)
print(f" 平均距离: {distances.mean():.2f}")
print(f" 最大距离: {distances.max():.2f}")
# 7. 评估聚类质量
print("\n" + "="*50)
print("聚类质量评估:")
from sklearn.metrics import silhouette_score, adjusted_rand_score
# 轮廓系数(-1到1,越大越好)
sil_score = silhouette_score(X, labels)
print(f"轮廓系数 (Silhouette Score): {sil_score:.3f}")
print(" 解释: >0.7(强聚类) 0.5-0.7(合理) <0.2(无意义)")
# 调整兰德指数(与真实标签比较,-1到1,越大越好)
ari_score = adjusted_rand_score(y_true, labels)
print(f"调整兰德指数 (Adjusted Rand Index): {ari_score:.3f}")
print(" 解释: 与真实标签的一致性,1表示完全一致")
# 8. 简单应用:对新点进行预测
print("\n" + "="*50)
print("预测新点属于哪个簇:")
# 创建一些新测试点
test_points = np.array([[0, 0], # 在中间
[4, 4], # 可能在簇1
[-2, 10]]) # 可能在簇2
# 预测这些点属于哪个簇
test_labels = kmeans.predict(test_points)
print("\n新点预测结果:")
for i, (point, label) in enumerate(zip(test_points, test_labels)):
print(f" 点 {point} → 属于 簇 {label} (颜色: {colors[label]})")
# 可视化新点
plt.figure(figsize=(8, 6))
for i in range(n_clusters):
cluster_points = X[labels == i]
plt.scatter(cluster_points[:, 0], cluster_points[:, 1],
c=colors[i], label=f'簇 {i}', s=50, alpha=0.3, edgecolors='k')
# 绘制簇中心
plt.scatter(centers[:, 0], centers[:, 1],
c='black', marker='X', s=200, label='簇中心')
# 绘制新点并突出显示
for i, point in enumerate(test_points):
plt.scatter(point[0], point[1],
c=colors[test_labels[i]], s=200,
marker='*', edgecolors='black', linewidth=2,
label=f'新点{i} (簇{test_labels[i]})')
plt.title('新点预测示例')
plt.xlabel('X 坐标')
plt.ylabel('Y 坐标')
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()