引言
监督学习有「标准答案」(标签),无监督学习没有——它的目标是发现数据内在的结构。两大任务:聚类(把样本分成几群)与降维(压缩特征但不丢失关键信息)。应用场景遍地都是:客户细分、异常检测、基因分型、推荐系统冷启动、高维可视化。
本文用零售客户数据走一遍完整实战:先讲 K-Means 的原理与「选几个簇」的核心难题(肘部法则、轮廓系数),再对比层次聚类与 DBSCAN 各自擅长的数据形态;降维部分讲清 PCA 的原理直觉、用它做可视化与去噪,最后用 t-SNE/UMAP 呈现高维数据。全程强调一个关键问题:无监督结果如何验证它真的有意义。
前置:[[ml]] 专题的环境与 pandas 基础(https://plumephp.com/ml-python-environment-setup/)。深度原理见 [[ai-ml]] 专题的无监督学习文章。
目录
- 1. 无监督学习:没有标签怎么学
- 2. 数据准备:零售客户细分数据集
- 3. K-Means 原理与实现
- 4. 选几个簇:肘部法则与轮廓系数
- 5. 层次聚类与谱系图
- 6. DBSCAN:不规则簇与噪声
- 7. PCA 降维:原理直觉与应用
- 8. 高维可视化:t-SNE 与 UMAP
- 9. 总结:无监督结果的验证套路
- 延伸阅读
1. 无监督学习:没有标签怎么学
1.1 聚类 vs 降维
| 任务 | 目标 | 输出 | 典型算法 |
|---|---|---|---|
| 聚类 | 把样本分组 | 每个样本一个簇标签 | K-Means、层次、DBSCAN |
| 降维 | 压缩特征 | 更少的新特征 | PCA、t-SNE、UMAP |
1.2 共同难题:没有「标准答案」
监督学习可以用测试集验证;无监督的「对不对」依赖领域判断——这就是为什么要结合业务语义去解释簇的含义。
2. 数据准备:零售客户细分数据集
2.1 构造客户特征
每位客户三个维度:年度消费金额、消费频次、平均客单价。
import numpy as np
import pandas as pd
np.random.seed(42)
n = 500
# 三种真实群体:高端少次高客单 / 大众高频低客单 / 低价沉默
group1 = np.random.normal([5000, 8, 650], [500, 2, 80], (170, 3))
group2 = np.random.normal([1500, 40, 40], [200, 6, 10], (180, 3))
group3 = np.random.normal([300, 3, 100], [80, 1, 25], (150, 3))
data = np.vstack([group1, group2, group3])
df = pd.DataFrame(data, columns=['年消费额', '消费频次', '客单价'])
print(df.describe())
2.2 聚类前必须标准化
聚类基于距离,量级大的特征会主导结果。
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X = scaler.fit_transform(df)
3. K-Means 原理与实现
3.1 算法四步
1. 随机选 k 个初始中心
2. 每个样本归到最近的中心
3. 重新计算每簇中心(均值)
4. 重复 2-3 直到中心几乎不动
3.2 用轮廓看分群效果
from sklearn.cluster import KMeans
import matplotlib.pyplot as plt
kmeans = KMeans(n_clusters=3, n_init=10, random_state=42)
labels = kmeans.fit_predict(X)
df['簇'] = labels
plt.scatter(df['年消费额'], df['消费频次'], c=df['簇'], cmap='viridis', alpha=0.6)
plt.xlabel('年消费额')
plt.ylabel('消费频次')
plt.title('K-Means (k=3)')
plt.show()
3.3 解释簇的含义
profile = df.groupby('簇').mean().round(1)
print(profile)
三簇分别对应「高端低频」「大众高频」「低价沉默」,可落地为差异化运营策略。
4. 选几个簇:肘部法则与轮廓系数
4.1 肘部法则(Elbow)
看「簇内误差平方和 SSE」随 k 的下降曲线,找拐点:
sse = []
for k in range(1, 9):
km = KMeans(n_clusters=k, n_init=10, random_state=42)
km.fit(X)
sse.append(km.inertia_) # SSE
plt.plot(range(1, 9), sse, 'o-')
plt.xlabel('k')
plt.ylabel('SSE')
plt.title('肘部法则')
plt.show()
4.2 轮廓系数(Silhouette)
衡量「样本与自己簇的紧密度 vs 与其他簇的分隔度」,取值 [-1,1],越高越好:
from sklearn.metrics import silhouette_score
for k in range(2, 7):
km = KMeans(n_clusters=k, n_init=10, random_state=42)
labels = km.fit_predict(X)
score = silhouette_score(X, labels)
print(f"k={k} 轮廓系数={score:.3f}")
4.3 综合判断
| 方法 | 怎么用 |
|---|---|
| 肘部法则 | SSE 下降「拐点」对应的 k |
| 轮廓系数 | 多个 k 里取最高 |
| 业务常识 | 簇数要可解释、可运营 |
5. 层次聚类与谱系图
5.1 原理:自底向上合并
从每个样本自成一簇开始,逐步合并距离最近的两簇,直到剩下指定簇数。
from sklearn.cluster import AgglomerativeClustering
from scipy.cluster.hierarchy import dendrogram, linkage
import matplotlib.pyplot as plt
Z = linkage(X, method='ward')
plt.figure(figsize=(10, 5))
dendrogram(Z, truncate_mode='level', p=5)
plt.title('谱系图')
plt.show()
5.2 谱系图怎么看
谱系图横线高度 = 合并时的距离。在高度差异大的地方切一刀,得到合理的簇划分。
agg = AgglomerativeClustering(n_clusters=3, linkage='ward')
agg_labels = agg.fit_predict(X)
5.3 与 K-Means 对比
| 维度 | K-Means | 层次聚类 |
|---|---|---|
| 簇形状 | 球形假设 | 更灵活 |
| 数据规模 | 可大规模 | 计算 O(n²),适合中小 |
| 是否需要预选 k | 需要 | 可从谱系图判断 |
| 可解释性 | 一般 | 谱系图直观 |
6. DBSCAN:不规则簇与噪声
6.1 为什么需要 DBSCAN
K-Means 假设「簇是球形」,遇到环形、狭长、带噪声的簇会失败。DBSCAN 基于密度:密度相连的区域成一簇,稀疏点是噪声。
6.2 构造环形数据并对比
from sklearn.datasets import make_moons
from sklearn.cluster import DBSCAN
Xm, _ = make_moons(n_samples=300, noise=0.05, random_state=42)
# K-Means 会把两个月牙错切成上下两半
km = KMeans(n_clusters=2, n_init=10, random_state=42)
km_labels = km.fit_predict(Xm)
# DBSCAN 正确识别两个月牙
db = DBSCAN(eps=0.3, min_samples=5)
db_labels = db.fit_predict(Xm)
fig, axes = plt.subplots(1, 2, figsize=(10, 4))
axes[0].scatter(Xm[:,0], Xm[:,1], c=km_labels); axes[0].set_title('K-Means')
axes[1].scatter(Xm[:,0], Xm[:,1], c=db_labels); axes[1].set_title('DBSCAN')
plt.show()
6.3 参数含义
| 参数 | 含义 | 影响 |
|---|---|---|
eps | 邻域半径 | 太小→碎片多;太大→合并 |
min_samples | 成为核心点的最少邻居 | 越大→越少簇 |
labels == -1 的点是噪声——这本身就是一种异常检测能力。
7. PCA 降维:原理直觉与应用
7.1 PCA 做了什么
PCA 找到数据方差最大的几个方向(主成分),把数据投影到这些方向,用少数几个新特征承载原数据的大部分信息。
原数据(28维) → PCA → 前2个主成分(2维) ← 保留主要结构
7.2 可视化高维数据
from sklearn.decomposition import PCA
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X)
plt.scatter(X_pca[:,0], X_pca[:,1], c=labels, cmap='viridis', alpha=0.6)
plt.xlabel('PC1')
plt.ylabel('PC2')
plt.title('PCA 降维后的客户分布')
plt.show()
7.3 看每个主成分解释多少方差
print(pca.explained_variance_ratio_)
# [0.62, 0.25] → 前两个主成分解释了 87% 的方差
7.4 PCA 的三种用途
| 用途 | 做法 |
|---|---|
| 可视化 | 降到 2-3 维画图 |
| 去噪/压缩 | 只保留前 k 个主成分 |
| 消除共线性 | 替代原始相关特征 |
PCA 是把「有损压缩」做得最好的线性方法;但它假设结构是线性的,非线性结构用 t-SNE/UMAP。
8. 高维可视化:t-SNE 与 UMAP
8.1 t-SNE:保留局部结构的非线性降维
from sklearn.manifold import TSNE
tsne = TSNE(n_components=2, perplexity=30, random_state=42)
X_tsne = tsne.fit_transform(X)
plt.scatter(X_tsne[:,0], X_tsne[:,1], c=labels, cmap='viridis', alpha=0.6)
plt.title('t-SNE')
plt.show()
8.2 UMAP:更快、保留更多全局结构
pip install umap-learn
import umap
reducer = umap.UMAP(random_state=42)
X_umap = reducer.fit_transform(X)
8.3 三种降维对比
| 方法 | 类型 | 速度 | 擅长 |
|---|---|---|---|
| PCA | 线性 | 快 | 压缩/去噪 |
| t-SNE | 非线性 | 慢 | 可视化聚类结构 |
| UMAP | 非线性 | 中 | 可视化 + 大规模 |
t-SNE/UMAP 的坐标不代表真实距离,只能看「分没分开」,不要解读坐标轴。
9. 总结:无监督结果的验证套路
9.1 完整工作流
标准化 → 降维(可选PCA) → 聚类(K-Means/DBSCAN)
→ 选簇数(肘部/轮廓) → 画图看分离度
→ 分簇画像(各簇均值) → 业务语义验证
9.2 无监督结果如何「验证」
没有标签,但有这些替代验证:
| 手段 | 说明 |
|---|---|
| 簇内紧凑、簇间分离 | 轮廓系数、可视化 |
| 分簇画像可解释 | 每簇均值符合业务直觉 |
| 稳定性 | 换种子/子样本,簇是否稳定 |
| 下游验证 | 分簇后做运营/推荐,看效果 |
9.3 一句话心法
无监督的价值在「把数据讲成人话」:聚类给出可解释的群组,降维让人看得见结构。指标是辅助,业务语义才是最终裁判。
延伸阅读
- https://plumephp.com/ml-supervised-classification/ — 有标签的评估体系(与无监督互补)
- https://plumephp.com/ml-feature-engineering/ — 特征工程:聚类/降维常与特征加工协作
- [[ai-ml]] 专题的无监督学习与特征工程深度文章
- scikit-learn 聚类文档
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。