☰
无监督学习实战:聚类、降维与关联规则挖掘核心指南
2026/10/1 18:34:26 网站建设 项目流程

1. 无监督学习到底在解决什么问题

先把话说直白一点:无监督学习就是让机器在没有标签的数据里自己找规律。你给它的数据只有特征,没有答案,它得自己琢磨出点门道来。这和 supervised learning 那种“我给你标准答案你照着学”的路子完全不同。

我刚开始接触这块的时候,总觉得无监督学习有点玄乎——没有标签,那怎么知道学得对不对?后来做项目多了才明白,无监督学习的价值恰恰在于:很多场景下你根本拿不到标签,或者打标签的成本高到离谱。比如你手头有几十万条用户行为日志,想看看用户能分成几类,你不可能雇人去一条条标注“这个用户属于价格敏感型”“那个用户属于品质追求型”。这时候聚类算法就派上用场了,它自己从数据里找出自然的分组。

无监督学习主要干三件事:聚类、降维、关联规则挖掘。聚类是把相似的东西归到一起,降维是把高维数据压缩成低维表示,关联规则是找数据项之间的共现关系。这三件事听起来简单,但真要做好,里面的门道不少。

注意:无监督学习没有“正确答案”这个概念,所以评估它的结果比监督学习要难得多。你没法直接算准确率,只能靠内部指标(如轮廓系数)或者外部业务验证。

适合谁来学这块?如果你已经了解机器学习的基本概念,知道什么是特征、什么是模型、什么是过拟合,那就可以直接上手无监督学习了。如果你连线性回归都没碰过,建议先补一下基础,不然有些术语会让你卡住。

2. 聚类算法:从K-Means到层次聚类,到底怎么选

2.1 K-Means:最常用但也最容易踩坑

K-Means 大概是所有人接触无监督学习时第一个遇到的算法。它的思路极其朴素:先随机选K个中心点,然后把每个样本分配给最近的中心点,再重新计算每个簇的中心,反复迭代直到中心点不再移动。

听起来很简单对吧?但我在实际项目里见过太多人直接调sklearn.cluster.KMeans(n_clusters=3)然后就完事了。问题在于,K值的选择直接决定了聚类结果的好坏,而很多人根本不知道该怎么选K。

选K值最常用的方法是肘部法则。具体做法是:对不同的K值分别跑K-Means,计算每个点到其所属簇中心的距离平方和(inertia),然后画一条曲线。随着K增大,inertia会下降,但下降速度会在某个点突然变缓,那个拐点就是比较合适的K值。

from sklearn.cluster import KMeans import matplotlib.pyplot as plt inertias = [] K_range = range(2, 11) for k in K_range: kmeans = KMeans(n_clusters=k, random_state=42, n_init=10) kmeans.fit(X_scaled) inertias.append(kmeans.inertia_) plt.plot(K_range, inertias, 'bo-') plt.xlabel('K') plt.ylabel('Inertia') plt.title('Elbow Method') plt.show()

但肘部法则不是万能的。有时候曲线很平滑,根本看不出明显的拐点。这时候可以结合轮廓系数来判断。轮廓系数的取值范围是-1到1,越接近1说明聚类效果越好。

from sklearn.metrics import silhouette_score sil_scores = [] for k in K_range: kmeans = KMeans(n_clusters=k, random_state=42, n_init=10) labels = kmeans.fit_predict(X_scaled) sil_scores.append(silhouette_score(X_scaled, labels))

实操心得:K-Means对数据的尺度非常敏感。如果你不做标准化,一个取值范围在0到1的特征和一个取值范围在0到10000的特征放在一起,后者会完全主导距离计算。所以跑K-Means之前一定要做StandardScaler或者MinMaxScaler,这是铁律。

还有一个坑是初始中心点的选择。K-Means默认用的是k-means++初始化,比纯随机好很多,但如果你把n_init设成1,结果可能不稳定。我一般会把n_init设成10,让算法多跑几次取最优。

