元数据卡
- 前置知识:ch04 ML 基础、Vol 10 线性代数
- 预计时间:45 分钟
- 核心难度:进阶
- 阅读模式:高度专注
- 完成标志:能实现 K-means 和 PCA,理解降维和异常检测的基本原理
你的进度
在模型工坊里待了这么久,你习惯了每次训练都要准备标签数据——每一条数据都标好了正确答案。但工坊的角落里有一堆从未标记的档案:几万条行为日志、上千张未分类的图纸、一堆没有分组的任务记录。
阿花在信里写:“有些事你自己看也能看出来规律,对吧?”
你看向工坊的“无监督”工作台。
你的任务
无监督学习在缺乏标签的情况下挖掘数据的内在模式。你学习三类核心任务:聚类(发现自然分组)、降维(压缩数据而不丢结构)、异常检测(找出离群的样本)。三者共享同一个信念:数据本身包含结构,不需要外部答案来定义。
本章分层
- 必读:K-means 聚类、PCA 主成分分析、简单异常检测
- 选读:DBSCAN 密度聚类、t-SNE 可视化
- 进阶:谱聚类与拉普拉斯特征映射
破局 · 溯源
你有一个仓库,里面堆着几千件物品。没有标签告诉你"这是工具""这是食材"——但你发现铁制的物品大多在一堆、木制的大多在另一堆。它们自己"聚合"了。
聚类就是在特征空间中发现这种自然聚合。而如果你发现某个物品离所有堆都很远——它可能是坏掉了,或者根本不属于这里(异常检测)。
K-means 聚类
K-means 是最直观的聚类算法。你指定要分成 K 堆,算法迭代优化:
- 随机初始化 K 个中心
- 每个点分配到最近的中心
- 更新中心为每个类的均值
- 重复直到收敛
import numpy as np
class KMeans:
def __init__(self, n_clusters=3, max_iter=100):
self.n_clusters = n_clusters
self.max_iter = max_iter
def fit(self, X):
# 随机初始化中心
idx = np.random.choice(len(X), self.n_clusters, replace=False)
self.centroids = X[idx]
for _ in range(self.max_iter):
# 分配步骤
distances = np.array([
np.sum((X - c) ** 2, axis=1) for c in self.centroids
])
self.labels = np.argmin(distances, axis=0)
# 更新步骤
new_centroids = np.array([
X[self.labels == k].mean(axis=0)
for k in range(self.n_clusters)
])
if np.allclose(self.centroids, new_centroids):
break
self.centroids = new_centroids
def predict(self, X):
distances = np.array([
np.sum((X - c) ** 2, axis=1) for c in self.centroids
])
return np.argmin(distances, axis=0)K-means 的问题:K 需要预先指定。肘部法(plot 不同 K 下的总距平方和)和轮廓分数可以帮你选 K。如果簇形状不是凸的(比如环形),K-means 完全失效——这时候需要 DBSCAN。
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score
kmeans = KMeans(n_clusters=3, random_state=42)
labels = kmeans.fit_predict(X)
score = silhouette_score(X, labels)
print(f"轮廓系数: {score:.3f}") # 越接近 1 越好(簇内紧凑、簇间分离)PCA 主成分分析
降维的核心动机:高维数据有两个问题——维度灾难(所需样本量随维度指数增长)和可视化困难(很难在高维画图)。
PCA 找到数据方差最大的方向(主成分),将数据投影到低维。
from sklearn.decomposition import PCA
import matplotlib.pyplot as plt
# 用 PCA 降维到 2 维做可视化
pca = PCA(n_components=2)
X_2d = pca.fit_transform(X)
plt.scatter(X_2d[:, 0], X_2d[:, 1], c=labels, cmap='viridis')
plt.xlabel('PC1')
plt.ylabel('PC2')
# plt.show()
# 查看每个主成分解释的方差比例
print(f"解释方差比: {pca.explained_variance_ratio_}")
print(f"累计解释方差: {pca.explained_variance_ratio_.cumsum()}")PCA 的数学核心:对协方差矩阵做奇异值分解,取最大的 K 个特征值对应的特征向量。这些特征向量就是数据方差最大的方向。
PCA 不是特征选择,是特征变换。每个主成分都是原始特征的线性组合——可解释性降低了,但信息保留率更高。
异常检测
异常检测是找"不一样"的样本。简单方法:如果一个点到它最近的聚类中心的距离超过某个阈值,它就可能是异常点。
# 基于距离的简单异常检测
from sklearn.ensemble import IsolationForest
iso_forest = IsolationForest(contamination=0.1, random_state=42)
anomaly_labels = iso_forest.fit_predict(X) # -1 为异常
anomaly_score = iso_forest.decision_function(X)
n_anomalies = sum(anomaly_labels == -1)
print(f"检测到 {n_anomalies} 个异常点")Isolation Forest 是更高效的异常检测器。它的想法很巧妙:异常点更容易被孤立。在随机划分的特征空间中,异常点往往只需要很少的分割就能被 Isolation(孤立)出来。
常见陷阱
- K-means 对初始化敏感。不同初始中心输出不同结果。多次运行取最佳(用 k-means++ 初始化可缓解)。
- K-means 假设各簇大小相似——一个巨大的簇旁边有个小簇,小簇会被吃掉。
- PCA 假设方差最大的方向最有信息量——但方差小也可能包含重要的判别信息。
- 异常检测中 contamination 参数设多少?如果真实异常率是 1% 但设成 10%,你会得到大量假阳性。
- 降维后的可视化可能误导——PCA 保留的只是方差信息,不代表原始数据中所有的结构关系。
通关挑战
- 热身(10 分钟):用 sklearn.datasets.make_blobs 生成 3 簇数据,用 K-means 聚类,打印轮廓系数。分别用 K=2,3,4,5 运行,看哪个 K 最好。
- 挑战(30 分钟):在 MNIST(手写数字)上跑 PCA。降到 2 维后可视化——观察同一数字的点是否聚在一起,不同数字是否分开。
- 观察:在异常检测中,把一个正常点的某个特征值改成极端值(比如从 0.5 变成 50),观察它是否被标记为异常点。
旅人笔记
无监督学习是没有标准答案的学习。聚类揭示数据的分组结构,降维压缩信息的保留核心语义,异常检测在"什么是正常"和"什么是异常"之间画了一条模糊的线。三者的共同前提:数据本身足够有结构。
-> 下一站预告
无论用什么模型,你都面临同一个问题:怎么知道模型好不好?下一章,评估与调优的工具箱。