Skip to content

元数据卡

  • 前置知识:ch04 ML 基础、Vol 10 线性代数
  • 预计时间:45 分钟
  • 核心难度:进阶
  • 阅读模式:高度专注
  • 完成标志:能实现 K-means 和 PCA,理解降维和异常检测的基本原理

你的进度

在模型工坊里待了这么久,你习惯了每次训练都要准备标签数据——每一条数据都标好了正确答案。但工坊的角落里有一堆从未标记的档案:几万条行为日志、上千张未分类的图纸、一堆没有分组的任务记录。

阿花在信里写:“有些事你自己看也能看出来规律,对吧?”

你看向工坊的“无监督”工作台。

你的任务

无监督学习在缺乏标签的情况下挖掘数据的内在模式。你学习三类核心任务:聚类(发现自然分组)、降维(压缩数据而不丢结构)、异常检测(找出离群的样本)。三者共享同一个信念:数据本身包含结构,不需要外部答案来定义。

本章分层

  • 必读:K-means 聚类、PCA 主成分分析、简单异常检测
  • 选读:DBSCAN 密度聚类、t-SNE 可视化
  • 进阶:谱聚类与拉普拉斯特征映射

破局 · 溯源

你有一个仓库,里面堆着几千件物品。没有标签告诉你"这是工具""这是食材"——但你发现铁制的物品大多在一堆、木制的大多在另一堆。它们自己"聚合"了。

聚类就是在特征空间中发现这种自然聚合。而如果你发现某个物品离所有堆都很远——它可能是坏掉了,或者根本不属于这里(异常检测)。

K-means 聚类

K-means 是最直观的聚类算法。你指定要分成 K 堆,算法迭代优化:

  1. 随机初始化 K 个中心
  2. 每个点分配到最近的中心
  3. 更新中心为每个类的均值
  4. 重复直到收敛
python
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。

python
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 找到数据方差最大的方向(主成分),将数据投影到低维。

python
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 不是特征选择,是特征变换。每个主成分都是原始特征的线性组合——可解释性降低了,但信息保留率更高。

异常检测

异常检测是找"不一样"的样本。简单方法:如果一个点到它最近的聚类中心的距离超过某个阈值,它就可能是异常点。

python
# 基于距离的简单异常检测
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),观察它是否被标记为异常点。

旅人笔记

无监督学习是没有标准答案的学习。聚类揭示数据的分组结构,降维压缩信息的保留核心语义,异常检测在"什么是正常"和"什么是异常"之间画了一条模糊的线。三者的共同前提:数据本身足够有结构。

-> 下一站预告

无论用什么模型,你都面临同一个问题:怎么知道模型好不好?下一章,评估与调优的工具箱。

Built with VitePress | Software Systems Atlas