2.2 层次聚类:不需要预设K值,但计算量大

层次聚类和K-Means最大的区别是:你不需要提前指定簇的数量。它会生成一棵树(树状图),你可以根据业务需求在任意高度切一刀,得到不同数量的簇。

层次聚类分两种:自底向上(凝聚型)和自顶向下(分裂型)。实际用得多的是凝聚型,过程是:先把每个点当成一个簇,然后每次合并最近的两个簇,直到所有点合并成一个大簇。

from scipy.cluster.hierarchy import dendrogram, linkage from sklearn.cluster import AgglomerativeClustering # 生成树状图 Z = linkage(X_scaled, method='ward') dendrogram(Z) plt.title('Hierarchical Clustering Dendrogram') plt.show() # 直接聚类 hc = AgglomerativeClustering(n_clusters=3, linkage='ward') labels = hc.fit_predict(X_scaled)

linkage参数决定了怎么计算两个簇之间的距离。常用的有:

linkage方法距离定义适用场景
ward合并后簇内方差增量最小大多数场景首选,簇比较紧凑
complete两簇中最远点的距离簇比较均匀时好用
average两簇中所有点对距离的平均折中方案
single两簇中最近点的距离容易产生链式效应,慎用

层次聚类最大的问题是计算复杂度高,时间复杂度是O(n³)或O(n²logn),数据量超过一万条就会很慢。所以它更适合小数据集,或者你只想看看数据的层次结构。

2.3 DBSCAN:能发现任意形状的簇

K-Means和层次聚类都有一个隐含假设:簇是球形的、凸的。但现实中的数据往往不是这样。比如地理数据中的城市群,形状可能是不规则的。这时候DBSCAN就派上用场了。

DBSCAN的核心思想是:如果一个点的邻域内至少有minPts个点,那它就是一个核心点;核心点之间可以连成一片,形成簇;不在任何核心点邻域内的点就是噪声。

from sklearn.cluster import DBSCAN dbscan = DBSCAN(eps=0.5, min_samples=5) labels = dbscan.fit_predict(X_scaled)

eps是邻域半径,min_samples是核心点所需的最小邻居数。这两个参数的选择非常关键。eps太小,大部分点都会被当成噪声;eps太大,所有点会合并成一个簇。

我一般用k-距离图来辅助选择eps:计算每个点到其第k个最近邻的距离,排序后画图,拐点处的距离就是比较合适的eps。

from sklearn.neighbors import NearestNeighbors import numpy as np k = 5 nbrs = NearestNeighbors(n_neighbors=k).fit(X_scaled) distances, indices = nbrs.kneighbors(X_scaled) distances = np.sort(distances[:, k-1], axis=0) plt.plot(distances) plt.xlabel('Points sorted by distance') plt.ylabel(f'{k}-th nearest neighbor distance') plt.title('K-distance Graph') plt.show()

注意:DBSCAN不需要指定簇的数量,但它对参数非常敏感。而且当数据密度不均匀时,单一的eps可能无法同时处理好密集区域和稀疏区域。这时候可以考虑HDBSCAN,它是DBSCAN的改进版,能自适应不同密度。

2.4 聚类效果评估:没有标签怎么判断好坏

无监督学习最头疼的就是评估。没有真实标签,你只能靠内部指标。常用的有:

  • 轮廓系数:综合考虑簇内紧密度和簇间分离度,范围-1到1
  • Calinski-Harabasz指数:簇间方差与簇内方差的比值,越大越好
  • Davies-Bouldin指数:簇内散度与簇间距离的比值,越小越好
from sklearn.metrics import calinski_harabasz_score, davies_bouldin_score ch_score = calinski_harabasz_score(X_scaled, labels) db_score = davies_bouldin_score(X_scaled, labels)

