有监督学习有"标准答案"(标签),无监督学习没有------它要从数据自身结构中"发现"规律。
本文覆盖两大无监督任务:聚类 (把相似的样本分到一堆)和降维(把高维数据压到低维便于可视化与分析),全程配 sklearn / umap-learn 代码。
一、无监督学习概述
1.1 三种学习范式对比
| 范式 | 数据 | 目标 | 典型任务 | 典型算法 |
|---|---|---|---|---|
| 有监督学习(Supervised) | 有标签 (X, y) | 学习 X→y 映射 | 分类、回归 | SVM、决策树、神经网络 |
| 无监督学习(Unsupervised) | 无标签 (X) | 发现数据内在结构 | 聚类、降维、密度估计 | KMeans、PCA、t-SNE |
| 半监督学习(Semi-supervised) | 少量有标签 + 大量无标签 | 用无标签数据辅助有标签训练 | 标签传播 | Label Spreading |
1.2 无监督学习的两大核心任务
聚类(Clustering):把样本按相似度分成若干组,同组内相似、跨组差异大。用途:客户分群、异常检测、图像分割。
降维(Dimensionality Reduction):把高维数据映射到低维空间,尽量保留原始信息。用途:数据可视化、特征压缩、去噪、加速下游模型训练。
1.3 为什么要学无监督
- 现实中无标签数据远多于有标签数据,标注成本高
- 探索性数据分析(EDA)的第一步:先看数据"长什么样"
- 很多场景根本没有标签可言(异常检测、用户画像分群)
- 降维是高维数据可视化的唯一手段------人眼最多看 3 维
二、聚类------把相似的样本分到一堆
2.1 KMeans:最经典的"分 K 堆"
原理
给定 K 个簇,KMeans 通过迭代两步收敛:
1. 初始化:随机选 K 个样本点作为簇中心(质心)
2. 分配:每个样本分到离它最近的质心所在的簇
3. 更新:每个簇的质心更新为该簇所有样本的均值
4. 重复 2-3,直到质心不再移动(或变化 < 阈值)
目标函数(最小化簇内平方误差和 SSE):
SSE = Σ_{k=1}^{K} Σ_{x∈C_k} ||x - μ_k||²
其中 μ_k 是第 k 个簇的质心,C_k 是属于该簇的样本集合。
K 值怎么选
KMeans 需要预先指定 K,这是它最大的"人为干预"环节。两种常用方法:
肘部法则(Elbow Method):对不同的 K 计算 SSE,画 K-SSE 曲线,选"拐弯"处(SSE 下降速率骤减的点)。
轮廓系数(Silhouette Score):对每个样本算一个 -1, 1 的分数,越接近 1 说明分得越好。取不同 K 看平均轮廓系数,选最大的。
sklearn 代码
from sklearn.cluster import KMeans
from sklearn.datasets import make_blobs
import matplotlib.pyplot as plt
# 1. 生成测试数据:3 个簇,共 300 个样本
X, y_true = make_blobs(n_samples=300, centers=3, cluster_std=0.6, random_state=42)
# 2. KMeans 聚类
kmeans = KMeans(n_clusters=3, random_state=42, n_init=10)
labels = kmeans.fit_predict(X)
centers = kmeans.cluster_centers_
# 3. 可视化
plt.scatter(X[:, 0], X[:, 1], c=labels, cmap='viridis', s=20)
plt.scatter(centers[:, 0], centers[:, 1], c='red', marker='x', s=200, linewidths=3)
plt.title('KMeans 聚类结果')
plt.show()
# 4. 查看簇内平方误差
print(f'SSE (inertia): {kmeans.inertia_:.2f}')
肘部法则选 K
sse_list = []
K_range = range(1, 11)
for k in K_range:
km = KMeans(n_clusters=k, random_state=42, n_init=10)
km.fit(X)
sse_list.append(km.inertia_)
plt.plot(K_range, sse_list, 'bo-')
plt.xlabel('K (簇数)')
plt.ylabel('SSE (簇内平方误差)')
plt.title('肘部法则选 K')
plt.axvline(x=3, color='red', linestyle='--', label='肘部 K=3')
plt.legend()
plt.show()
优缺点
| 优点 | 缺点 |
|---|---|
| 简单高效,O(nKt) 复杂度 | 需预先指定 K |
| 适合球形/凸形簇 | 对非球形簇效果差(如环形) |
| 结果可解释性强 | 对异常值敏感(质心被拉偏) |
K 个初始质心随机选,可能陷入局部最优(n_init 多跑几次取最优) |
2.2 层次聚类:自底向上"合并树"
原理
层次聚类不需要预先指定 K,它生成一棵"聚类树"(树状图 Dendrogram),你可以在任意高度"剪一刀"得到 K 个簇。
两种策略:
| 策略 | 做法 | sklearn 参数 |
|---|---|---|
| 凝聚法(Agglomerative,自底向上) | 每个样本初始为一个簇,反复合并最相似的两簇 | AgglomerativeClustering(常用) |
| 分裂法(Divisive,自顶向下) | 所有样本初始为一个簇,反复分裂最不均匀的簇 | sklearn 未直接实现 |
簇间距离怎么算
合并两个簇时,"两个簇之间的距离"有多种定义:
| 链接方式 | 定义 | 特点 |
|---|---|---|
| 单链接(Single / Ward min) | 两簇中最近的两点距离 | 容易"链式效应",把长条形数据拉成一串 |
| 全链接(Complete / Ward max) | 两簇中最远的两点距离 | 偏好紧凑球形簇 |
| 平均链接(Average) | 两簇所有点对的平均距离 | 折中 |
| Ward 链接 | 合并后使SSE 增量最小 | 最常用,效果接近 KMeans |
sklearn 代码
from sklearn.cluster import AgglomerativeClustering
from scipy.cluster.hierarchy import dendrogram, linkage
import matplotlib.pyplot as plt
# 1. 凝聚层次聚类(指定最终 3 个簇,Ward 链接)
agg = AgglomerativeClustering(n_clusters=3, linkage='ward')
labels = agg.fit_predict(X)
# 2. 画树状图(看不同高度"剪"出几个簇)
Z = linkage(X, method='ward') # linkage 矩阵
plt.figure(figsize=(12, 5))
dendrogram(Z, truncate_mode='level', p=5) # p=显示最后 5 层
plt.title('层次聚类树状图(Dendrogram)')
plt.xlabel('样本索引 / 簇')
plt.ylabel('距离')
plt.show()
怎么用树状图选 K:看纵轴------最长的那条"竖线"(未被横线截断的最大间距)对应的位置横切一刀,切到的横线数就是推荐 K。
优缺点
| 优点 | 缺点 |
|---|---|
| 不需预先指定 K | 计算复杂度高 O(n³),大数据集慢 |
| 树状图直观,可交互式选 K | 一旦合并/分裂不可撤销 |
| 可发现数据的层次结构 | 对噪声敏感 |
| 任意形状簇都能处理 |
2.3 DBSCAN:基于密度的"自动找簇"
原理
DBSCAN(Density-Based Spatial Clustering of Applications with Noise)的核心思想:簇是高密度区域,由低密度区域分隔。
两个参数:
| 参数 | 含义 |
|---|---|
eps(ε) |
邻域半径:以某点为圆心、eps 为半径的圆内的点都算"邻居" |
min_samples |
核心点阈值:一个点的 eps 邻域内至少有 min_samples 个点,它才是"核心点" |
三种点:
| 类型 | 定义 | 簇归属 |
|---|---|---|
| 核心点(Core) | eps 邻域内点数 ≥ min_samples | 簇的"骨架" |
| 边界点(Border) | 不是核心点,但在某核心点的 eps 邻域内 | 归属该核心点所在簇 |
| 噪声点(Noise) | 既不是核心点,也不在任何核心点邻域内 | 标记为 -1,不归属任何簇 |
算法流程:
1. 对每个未访问的点 p:
a. 找 p 的 eps 邻域
b. 若邻域点数 < min_samples → 暂标记为噪声(后续可能被重新归为边界点)
c. 若邻域点数 ≥ min_samples → p 是核心点,创建新簇
→ 把 p 的所有邻居加入簇
→ 对每个邻居递归重复(若邻居也是核心点,扩展其邻居)
2. 直到所有点都被访问
sklearn 代码
from sklearn.cluster import DBSCAN
from sklearn.datasets import make_moons
# 1. 生成月牙形数据(KMeans 处理不了的非凸簇)
X_moons, _ = make_moons(n_samples=300, noise=0.1, random_state=42)
# 2. DBSCAN 聚类
db = DBSCAN(eps=0.2, min_samples=5)
labels = db.fit_predict(X_moons)
# 3. 可视化(-1 是噪声点,画成黑色)
plt.scatter(X_moons[:, 0], X_moons[:, 1], c=labels, cmap='viridis', s=20)
plt.title(f'DBSCAN: {len(set(labels)) - (1 if -1 in labels else 0)} 个簇, '
f'{list(labels).count(-1)} 个噪声点')
plt.show()
eps 怎么选
经验方法:对每个点算到最近邻居的距离,排序后画 k-距离图(k=min_samples),曲线"拐弯"处对应 eps。
from sklearn.neighbors import NearestNeighbors
nn = NearestNeighbors(n_neighbors=5)
nn.fit(X_moons)
distances, _ = nn.kneighbors(X_moons)
distances = np.sort(distances[:, -1]) # 第 5 近邻的距离,排序
plt.plot(distances)
plt.xlabel('样本(按距离排序)')
plt.ylabel('第 5 近邻距离')
plt.title('k-距离图选 eps')
plt.show()
优缺点
| 优点 | 缺点 |
|---|---|
| 不需指定簇数 K | eps 和 min_samples 对结果影响大 |
| 能发现任意形状的簇 | 密度差异大的数据集(有的簇密、有的疏)效果差 |
| 自动识别噪声点(异常检测) | 高维数据"维度灾难"------距离失效 |
| 对异常值鲁棒 |
2.4 三种聚类方法对比
| 方法 | 簇形状 | 需指定 K | 噪声处理 | 复杂度 | 适用场景 |
|---|---|---|---|---|---|
| KMeans | 球形 | ✅ | ❌ | O(nKt) | 簇形状规则、数据量大 |
| 层次聚类 | 任意 | ❌(树状图选) | ❌ | O(n³) | 小数据集、需层次结构 |
| DBSCAN | 任意 | ❌ | ✅ | O(n log n) | 非凸簇、异常检测 |
2.5 聚类评估指标
无标签时怎么评估聚类好坏?三个内部指标:
| 指标 | 公式思想 | 取值范围 | sklearn API | 越大/越小越好 |
|---|---|---|---|---|
| 轮廓系数(Silhouette) | (b-a)/max(a,b),a=簇内平均距离,b=最近簇间平均距离 | -1, 1 | silhouette_score |
越大越好(>0.5 好,<0 差) |
| CH 指数(Calinski-Harabasz) | 簇间离散度 / 簇内离散度 × 样本数修正 | [0, ∞) | calinski_harabasz_score |
越大越好 |
| Davies-Bouldin | 各簇间最大相似度平均 | [0, ∞) | davies_bouldin_score |
越小越好 |
from sklearn.metrics import silhouette_score, calinski_harabasz_score, davies_bouldin_score
# 假设 X 是数据,labels 是聚类结果
print(f'轮廓系数: {silhouette_score(X, labels):.4f}') # 越大越好
print(f'CH 指数: {calinski_harabasz_score(X, labels):.4f}') # 越大越好
print(f'DB 指数: {davies_bouldin_score(X, labels):.4f}') # 越小越好
有标签时(用聚类结果跟真实标签对比,注意聚类标签编号可能跟真实标签不一致):
| 指标 | sklearn API | 说明 |
|---|---|---|
| ARI(调整兰德指数) | adjusted_rand_score |
-1, 1,0=随机,1=完全一致 |
| NMI(归一化互信息) | normalized_mutual_info_score |
0, 1,1=完全一致 |
| 同质性 / 完整性 / V-measure | homogeneity_completeness_v_measure |
0, 1 |
三、降维------把高维压到低维
3.1 为什么降维
- 可视化:人最多看 3 维,高维数据必须降到 2-3 维才能画图
- 去噪:低维子空间往往保留了信号、滤掉了噪声
- 加速:特征少了,下游模型训练更快、内存更省
- 缓解维度灾难:高维空间中样本稀疏,距离/密度都失效
3.2 PCA:线性降维的"老大哥"
原理
PCA(Principal Component Analysis,主成分分析)通过正交变换把原始特征投影到新的坐标系,新坐标轴按数据方差大小排序------前几个轴(主成分)保留了最多信息。
数学步骤:
1. 数据中心化:X = X - mean(X) (每个特征减去均值,平移到原点)
2. 计算协方差矩阵:C = XᵀX / (n-1)
3. 对 C 做特征值分解:C = V Λ Vᵀ
4. 取前 k 个最大特征值对应的特征向量,组成投影矩阵 W (d×k)
5. 降维:X_reduced = X · W (n×d · d×k = n×k)
直觉理解:找数据"方差最大"的方向作为第一主成分,再在与它正交的方向里找方差最大的作为第二主成分,依此类推。方差 = 信息量。
sklearn 代码
from sklearn.decomposition import PCA
from sklearn.datasets import load_digits
# 1. 加载手写数字数据集:1797 个样本,每个 8×8=64 维
digits = load_digits()
X, y = digits.data, digits.target # X.shape = (1797, 64)
# 2. PCA 降到 2 维(可视化用)
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X) # X_pca.shape = (1797, 2)
# 3. 可视化:不同数字用不同颜色
plt.scatter(X_pca[:, 0], X_pca[:, 1], c=y, cmap='tab10', s=10, alpha=0.7)
plt.colorbar(label='数字类别')
plt.title(f'PCA 降维 (64→2), 解释方差比: {pca.explained_variance_ratio_.sum():.2%}')
plt.xlabel(f'PC1 ({pca.explained_variance_ratio_[0]:.2%})')
plt.ylabel(f'PC2 ({pca.explained_variance_ratio_[1]:.2%})')
plt.show()
# 4. 查看各主成分解释了多少方差
print(f'前 2 个主成分解释方差比: {pca.explained_variance_ratio_}')
print(f'累计解释方差: {pca.explained_variance_ratio_.sum():.2%}')
保留多少维
# 画累计解释方差曲线,找"够用"的维度数
pca_full = PCA().fit(X)
cumvar = np.cumsum(pca_full.explained_variance_ratio_)
plt.plot(range(1, len(cumvar) + 1), cumvar, 'b-')
plt.axhline(y=0.95, color='r', linestyle='--', label='95% 方差')
plt.xlabel('主成分数')
plt.ylabel('累计解释方差比')
plt.title('PCA 选维度数')
plt.legend()
plt.show()
# 或者直接指定保留 95% 方差
pca_95 = PCA(n_components=0.95) # 传 0~1 的浮点数 = 保留的方差比例
X_95 = pca_95.fit_transform(X)
print(f'保留 95% 方差需要 {pca_95.n_components_} 维(原始 64 维)')
优缺点
| 优点 | 缺点 |
|---|---|
| 线性变换,计算高效 | 只能捕捉线性结构,非线性流形无能为力 |
| 降维后各主成分不相关(正交) | 主成分可解释性差(是原始特征的线性组合) |
| 可逆(可重建原始数据近似) | 对异常值敏感(协方差矩阵被拉偏) |
| 解释方差比量化信息保留量 |
3.3 t-SNE:非线性降维的可视化利器
原理
t-SNE(t-Distributed Stochastic Neighbor Embedding)专门为可视化 设计(降到 2-3 维),核心思想:高维空间中"近邻"的点,在低维空间中仍然近邻。
两步走:
1. 高维空间:用高斯分布算点对之间的"相似度"(条件概率)
p_{j|i} = exp(-||x_i - x_j||² / 2σ_i²) / Σ_k exp(-||x_i - x_k||² / 2σ_i²)
2. 低维空间:用 t 分布(自由度=1,即柯西分布)算相似度
q_{ij} = (1 + ||y_i - y_j||²)⁻¹ / Σ_k (1 + ||y_k - y_l||²)⁻¹
3. 最小化 KL 散度:KL(P || Q) = Σ_i Σ_j p_{ij} log(p_{ij} / q_{ij})
→ 用梯度下降优化低维坐标 y_i
为什么用 t 分布而不是高斯分布:高维空间中"中等距离"的点很多(维度灾难),如果低维也用高斯分布,这些点会被挤成一团。t 分布"尾巴更重",能把中等距离的点推到更远的地方,缓解"拥挤问题"。
sklearn 代码
from sklearn.manifold import TSNE
# 1. t-SNE 降到 2 维
tsne = TSNE(n_components=2, perplexity=30, learning_rate='auto',
init='pca', random_state=42, n_iter=1000)
X_tsne = tsne.fit_transform(X)
# 2. 可视化
plt.scatter(X_tsne[:, 0], X_tsne[:, 1], c=y, cmap='tab10', s=10, alpha=0.7)
plt.colorbar(label='数字类别')
plt.title('t-SNE 降维 (64→2)')
plt.show()
关键参数
| 参数 | 含义 | 调参建议 |
|---|---|---|
perplexity |
每个点关注的"有效近邻数",平衡局部/全局结构 | 5~50,默认 30;样本多就调大,perplexity < n_samples |
learning_rate |
梯度下降学习率 | 10~1000;太大簇压缩成点,太远簇缩成密集球 |
n_iter |
最大迭代次数 | ≥1000,默认 1000 |
init |
低维坐标初始化 | 'pca'(用 PCA 结果初始化,比随机更稳) |
perplexity 对比实验:
fig, axes = plt.subplots(1, 3, figsize=(18, 5))
for i, perp in enumerate([5, 30, 100]):
tsne = TSNE(n_components=2, perplexity=perp, random_state=42, init='pca')
X_tsne = tsne.fit_transform(X)
axes[i].scatter(X_tsne[:, 0], X_tsne[:, 1], c=y, cmap='tab10', s=10)
axes[i].set_title(f'perplexity={perp}')
plt.suptitle('t-SNE: perplexity 对比')
plt.show()
优缺点
| 优点 | 缺点 |
|---|---|
| 非线性,能展开复杂流形 | 只用于可视化,不适合作为下游模型输入 |
| 簇分离效果好,视觉漂亮 | 计算慢 O(n²),大数据集需先 PCA 降到 50 维再 t-SNE |
| 簇间距离无意义------t-SNE 会"放大"簇间距离让图好看,但不反映真实距离 | |
| 簇大小无意义------t-SNE 会"均匀化"簇大小 | |
| 非确定性(每次运行结果不同,需固定 random_state) | |
| 不能新增点降维(没有 transform 方法,只能 fit_transform) |
3.4 UMAP:又快又好的"新秀"
原理
UMAP(Uniform Manifold Approximation and Projection)基于代数拓扑,核心思想与 t-SNE 类似(保持局部近邻关系),但数学基础更扎实、速度更快。
两步走:
1. 构建高维拓扑表示:用 k-近邻图(每个点连 k 个最近邻,边权=距离)
2. 优化低维表示:在低维构建类似的图,最小化两个图之间的交叉熵
与 t-SNE 的关键区别:UMAP 用指数衰减 的边权(不是 t 分布),且优化目标是交叉熵(不是 KL 散度),这让它:
- 速度更快(O(n·k),k 是近邻数,通常 15)
- 保留了更多全局结构(t-SNE 只保局部)
- 可以 transform 新数据 (有
transform方法,能增量降维) - 可以降到任意维度(不限于 2-3 维,也能做特征压缩)
代码(需安装:pip install umap-learn)
import umap
# 1. UMAP 降到 2 维
reducer = umap.UMAP(n_components=2, n_neighbors=15, min_dist=0.1, random_state=42)
X_umap = reducer.fit_transform(X)
# 2. 可视化
plt.scatter(X_umap[:, 0], X_umap[:, 1], c=y, cmap='tab10', s=10, alpha=0.7)
plt.colorbar(label='数字类别')
plt.title('UMAP 降维 (64→2)')
plt.show()
# 3. UMAP 可以 transform 新数据(t-SNE 做不到)
X_new = X[:10] # 假设这是新数据
X_new_umap = reducer.transform(X_new)
关键参数
| 参数 | 含义 | 调参建议 |
|---|---|---|
n_neighbors |
近邻数,平衡局部/全局 | 2~200,默认 15;小值重局部细节,大值重全局结构 |
min_dist |
低维中点之间的最小距离 | 0.0~0.99,默认 0.1;小值簇更紧密,大值簇更松散 |
n_components |
降维目标维度 | 2~100,可视化用 2-3,特征压缩可更高 |
metric |
距离度量 | 'euclidean'(默认)、'cosine'、'manhattan' 等 |
优缺点
| 优点 | 缺点 |
|---|---|
| 速度快(比 t-SNE 快 5-10 倍) | 数学理论较深,参数直觉不如 t-SNE 直观 |
| 保留局部+全局结构 | 不同 random_state 结果差异比 t-SNE 小但仍存在 |
| 可以 transform 新数据 | |
| 可降到任意维度(不限于可视化) | |
| 支持非欧距离(cosine 等,适合文本/图像) |
3.5 三种降维方法对比
| 方法 | 线性/非线性 | 速度 | 保留结构 | 可 transform | 适用维度 | 典型用途 |
|---|---|---|---|---|---|---|
| PCA | 线性 | ⚡⚡⚡ 快 | 全局(方差) | ✅ | 任意 | 特征压缩、去噪、预处理 |
| t-SNE | 非线性 | 🐢 慢 (O(n²)) | 局部 | ❌ | 2-3 | 纯可视化 |
| UMAP | 非线性 | ⚡⚡ 较快 | 局部+全局 | ✅ | 任意 | 可视化 + 特征压缩 |
经验流程:
高维数据
│
├─ 只为可视化 → UMAP(首选)或 t-SNE
│
├─ 为下游模型做特征压缩 → PCA(先试)或 UMAP(PCA 不够好再试)
│
└─ 数据量很大(>10万)→ 先 PCA 降到 50 维 → 再 UMAP/t-SNE 降到 2 维
四、实战案例:手写数字聚类与可视化
把聚类和降维串起来,用 sklearn 的 digits 数据集(1797 个 8×8 手写数字图片,64 维,0-9 共 10 类)做完整流程。
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import load_digits
from sklearn.cluster import KMeans, DBSCAN
from sklearn.decomposition import PCA
from sklearn.manifold import TSNE
from sklearn.metrics import (silhouette_score, adjusted_rand_score,
normalized_mutual_info_score)
import umap
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# ========== 1. 数据准备 ==========
digits = load_digits()
X, y = digits.data, digits.target
print(f'数据形状: {X.shape}') # (1797, 64)
print(f'类别: {np.unique(y)}') # [0 1 2 3 4 5 6 7 8 9]
# ========== 2. 降维可视化对比 ==========
fig, axes = plt.subplots(1, 3, figsize=(18, 5))
# 2.1 PCA
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X)
axes[0].scatter(X_pca[:, 0], X_pca[:, 1], c=y, cmap='tab10', s=10, alpha=0.7)
axes[0].set_title(f'PCA (解释方差: {pca.explained_variance_ratio_.sum():.1%})')
# 2.2 t-SNE
tsne = TSNE(n_components=2, perplexity=30, init='pca', random_state=42)
X_tsne = tsne.fit_transform(X)
axes[1].scatter(X_tsne[:, 0], X_tsne[:, 1], c=y, cmap='tab10', s=10, alpha=0.7)
axes[1].set_title('t-SNE')
# 2.3 UMAP
reducer = umap.UMAP(n_components=2, random_state=42)
X_umap = reducer.fit_transform(X)
axes[2].scatter(X_umap[:, 0], X_umap[:, 1], c=y, cmap='tab10', s=10, alpha=0.7)
axes[2].set_title('UMAP')
plt.suptitle('手写数字 64→2 降维对比(颜色=真实标签)', fontsize=14)
plt.tight_layout()
plt.show()
# 预期:PCA 簇重叠多(线性方法不够),t-SNE/UMAP 簇分离清晰
# ========== 3. KMeans 聚类 + 评估 ==========
kmeans = KMeans(n_clusters=10, random_state=42, n_init=10)
labels_km = kmeans.fit_predict(X)
print('\n===== KMeans 聚类评估 =====')
print(f'轮廓系数: {silhouette_score(X, labels_km):.4f}')
print(f'ARI: {adjusted_rand_score(y, labels_km):.4f}')
print(f'NMI: {normalized_mutual_info_score(y, labels_km):.4f}')
# ========== 4. 先降维再聚类(加速 + 去噪) ==========
pca_50 = PCA(n_components=50)
X_50 = pca_50.fit_transform(X)
labels_km_pca = KMeans(n_clusters=10, random_state=42, n_init=10).fit_predict(X_50)
print('\n===== PCA(64→50) + KMeans 评估 =====')
print(f'轮廓系数: {silhouette_score(X_50, labels_km_pca):.4f}')
print(f'ARI: {adjusted_rand_score(y, labels_km_pca):.4f}')
print(f'NMI: {normalized_mutual_info_score(y, labels_km_pca):.4f}')
# ========== 5. 聚类结果可视化(用 UMAP 降维画图) ==========
plt.figure(figsize=(12, 5))
plt.subplot(1, 2, 1)
plt.scatter(X_umap[:, 0], X_umap[:, 1], c=y, cmap='tab10', s=10, alpha=0.7)
plt.title('真实标签')
plt.subplot(1, 2, 2)
plt.scatter(X_umap[:, 0], X_umap[:, 1], c=labels_km, cmap='tab10', s=10, alpha=0.7)
plt.title('KMeans 聚类结果')
plt.suptitle('UMAP 可视化:真实标签 vs KMeans 聚类')
plt.tight_layout()
plt.show()
预期输出:
数据形状: (1797, 64)
类别: [0 1 2 3 4 5 6 7 8 9]
===== KMeans 聚类评估 =====
轮廓系数: 0.1821
ARI: 0.7366
NMI: 0.7439
===== PCA(64→50) + KMeans 评估 =====
轮廓系数: 0.1832
ARI: 0.7366
NMI: 0.7439
解读:
- PCA 降到 50 维后聚类效果几乎不变------说明后 14 维主要是噪声,PCA 起到了去噪作用
- ARI=0.74 说明 KMeans 聚类结果与真实标签有 74% 的一致性------无监督方法能"猜"对大部分
- t-SNE/UMAP 的可视化中簇分离远比 PCA 清晰------非线性降维在可视化上完胜
五、知识速查表
| 知识点 | 核心要点 |
|---|---|
| 无监督学习 | 无标签,从数据结构中发现规律;两大任务:聚类 + 降维 |
| KMeans | 指定 K,迭代"分配+更新质心",最小化 SSE;适合球形簇;肘部法则选 K |
| 层次聚类 | 不需指定 K,生成树状图,Ward 链接最常用;O(n³) 慢 |
| DBSCAN | 基于 eps 邻域和 min_samples 密度,自动找簇+识别噪声;任意形状簇 |
| 轮廓系数 | -1,1,越大越好,>0.5 为好聚类;无需标签 |
| ARI/NMI | 有标签时对比聚类结果与真实标签,1=完全一致 |
| PCA | 线性降维,最大化方差=信息量,正交主成分;可逆、可 transform、快 |
| t-SNE | 非线性,保局部近邻,t 分布缓解拥挤;只用于 2-3 维可视化,O(n²) 慢 |
| UMAP | 非线性,保局部+全局,可 transform,可降到任意维度,比 t-SNE 快 |
| 降维流程 | 纯可视化→UMAP/t-SNE;特征压缩→PCA 先试;大数据→先 PCA 再 UMAP |
| 聚类+降维 | 先降维去噪再聚类,效果常优于直接高维聚类 |