公司动态
无监督学习核心算法解析:从聚类、降维到关联规则实战
1. 从“有”到“无”无监督学习的核心思想与价值在AI的世界里我们常常听到“监督学习”这个词它就像一个手把手教孩子认字的老师给每个样本都贴上明确的标签比如“这是猫”、“那是狗”。但现实世界的数据绝大多数都是没有标签的。想象一下你面对一个装满各种未知物品的仓库没人告诉你它们是什么但你需要把它们分门别类或者找出它们内在的摆放规律。这就是无监督学习要解决的问题。无监督学习的核心魅力在于“发现”而非“预测”。它不关心“这张图片是不是猫”而是试图回答“这些数据点之间有什么关系它们可以自然地分成几组数据背后隐藏着什么样的结构或分布” 这种能力让无监督学习成为探索未知数据、进行数据预处理、特征提取乃至理解复杂系统底层模式的强大工具。从电商平台根据你的浏览行为将商品聚类推荐到生物信息学中通过基因表达数据发现新的细胞亚型再到金融领域检测异常交易模式无监督学习的应用无处不在。它处理的正是那个庞大、杂乱、未被标记的“现实世界”。2. 无监督学习的三大支柱聚类、降维与关联无监督学习并非一个单一算法而是一个包含多种任务的大家族。理解其核心任务是掌握其原理的第一步。我们可以将其归纳为三大支柱性任务。2.1 聚类分析物以类聚人以群分聚类可能是最直观的无监督学习任务。它的目标是将数据集中的样本划分为若干个“簇”使得同一簇内的样本彼此相似而不同簇的样本差异较大。这里的关键在于“相似性”如何定义这直接引出了不同的聚类算法。K-Means算法是其中最著名、最常用的代表。它的思想非常朴素先随机指定K个中心点然后将每个数据点分配给离它最近的中心点所属的簇接着重新计算每个簇所有点的均值作为新的中心点不断迭代直到中心点不再变化或变化很小。这个过程就像不断调整聚会的中心位置让每个客人都去离自己最近的聚会点同时聚会点也根据客人的位置不断移动最终达到稳定。K-Means的优势是简单、高效适用于球形分布且规模较大的数据集。但它需要预先指定K值且对初始中心点的选择敏感对非球形分布或大小差异大的簇效果不佳。为了克服K值的难题层次聚类提供了另一种思路。它不需要预先指定簇的数量而是构建一个树状的聚类结构。有两种主要策略凝聚的自底向上和分裂的自顶向下。凝聚法开始时将每个样本视作一个单独的簇然后迭代地合并最相似的两个簇直到所有样本合并为一个簇形成一棵树树状图。通过在这棵树上选择一个“切割”高度就可以得到任意数量的簇。这种方法能提供更丰富的层次信息但计算复杂度较高。DBSCAN算法则从密度的角度出发它将簇定义为密度相连的点的最大集合并能有效识别噪声点。它不需要指定簇数能发现任意形状的簇但对密度参数敏感。2.2 降维技术从纷繁复杂中抓住本质当数据的特征维度成百上千时例如图像像素、文本词向量我们会面临“维度灾难”数据稀疏、计算困难、难以可视化且很多特征是冗余或噪声。降维的目标就是在尽可能保留原始数据重要信息如方差、结构的前提下将数据投影到低维空间。主成分分析是线性降维的基石。PCA寻找数据方差最大的方向主成分作为新的坐标轴。第一个主成分是数据方差最大的方向第二个主成分是与第一个正交且方差次大的方向依此类推。通过选取前几个主成分就能用少数几个不相关的“综合特征”来近似表示原始数据。这就像给一个三维物体拍照从某个最佳角度主成分方向拍下的二维照片最大程度地保留了物体的轮廓信息。PCA计算高效有完美的数学解但它是一种线性方法无法捕捉复杂的非线性关系。为了处理非线性结构t-SNE和UMAP等非线性降维方法应运而生。t-SNE的核心思想是在高维空间相似的点在低维映射中距离应该近不相似的点距离应该远。它通过优化一个基于概率分布通常是高斯分布和t分布的损失函数来实现。t-SNE在可视化高维数据如MNIST手写数字、基因数据时效果惊人能清晰展现出复杂的流形结构。但它的计算开销大且低维嵌入的结果不具有全局意义距离不能直接比较。UMAP在理论基础上与t-SNE有相似之处但采用了不同的拓扑和优化方法通常速度更快并能更好地保留数据的全局结构。2.3 关联规则学习挖掘“啤酒与尿布”的故事关联规则学习旨在从大规模数据集中发现项item之间的有趣关系最经典的应用就是零售业的购物篮分析即著名的“啤酒与尿布”案例。它的目标是找出形如{尿布} - {啤酒}的规则表示购买尿布的顾客很可能也会购买啤酒。Apriori算法是解决这一问题的先驱。它的核心基于一个先验性质一个频繁项集的所有子集也一定是频繁的。反之如果一个项集不是频繁的那么它的所有超集也不可能是频繁的。算法通过多次扫描数据库迭代地生成候选频繁项集并计算其支持度出现的频率从而逐步找出所有满足最小支持度阈值的频繁项集再从这些频繁项集中产生置信度规则可靠性够高的关联规则。Apriori的缺点是需要多次扫描数据库I/O开销大当项很多时候选集数量会爆炸式增长。FP-Growth算法则采用了不同的策略。它首先构建一个称为FP-tree频繁模式树的压缩数据结构将数据库中的频繁项信息压缩存储在一棵树中。然后通过递归地挖掘这棵树来发现所有的频繁项集而无需生成大量的候选集。FP-Growth通常比Apriori快一个数量级以上尤其是在处理稠密数据集时优势明显。3. 算法实现解析从理论到代码的跨越理解了原理下一步就是动手实现。这里我们选择几个代表性算法用Python借助scikit-learn库来展示其核心实现逻辑和关键参数。纸上得来终觉浅绝知此事要躬行。3.1 K-Means聚类实战手写数字的自动分组我们以经典的MNIST手写数字数据集简化版为例看看K-Means如何在没有标签的情况下将数字图像分组。import numpy as np import matplotlib.pyplot as plt from sklearn.datasets import load_digits from sklearn.cluster import KMeans from sklearn.decomposition import PCA from sklearn.metrics import silhouette_score # 加载数据 digits load_digits() X digits.data # 图像数据每行是一个64维的向量8x8图像展平 y digits.target # 真实标签用于后续评估注意聚类本身不知道这个 # 使用K-Means聚类假设我们想分成10类因为数字是0-9 kmeans KMeans(n_clusters10, initk-means, n_init10, random_state42) cluster_labels kmeans.fit_predict(X) # 评估聚类效果轮廓系数-1到1越大越好 silhouette_avg silhouette_score(X, cluster_labels) print(f轮廓系数所有样本: {silhouette_avg:.3f}) # 可视化用PCA降到2维来看聚类结果 pca PCA(n_components2) X_pca pca.fit_transform(X) plt.figure(figsize(10, 5)) # 子图1根据真实标签着色 plt.subplot(1, 2, 1) scatter plt.scatter(X_pca[:, 0], X_pca[:, 1], cy, cmaptab10, s5) plt.colorbar(scatter, label真实数字) plt.title(PCA投影真实标签) plt.xlabel(主成分1) plt.ylabel(主成分2) # 子图2根据聚类标签着色 plt.subplot(1, 2, 2) scatter plt.scatter(X_pca[:, 0], X_pca[:, 1], ccluster_labels, cmaptab10, s5) plt.colorbar(scatter, label聚类标签) plt.title(PCA投影K-Means聚类结果) plt.xlabel(主成分1) plt.ylabel(主成分2) plt.tight_layout() plt.show() # 查看聚类中心原型 fig, axes plt.subplots(2, 5, figsize(10, 4)) centers kmeans.cluster_centers_.reshape(10, 8, 8) # 将中心点变回8x8图像 for ax, center in zip(axes.flat, centers): ax.imshow(center, interpolationnearest, cmapplt.cm.binary) ax.set_xticks([]) ax.set_yticks([]) plt.suptitle(K-Means学习到的10个聚类中心数字原型) plt.show()这段代码揭示了几个关键点初始化的重要性initk-means是一种智能初始化方法能有效避免糟糕的初始中心导致收敛到局部最优解比纯随机初始化好得多。K值的选择我们凭先验知识设了10。但在无监督场景下通常需要用“肘部法则”绘制不同K值对应的误差平方和找拐点或轮廓系数来辅助选择。评估的困境轮廓系数是一种内部评估指标不依赖真实标签。我们虽然有真实标签y可以计算调整兰德指数等外部指标来评估但在真正的无监督任务中这些标签是不存在的评估聚类质量本身就是一个挑战。结果的解读从可视化结果可以看到聚类结果与真实标签有对应关系但并不完全一致。有些“2”和“7”可能被分到一起因为书写风格相似。这恰恰说明了聚类是根据数据本身的分布像素相似度来划分而不是我们人类理解的语义标签。3.2 PCA降维与可视化揭开高维数据的面纱让我们用PCA来探索一个更高维的数据集比如鸢尾花数据集并将其与原始特征空间进行对比。from sklearn.datasets import load_iris from sklearn.preprocessing import StandardScaler iris load_iris() X iris.data y iris.target feature_names iris.feature_names # 标准化对于PCA非常重要因为PCA对变量的尺度敏感 scaler StandardScaler() X_scaled scaler.fit_transform(X) # 执行PCA保留2个主成分用于可视化 pca PCA(n_components2) X_pca pca.fit_transform(X_scaled) print(主成分解释方差比例:, pca.explained_variance_ratio_) print(累计解释方差比例:, np.cumsum(pca.explained_variance_ratio_)) plt.figure(figsize(12, 5)) # 子图1原始特征空间选择两个特征 plt.subplot(1, 2, 1) for target, color in zip([0,1,2], [navy, turquoise, darkorange]): plt.scatter(X[ytarget, 0], X[ytarget, 1], colorcolor, alpha.8, lw2, labeliris.target_names[target]) plt.xlabel(feature_names[0]) plt.ylabel(feature_names[1]) plt.legend(locbest, shadowFalse, scatterpoints1) plt.title(原始特征空间萼片长 vs 萼片宽) # 子图2PCA降维后的空间 plt.subplot(1, 2, 2) for target, color in zip([0,1,2], [navy, turquoise, darkorange]): plt.scatter(X_pca[ytarget, 0], X_pca[ytarget, 1], colorcolor, alpha.8, lw2, labeliris.target_names[target]) plt.xlabel(主成分 1 (方差占比: {:.2f}).format(pca.explained_variance_ratio_[0])) plt.ylabel(主成分 2 (方差占比: {:.2f}).format(pca.explained_variance_ratio_[1])) plt.legend(locbest, shadowFalse, scatterpoints1) plt.title(PCA降维空间) plt.tight_layout() plt.show() # 查看主成分的构成载荷 print(\n主成分载荷特征向量) components pca.components_ for i, component in enumerate(components): print(fPC{i1}:, end ) for name, weight in zip(feature_names, component): print(f{name}: {weight:.3f}, end | ) print()通过这个例子我们可以深入理解PCA标准化是必须的如果特征量纲不同如厘米和克数值大的特征会主导方差导致PCA结果失真。标准化使所有特征均值为0方差为1处于同等地位。解释方差第一个主成分解释了约73%的总方差第二个解释了约23%两个主成分加起来解释了超过95%的方差。这意味着我们仅用两个维度就抓住了原始四个维度几乎全部的信息。载荷分析从输出可以看到PC1在所有原始特征上都有较高的正载荷可以理解为是一个“整体尺寸”因子。PC2则在花瓣长度和宽度上有正载荷在萼片宽度上有负载荷可能反映了“花瓣与萼片比例”的差异。这为数据提供了可解释的新视角。可视化对比在原始特征空间中setosa类山鸢尾与其他两类线性可分但versicolor和virginica变色鸢尾和维吉尼亚鸢尾严重重叠。在PCA空间中三个类别的分离度明显改善尤其是PC1轴几乎完美地将setosa区分开。这展示了PCA在保留类别判别信息方面的潜力。3.3 使用t-SNE探索复杂流形对于更复杂、非线性的高维数据如手写数字t-SNE能提供更惊艳的可视化效果。from sklearn.manifold import TSNE import time # 使用之前加载的digits数据 X_digits digits.data y_digits digits.target # 为了演示速度先取一个子集 np.random.seed(42) subset_indices np.random.choice(len(X_digits), 500, replaceFalse) X_subset X_digits[subset_indices] y_subset y_digits[subset_indices] print(开始t-SNE降维...) start_time time.time() tsne TSNE(n_components2, initpca, random_state42, perplexity30, n_iter1000) X_tsne tsne.fit_transform(X_subset) print(ft-SNE完成耗时 {time.time() - start_time:.2f} 秒) plt.figure(figsize(10, 8)) scatter plt.scatter(X_tsne[:, 0], X_tsne[:, 1], cy_subset, cmaptab10, s20, alpha0.8) plt.colorbar(scatter, label数字标签) plt.title(t-SNE可视化手写数字500个样本) plt.xlabel(t-SNE维度 1) plt.ylabel(t-SNE维度 2) plt.grid(True, alpha0.3) plt.show()关键参数与实操心得perplexity困惑度这是t-SNE最重要的参数之一可以理解为有效近邻数量的平滑度量。通常设置在5到50之间。值太小会形成许多细碎的小簇值太大可能模糊不同簇之间的边界。对于MNIST这种规模的数据30-50是一个不错的起点。你需要根据数据规模和期望的聚类粒度进行调整。init初始化使用pcaPCA初始化通常比随机初始化更稳定能产生更好的全局结构并减少迭代次数。n_iter迭代次数默认是1000。如果算法没有收敛最后几次迭代损失函数下降不明显可以适当增加。可以通过检查tsne.kl_divergence_属性来观察最终的KL散度。性能警告t-SNE的计算复杂度是O(N^2)对于上万级别的样本就非常慢了。通常先用PCA将维度降到50左右再应用t-SNE可以大幅提速且不影响效果。结果解读在t-SNE图中你会看到同一数字的样本紧密地聚集在一起形成明显的“群岛”。不同数字的“群岛”之间被空白区域隔开。但要注意图中点与点之间的距离没有绝对意义不能像PCA那样解释轴的含义。t-SNE图擅长展示局部结构和聚类但牺牲了全局距离的准确性。4. 无监督学习的高级话题与前沿方向掌握了基础算法和实现我们可以将视野放宽看看无监督学习领域正在发生什么以及如何应对更复杂的挑战。4.1 深度生成模型学习数据的本质分布传统的聚类和降维可以看作是在学习数据的“表示”而深度生成模型的目标是学习数据本身的“分布”从而能够生成新的、与原始数据相似的数据样本。这代表了无监督学习的一个高峰。变分自编码器是其中的杰出代表。VAE的结构包含一个编码器将输入数据压缩为潜在空间中的分布参数如均值和方差和一个解码器从潜在空间采样并重构数据。其核心思想是强制潜在空间服从一个简单的先验分布如标准正态分布并通过最大化证据下界来训练。这样我们不仅得到了一个可以降维的编码器还得到了一个能从噪声生成新数据的解码器。VAE生成的图像可能模糊但它在潜在空间插值平滑具有良好的数学解释性。生成对抗网络则采用了博弈论的思路。它包含一个生成器G和一个判别器D。G的目标是生成足以乱真的假数据D的目标是准确区分真实数据和G生成的假数据。两者在对抗中共同进化最终G能生成极其逼真的样本。GAN在图像生成、风格迁移等领域取得了震撼人心的成果。然而GAN的训练 notoriously 不稳定容易出现模式崩溃生成器只生成少数几种样本等问题。注意深度生成模型训练需要大量的数据、计算资源和调参经验。对于初学者建议从在标准数据集如MNIST, Fashion-MNIST上复现VAE/GAN开始深刻理解其损失函数和训练动态再尝试应用到自己的领域。4.2 自监督学习创造标签自我监督自监督学习是无监督学习的一个热门分支其核心思想是从数据本身自动构造监督信号伪标签来进行学习。这打破了“无标签就不能进行深度表征学习”的局限。一个经典的例子是对比学习如SimCLR、MoCo等框架。它们的基本流程是对同一张图像进行两次不同的随机数据增强如裁剪、颜色抖动、模糊得到两个相关的视图。这两个视图被视为正样本对而同一批次中其他图像产生的视图则被视为负样本。模型的目标是学习一个编码器使得正样本对在表征空间中的距离很近而与负样本的距离很远。通过这种方式模型学会了忽略无关的像素变化抓住图像语义不变的特征。预训练好的编码器可以轻松迁移到下游的有监督任务如图像分类只需少量标签就能取得优异性能。在自然语言处理领域BERT等模型的预训练阶段本质上也是一种自监督学习。通过“掩码语言模型”任务随机遮盖句子中的词让模型预测和“下一句预测”任务模型从海量无标签文本中学习了丰富的语言知识。4.3 异常检测在常态中发现异类异常检测是无监督学习的一个重要应用方向其目标是识别与大多数数据显著不同的稀有样本或事件。这在欺诈检测、设备故障预警、网络入侵发现等领域至关重要。孤立森林是一种高效且直观的异常检测算法。它的基本思想是异常点由于与正常点差异大更容易在随机划分的特征空间中被“孤立”。算法通过随机选择特征和划分值来构建多棵二叉树森林。数据点从根节点走到叶子节点的路径长度越短说明它越容易被孤立也就越可能是异常点。IForest对高维大数据集处理效率高且不需要对数据分布做任何假设。局部离群因子则从密度的角度定义异常。一个点的LOF分数表征了其局部密度与邻居局部密度的比值。如果某点的密度远低于其邻居的密度即LOF 1那么它很可能是一个异常点。LOF是一种相对度量能识别出局部区域的异常适用于密度不均匀的数据集。在实际应用中选择哪种异常检测方法取决于数据特点和业务需求。通常需要结合多种方法并设定一个合理的阈值如IForest的异常分数阈值或LOF的倍数来判定异常。更重要的是检测出的“异常”需要业务专家进行解读区分是真正的故障/欺诈还是仅仅是一种罕见但正常的模式。5. 实践中的挑战、评估与调优策略将无监督学习算法应用到真实项目中远比跑通一个Demo复杂。你会面临数据、算法、评估、调优等一系列挑战。5.1 数据预处理成败在此一举无监督学习对数据质量异常敏感因为没有标签来纠正模型的偏差。缺失值处理简单的删除或均值填充可能引入偏差。对于聚类可以考虑使用能够处理缺失值的算法如某些基于模型的聚类或使用迭代插补法。特征缩放基于距离的算法如K-Means、层次聚类、DBSCAN和PCA必须进行特征缩放如标准化或归一化否则量纲大的特征将主导结果。树模型如孤立森林则通常不需要。类别特征编码对于包含类别特征的数据不能直接使用欧氏距离。需要将其转换为数值形式如独热编码。但要注意独热编码会大幅增加维度且可能使距离计算失真。可以考虑使用能够处理混合类型数据的算法或使用专门的距离度量如Gower距离。降维作为预处理对于超高维数据如文本TF-IDF向量直接进行聚类效果往往很差。可以先使用PCA、TruncatedSVD或UMAP进行降维去除噪声和冗余再在低维空间进行聚类效果和效率都会提升。5.2 如何评估没有标签的结果这是无监督学习最大的痛点。我们分内部评估和外部评估来看。内部评估指标仅使用数据集本身和聚类结果来计算。轮廓系数计算一个样本与同簇其他样本的平均距离内聚度a和与最近其他簇中所有样本的平均距离分离度b轮廓系数 s (b - a) / max(a, b)。取值范围[-1,1]越大越好。适用于凸形簇。戴维森堡丁指数簇内平均距离与簇间平均距离的比值越小越好。Calinski-Harabasz指数簇间离散度与簇内离散度的比值越大越好。注意这些指标各有侧重且都有其局限性。例如DBI对于非凸簇效果不佳。最好的做法是结合多个指标并与可视化如降维后绘图一起观察。外部评估指标在有真实标签的情况下使用这通常用于验证算法有效性而非实际无监督场景。调整兰德指数衡量两个划分聚类结果与真实标签之间的一致性取值范围[-1,1]1表示完全一致0表示随机划分。互信息衡量两个划分共享的信息量有标准化版本NMI可以消除聚类数的影响。实践中的“土办法”在没有标签时业务专家的解读至关重要。可以将聚类结果抽样展示给领域专家看分群是否有业务意义。或者将聚类标签作为一个新特征输入到一个下游预测任务如果有的话看是否能提升模型性能。5.3 关键参数调优实战指南以最常用的K-Means和DBSCAN为例分享我的调参经验。K-Means寻找最佳的K肘部法则计算K从1到一个较大值如10时模型的惯性样本到其簇中心的距离平方和。绘制K-惯性曲线。惯性会随着K增大而减小我们寻找曲线上的“拐点”肘部即惯性下降速度突然变缓的点。inertias [] K_range range(1, 11) for k in K_range: kmeans KMeans(n_clustersk, random_state42).fit(X_scaled) inertias.append(kmeans.inertia_) plt.plot(K_range, inertias, bx-) plt.xlabel(k) plt.ylabel(惯性) plt.title(肘部法则) plt.show()轮廓系数法计算不同K值下的平均轮廓系数选择系数最高的K。轮廓系数综合考虑了内聚度和分离度。from sklearn.metrics import silhouette_samples, silhouette_score silhouette_avgs [] for k in K_range[1:]: # 轮廓系数要求k2 kmeans KMeans(n_clustersk, random_state42).fit(X_scaled) score silhouette_score(X_scaled, kmeans.labels_) silhouette_avgs.append(score) print(fK{k}, 轮廓系数{score:.3f})业务约束有时K值由业务决定比如你想把客户分成高、中、低价值3档。DBSCAN理解eps和min_sampleseps邻域半径。这是决定两个点是否“密度相连”的核心参数。值太小每个点都可能成为噪声或形成大量小簇值太大所有点可能被合并成一个簇。一个经验法则是使用k-距离图计算每个点到其第k个最近邻的距离按降序排序后绘图。寻找距离发生突变拐点的位置作为eps的参考值。min_samples通常就取这个k值一个起始点是4或5。from sklearn.neighbors import NearestNeighbors neigh NearestNeighbors(n_neighbors4) # min_samples通常设为4 nbrs neigh.fit(X_scaled) distances, indices nbrs.kneighbors(X_scaled) distances np.sort(distances[:, -1]) # 取第4近邻的距离 plt.plot(distances) plt.xlabel(Points sorted by distance) plt.ylabel(4th nearest neighbor distance) plt.title(k-距离图 (用于估计eps)) plt.show()min_samples核心点的邻域内至少需要的样本数包括点自身。它决定了形成簇所需的最小密度。对于噪声较多或维度较高的数据需要适当调高此值以使算法对噪声更鲁棒。通常从4开始尝试。调参是一个迭代过程。设定参数后一定要可视化结果通过降维并检查每个簇的大小和噪声点的比例看是否符合业务直觉。没有“银弹”参数需要结合数据特点和算法原理反复试验。