但这些指标都偏向于球形簇。如果你的数据是任意形状的簇,这些指标可能会误导你。最终还是要结合业务来解释聚类结果——如果分出来的簇在业务上有明显的区分度,那这个聚类就是有意义的。

3. 降维:从PCA到t-SNE,把高维数据压扁

3.1 PCA:最经典的线性降维方法

PCA(主成分分析)的思路是:找到数据方差最大的方向,把数据投影到这个方向上。第一个主成分是方差最大的方向,第二个主成分是与第一个正交且方差次大的方向,以此类推。

from sklearn.decomposition import PCA pca = PCA(n_components=2) X_pca = pca.fit_transform(X_scaled) print(pca.explained_variance_ratio_)

explained_variance_ratio_告诉你每个主成分解释了多少方差。通常我们会看累计解释方差达到85%或90%时需要多少个主成分。

pca_full = PCA() pca_full.fit(X_scaled) cumulative_variance = np.cumsum(pca_full.explained_variance_ratio_) plt.plot(range(1, len(cumulative_variance)+1), cumulative_variance, 'bo-') plt.axhline(y=0.9, color='r', linestyle='--') plt.xlabel('Number of Components') plt.ylabel('Cumulative Explained Variance') plt.show()

PCA的好处是可解释性强,你知道每个主成分是原始特征的线性组合。但它的局限也很明显:只能捕捉线性关系。如果数据的内在结构是非线性的,PCA会丢失很多信息。

实操心得:PCA之前一定要做标准化。因为PCA对特征的尺度敏感,如果某个特征方差特别大,它会主导主成分的方向。标准化之后,每个特征的方差都是1,PCA才能公平地对待每个特征。

3.2 t-SNE:可视化神器,但不能用来做特征工程

t-SNE(t分布随机邻域嵌入)是专门为可视化设计的降维方法。它能把高维数据映射到2维或3维空间,同时保留数据的局部结构。

from sklearn.manifold import TSNE tsne = TSNE(n_components=2, perplexity=30, random_state=42) X_tsne = tsne.fit_transform(X_scaled) plt.scatter(X_tsne[:, 0], X_tsne[:, 1], c=labels, cmap='viridis', s=10) plt.title('t-SNE Visualization') plt.show()

perplexity是t-SNE最重要的参数,它大致等于每个点考虑的近邻数量。通常取值在5到50之间。perplexity太小,局部结构会被过度强调;太大,全局结构会变得模糊。

但t-SNE有几个坑必须说清楚:

  1. t-SNE的结果不能用于后续建模。它只是可视化工具,降维后的坐标没有实际的物理意义。
  2. t-SNE的计算复杂度很高,数据量超过几万条就会很慢。
  3. t-SNE的簇间距离没有意义。两个簇在图上离得远,不代表它们在原始空间中就离得远。
  4. 每次跑t-SNE结果都不一样,因为它是随机初始化的。要多跑几次看结果是否稳定。

3.3 UMAP:比t-SNE更快,全局结构保留更好

UMAP(统一流形近似与投影)是近几年比较火的降维方法。它比t-SNE快很多,而且能更好地保留全局结构。

import umap reducer = umap.UMAP(n_components=2, n_neighbors=15, min_dist=0.1, random_state=42) X_umap = reducer.fit_transform(X_scaled) plt.scatter(X_umap[:, 0], X_umap[:, 1], c=labels, cmap='viridis', s=10) plt.title('UMAP Visualization') plt.show()

n_neighbors控制局部和全局结构的平衡,值越大越强调全局结构。min_dist控制点在低维空间中的聚集程度,值越小点越聚集。

UMAP和t-SNE一样,降维结果主要用于可视化,不建议直接作为特征输入到其他模型中。但UMAP的速度优势很明显,大数据集上比t-SNE快一个数量级。

4. 关联规则挖掘:从购物篮到推荐系统

4.1 Apriori算法:经典的频繁项集挖掘

