本文面向零基础小白,用最通俗的语言和生活化的例子,带你理解机器学习中两个带 "K" 的经典算法。
目录
- 开篇:两个带 "K" 的算法有什么不同?
- KNN(K-近邻算法)
- 2.1 核心思想:近朱者赤,近墨者黑
- 2.2 距离度量:如何衡量"近"
- 2.3 K 值的选择
- 2.4 KNN 做分类
- 2.5 KNN 做回归
- 2.6 实战:电影类型推荐
- K-Means(K-均值聚类)
- 3.1 核心思想:物以类聚
- 3.2 质心初始化
- 3.3 迭代过程详解
- 3.4 肘部法则选 K
- 3.5 实战:客户分群
- KNN vs K-Means 全面对比
- 常见问题与总结
1. 开篇:两个带 "K" 的算法有什么不同?
很多初学者容易把 KNN 和 K-Means 搞混,因为它们名字里都有一个 "K"。 但它们本质上是完全不同的两类算法:
┌─────────────────────────────────────────────────────────┐
│ KNN vs K-Means 速览 │
├────────────────┬────────────────────────────────────────┤
│ │ KNN (K-近邻) │ K-Means (K-均值) │
├────────────────┼────────────────────────────────────────┤
│ 类型 │ 监督学习 │ 无监督学习 │
│ 目的 │ 预测/分类 │ 聚类/分群 │
│ 需要标签吗? │ 需要 ✓ │ 不需要 ✗ │
│ K 的含义 │ 看 K 个邻居 │ 分成 K 个簇 │
│ 有训练过程吗? │ 没有(懒惰学习) │ 有(迭代优化) │
│ 生活比喻 │ 看你周围的人 │ 把相似的东西归堆 │
└────────────────┴────────────────────────────────────────┘
一句话总结:
- KNN:告诉我你的邻居是谁,我就知道你是谁。
- K-Means:我来帮你把这堆东西分成 K 组。
2. KNN(K-近邻算法)
2.1 核心思想:近朱者赤,近墨者黑
KNN 的思想简单到令人感动——
一个样本的类别,由离它最近的 K 个邻居投票决定。
生活中的例子:
- 你搬到一个新小区,不知道该去哪个菜市场买菜。
- 你问了离你最近的 5 户邻居(K=5),其中 4 户说去东边的市场,1 户说去西边。
- 那你大概率也会去东边的市场。
这就是 KNN 的全部核心思想!
KNN 分类示意图(K=3)
判断 "?" 属于 ▲ 还是 ●?
▲
▲ ●
▲ ┌─────────┐
│ ● ? │ ← 找最近的 3 个邻居
● │ ● │
└─────────┘
▲ ●
●
最近的 3 个邻居:● ● ●
投票结果:● 获胜(3:0)
所以 ? → ●
2.2 距离度量:如何衡量"近"
要找"最近的邻居",首先得定义什么叫"近"。 常用的距离度量有两种:
(1)欧氏距离(Euclidean Distance)—— 直线距离
就是两点之间的直线距离,也就是初中学的勾股定理。
公式:d = √[(x₁-x₂)² + (y₁-y₂)²]
示意图:
B(4,4)
╱|
╱ | 欧氏距离 = √[(4-1)² + (4-1)²]
╱ | = √[9 + 9]
╱ | = √18 ≈ 4.24
A────+
(1,1) (4,1)
走斜线(直线距离)= 4.24
(2)曼哈顿距离(Manhattan Distance)—— 街区距离
想象你在纽约曼哈顿的街区里走路,只能横着走或竖着走,不能穿墙斜着走。
公式:d = |x₁-x₂| + |y₁-y₂|
示意图:
B(4,4)
│
│ 曼哈顿距离 = |4-1| + |4-1|
│ = 3 + 3
│ = 6
│
A───┘
(1,1)
先向右走 3 步 + 再向上走 3 步 = 6 步
两种距离的直观对比
┌────────────────────────────┐
│ B │
│ ╲ │
│ ╲ ← 欧氏(直线)≈ 4.24 │
│ ╲ │
│ ╲ ┌──→──→──┐ │
│ ╲ ↑ ↓ │
│ A ← 曼哈顿 = 6 │
│ (横+竖,沿街区走) │
└────────────────────────────┘
欧氏距离 ≤ 曼哈顿距离(永远成立)
小贴士:大多数场景下用欧氏距离就够了。 如果特征维度很高且有很多稀疏特征(如文本分析),可以尝试曼哈顿距离。
2.3 K 值的选择
K 的选择至关重要,选不好会导致完全不同的结果:
K 值选择的影响:
K 太小(如 K=1): K 太大(如 K=N):
┌──────────────────┐ ┌──────────────────┐
│ ● ▲ │ │ ● ▲ │
│ ?←只看最近1个 │ │ ?←看所有样本 │
│ ▲ ● │ │ ▲ ● │
│ │ │ ● ● ▲ │
│ 容易受噪声干扰 │ │ 永远预测多数类 │
│ 过拟合! │ │ 欠拟合! │
└──────────────────┘ └──────────────────┘
K 适中(如 K=5):
┌──────────────────┐
│ ▲ ● │
│ ● ? ● │
│ ▲ ● │
│ 平衡了噪声和信号 │
│ 刚刚好! ✓ │
└──────────────────┘
选择 K 的经验法则:
- K 一般取奇数(避免投票平局)
- 常见起点:K = √n(n 为训练样本数)
- 通过交叉验证找到最佳 K
- K 通常在 3~10 之间效果不错
2.4 KNN 做分类
分类就是"投票制"——少数服从多数。
例子:判断一个水果是苹果还是橙子
已知数据:
甜度
高 │ 🍎 🍎 🍊
│ 🍎 🍊 🍊
│ 🍎 ?
│ 🍊 🍊
低 │ 🍎 🍊
└──────────────
小 重量 大
新水果 "?" 的 K=5 个最近邻居:
🍎 🍎 🍊 🍎 🍊
投票:🍎 = 3 票, 🍊 = 2 票
结果:? → 苹果 🍎
2.5 KNN 做回归
回归不是投票,而是求平均值。
例子:预测一套房子的价格
已知数据(面积 → 价格):
80㎡ → 200万
90㎡ → 230万
85㎡ → 220万
100㎡ → 280万
95㎡ → 250万
新房子:88㎡,K=3
最近的 3 个邻居:
90㎡(230万), 85㎡(220万), 80㎡(200万)
预测价格 = (230 + 220 + 200) / 3 = 216.7 万
2.6 实战:电影类型推荐
假设我们要根据电影的"打斗镜头数"和"接吻镜头数"来判断电影是动作片还是爱情片。
# ============================================
# KNN 实战:电影类型分类
# ============================================
from sklearn.neighbors import KNeighborsClassifier
import numpy as np
# 训练数据:[打斗镜头数, 接吻镜头数]
X_train = np.array([
[3, 104], # 爱情片
[2, 100], # 爱情片
[1, 81], # 爱情片
[101, 10], # 动作片
[99, 5], # 动作片
[98, 2], # 动作片
])
# 标签:0 = 爱情片, 1 = 动作片
y_train = np.array([0, 0, 0, 1, 1, 1])
# 创建 KNN 模型,K=3
knn = KNeighborsClassifier(n_neighbors=3)
knn.fit(X_train, y_train)
# 预测新电影:打斗 18 次,接吻 90 次
new_movie = np.array([[18, 90]])
prediction = knn.predict(new_movie)
print(f"预测结果:{'动作片' if prediction[0] == 1 else '爱情片'}")
# 输出:预测结果:爱情片
# ============================================
# KNN 回归:预测电影评分
# ============================================
from sklearn.neighbors import KNeighborsRegressor
# 训练数据:[电影时长(分钟), 特效预算(百万)]
X_train = np.array([
[120, 50],
[90, 20],
[150, 80],
[100, 30],
[130, 60],
])
# 评分(1~10)
y_train = np.array([7.5, 6.0, 8.5, 6.5, 8.0])
# 创建 KNN 回归模型
knn_reg = KNeighborsRegressor(n_neighbors=3)
knn_reg.fit(X_train, y_train)
# 预测:时长 110 分钟,特效预算 40 百万
new_movie = np.array([[110, 40]])
predicted_score = knn_reg.predict(new_movie)
print(f"预测评分:{predicted_score[0]:.1f}")
# 输出:预测评分:6.7(附近3部电影评分的平均值)
3. K-Means(K-均值聚类)
3.1 核心思想:物以类聚
K-Means 是一种无监督学习算法——也就是说,数据没有标签, 算法自动把相似的数据归为一组。
生活比喻:你面前有一桌子水果(苹果、橙子、香蕉混在一起), 你不知道它们的名字,但你可以按颜色/形状/大小把它们分成几堆。 这就是聚类!
K-Means 的意思是:把数据分成 K 个簇(cluster),每个簇有一个中心点(质心)。
K-Means 聚类前 vs 聚类后(K=3)
聚类前(一片混沌): 聚类后(清晰分组):
· · · · ○ ○ △ △
· · · ○ △ △
· · · ○ ○ △
· · □ □
· · · ○ □ □
· · ○ □
全部是同一种符号 ○ = 簇A △ = 簇B □ = 簇C
看不出任何结构 每个簇内部的点彼此相似
3.2 质心初始化
K-Means 的第一步是随机选择 K 个初始质心。
初始化示意图(K=3):
步骤 1:随机选 3 个点作为初始质心
· · · ·
★ · ★ = 初始质心A
· · ·
· ★ ★ = 初始质心B
· ·
· · ★ = 初始质心C
注意:初始位置不同,最终结果可能不同!
这就是为什么 sklearn 默认会运行多次取最优。
改进方法:
K-Means++是一种更聪明的初始化方法。 它让初始质心尽量彼此远离,避免都挤在一起。 sklearn 默认就是用K-Means++。
3.3 迭代过程详解
K-Means 的核心就是反复执行两个步骤,直到收敛:
步骤 A:分配 —— 每个点归入离它最近的质心所在的簇
步骤 B:更新 —— 每个簇重新计算质心(簇内所有点的平均值)
让我们用一个完整的例子来看迭代过程:
═══════════════════════════════════════════════════
K-Means 迭代过程演示(K=2, 二维数据)
═══════════════════════════════════════════════════
数据点:A(1,1) B(1,2) C(2,1) D(6,6) E(7,6) F(6,7)
─── 第 0 轮:随机初始化 ───
7 │ F
6 │ D E
5 │
4 │
3 │
2 │ B
1 │ A C
└──────────────────
1 2 3 4 5 6 7
随机选择初始质心:★1 = A(1,1) ★2 = D(6,6)
─── 第 1 轮:分配 + 更新 ───
【分配】每个点离哪个质心更近?
A(1,1) → ★1 距离 0.0 vs ★2 距离 7.1 → 簇1 ○
B(1,2) → ★1 距离 1.0 vs ★2 距离 6.4 → 簇1 ○
C(2,1) → ★1 距离 1.0 vs ★2 距离 6.4 → 簇1 ○
D(6,6) → ★1 距离 7.1 vs ★2 距离 0.0 → 簇2 □
E(7,6) → ★1 距离 7.8 vs ★2 距离 1.0 → 簇2 □
F(6,7) → ★1 距离 7.8 vs ★2 距离 1.0 → 簇2 □
【更新】重新计算质心
★1 新位置 = ((1+1+2)/3, (1+2+1)/3) = (1.33, 1.33)
★2 新位置 = ((6+7+6)/3, (6+6+7)/3) = (6.33, 6.33)
7 │ □
6 │ □ □
5 │ ★2(6.33,6.33)
4 │
3 │
2 │ ○
1 │ ○ ○ ★1(1.33,1.33)
└──────────────────
1 2 3 4 5 6 7
─── 第 2 轮:分配 + 更新 ───
【分配】用新质心重新分配 → 结果和上一轮完全一样
【更新】质心没有变化
✓ 收敛!算法结束!
最终结果:
簇1(○):A, B, C — 左下角的一群点
簇2(□):D, E, F — 右上角的一群点
═══════════════════════════════════════════════════
3.4 肘部法则(Elbow Method)选 K
K-Means 需要你事先指定 K 值。那怎么知道该分几组呢?
肘部法则的思路:尝试不同的 K 值,看**组内误差(SSE)**的变化趋势。 SSE = 每个点到其所属簇质心的距离之和。
肘部法则示意图
SSE(组内误差)
│
800 │ ×
│ ╲
600 │ ╲
│ ╲
400 │ ×
│ ╲
200 │ ╲
│ × ── × ── × ── × ── ×
100 │ ↑
│ 这里是"肘部"!
0 │ 最佳 K = 3
└──────────────────────────────
1 2 3 4 5 6 7
K 的取值
解读:
- K=1 到 K=3:SSE 急剧下降(信息增益大)
- K=3 到 K=7:SSE 下降变缓(增加K已无太大意义)
- 拐点处(肘部)的 K 就是最佳选择
类比:就像你搬家,从 1 间房换到 3 间房,幸福感大增; 但从 3 间换到 7 间,幸福感增加就很有限了。 那 3 间就是最佳"性价比"。
# ============================================
# 肘部法则:确定最佳 K 值
# ============================================
from sklearn.cluster import KMeans
import numpy as np
# 示例数据
np.random.seed(42)
X = np.vstack([
np.random.randn(50, 2) + [2, 2], # 簇 1
np.random.randn(50, 2) + [8, 8], # 簇 2
np.random.randn(50, 2) + [2, 8], # 簇 3
])
# 尝试不同的 K 值
sse_list = []
K_range = range(1, 10)
for k in K_range:
kmeans = KMeans(n_clusters=k, random_state=42, n_init=10)
kmeans.fit(X)
sse_list.append(kmeans.inertia_) # inertia_ 就是 SSE
# 打印结果
print("K 值 | SSE(组内误差)")
print("─" * 30)
for k, sse in zip(K_range, sse_list):
bar = "█" * int(sse / 50)
print(f" {k} | {sse:8.1f} {bar}")
# 输出类似:
# K 值 | SSE
# ──────────────────────────────
# 1 | 3200.5 ████████████████████████████
# 2 | 1500.2 ██████████████
# 3 | 280.1 ██ ← 这里明显下降变缓,选 K=3
# 4 | 260.3 ██
# 5 | 240.5 ██
3.5 实战:客户分群
这是 K-Means 最经典的应用之一:根据客户的消费行为把他们分群, 从而进行精准营销。
# ============================================
# K-Means 实战:客户分群
# ============================================
from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler
import numpy as np
# 客户数据:[年收入(万元), 年消费金额(万元)]
customers = np.array([
# 低收入低消费群体
[15, 5], [18, 8], [20, 10], [12, 3], [16, 7],
# 高收入高消费群体
[80, 70], [85, 75], [90, 80], [78, 65], [88, 78],
# 高收入低消费群体(节俭型)
[75, 10], [82, 12], [70, 8], [78, 15], [85, 11],
])
# 数据标准化(非常重要!消除量纲影响)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(customers)
# K-Means 聚类,分成 3 组
kmeans = KMeans(n_clusters=3, random_state=42, n_init=10)
labels = kmeans.fit_predict(X_scaled)
# 查看每个客户的分组
group_names = {0: "?", 1: "?", 2: "?"} # 先看结果再命名
for i, (customer, label) in enumerate(zip(customers, labels)):
print(f"客户{i+1:2d}: 年收入={customer[0]:3.0f}万, "
f"年消费={customer[1]:3.0f}万 → 群组 {label}")
# 查看每个簇的质心(还原到原始尺度)
centers = scaler.inverse_transform(kmeans.cluster_centers_)
print("\n各群组质心(平均特征):")
for i, center in enumerate(centers):
print(f" 群组{i}: 平均年收入={center[0]:.0f}万, "
f"平均年消费={center[1]:.0f}万")
预期输出分析:
┌──────────────────────────────────────────────────────┐
│ 群组 0:低收入低消费群体(经济型客户) │
│ → 营销策略:推送高性价比产品、优惠券 │
│ │
│ 群组 1:高收入高消费群体(VIP 客户) │
│ → 营销策略:推送高端产品、专属会员服务 │
│ │
│ 群组 2:高收入低消费群体(潜力客户) │
│ → 营销策略:重点培养!推送个性化推荐,挖掘消费潜力 │
└──────────────────────────────────────────────────────┘
# ============================================
# 可视化客户分群结果(可选)
# ============================================
# 如果你安装了 matplotlib,可以画出漂亮的散点图
# import matplotlib.pyplot as plt
#
# colors = ['red', 'blue', 'green']
# for i in range(3):
# mask = labels == i
# plt.scatter(customers[mask, 0], customers[mask, 1],
# c=colors[i], label=f'群组{i}', s=100)
#
# # 画出质心
# plt.scatter(centers[:, 0], centers[:, 1],
# c='black', marker='X', s=200, label='质心')
#
# plt.xlabel('年收入(万元)')
# plt.ylabel('年消费金额(万元)')
# plt.title('客户分群结果')
# plt.legend()
# plt.show()
4. KNN vs K-Means 全面对比
╔══════════════════╦═══════════════════════╦═══════════════════════╗
║ 对比维度 ║ KNN(K-近邻) ║ K-Means(K-均值) ║
╠══════════════════╬═══════════════════════╬═══════════════════════╣
║ 学习类型 ║ 监督学习 ║ 无监督学习 ║
║ 是否需要标签 ║ 需要 ║ 不需要 ║
║ 任务类型 ║ 分类 / 回归 ║ 聚类 ║
║ K 的含义 ║ 邻居个数 ║ 簇的个数 ║
║ 是否有训练过程 ║ 无(懒惰学习) ║ 有(迭代优化) ║
║ 预测速度 ║ 慢(每次都要算距离) ║ 快(只需找最近质心) ║
║ 对异常值敏感 ║ K 较大时不太敏感 ║ 敏感(会拉偏质心) ║
║ 数据需标准化 ║ 是 ║ 是 ║
║ 典型应用 ║ 推荐系统、手写识别 ║ 客户分群、图像压缩 ║
║ 算法复杂度 ║ O(n·d) 每次预测 ║ O(n·K·d·i) 总计 ║
╚══════════════════╩═══════════════════════╩═══════════════════════╝
注:n=样本数, d=特征维度, i=迭代次数
形象记忆法
KNN 像"找朋友": K-Means 像"分组活动":
你是新来的同学 (?) 老师说:"大家分成3组!"
看看身边坐的是谁 同学们自己找相似的人
跟谁最近就和谁一组 站到一起就是一组
┌─────────┐ ┌─────────────────┐
│ A A ? B │ │ ○○○ △△△ □□□ │
│ A B B │ │ ○○ △△ □□ │
│ → ? 跟 B │ │ → 自动形成3个组 │
└─────────┘ └─────────────────┘
什么时候用哪个?
你有标签数据吗?
│
├── 有 → 你想做什么?
│ │
│ ├── 预测类别 → KNN 分类
│ └── 预测数值 → KNN 回归
│
└── 没有 → 你想发现数据中的隐藏分组吗?
│
└── 是 → K-Means 聚类
5. 常见问题与总结
Q1:KNN 为什么叫"懒惰学习"?
因为 KNN 没有显式的训练过程。它只是把训练数据存起来,等到有新数据 需要预测时,才临时去计算距离、找邻居。所以它"训练"很快(什么都不做), 但"预测"很慢(每次都要算一遍距离)。
Q2:为什么要做数据标准化?
反面例子(不标准化):
特征A:身高(cm) 范围 150~190
特征B:年龄(岁) 范围 20~60
距离 = √[(190-150)² + (60-20)²]
= √[1600 + 1600]
看起来两个特征贡献一样?不对!
如果身高单位换成毫米(1500~1900),距离就被身高完全主导了。
标准化后:所有特征都在同一尺度(均值0,标准差1),公平竞争。
# 标准化代码
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# 现在每个特征的均值=0,标准差=1
Q3:K-Means 一定能找到最优解吗?
不一定! K-Means 只能找到局部最优解,结果取决于初始质心的位置。
初始化不同 → 结果可能不同:
初始化 A: 初始化 B:
★ 在左边两个簇里 ★ 分布在三个区域
★ · · · ★ · · ·
· ★ · · · · ·
· · ★ · · ★ ·
· · · ★·
→ 可能得到错误分组 → 得到正确分组
解决办法:
- 使用
K-Means++初始化(sklearn 默认使用) - 多次运行取最优(sklearn 的
n_init=10参数,默认运行 10 次)
Q4:K-Means 能处理非球形簇吗?
K-Means 擅长: K-Means 不擅长:
○○○ □□□ ○○○○○○○○
○○○ □□□ □□□□
○○○ □□□ ○○○○○○○○
球形/团状簇 ✓ 环形/月牙形 ✗
对于非球形簇,可以考虑 DBSCAN 等其他聚类算法。
核心要点回顾
┌────────────────────────────────────────────────────┐
│ KNN 核心要点 │
├────────────────────────────────────────────────────┤
│ 1. 近朱者赤:看最近的 K 个邻居来做决策 │
│ 2. 距离度量:欧氏距离最常用 │
│ 3. K 值选择:交叉验证,一般取奇数 │
│ 4. 数据预处理:一定要标准化! │
│ 5. 优点:简单直观,无需训练 │
│ 6. 缺点:预测慢,高维数据效果差(维度灾难) │
└────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────┐
│ K-Means 核心要点 │
├────────────────────────────────────────────────────┤
│ 1. 物以类聚:自动将数据分成 K 组 │
│ 2. 迭代过程:分配 → 更新 → 分配 → 更新 ... 收敛 │
│ 3. K 值选择:肘部法则(Elbow Method) │
│ 4. 初始化:优先使用 K-Means++ │
│ 5. 优点:简单高效,适合大数据集 │
│ 6. 缺点:需要指定 K,对异常值和非球形簇敏感 │
└────────────────────────────────────────────────────┘
附录:完整可运行代码汇总
# ============================================
# 完整示例:KNN 分类 + K-Means 聚类
# 安装依赖:pip install scikit-learn numpy
# ============================================
import numpy as np
from sklearn.neighbors import KNeighborsClassifier
from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import cross_val_score
# ------------------------------------------
# 第一部分:KNN 分类 — 鸢尾花数据集
# ------------------------------------------
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
# 加载经典鸢尾花数据集
iris = load_iris()
X, y = iris.data, iris.target
# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.3, random_state=42
)
# 标准化
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
# 用交叉验证选择最佳 K
print("=" * 50)
print("KNN:通过交叉验证选择最佳 K")
print("=" * 50)
best_k = 1
best_score = 0
for k in range(1, 20, 2): # 只尝试奇数
knn = KNeighborsClassifier(n_neighbors=k)
scores = cross_val_score(knn, X_train_scaled, y_train, cv=5)
avg_score = scores.mean()
bar = "█" * int(avg_score * 30)
print(f" K={k:2d} 准确率={avg_score:.4f} {bar}")
if avg_score > best_score:
best_score = avg_score
best_k = k
print(f"\n最佳 K = {best_k},交叉验证准确率 = {best_score:.4f}")
# 用最佳 K 训练并测试
knn_best = KNeighborsClassifier(n_neighbors=best_k)
knn_best.fit(X_train_scaled, y_train)
test_score = knn_best.score(X_test_scaled, y_test)
print(f"测试集准确率 = {test_score:.4f}")
# ------------------------------------------
# 第二部分:K-Means 聚类 + 肘部法则
# ------------------------------------------
print("\n" + "=" * 50)
print("K-Means:肘部法则确定最佳 K")
print("=" * 50)
# 生成模拟数据(3个簇)
np.random.seed(42)
X_cluster = np.vstack([
np.random.randn(100, 2) * 0.8 + [0, 0],
np.random.randn(100, 2) * 0.8 + [5, 5],
np.random.randn(100, 2) * 0.8 + [10, 0],
])
# 肘部法则
sse_list = []
for k in range(1, 11):
km = KMeans(n_clusters=k, random_state=42, n_init=10)
km.fit(X_cluster)
sse_list.append(km.inertia_)
print("\n K | SSE")
print(" " + "─" * 30)
for k, sse in zip(range(1, 11), sse_list):
bar_len = int(sse / max(sse_list) * 40)
bar = "█" * bar_len
print(f" {k:2d} | {sse:8.1f} {bar}")
# 用 K=3 做最终聚类
km_final = KMeans(n_clusters=3, random_state=42, n_init=10)
labels = km_final.fit_predict(X_cluster)
print(f"\n最终聚类结果(K=3):")
for i in range(3):
count = np.sum(labels == i)
center = km_final.cluster_centers_[i]
print(f" 簇 {i}: {count} 个样本, "
f"质心 = ({center[0]:.2f}, {center[1]:.2f})")
print("\n完成!")
最后的话:KNN 和 K-Means 虽然名字相似,但一个是"看邻居做决定", 一个是"自动分组"。掌握了它们,你就已经入门了监督学习和无监督学习 这两大机器学习范式。继续加油!