本章我们将深入探索无监督学习的核心算法,掌握K-Means、层次聚类、DBSCAN等聚类方法,以及PCA、t-SNE等降维技术,学会从数据中发现隐藏的模式与结构。
环境声明
- Python版本 :
Python 3.12+(建议使用3.10以上版本) - 主要库 :
scikit-learn 1.5+、numpy、pandas、matplotlib、seaborn、plotly - 开发工具 :
PyCharm或VS Code - 操作系统 :
Windows/macOS/Linux(通用)
学习目标
完成本讲学习后,你将能够:
- 理解无监督学习的核心思想,区分聚类与降维两大任务类型
- 掌握K-Means聚类算法的原理,熟练使用肘部法则和轮廓系数确定最佳聚类数
- 理解层次聚类的工作机制,能够解读树状图并选择合适的聚合策略
- 掌握DBSCAN密度聚类算法,能够根据数据特点调整eps和min_samples参数
- 深入理解PCA主成分分析原理,学会计算方差解释率并应用于特征降维
- 熟练使用t-SNE进行高维数据可视化,掌握perplexity等关键参数的调优方法
- 能够综合运用多种无监督学习方法解决实际业务问题
1. 无监督学习概述
1.1 什么是无监督学习
无监督学习是机器学习中一类重要的学习方法,与监督学习最大的区别在于:训练数据没有标签。无监督学习的核心目标是让算法自动从数据中发现隐藏的结构、模式或规律。
打个比方,监督学习就像是有老师指导的学习过程,每道题都有标准答案;而无监督学习则像是独自探索一个新领域,你需要自己发现事物之间的相似性和差异性,将它们归类整理。比如,给你一堆水果图片但没有标签,你要自己根据颜色、形状、大小等特征将它们分成不同的类别。
1.2 无监督学习的主要任务
根据2026年最新的机器学习课程体系,无监督学习主要包含两大类任务:
| 任务类型 | 核心目标 | 典型算法 | 应用场景 |
|---|---|---|---|
| 聚类分析 | 将数据划分为不同的组 | K-Means、层次聚类、DBSCAN | 客户分群、异常检测、图像分割 |
| 降维处理 | 减少数据维度同时保留主要信息 | PCA、t-SNE、UMAP | 数据可视化、特征压缩、去噪 |
1.3 无监督学习的应用价值
在当今数据驱动的时代,无监督学习具有不可替代的价值:
- 数据探索:在缺乏先验知识的情况下,帮助数据科学家理解数据的内在结构
- 异常检测:识别与正常模式显著不同的数据点,用于欺诈检测、设备故障预警
- 特征学习:自动提取数据的代表性特征,为后续监督学习提供输入
- 数据压缩:降低数据存储和计算成本,提高模型训练效率
1.4 2026年无监督学习发展趋势
根据2026年人工智能领域的最新研究动态,无监督学习正呈现以下发展趋势:
- 深度聚类方法:结合深度学习与聚类算法,如深度嵌入聚类(DEC),能够处理更复杂的高维数据
- 对比学习:通过构造正负样本对进行学习,在表示学习领域取得突破性进展
- 自监督学习:利用数据本身的结构信息生成监督信号,成为当前研究热点
- 可解释性提升:研究者越来越关注聚类结果的可解释性,开发能够解释聚类原因的算法
2. K-Means聚类算法
2.1 算法原理
K-Means是最经典、应用最广泛的聚类算法之一。其核心思想可以概括为:将n个样本划分为k个簇,使得簇内样本之间的相似度最大化,簇间样本的相似度最小化。
算法的直观理解:想象你要在地图上开设k个便利店,目标是让每个居民都尽可能就近购物。K-Means算法就是不断调整便利店位置(聚类中心),直到找到最优布局。
算法步骤如下:
- 初始化:随机选择k个样本作为初始聚类中心
- 分配样本:将每个样本分配到距离最近的聚类中心所属的簇
- 更新中心:重新计算每个簇的中心点(取簇内所有样本的均值)
- 迭代优化:重复步骤2和3,直到聚类中心不再变化或达到最大迭代次数
2.2 K-Means的数学表达
K-Means算法的目标是最小化簇内平方和(Within-Cluster Sum of Squares,WCSS):
J = Σ(i=1 to k) Σ(x in Ci) ||x - μi||^2
其中,k是聚类数,Ci是第i个簇,μi是第i个簇的中心点,||x - μi||表示样本x到中心点的欧氏距离。
2.3 K-Means代码实现
python
import numpy as np
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans
from sklearn.datasets import make_blobs
from sklearn.preprocessing import StandardScaler
# 生成模拟数据
X, y_true = make_blobs(n_samples=300, centers=4, cluster_std=0.60, random_state=42)
# 数据标准化(K-Means对尺度敏感,建议先标准化)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# 创建K-Means模型
kmeans = KMeans(n_clusters=4, random_state=42, n_init=10)
# 训练模型
kmeans.fit(X_scaled)
# 获取聚类标签和中心点
labels = kmeans.labels_
centers = kmeans.cluster_centers_
# 可视化结果
plt.figure(figsize=(10, 6))
plt.scatter(X_scaled[:, 0], X_scaled[:, 1], c=labels, cmap='viridis', alpha=0.6)
plt.scatter(centers[:, 0], centers[:, 1], c='red', marker='x', s=200, linewidths=3)
plt.title('K-Means聚类结果')
plt.xlabel('特征1')
plt.ylabel('特征2')
plt.show()
print(f"聚类中心:\n{centers}")
print(f"迭代次数:{kmeans.n_iter_}")
print(f"惯性(WCSS):{kmeans.inertia_:.2f}")
2.4 肘部法则确定最佳聚类数
K-Means算法需要预先指定聚类数k,但如何选择合适的k值呢?肘部法则(Elbow Method)是一种常用的启发式方法。
原理:随着k的增加,WCSS会不断减小。当k小于真实聚类数时,WCSS下降很快;当k超过真实聚类数后,WCSS下降趋于平缓。这个转折点就像人的手肘,因此称为肘部法则。
python
# 肘部法则确定最佳聚类数
inertias = []
K_range = range(1, 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.figure(figsize=(10, 6))
plt.plot(K_range, inertias, 'bo-')
plt.xlabel('聚类数k')
plt.ylabel('惯性(WCSS)')
plt.title('肘部法则确定最佳聚类数')
plt.grid(True)
plt.show()
2.5 轮廓系数评估聚类质量
轮廓系数(Silhouette Coefficient)是另一种评估聚类效果的重要指标,取值范围为[-1, 1]:
- 接近1:表示样本聚类合理,与所在簇内样本相似度高,与其他簇样本差异大
- 接近0:表示样本处于两个簇的边界上
- 接近-1:表示样本可能被分到错误的簇
python
from sklearn.metrics import silhouette_score, silhouette_samples
# 计算不同k值下的轮廓系数
silhouette_scores = []
for k in range(2, 11):
kmeans = KMeans(n_clusters=k, random_state=42, n_init=10)
labels = kmeans.fit_predict(X_scaled)
score = silhouette_score(X_scaled, labels)
silhouette_scores.append(score)
print(f"k={k}时,轮廓系数:{score:.3f}")
# 绘制轮廓系数图
plt.figure(figsize=(10, 6))
plt.plot(range(2, 11), silhouette_scores, 'ro-')
plt.xlabel('聚类数k')
plt.ylabel('轮廓系数')
plt.title('轮廓系数评估聚类质量')
plt.grid(True)
plt.show()
避坑小贴士
- K-Means对初始值敏感:不同的初始中心可能导致不同的聚类结果。解决方案是使用n_init参数多次随机初始化,选择最优结果
- K-Means假设簇是凸形的:对于非球形或不规则形状的簇,K-Means效果较差。此时应考虑DBSCAN等密度聚类方法
- K-Means对异常值敏感:异常值会显著影响聚类中心的位置。建议先进行异常值检测和处理
- 特征尺度问题:K-Means基于距离计算,不同特征的量纲会影响结果。务必先进行标准化或归一化
3. 层次聚类算法
3.1 算法原理
层次聚类是一种自底向上(凝聚式)或自顶向下(分裂式)的聚类方法。凝聚式层次聚类从每个样本作为一个独立的簇开始,逐步合并最相似的簇,直到所有样本聚为一类。
算法的直观理解:想象你有一堆散落的树叶,你要将它们整理成一束。层次聚类就是不断将最相似的树叶(或叶束)绑在一起,最终形成一棵完整的树。
3.2 聚合方式(链接准则)
层次聚类中,如何度量两个簇之间的相似度?常用的链接准则包括:
| 链接方式 | 定义 | 特点 |
|---|---|---|
| 单链接(Single) | 两个簇中最近样本的距离 | 容易产生链式效应,对噪声敏感 |
| 全链接(Complete) | 两个簇中最远样本的距离 | 倾向于生成紧凑的球形簇 |
| 平均链接(Average) | 两个簇中所有样本对的平均距离 | 平衡了单链接和全链接的特点 |
| 沃德链接(Ward) | 合并后簇内方差增量 | 倾向于生成大小相近的簇,效果通常最好 |
3.3 树状图解读
树状图(Dendrogram)是层次聚类的可视化表示,展示了簇的合并过程。通过观察树状图,可以:
- 了解数据的层次结构
- 根据业务需求选择合适的聚类数(在特定高度切割树状图)
- 发现异常值或离群点
python
import scipy.cluster.hierarchy as sch
from sklearn.cluster import AgglomerativeClustering
# 绘制树状图
plt.figure(figsize=(12, 6))
dendrogram = sch.dendrogram(sch.linkage(X_scaled, method='ward'))
plt.title('层次聚类树状图')
plt.xlabel('样本索引')
plt.ylabel('距离')
plt.show()
# 使用凝聚式层次聚类
agg_clustering = AgglomerativeClustering(
n_clusters=4,
linkage='ward',
metric='euclidean'
)
labels_agg = agg_clustering.fit_predict(X_scaled)
# 可视化聚类结果
plt.figure(figsize=(10, 6))
plt.scatter(X_scaled[:, 0], X_scaled[:, 1], c=labels_agg, cmap='viridis', alpha=0.6)
plt.title('层次聚类结果(Ward链接)')
plt.xlabel('特征1')
plt.ylabel('特征2')
plt.show()
3.4 不同链接方式对比
python
# 对比不同链接方式的聚类效果
linkage_methods = ['ward', 'complete', 'average', 'single']
fig, axes = plt.subplots(2, 2, figsize=(14, 12))
axes = axes.ravel()
for idx, method in enumerate(linkage_methods):
agg = AgglomerativeClustering(n_clusters=4, linkage=method)
labels = agg.fit_predict(X_scaled)
axes[idx].scatter(X_scaled[:, 0], X_scaled[:, 1], c=labels, cmap='viridis', alpha=0.6)
axes[idx].set_title(f'链接方式:{method}')
axes[idx].set_xlabel('特征1')
axes[idx].set_ylabel('特征2')
plt.tight_layout()
plt.show()
避坑小贴士
- 计算复杂度高 :层次聚类的时间复杂度为O(n2)或O(n3),不适合大规模数据集(n>10000)。对于大数据集,可以先采样或使用K-Means预聚类
- 一旦合并无法撤销:层次聚类的决策是确定性的,早期的合并错误会影响后续结果
- 选择合适的链接方式:Ward链接通常效果较好,但对于 elongated( elongated)形状的簇,单链接可能更合适
4. DBSCAN密度聚类算法
4.1 算法原理
DBSCAN(Density-Based Spatial Clustering of Applications with Noise)是一种基于密度的聚类算法,能够发现任意形状的簇,并自动识别噪声点。
核心概念:
- 核心点(Core Point):在半径eps内包含至少min_samples个样本的点
- 边界点(Border Point):在eps内样本数少于min_samples,但落在某个核心点的邻域内的点
- 噪声点(Noise Point):既不是核心点也不是边界点的点
算法的直观理解:想象你在一片森林中寻找人群聚集的地方。DBSCAN就像是在寻找人口密度较高的区域,密度低的边缘地带属于同一个聚集区,而孤立的个体则被视为噪声。
4.2 算法流程
- 随机选择一个未访问的样本点
- 如果该点的eps邻域内包含至少min_samples个点,则创建一个新簇
- 将邻域内的所有点加入该簇,并对这些点递归执行步骤2
- 重复步骤1-3,直到所有点都被访问
- 未被归入任何簇的点标记为噪声
4.3 DBSCAN代码实现
python
from sklearn.cluster import DBSCAN
# 创建DBSCAN模型
dbscan = DBSCAN(eps=0.5, min_samples=5)
# 训练并预测
labels_dbscan = dbscan.fit_predict(X_scaled)
# 统计簇的数量(-1表示噪声点)
n_clusters = len(set(labels_dbscan)) - (1 if -1 in labels_dbscan else 0)
n_noise = list(labels_dbscan).count(-1)
print(f"估计的聚类数:{n_clusters}")
print(f"噪声点数量:{n_noise}")
# 可视化结果
plt.figure(figsize=(10, 6))
unique_labels = set(labels_dbscan)
colors = plt.cm.Spectral(np.linspace(0, 1, len(unique_labels)))
for label, color in zip(unique_labels, colors):
if label == -1:
# 噪声点用黑色表示
color = [0, 0, 0, 1]
cluster_mask = labels_dbscan == label
plt.scatter(X_scaled[cluster_mask, 0], X_scaled[cluster_mask, 1],
c=[color], label=f'簇 {label}' if label != -1 else '噪声',
alpha=0.6, s=50)
plt.title('DBSCAN聚类结果')
plt.xlabel('特征1')
plt.ylabel('特征2')
plt.legend()
plt.show()
4.4 参数选择方法
DBSCAN的两个核心参数eps和min_samples对聚类结果影响很大:
min_samples的选择:
- 一般设置为维度数+1或维度数的2倍
- 对于高维数据,可以适当增大
eps的选择(K-距离图法):
python
from sklearn.neighbors import NearestNeighbors
# 计算每个点到其第k个最近邻的距离
k = 5 # min_samples值
neighbors = NearestNeighbors(n_neighbors=k)
neighbors_fit = neighbors.fit(X_scaled)
distances, indices = neighbors_fit.kneighbors(X_scaled)
# 排序并绘制K-距离图
distances = np.sort(distances[:, k-1])
plt.figure(figsize=(10, 6))
plt.plot(distances)
plt.xlabel('样本索引(按距离排序)')
plt.ylabel(f'{k}-距离')
plt.title('K-距离图(用于选择eps参数)')
plt.grid(True)
plt.show()
在K-距离图中,寻找曲线的"拐点"(elbow),该点对应的距离值即为推荐的eps值。
4.5 DBSCAN的优缺点
优点:
- 不需要预先指定聚类数
- 能够发现任意形状的簇
- 对噪声点具有鲁棒性
- 只需扫描一遍数据集
缺点:
- 对参数eps和min_samples敏感
- 对于密度差异大的数据集效果不佳
- 高维数据中距离度量失效(维度灾难)
避坑小贴士
- 参数调优是关键:DBSCAN的效果高度依赖eps和min_samples的选择,建议结合K-距离图和业务经验进行调整
- 注意数据密度差异:如果数据中存在密度差异很大的簇,DBSCAN可能无法同时识别。此时可以考虑HDBSCAN等改进算法
- 高维数据慎用:在高维空间中,距离度量会失效,DBSCAN效果会下降。建议先进行降维处理
5. 主成分分析PCA
5.1 降维的必要性
在数据科学实践中,我们经常会遇到高维数据(特征数很多)。高维数据带来以下挑战:
- 维度灾难:随着维度增加,数据变得稀疏,距离度量失效
- 计算成本:高维数据需要更多的存储空间和计算资源
- 可视化困难:人类只能直观理解三维及以下的数据
- 过拟合风险:特征过多可能导致模型过拟合
5.2 PCA原理
PCA(Principal Component Analysis,主成分分析)是最常用的线性降维方法。其核心思想是:通过正交变换将原始特征转换为一组线性不相关的新特征(主成分),这些主成分按照方差大小排序,保留方差最大的前k个主成分即可实现降维。
算法的直观理解:想象你拍摄一个三维物体的照片,PCA就是找到最佳的拍摄角度,使得物体在二维照片上的信息损失最小。第一个主成分对应最佳拍摄角度,第二个主成分对应次佳角度,以此类推。
数学步骤:
- 数据标准化:对原始数据进行中心化处理(均值为0)
- 计算协方差矩阵:度量特征之间的相关性
- 特征值分解:对协方差矩阵进行特征值分解
- 选择主成分:选择特征值最大的k个特征向量,构成投影矩阵
- 数据投影:将原始数据投影到选定的特征向量上,得到降维后的数据
5.3 PCA代码实现
python
from sklearn.decomposition import PCA
from sklearn.datasets import load_iris
# 加载鸢尾花数据集
iris = load_iris()
X_iris = iris.data
y_iris = iris.target
# 数据标准化
scaler = StandardScaler()
X_iris_scaled = scaler.fit_transform(X_iris)
# 创建PCA模型,保留所有主成分
pca = PCA()
X_pca = pca.fit_transform(X_iris_scaled)
# 查看各主成分的方差解释率
print("各主成分的方差解释率:")
for i, ratio in enumerate(pca.explained_variance_ratio_):
print(f" 主成分{i+1}: {ratio:.4f} ({ratio*100:.2f}%)")
print(f"\n累积方差解释率:")
cumsum = np.cumsum(pca.explained_variance_ratio_)
for i, ratio in enumerate(cumsum):
print(f" 前{i+1}个主成分: {ratio:.4f} ({ratio*100:.2f}%)")
5.4 方差解释率与降维维度选择
python
# 绘制方差解释率图
plt.figure(figsize=(12, 5))
# 子图1:各主成分的方差解释率
plt.subplot(1, 2, 1)
plt.bar(range(1, len(pca.explained_variance_ratio_) + 1),
pca.explained_variance_ratio_, alpha=0.7)
plt.xlabel('主成分')
plt.ylabel('方差解释率')
plt.title('各主成分的方差解释率')
plt.xticks(range(1, len(pca.explained_variance_ratio_) + 1))
# 子图2:累积方差解释率
plt.subplot(1, 2, 2)
plt.plot(range(1, len(cumsum) + 1), cumsum, 'bo-')
plt.axhline(y=0.95, color='r', linestyle='--', label='95%阈值')
plt.xlabel('主成分数量')
plt.ylabel('累积方差解释率')
plt.title('累积方差解释率')
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()
# 根据累积方差解释率选择降维维度
n_components_95 = np.argmax(cumsum >= 0.95) + 1
print(f"保留95%方差信息需要{n_components_95}个主成分")
5.5 PCA可视化应用
python
# 使用PCA将数据降至2维进行可视化
pca_2d = PCA(n_components=2)
X_pca_2d = pca_2d.fit_transform(X_iris_scaled)
plt.figure(figsize=(10, 6))
colors = ['red', 'green', 'blue']
for i, color in enumerate(colors):
mask = y_iris == i
plt.scatter(X_pca_2d[mask, 0], X_pca_2d[mask, 1],
c=color, label=iris.target_names[i], alpha=0.7, s=60)
plt.xlabel(f'第一主成分 ({pca_2d.explained_variance_ratio_[0]*100:.1f}%)')
plt.ylabel(f'第二主成分 ({pca_2d.explained_variance_ratio_[1]*100:.1f}%)')
plt.title('PCA降维可视化(鸢尾花数据集)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()
5.6 PCA的应用场景
- 数据可视化:将高维数据降至2D或3D进行可视化
- 特征压缩:减少特征数量,降低存储和计算成本
- 噪声过滤:舍弃方差较小的主成分,相当于去除噪声
- 特征提取:提取数据的主要变化方向作为新特征
- 数据预处理:在机器学习前进行降维,缓解维度灾难
避坑小贴士
- PCA对尺度敏感:不同量纲的特征会影响PCA结果,务必先进行标准化
- PCA丢失非线性关系:PCA是线性降维方法,无法捕捉数据中的非线性结构。对于非线性数据,可以考虑Kernel PCA或t-SNE
- 主成分可解释性:主成分是原始特征的线性组合,有时难以解释其业务含义
- 不适合所有场景:如果所有特征都很重要,降维可能导致信息损失,影响模型性能
6. t-SNE降维可视化
6.1 t-SNE原理
t-SNE(t-Distributed Stochastic Neighbor Embedding)是一种非线性降维技术,特别适用于高维数据的可视化。与PCA不同,t-SNE专注于保持数据点之间的局部相似性,能够更好地揭示数据的聚类结构。
核心思想:
- 高维空间:计算每对样本之间的相似度,用条件概率表示
- 低维空间:在低维空间中构建相似的概率分布
- 优化目标:最小化高维和低维分布之间的KL散度,使两个分布尽可能接近
t-SNE使用t分布(自由度为1)来计算低维空间中的相似度,这使得远离的点在低维空间中距离更远,有效解决了拥挤问题(Crowding Problem)。
6.2 t-SNE代码实现
python
from sklearn.manifold import TSNE
# 使用t-SNE降维至2维
tsne = TSNE(n_components=2, random_state=42, perplexity=30)
X_tsne = tsne.fit_transform(X_iris_scaled)
# 可视化结果
plt.figure(figsize=(10, 6))
colors = ['red', 'green', 'blue']
for i, color in enumerate(colors):
mask = y_iris == i
plt.scatter(X_tsne[mask, 0], X_tsne[mask, 1],
c=color, label=iris.target_names[i], alpha=0.7, s=60)
plt.xlabel('t-SNE维度1')
plt.ylabel('t-SNE维度2')
plt.title('t-SNE降维可视化(鸢尾花数据集)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()
6.3 关键参数调优
t-SNE有几个关键参数需要调优:
perplexity(困惑度):
- 可以理解为近邻的数量,通常取值在5到50之间
- 较小的值关注局部结构,较大的值关注全局结构
- 建议尝试多个值,观察结果差异
learning_rate(学习率):
- 通常设置为10到1000之间
- 过小会导致收敛慢,过大可能导致发散
n_iter(迭代次数):
- 默认1000次,对于大数据集可能需要增加
- 建议至少1000次,确保充分收敛
python
# 对比不同perplexity值的效果
perplexities = [5, 30, 50, 100]
fig, axes = plt.subplots(2, 2, figsize=(14, 12))
axes = axes.ravel()
for idx, perp in enumerate(perplexities):
tsne = TSNE(n_components=2, random_state=42, perplexity=perp)
X_tsne = tsne.fit_transform(X_iris_scaled)
for i, color in enumerate(colors):
mask = y_iris == i
axes[idx].scatter(X_tsne[mask, 0], X_tsne[mask, 1],
c=color, label=iris.target_names[i], alpha=0.7, s=60)
axes[idx].set_title(f'perplexity = {perp}')
axes[idx].set_xlabel('t-SNE维度1')
axes[idx].set_ylabel('t-SNE维度2')
axes[idx].legend()
plt.tight_layout()
plt.show()
6.4 PCA与t-SNE对比
| 特性 | PCA | t-SNE |
|---|---|---|
| 类型 | 线性降维 | 非线性降维 |
| 计算速度 | 快 | 慢(O(n^2)复杂度) |
| 全局结构 | 保留 | 不保证保留 |
| 局部结构 | 一般 | 优秀 |
| 可逆性 | 可逆(可还原) | 不可逆 |
| 新数据 | 支持transform | 需重新训练 |
| 适用场景 | 特征压缩、预处理 | 可视化、探索性分析 |
6.5 实战建议
在实际应用中,建议采用PCA+t-SNE的组合策略:
- 先用PCA将高维数据降至适中维度(如50维),去除噪声和冗余
- 再用t-SNE将PCA结果降至2D或3D进行可视化
这样可以兼顾计算效率和可视化效果。
python
# PCA + t-SNE组合策略
pca_50 = PCA(n_components=50)
X_pca_50 = pca_50.fit_transform(X_iris_scaled)
tsne = TSNE(n_components=2, random_state=42, perplexity=30)
X_tsne_combined = tsne.fit_transform(X_pca_50)
plt.figure(figsize=(10, 6))
for i, color in enumerate(colors):
mask = y_iris == i
plt.scatter(X_tsne_combined[mask, 0], X_tsne_combined[mask, 1],
c=color, label=iris.target_names[i], alpha=0.7, s=60)
plt.xlabel('t-SNE维度1')
plt.ylabel('t-SNE维度2')
plt.title('PCA + t-SNE组合降维可视化')
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()
避坑小贴士
- t-SNE结果具有随机性:由于包含随机初始化,多次运行可能得到不同的结果。建议设置random_state保证可重复性
- 簇的大小和距离不可解释:t-SNE会倾向于将数据点均匀分布,因此簇的大小和簇间距离没有实际意义
- 不适合高维到低维的特征工程:t-SNE不可逆,且对新数据需要重新训练,不适合作为机器学习模型的输入
- 大数据集计算慢:对于超过10000个样本的数据集,t-SNE计算会很慢。建议先采样或使用Barnes-Hut近似算法
7. 实战练习:客户分群分析
7.1 业务背景
某电商平台希望对其客户进行分群,以便实施精准营销策略。我们有客户的以下数据:
- 年龄
- 年收入
- 消费评分(1-100)
7.2 完整代码实现
python
import pandas as pd
from sklearn.datasets import make_blobs
# 生成模拟客户数据(实际项目中应使用真实数据)
np.random.seed(42)
n_customers = 200
# 创建具有5个簇的客户数据
centers = [[20, 30, 60], [35, 70, 40], [50, 100, 80], [60, 40, 30], [45, 80, 90]]
X_customers, _ = make_blobs(n_samples=n_customers, centers=centers,
cluster_std=5, random_state=42)
# 创建DataFrame
df_customers = pd.DataFrame(X_customers, columns=['年龄', '年收入(千元)', '消费评分'])
df_customers['年龄'] = df_customers['年龄'].clip(18, 70).astype(int)
df_customers['年收入(千元)'] = df_customers['年收入(千元)'].clip(15, 150).astype(int)
df_customers['消费评分'] = df_customers['消费评分'].clip(1, 100).astype(int)
print("客户数据预览:")
print(df_customers.head(10))
print(f"\n数据统计:")
print(df_customers.describe())
# 数据标准化
X_customers_scaled = StandardScaler().fit_transform(df_customers)
# 使用肘部法则确定最佳聚类数
inertias = []
for k in range(1, 11):
kmeans = KMeans(n_clusters=k, random_state=42, n_init=10)
kmeans.fit(X_customers_scaled)
inertias.append(kmeans.inertia_)
# 计算轮廓系数
silhouette_scores = []
for k in range(2, 11):
kmeans = KMeans(n_clusters=k, random_state=42, n_init=10)
labels = kmeans.fit_predict(X_customers_scaled)
score = silhouette_score(X_customers_scaled, labels)
silhouette_scores.append(score)
# 可视化评估结果
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
axes[0].plot(range(1, 11), inertias, 'bo-')
axes[0].set_xlabel('聚类数k')
axes[0].set_ylabel('惯性(WCSS)')
axes[0].set_title('肘部法则')
axes[0].grid(True)
axes[1].plot(range(2, 11), silhouette_scores, 'ro-')
axes[1].set_xlabel('聚类数k')
axes[1].set_ylabel('轮廓系数')
axes[1].set_title('轮廓系数评估')
axes[1].grid(True)
plt.tight_layout()
plt.show()
# 根据评估结果选择最佳聚类数(这里选择k=5)
optimal_k = 5
kmeans_final = KMeans(n_clusters=optimal_k, random_state=42, n_init=10)
df_customers['聚类标签'] = kmeans_final.fit_predict(X_customers_scaled)
# 分析各簇特征
print("\n各客户群体的特征分析:")
cluster_summary = df_customers.groupby('聚类标签').agg({
'年龄': ['mean', 'std'],
'年收入(千元)': ['mean', 'std'],
'消费评分': ['mean', 'std'],
'聚类标签': 'count'
}).round(2)
cluster_summary.columns = ['年龄均值', '年龄标准差', '年收入均值', '年收入标准差',
'消费评分均值', '消费评分标准差', '客户数量']
print(cluster_summary)
# 使用PCA进行可视化
pca_viz = PCA(n_components=2)
X_pca_viz = pca_viz.fit_transform(X_customers_scaled)
plt.figure(figsize=(10, 6))
scatter = plt.scatter(X_pca_viz[:, 0], X_pca_viz[:, 1],
c=df_customers['聚类标签'], cmap='viridis', alpha=0.7, s=60)
plt.colorbar(scatter, label='聚类标签')
plt.xlabel(f'第一主成分 ({pca_viz.explained_variance_ratio_[0]*100:.1f}%)')
plt.ylabel(f'第二主成分 ({pca_viz.explained_variance_ratio_[1]*100:.1f}%)')
plt.title('客户分群结果可视化(PCA降维)')
plt.grid(True, alpha=0.3)
plt.show()
# 使用t-SNE进行可视化
tsne_viz = TSNE(n_components=2, random_state=42, perplexity=30)
X_tsne_viz = tsne_viz.fit_transform(X_customers_scaled)
plt.figure(figsize=(10, 6))
scatter = plt.scatter(X_tsne_viz[:, 0], X_tsne_viz[:, 1],
c=df_customers['聚类标签'], cmap='viridis', alpha=0.7, s=60)
plt.colorbar(scatter, label='聚类标签')
plt.xlabel('t-SNE维度1')
plt.ylabel('t-SNE维度2')
plt.title('客户分群结果可视化(t-SNE降维)')
plt.grid(True, alpha=0.3)
plt.show()
7.3 业务解读与营销建议
基于聚类结果,我们可以对客户群体进行画像分析:
| 客户群体 | 特征描述 | 营销策略 |
|---|---|---|
| 群体0 | 年轻、低收入、中等消费 | 推荐性价比高的入门级产品 |
| 群体1 | 中年、高收入、低消费 | 推送高端产品,培养消费习惯 |
| 群体2 | 中老年、高收入、高消费 | VIP专属服务,新品优先体验 |
| 群体3 | 中老年、低收入、低消费 | 促销活动,优惠券刺激 |
| 群体4 | 中年、高收入、高消费 | 会员积分,专属折扣 |
8. 本章小结
本章我们系统学习了无监督学习的核心算法,包括聚类和降维两大类任务。
聚类算法总结:
| 算法 | 核心思想 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| K-Means | 最小化簇内方差 | 简单高效 | 需预设k值,对异常值敏感 | 球形簇,大规模数据 |
| 层次聚类 | 自底向上/向下合并 | 无需预设k值,可解释性强 | 计算复杂度高 | 小规模数据,层次结构分析 |
| DBSCAN | 基于密度发现簇 | 发现任意形状,识别噪声 | 参数敏感,不适合密度差异大 | 异常检测,不规则簇 |
降维方法总结:
| 方法 | 类型 | 核心思想 | 适用场景 |
|---|---|---|---|
| PCA | 线性 | 最大化方差 | 特征压缩,预处理 |
| t-SNE | 非线性 | 保持局部相似性 | 数据可视化 |
一句话总结:无监督学习是数据探索的利器,聚类帮助发现数据的内在分组,降维帮助理解高维数据的结构。在实际应用中,建议多种方法结合使用,相互验证结果。
如果本讲内容对你有帮助,欢迎点赞、收藏、评论交流。你的支持是我持续创作的动力!