关联规则挖掘最典型的场景就是购物篮分析:买了面包的人,有多大比例也会买牛奶。Apriori算法是解决这个问题的经典方法。

先解释几个核心概念:

  • 支持度:某个项集在所有交易中出现的频率。比如“面包+牛奶”出现在10%的交易中,支持度就是0.1。
  • 置信度:在买了A的条件下,也买B的概率。比如买了面包的人中有60%也买了牛奶,置信度就是0.6。
  • 提升度:置信度除以B的基准概率。提升度大于1说明A和B正相关,小于1说明负相关。
from mlxtend.frequent_patterns import apriori, association_rules # 频繁项集挖掘 frequent_itemsets = apriori(df, min_support=0.05, use_colnames=True) # 生成关联规则 rules = association_rules(frequent_itemsets, metric="confidence", min_threshold=0.5) rules = rules.sort_values(by="lift", ascending=False)

Apriori的核心原理是先验性质:如果一个项集是频繁的,那它的所有子集也一定是频繁的;反过来,如果一个项集不频繁,那它的所有超集也一定不频繁。利用这个性质可以大幅减少需要检查的候选项集数量。

但Apriori有个明显的瓶颈:需要多次扫描数据集。每生成一层候选项集就要扫一遍数据,数据量大或者项集长度长的时候效率很低。

4.2 FP-Growth:更高效的替代方案

FP-Growth通过构建一棵FP树(频繁模式树)来压缩数据,只需要扫描数据集两次,效率比Apriori高很多。

from mlxtend.frequent_patterns import fpgrowth frequent_itemsets = fpgrowth(df, min_support=0.05, use_colnames=True) rules = association_rules(frequent_itemsets, metric="lift", min_threshold=1.0)

FP-Growth的缺点是内存消耗大,因为要把整个数据集压缩到一棵树里。如果数据太稀疏,FP树会变得很大,反而不如Apriori。

实操心得:关联规则挖掘的结果往往非常多,但真正有价值的很少。我一般会按提升度排序,优先看提升度大于2的规则。另外,支持度太低的规则虽然提升度高,但可能只是偶然现象,需要结合业务判断。

4.3 关联规则的实际应用场景

关联规则不只是用在购物篮分析上。我做过一个项目,用关联规则分析社区服务需求:把每个居民的需求当成一个项集,挖掘哪些需求经常同时出现。结果发现“老年人助餐”和“健康监测”经常一起出现,这就为社区服务打包提供了数据支撑。

另一个常见场景是故障诊断:把每次设备故障的报警信号当成一个项集,挖掘哪些报警经常同时出现。如果“温度过高”和“振动异常”经常一起出现,那就可以推断这两个信号背后可能有共同的根因。

5. 无监督学习的实战避坑指南

5.1 数据预处理比算法选择更重要

我见过太多人花大量时间调算法参数,却忽略了数据预处理。实际上,在无监督学习中,数据预处理的重要性至少占70%。

几个必须做的预处理步骤:

  • 缺失值处理:无监督学习对缺失值很敏感。K-Means直接没法处理缺失值,DBSCAN会把缺失值当成噪声。要么删除,要么填充。
  • 标准化/归一化:前面反复强调了,距离-based的算法必须做标准化。
  • 异常值处理:异常值会严重扭曲聚类结果。可以用IQR或者Z-score先识别出来。
  • 特征选择:不是特征越多越好。无关特征会引入噪声,降低聚类效果。
from sklearn.preprocessing import StandardScaler from sklearn.impute import SimpleImputer # 缺失值填充 imputer = SimpleImputer(strategy='median') X_imputed = imputer.fit_transform(X) # 标准化 scaler = StandardScaler() X_scaled = scaler.fit_transform(X_imputed)

5.2 聚类结果不稳定怎么办

