ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

无监督学习实战:聚类、降维与异常检测的算法选型与避坑指南

无监督学习实战:聚类、降维与异常检测的算法选型与避坑指南 1. 无监督学习到底在解决什么问题很多人学机器学习是从线性回归、逻辑回归这些有监督算法入门的手里有特征有标签模型学的是从X到Y的映射关系。但实际工作中你拿到的数据绝大多数时候是没有标签的。比如一批用户行为日志、一堆商品描述文本、一组传感器读数没人告诉你哪些用户是一类、哪些文本属于同一主题。这时候有监督学习那套玩法直接失效因为你连Y是什么都不知道。无监督学习就是干这个的。它不依赖标签直接从数据本身的结构出发找出隐藏在特征空间里的规律。核心任务大致分三类聚类把相似的样本归到一起、降维把高维数据压缩到低维同时保留关键信息、密度估计判断数据在空间中的分布密度进而发现异常点。这三类任务对应的典型算法分别是K-Means、PCA和核密度估计后面会逐一拆开讲。这篇文章适合谁看如果你已经了解机器学习的基本概念知道什么是特征、什么是训练集但一碰到没有标签的数据就不知道从哪下手那这篇内容就是写给你的。我会从算法原理、参数选择、代码实操到踩坑经验把无监督学习这条线完整串一遍。所有代码基于Python的scikit-learn生态版本建议0.24以上Python 3.8。先明确一个认知无监督学习的结果没有“正确答案”可以对照。有监督学习你可以算准确率、召回率无监督学习不行。它的输出需要你用业务逻辑去解释和验证。这是很多人从有监督转到无监督时最不适应的地方也是后面我会反复强调“业务验证”的原因。2. 聚类算法从K-Means到DBSCAN的选型逻辑2.1 K-Means为什么是最常用的起点K-Means的思路极其朴素假设数据要分成K个簇每个簇有一个中心点把每个样本分配到离它最近的中心然后重新计算中心反复迭代直到中心不再移动。目标函数是最小化每个样本到其所属簇中心的距离平方和也就是常说的SSESum of Squared Errors。它的优势在于计算复杂度低O(n K d * t)n是样本数K是簇数d是维度t是迭代次数。对于几万到几十万级别的数据普通笔记本就能跑。但它的局限也很明显需要预先指定K值对初始中心敏感只能发现球形簇对异常值不鲁棒。K值怎么选最常用的是肘部法则。具体做法是让K从2开始递增每次跑一遍K-Means记录SSE。把K作为横轴、SSE作为纵轴画出来曲线会在某个K值处出现明显的拐点像手肘一样那个位置就是相对合理的K。但肘部法则不是万能的有时候曲线很平滑没有明显拐点。这时候可以结合轮廓系数Silhouette Score来判断轮廓系数越接近1说明簇内越紧凑、簇间越分离。初始中心的选择上scikit-learn默认用的是k-means它不是随机选K个点而是第一个中心随机选后续每个中心都选离已选中心尽可能远的点。这个改进让K-Means的收敛速度和结果稳定性提升了很多。如果你自己实现K-Means强烈建议也用k-means不要用纯随机初始化。from sklearn.cluster import KMeans from sklearn.metrics import silhouette_score import matplotlib.pyplot as plt sse [] sil_scores [] K_range range(2, 11) for k in K_range: km KMeans(n_clustersk, initk-means, n_init10, random_state42) labels km.fit_predict(X_scaled) sse.append(km.inertia_) sil_scores.append(silhouette_score(X_scaled, labels)) fig, ax1 plt.subplots() ax1.plot(K_range, sse, bo-) ax1.set_xlabel(K) ax1.set_ylabel(SSE) ax2 ax1.twinx() ax2.plot(K_range, sil_scores, ro--) ax2.set_ylabel(Silhouette Score) plt.show()这段代码里有个参数n_init10意思是跑10次不同的初始化取SSE最小的那次结果。scikit-learn 1.4之后默认值从10改成了‘auto’但手动设成10更稳妥。random_state固定住是为了结果可复现实际生产中可以多跑几个随机种子看稳定性。2.2 层次聚类和DBSCAN的适用场景K-Means搞不定的情况层次聚类和DBSCAN可以补位。层次聚类分两种自底向上凝聚型和自顶向下分裂型。常用的是凝聚型每个样本一开始自成一簇然后不断合并距离最近的两个簇直到所有样本合并成一个大簇。这个过程会生成一棵树状图Dendrogram你可以通过观察树状图来决定切在哪里从而确定簇的数量。它的好处是不需要预先指定K而且能发现非球形簇。缺点是计算复杂度高O(n^3)起步样本量超过一万就不太适合了。DBSCAN是另一种思路它不关心簇的形状而是基于密度。核心参数有两个eps邻域半径和min_samples成为核心点所需的最小邻居数。如果一个点的eps邻域内至少有min_samples个点它就是核心点核心点及其密度可达的点构成一个簇不属于任何簇的点被标记为噪声。DBSCAN最大的优势是不需要指定簇的数量能发现任意形状的簇还能自动识别异常点。缺点是对eps和min_samples敏感而且当数据密度不均匀时效果会打折扣。eps怎么选一个实用的方法是画k-距离图。计算每个点到它第k个最近邻的距离k通常取min_samples的值把这些距离从小到大排序画出来曲线拐弯的地方对应的距离就是比较合适的eps。这个方法我在多个项目里用过比盲猜靠谱得多。from sklearn.neighbors import NearestNeighbors from sklearn.cluster import DBSCAN import numpy as np # k-距离图选eps k 5 nbrs NearestNeighbors(n_neighborsk).fit(X_scaled) distances, indices nbrs.kneighbors(X_scaled) distances np.sort(distances[:, k-1], axis0) plt.plot(distances) plt.xlabel(Points sorted by distance) plt.ylabel(f{k}-th nearest neighbor distance) plt.show() # 根据拐点选eps比如0.5 db DBSCAN(eps0.5, min_samples5) labels db.fit_predict(X_scaled) n_clusters len(set(labels)) - (1 if -1 in labels else 0) n_noise list(labels).count(-1) print(f簇数量: {n_clusters}, 噪声点数量: {n_noise})注意DBSCAN的labels里-1代表噪声点不是某个簇。计算簇数量时一定要把-1排除掉这个坑我见过不止一个新手踩。2.3 聚类效果怎么验证无监督学习没有标签但不代表完全没法评估。除了前面提到的轮廓系数还有Calinski-Harabasz指数也叫方差比准则和Davies-Bouldin指数。这三个指标各有侧重轮廓系数综合考虑簇内紧凑度和簇间分离度取值[-1,1]越大越好Calinski-Harabasz指数是簇间离散度与簇内离散度的比值越大越好Davies-Bouldin指数是簇内散度与簇间距离的比值越小越好。但我要说一句实在话这些指标只能作为参考不能作为唯一标准。最终还是要回到业务上去看。比如你把用户聚成5类每一类的行为特征是否能用业务语言描述清楚运营策略是否能针对性地落地如果聚类结果无法解释那指标再好看也没有意义。3. 降维技术PCA、t-SNE与UMAP的实战取舍3.1 PCA的数学直觉和实操细节PCA主成分分析是最经典的线性降维方法。它的核心思想是找到一组新的坐标轴使得数据投影到这些轴上的方差最大化。第一个新轴第一主成分是数据方差最大的方向第二个新轴与第一个正交且方差次大以此类推。通过保留前几个主成分就能在尽量不丢失信息的前提下降低维度。数学上PCA就是对数据的协方差矩阵做特征值分解或对数据矩阵做SVD。特征值大的对应的特征向量就是主成分方向特征值的大小反映了该方向上的方差大小。解释方差比explained variance ratio就是每个主成分的方差占总方差的比例通常我们会看累计解释方差比达到85%或90%时需要保留多少个主成分。from sklearn.decomposition import PCA from sklearn.preprocessing import StandardScaler # PCA之前必须标准化 scaler StandardScaler() X_scaled scaler.fit_transform(X) pca PCA(n_components0.9) # 保留90%方差 X_pca pca.fit_transform(X_scaled) print(f保留主成分数量: {pca.n_components_}) print(f累计解释方差比: {pca.explained_variance_ratio_.sum():.4f})注意PCA之前一定要做标准化。如果某个特征的量纲是几千另一个是零点几不做标准化的话量纲大的特征会主导主成分方向降维结果就偏了。这个细节看起来简单但实际项目中我见过不少人忘记。PCA的局限在于它是线性的。如果数据分布在非线性流形上比如瑞士卷那种结构PCA降维后会丢失大量信息。这时候就需要t-SNE或UMAP。3.2 t-SNE和UMAP可视化利器但不是万能药t-SNEt分布随机邻域嵌入的核心思路是在高维空间中用高斯分布衡量样本之间的相似度在低维空间中用t分布衡量相似度然后最小化两个分布之间的KL散度。t分布的长尾特性使得低维空间中不同簇之间的距离被拉大所以t-SNE的可视化效果通常很惊艳簇与簇之间分得很开。但t-SNE有几个必须知道的坑。第一它主要用于可视化不适合作为特征提取的前置步骤。因为t-SNE不保留全局结构只保留局部邻域关系降维后的坐标没有距离意义。第二perplexity参数很关键通常取5到50之间小数据集取小值大数据集取大值。第三t-SNE计算量大O(n^2)起步样本超过几万就跑得很慢。第四每次运行结果不一样因为它是随机初始化的虽然可以通过random_state固定但不同参数下的结果差异可能很大。UMAPUniform Manifold Approximation and Projection是近几年流行起来的降维方法可以看作是t-SNE的改进版。它基于黎曼几何和代数拓扑的理论计算速度比t-SNE快而且能更好地保留全局结构。UMAP的两个核心参数是n_neighbors控制局部与全局的平衡和min_dist控制低维空间中点的聚集程度。n_neighbors越大越强调全局结构min_dist越小点越聚集。import umap from sklearn.manifold import TSNE # t-SNE tsne TSNE(n_components2, perplexity30, random_state42) X_tsne tsne.fit_transform(X_scaled) # UMAP reducer umap.UMAP(n_neighbors15, min_dist0.1, random_state42) X_umap reducer.fit_transform(X_scaled)实际选型上我的经验是如果只是做可视化展示t-SNE和UMAP都可以UMAP速度更快、全局结构更好如果需要降维后作为其他模型的输入优先考虑PCA因为PCA有明确的数学变换可以应用到新数据上而t-SNE和UMAP不支持transform新数据UMAP可以通过训练好的模型transform但稳定性不如PCA。4. 密度估计与异常检测从理论到落地4.1 核密度估计的基本原理密度估计的目标是估计数据在空间中的概率密度函数。最朴素的方法是直方图但直方图对bin的宽度和起点敏感而且不连续。核密度估计KDE用一个平滑的核函数通常是高斯核在每个数据点处放置一个“小山峰”然后把这些山峰叠加起来得到平滑的密度曲线。带宽bandwidth是KDE最关键的参数带宽太小曲线太尖锐带宽太大曲线太平滑。scikit-learn的KernelDensity类默认用高斯核带宽可以通过交叉验证或者经验法则如Silverman规则来选择。KDE在异常检测中的用法很直接密度低的地方就是异常点。你可以设定一个密度阈值低于阈值的样本标记为异常。但KDE在高维空间中效果会急剧下降因为高维空间中数据稀疏密度估计变得不可靠。所以KDE通常用于一维或二维数据的异常检测高维数据更适合用Isolation Forest或One-Class SVM。4.2 Isolation Forest和One-Class SVM的对比Isolation Forest孤立森林的思路很巧妙异常点是少数且容易被孤立的。它随机选择特征和分割点递归地分割数据直到每个点都被孤立。异常点因为离群通常只需要很少的分割次数就能被孤立所以在树中的路径长度短。Isolation Forest就是基于路径长度来判断异常程度的。One-Class SVM则是把数据映射到高维空间找到一个包含绝大多数样本的超球面球外的样本视为异常。它的核心参数是nu异常点比例的上界和kernel通常用RBF。对比维度Isolation ForestOne-Class SVM计算复杂度O(n log n)适合大数据O(n^2)到O(n^3)适合小数据参数敏感度较低主要调n_estimators和contamination较高nu和gamma都需要仔细调高维表现较好一般需要调核函数输出异常分数可排序二值标签或决策函数值适用场景大规模数据、高维数据小规模数据、边界清晰的场景from sklearn.ensemble import IsolationForest from sklearn.svm import OneClassSVM # Isolation Forest iso IsolationForest(n_estimators100, contamination0.05, random_state42) y_pred_iso iso.fit_predict(X_scaled) # -1为异常1为正常 # One-Class SVM ocsvm OneClassSVM(nu0.05, kernelrbf, gammaauto) y_pred_svm ocsvm.fit_predict(X_scaled)注意contamination参数表示你预计异常点的比例。如果你不确定可以先设一个保守的值如0.01然后根据业务反馈调整。设得太大正常点会被误判为异常设得太小真正的异常会被漏掉。5. 完整实操流程从原始数据到业务洞察5.1 数据预处理的关键步骤无监督学习对数据预处理的要求比有监督学习更高因为没有标签来“兜底”。缺失值处理、异常值处理、标准化、特征选择每一步都会直接影响最终结果。缺失值处理上如果缺失比例低于5%可以直接删除缺失样本如果缺失比例较高需要考虑填充。均值填充适合近似正态分布的特征中位数填充适合偏态分布KNN填充适合特征之间有相关性的情况。但要注意填充会引入人为的规律可能影响聚类结果。标准化方面Z-score标准化StandardScaler是最常用的把每个特征变成均值0、方差1。如果数据有极端异常值可以考虑RobustScaler它用中位数和四分位距来缩放对异常值更鲁棒。如果特征都是正数且量纲差异不大MinMaxScaler也可以。特征选择上无监督场景没有标签不能用卡方检验或互信息这些有监督方法。可以用方差阈值法VarianceThreshold去掉方差极低的特征或者用相关性分析去掉高度共线的特征。PCA本身也可以看作一种特征提取方法但PCA降维后的主成分失去了原始特征的可解释性需要权衡。from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler, RobustScaler from sklearn.impute import SimpleImputer from sklearn.feature_selection import VarianceThreshold preprocess Pipeline([ (imputer, SimpleImputer(strategymedian)), (scaler, RobustScaler()), (var_thresh, VarianceThreshold(threshold0.01)) ]) X_processed preprocess.fit_transform(X_raw)5.2 聚类降维异常检测的组合拳实际项目中这三种技术往往是组合使用的。一个典型的流程是先用PCA降维去掉噪声和冗余特征然后在降维后的空间上做聚类最后用Isolation Forest在聚类结果的基础上识别异常簇或异常样本。为什么要先降维再聚类因为高维空间中距离度量会失效维度灾难K-Means和DBSCAN的效果都会变差。PCA降维后数据密度增加距离度量更有意义。但降维会丢失信息所以保留多少主成分需要权衡。我的经验是保留85%到95%的方差具体看降维后的聚类指标和业务解释性。from sklearn.decomposition import PCA from sklearn.cluster import KMeans from sklearn.ensemble import IsolationForest # 第一步PCA降维 pca PCA(n_components0.9, random_state42) X_pca pca.fit_transform(X_processed) # 第二步K-Means聚类 kmeans KMeans(n_clusters5, initk-means, n_init10, random_state42) cluster_labels kmeans.fit_predict(X_pca) # 第三步在每个簇内做异常检测 anomaly_scores np.zeros(len(X_pca)) for cluster_id in range(5): mask cluster_labels cluster_id if mask.sum() 10: continue iso IsolationForest(contamination0.05, random_state42) iso.fit(X_pca[mask]) anomaly_scores[mask] iso.decision_function(X_pca[mask]) # 综合判断簇标签 异常分数 result pd.DataFrame({ cluster: cluster_labels, anomaly_score: anomaly_scores })这个组合拳的逻辑是先粗粒度地把样本分成几个大群然后在每个群内部找异常。这样比全局异常检测更精准因为不同群的正常行为模式可能不同。比如电商用户里高频买家和低频买家的正常行为差异很大全局异常检测可能会把低频买家误判为异常但分群之后就不会。5.3 结果解释与业务落地无监督学习的最终价值在于业务落地。聚类结果出来后你需要做的是统计每个簇的样本量、计算每个簇在各特征上的均值或中位数、对比不同簇的特征差异、给每个簇起一个业务上能理解的名字。举个例子假设你对一批用户做了聚类得到5个簇。你发现簇0的用户年龄偏大、消费频次低但客单价高可以命名为“高价值低频用户”簇1的用户年龄偏小、消费频次高但客单价低可以命名为“高频低客单用户”。然后针对不同簇制定不同的运营策略。这才是无监督学习的完整闭环。如果聚类结果无法用业务语言解释那要么是特征选得不对要么是K值选得不合适要么是数据本身就没有明显的簇结构。这时候不要硬解释回到数据预处理和特征工程阶段重新审视。6. 常见问题与排查技巧实录6.1 聚类结果每次运行都不一样怎么办这是新手最常问的问题。原因通常有三个一是没有固定random_stateK-Means的初始中心是随机的t-SNE的初始化也是随机的二是n_init设得太小K-Means只跑了一次初始化结果不稳定三是数据本身就没有明显的簇结构算法在随机划分。解决办法固定random_state把n_init设成10以上如果还是不稳定说明数据可能不适合聚类。你可以计算多次运行下轮廓系数的均值和标准差如果标准差很大说明结果不可靠。6.2 高维数据聚类效果差怎么破高维数据下所有样本对之间的距离趋于相等距离度量失去区分度这就是维度灾难。解决办法有三个方向一是降维用PCA或UMAP把维度降到几十维以下二是特征选择去掉冗余和噪声特征三是换用对高维更鲁棒的算法比如Isolation Forest或基于密度的算法。我个人的经验是先做特征选择去掉明显无关的特征再用PCA降到50维左右然后在降维后的空间上聚类。如果效果还是不好考虑用UMAP降到2到10维再做聚类但要注意UMAP降维后的坐标不适合直接做K-Means因为UMAP不保留全局距离更适合用DBSCAN或HDBSCAN。6.3 异常检测误报太多怎么调误报多的原因通常是contamination设得太大或者特征里包含了太多噪声。先把contamination调小从0.01开始试。如果还是误报多检查特征有没有量纲差异极大的特征有没有高度相关的特征有没有包含时间戳这种不应该作为特征的列另外Isolation Forest的n_estimators也可以调大默认100调到200或300会更稳定但计算量增加。还有一个容易被忽略的点异常检测的结果需要结合业务规则做二次过滤。比如你检测出一批异常用户但其中有些是刚注册的新用户行为本来就少这种不应该算异常。所以异常检测的输出最好和业务规则做交集或并集而不是直接使用。6.4 常见问题速查表问题现象可能原因排查方向解决方案聚类结果每次不同随机初始化、n_init太小检查random_state和n_init固定随机种子n_init≥10所有样本归为一簇eps太大或K太小检查DBSCAN的eps和K-Means的K调小eps增大K大量样本标记为噪声eps太小或min_samples太大检查k-距离图调大eps调小min_samples降维后可视化重叠严重perplexity或n_neighbors不合适尝试不同参数调整perplexity5-50或n_neighbors5-50异常检测误报率高contamination太大、特征噪声多检查特征质量和contamination调小contamination做特征选择PCA降维后效果变差未标准化、主成分太少检查是否标准化、累计方差比先标准化保留90%以上方差7. 几个我踩过的坑和实操心得第一个坑在未标准化的数据上直接跑K-Means。有一次我拿到的数据里有一个特征是金额范围几千到几万另一个是点击次数范围0到几十。没标准化直接跑结果聚类完全被金额主导点击次数的影响几乎为零。后来加了StandardScaler结果才合理。这个错误看起来低级但数据特征量纲差异大的时候特别容易犯。第二个坑用t-SNE降维后的坐标做聚类。t-SNE的输出坐标没有距离意义只保留局部邻域关系。我试过在t-SNE结果上跑K-Means簇的边界很怪而且换一个perplexity结果就完全变了。后来改成用PCA降维后再聚类稳定得多。t-SNE就老老实实做可视化不要拿来做特征。第三个坑DBSCAN的eps用默认值。DBSCAN的默认eps是0.5但这个值对不同的数据集意义完全不同。我一开始没调结果所有点都被归为噪声或者全归为一簇。后来学会画k-距离图来选eps才稳定下来。k-距离图这个方法强烈推荐比盲试效率高得多。第四个坑忽略异常检测结果的业务含义。有一次我用Isolation Forest检测出一批异常订单兴冲冲地拿给业务方看结果业务方说这些是促销期间的正常大额订单。后来我学乖了异常检测的结果一定要和业务方一起过一遍确认哪些是真异常哪些是业务上合理的特殊情况。技术指标再好看业务不认可就是白搭。最后一个心得无监督学习的输出不是终点而是起点。聚类和异常检测的结果应该作为新的特征输入到后续的有监督模型中或者作为业务规则的触发条件。比如把聚类标签作为一个类别特征加入到用户流失预测模型里往往能提升模型效果。这个思路在实际项目中屡试不爽。提示无监督学习的代码和参数需要反复调试建议用Jupyter Notebook做探索性分析每一步都画图确认。不要闷头写脚本跑完再看结果中间过程的可视化能帮你快速发现数据问题。
返回列表