K-Means和t-SNE都有随机初始化的成分,不同随机种子可能得到不同结果。如果你发现每次跑出来的聚类结果差异很大,说明数据本身可能没有明显的簇结构。

解决办法:

  1. 多跑几次取最优:K-Means的n_init参数就是干这个的。
  2. 用共识聚类:跑多次聚类,统计每对样本被分到同一簇的频率,构建共识矩阵。
  3. 换算法:如果K-Means不稳定,试试DBSCAN或者层次聚类。

5.3 高维数据聚类的维度灾难

当特征维度很高时,所有点之间的距离都会变得差不多,基于距离的聚类算法会失效。这就是所谓的维度灾难。

解决办法是先降维再聚类。但要注意,PCA降维后再聚类,簇的形状可能会发生变化。我一般会先用PCA降到10到50维,然后再跑聚类。

# 先降维再聚类 pca = PCA(n_components=0.9) # 保留90%方差 X_reduced = pca.fit_transform(X_scaled) kmeans = KMeans(n_clusters=5, n_init=10) labels = kmeans.fit_predict(X_reduced)

5.4 常见问题速查表

问题现象可能原因排查方向
所有点被分到一个簇eps太大或K太小调整参数,检查数据尺度
大量点被标为噪声eps太小或min_samples太大用k-距离图重新选eps
聚类结果每次都不一样随机初始化或数据无结构增加n_init,检查轮廓系数
高维数据聚类效果差维度灾难先PCA降维再聚类
轮廓系数很高但业务无意义指标与业务不匹配结合业务解释聚类结果
t-SNE图上看不出分离perplexity不合适尝试不同的perplexity值

6. 从理论到落地:一个完整的无监督学习项目流程

6.1 项目流程概览

一个完整的无监督学习项目通常包含以下步骤:

  1. 明确业务目标:你是想做客户分群、异常检测、还是数据压缩?目标不同,方法选择完全不同。
  2. 数据收集与清洗:处理缺失值、异常值、重复值。
  3. 探索性数据分析:看分布、看相关性、看有没有明显的结构。
  4. 特征工程:标准化、降维、特征选择。
  5. 算法选择与调参:根据数据特点选择合适的算法。
  6. 结果评估与解释:用内部指标评估,结合业务解释。
  7. 落地应用:把聚类结果用于推荐、分群、异常检测等。

6.2 以客户分群为例的完整代码

假设你有一份客户消费数据,想做客户分群。完整流程如下:

import pandas as pd import numpy as np from sklearn.preprocessing import StandardScaler from sklearn.decomposition import PCA from sklearn.cluster import KMeans from sklearn.metrics import silhouette_score import matplotlib.pyplot as plt # 1. 加载数据 df = pd.read_csv('customer_data.csv') # 2. 选择特征 features = ['annual_income', 'spending_score', 'purchase_frequency', 'avg_order_value'] X = df[features].copy() # 3. 处理缺失值 X = X.fillna(X.median()) # 4. 标准化 scaler = StandardScaler() X_scaled = scaler.fit_transform(X) # 5. 确定最佳K值 inertias = [] sil_scores = [] K_range = range(2, 11) for k in K_range: kmeans = KMeans(n_clusters=k, random_state=42, n_init=10) labels = kmeans.fit_predict(X_scaled) inertias.append(kmeans.inertia_) sil_scores.append(silhouette_score(X_scaled, labels)) # 画肘部图和轮廓系数图 fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4)) ax1.plot(K_range, inertias, 'bo-') ax1.set_xlabel('K') ax1.set_ylabel('Inertia') ax1.set_title('Elbow Method') ax2.plot(K_range, sil_scores, 'ro-') ax2.set_xlabel('K') ax2.set_ylabel('Silhouette Score') ax2.set_title('Silhouette Score') plt.show() # 6. 用最佳K值聚类 best_k = 4 # 根据上面的图确定 kmeans = KMeans(n_clusters=best_k, random_state=42, n_init=10) df['cluster'] = kmeans.fit_predict(X_scaled) # 7. 分析每个簇的特征 cluster_summary = df.groupby('cluster')[features].mean() print(cluster_summary) # 8. 可视化 pca = PCA(n_components=2) X_pca = pca.fit_transform(X_scaled) plt.scatter(X_pca[:, 0], X_pca[:, 1], c=df['cluster'], cmap='viridis', s=30) plt.xlabel('PC1') plt.ylabel('PC2') plt.title('Customer Segments') plt.colorbar(label='Cluster') plt.show()

6.3 结果解释与业务落地

聚类跑完之后,最关键的一步是解释每个簇的含义。比如上面的客户分群,你可能会得到这样的结果:

簇年收入消费得分购买频率客单价业务含义
0高高高高高价值客户
1低低低低低价值客户
2高低中高潜力客户
3低高高低价格敏感型

有了这个分群,你就可以针对不同群体制定不同的营销策略。高价值客户给VIP待遇,潜力客户给定向优惠,价格敏感型客户给折扣券。

实操心得:聚类结果的业务解释往往比算法本身更重要。我见过很多项目,聚类指标很漂亮,但业务方看了之后说“这跟我拍脑袋分的一样”。这时候就要反思:是不是特征选得不对?是不是应该用业务规则先做一层筛选?

7. 无监督学习的边界与局限

7.1 无监督学习不是万能的

很多人对无监督学习有误解,觉得它能从数据里自动发现所有规律。实际上,无监督学习只能发现数据中“显著”的结构,但“显著”不等于“有意义”。

聚类算法总能给你分出几个簇,哪怕数据本身是均匀分布的。降维算法总能给你画出漂亮的图,哪怕数据本身没有低维结构。所以,无监督学习的结果必须结合业务来判断。

7.2 评估难是无监督学习的根本痛点

监督学习有准确率、召回率、F1值这些明确的指标。无监督学习没有。你只能靠内部指标和业务验证。这就导致无监督学习项目的迭代周期往往更长,因为你需要反复跟业务方确认“这个结果有没有用”。

我的经验是:在项目开始之前,就跟业务方明确“什么样的结果算好”。比如客户分群,如果业务方说“我希望能分出至少一个高价值群体和一个低价值群体”,那你就有了明确的验证标准。

7.3 什么时候该用无监督学习

无监督学习适合以下场景:

  • 没有标签,或者打标签成本太高
  • 想做探索性分析,看看数据里有什么结构
  • 想做数据压缩或特征提取,为后续监督学习做准备
  • 想做异常检测,找出跟大多数样本不一样的少数样本

不适合的场景:

  • 有大量高质量标签,直接上监督学习效果更好
  • 业务目标非常明确,比如“预测用户会不会流失”,这就是监督学习的问题
  • 数据量太小,无监督学习很难发现稳定的结构

8. 我个人的一些经验体会

做了这么多无监督学习的项目,最大的体会是:算法只是工具,业务理解才是核心。同样的K-Means,用在客户分群上可能效果很好,用在图像分割上可能一塌糊涂。关键不在于你用了多高级的算法,而在于你是否真正理解了数据背后的业务逻辑。

另一个体会是:可视化非常重要。无监督学习的结果往往很抽象,你需要用t-SNE或者UMAP把它画出来,才能直观地判断聚类效果。我养成了一个习惯:每次跑完聚类,先画个图看看,如果图上看不出明显的分离,那这个聚类结果大概率不靠谱。

最后分享一个小技巧:如果你不确定该用哪种聚类算法,先把所有算法都跑一遍,用轮廓系数和Calinski-Harabasz指数对比一下。虽然这些指标不完美,但至少能给你一个初步的参考。然后再结合业务判断,选出最合适的方案。

无监督学习这条路,入门容易精通难。但只要你坚持“业务驱动、数据说话”的原则,慢慢就能找到感觉。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询