【Python数据科学实战之路】第12章 | 无监督学习算法实战:聚类与降维的奥秘

本章我们将深入探索无监督学习的核心算法,掌握K-Means、层次聚类、DBSCAN等聚类方法,以及PCA、t-SNE等降维技术,学会从数据中发现隐藏的模式与结构。


环境声明

  • Python版本Python 3.12+(建议使用3.10以上版本)
  • 主要库scikit-learn 1.5+numpypandasmatplotlibseabornplotly
  • 开发工具PyCharmVS Code
  • 操作系统Windows / macOS / Linux(通用)

学习目标

完成本讲学习后,你将能够:

  1. 理解无监督学习的核心思想,区分聚类与降维两大任务类型
  2. 掌握K-Means聚类算法的原理,熟练使用肘部法则和轮廓系数确定最佳聚类数
  3. 理解层次聚类的工作机制,能够解读树状图并选择合适的聚合策略
  4. 掌握DBSCAN密度聚类算法,能够根据数据特点调整eps和min_samples参数
  5. 深入理解PCA主成分分析原理,学会计算方差解释率并应用于特征降维
  6. 熟练使用t-SNE进行高维数据可视化,掌握perplexity等关键参数的调优方法
  7. 能够综合运用多种无监督学习方法解决实际业务问题

1. 无监督学习概述

1.1 什么是无监督学习

无监督学习是机器学习中一类重要的学习方法,与监督学习最大的区别在于:训练数据没有标签。无监督学习的核心目标是让算法自动从数据中发现隐藏的结构、模式或规律。

打个比方,监督学习就像是有老师指导的学习过程,每道题都有标准答案;而无监督学习则像是独自探索一个新领域,你需要自己发现事物之间的相似性和差异性,将它们归类整理。比如,给你一堆水果图片但没有标签,你要自己根据颜色、形状、大小等特征将它们分成不同的类别。

1.2 无监督学习的主要任务

根据2026年最新的机器学习课程体系,无监督学习主要包含两大类任务:

任务类型 核心目标 典型算法 应用场景
聚类分析 将数据划分为不同的组 K-Means、层次聚类、DBSCAN 客户分群、异常检测、图像分割
降维处理 减少数据维度同时保留主要信息 PCA、t-SNE、UMAP 数据可视化、特征压缩、去噪

1.3 无监督学习的应用价值

在当今数据驱动的时代,无监督学习具有不可替代的价值:

  1. 数据探索:在缺乏先验知识的情况下,帮助数据科学家理解数据的内在结构
  2. 异常检测:识别与正常模式显著不同的数据点,用于欺诈检测、设备故障预警
  3. 特征学习:自动提取数据的代表性特征,为后续监督学习提供输入
  4. 数据压缩:降低数据存储和计算成本,提高模型训练效率

1.4 2026年无监督学习发展趋势

根据2026年人工智能领域的最新研究动态,无监督学习正呈现以下发展趋势:

  1. 深度聚类方法:结合深度学习与聚类算法,如深度嵌入聚类(DEC),能够处理更复杂的高维数据
  2. 对比学习:通过构造正负样本对进行学习,在表示学习领域取得突破性进展
  3. 自监督学习:利用数据本身的结构信息生成监督信号,成为当前研究热点
  4. 可解释性提升:研究者越来越关注聚类结果的可解释性,开发能够解释聚类原因的算法

2. K-Means聚类算法

2.1 算法原理

K-Means是最经典、应用最广泛的聚类算法之一。其核心思想可以概括为:将n个样本划分为k个簇,使得簇内样本之间的相似度最大化,簇间样本的相似度最小化。

算法的直观理解:想象你要在地图上开设k个便利店,目标是让每个居民都尽可能就近购物。K-Means算法就是不断调整便利店位置(聚类中心),直到找到最优布局。

算法步骤如下:

  1. 初始化:随机选择k个样本作为初始聚类中心
  2. 分配样本:将每个样本分配到距离最近的聚类中心所属的簇
  3. 更新中心:重新计算每个簇的中心点(取簇内所有样本的均值)
  4. 迭代优化:重复步骤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()

避坑小贴士

  1. K-Means对初始值敏感:不同的初始中心可能导致不同的聚类结果。解决方案是使用n_init参数多次随机初始化,选择最优结果
  2. K-Means假设簇是凸形的:对于非球形或不规则形状的簇,K-Means效果较差。此时应考虑DBSCAN等密度聚类方法
  3. K-Means对异常值敏感:异常值会显著影响聚类中心的位置。建议先进行异常值检测和处理
  4. 特征尺度问题:K-Means基于距离计算,不同特征的量纲会影响结果。务必先进行标准化或归一化

3. 层次聚类算法

3.1 算法原理

层次聚类是一种自底向上(凝聚式)或自顶向下(分裂式)的聚类方法。凝聚式层次聚类从每个样本作为一个独立的簇开始,逐步合并最相似的簇,直到所有样本聚为一类。

算法的直观理解:想象你有一堆散落的树叶,你要将它们整理成一束。层次聚类就是不断将最相似的树叶(或叶束)绑在一起,最终形成一棵完整的树。

3.2 聚合方式(链接准则)

层次聚类中,如何度量两个簇之间的相似度?常用的链接准则包括:

链接方式 定义 特点
单链接(Single) 两个簇中最近样本的距离 容易产生链式效应,对噪声敏感
全链接(Complete) 两个簇中最远样本的距离 倾向于生成紧凑的球形簇
平均链接(Average) 两个簇中所有样本对的平均距离 平衡了单链接和全链接的特点
沃德链接(Ward) 合并后簇内方差增量 倾向于生成大小相近的簇,效果通常最好

3.3 树状图解读

树状图(Dendrogram)是层次聚类的可视化表示,展示了簇的合并过程。通过观察树状图,可以:

  1. 了解数据的层次结构
  2. 根据业务需求选择合适的聚类数(在特定高度切割树状图)
  3. 发现异常值或离群点
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()

避坑小贴士

  1. 计算复杂度高 :层次聚类的时间复杂度为O(n2)或O(n3),不适合大规模数据集(n>10000)。对于大数据集,可以先采样或使用K-Means预聚类
  2. 一旦合并无法撤销:层次聚类的决策是确定性的,早期的合并错误会影响后续结果
  3. 选择合适的链接方式:Ward链接通常效果较好,但对于 elongated( elongated)形状的簇,单链接可能更合适

4. DBSCAN密度聚类算法

4.1 算法原理

DBSCAN(Density-Based Spatial Clustering of Applications with Noise)是一种基于密度的聚类算法,能够发现任意形状的簇,并自动识别噪声点。

核心概念:

  1. 核心点(Core Point):在半径eps内包含至少min_samples个样本的点
  2. 边界点(Border Point):在eps内样本数少于min_samples,但落在某个核心点的邻域内的点
  3. 噪声点(Noise Point):既不是核心点也不是边界点的点

算法的直观理解:想象你在一片森林中寻找人群聚集的地方。DBSCAN就像是在寻找人口密度较高的区域,密度低的边缘地带属于同一个聚集区,而孤立的个体则被视为噪声。

4.2 算法流程

  1. 随机选择一个未访问的样本点
  2. 如果该点的eps邻域内包含至少min_samples个点,则创建一个新簇
  3. 将邻域内的所有点加入该簇,并对这些点递归执行步骤2
  4. 重复步骤1-3,直到所有点都被访问
  5. 未被归入任何簇的点标记为噪声

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敏感
  • 对于密度差异大的数据集效果不佳
  • 高维数据中距离度量失效(维度灾难)

避坑小贴士

  1. 参数调优是关键:DBSCAN的效果高度依赖eps和min_samples的选择,建议结合K-距离图和业务经验进行调整
  2. 注意数据密度差异:如果数据中存在密度差异很大的簇,DBSCAN可能无法同时识别。此时可以考虑HDBSCAN等改进算法
  3. 高维数据慎用:在高维空间中,距离度量会失效,DBSCAN效果会下降。建议先进行降维处理

5. 主成分分析PCA

5.1 降维的必要性

在数据科学实践中,我们经常会遇到高维数据(特征数很多)。高维数据带来以下挑战:

  1. 维度灾难:随着维度增加,数据变得稀疏,距离度量失效
  2. 计算成本:高维数据需要更多的存储空间和计算资源
  3. 可视化困难:人类只能直观理解三维及以下的数据
  4. 过拟合风险:特征过多可能导致模型过拟合

5.2 PCA原理

PCA(Principal Component Analysis,主成分分析)是最常用的线性降维方法。其核心思想是:通过正交变换将原始特征转换为一组线性不相关的新特征(主成分),这些主成分按照方差大小排序,保留方差最大的前k个主成分即可实现降维。

算法的直观理解:想象你拍摄一个三维物体的照片,PCA就是找到最佳的拍摄角度,使得物体在二维照片上的信息损失最小。第一个主成分对应最佳拍摄角度,第二个主成分对应次佳角度,以此类推。

数学步骤:

  1. 数据标准化:对原始数据进行中心化处理(均值为0)
  2. 计算协方差矩阵:度量特征之间的相关性
  3. 特征值分解:对协方差矩阵进行特征值分解
  4. 选择主成分:选择特征值最大的k个特征向量,构成投影矩阵
  5. 数据投影:将原始数据投影到选定的特征向量上,得到降维后的数据

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的应用场景

  1. 数据可视化:将高维数据降至2D或3D进行可视化
  2. 特征压缩:减少特征数量,降低存储和计算成本
  3. 噪声过滤:舍弃方差较小的主成分,相当于去除噪声
  4. 特征提取:提取数据的主要变化方向作为新特征
  5. 数据预处理:在机器学习前进行降维,缓解维度灾难

避坑小贴士

  1. PCA对尺度敏感:不同量纲的特征会影响PCA结果,务必先进行标准化
  2. PCA丢失非线性关系:PCA是线性降维方法,无法捕捉数据中的非线性结构。对于非线性数据,可以考虑Kernel PCA或t-SNE
  3. 主成分可解释性:主成分是原始特征的线性组合,有时难以解释其业务含义
  4. 不适合所有场景:如果所有特征都很重要,降维可能导致信息损失,影响模型性能

6. t-SNE降维可视化

6.1 t-SNE原理

t-SNE(t-Distributed Stochastic Neighbor Embedding)是一种非线性降维技术,特别适用于高维数据的可视化。与PCA不同,t-SNE专注于保持数据点之间的局部相似性,能够更好地揭示数据的聚类结构。

核心思想:

  1. 高维空间:计算每对样本之间的相似度,用条件概率表示
  2. 低维空间:在低维空间中构建相似的概率分布
  3. 优化目标:最小化高维和低维分布之间的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的组合策略:

  1. 先用PCA将高维数据降至适中维度(如50维),去除噪声和冗余
  2. 再用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()

避坑小贴士

  1. t-SNE结果具有随机性:由于包含随机初始化,多次运行可能得到不同的结果。建议设置random_state保证可重复性
  2. 簇的大小和距离不可解释:t-SNE会倾向于将数据点均匀分布,因此簇的大小和簇间距离没有实际意义
  3. 不适合高维到低维的特征工程:t-SNE不可逆,且对新数据需要重新训练,不适合作为机器学习模型的输入
  4. 大数据集计算慢:对于超过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 非线性 保持局部相似性 数据可视化

一句话总结:无监督学习是数据探索的利器,聚类帮助发现数据的内在分组,降维帮助理解高维数据的结构。在实际应用中,建议多种方法结合使用,相互验证结果。


如果本讲内容对你有帮助,欢迎点赞、收藏、评论交流。你的支持是我持续创作的动力!

相关推荐
MoRanzhi12032 小时前
Pillow 灰度化、二值化与阈值处理
图像处理·python·pillow·二值化·图像预处理·阈值处理·灰度化
泯仲2 小时前
从零起步学习MySQL 第三章:DML语句定义及常见用法示例
数据库·学习·mysql
像素猎人2 小时前
数据结构之顺序表的插入+删除+查找+修改操作【主函数一步一输出,代码更加清晰直观】
数据结构·c++·算法
飞Link2 小时前
告别复杂调参:Prophet 加法模型深度解析与实战
开发语言·python·数据挖掘
钰衡大师2 小时前
Vue 3 源码学习教程
前端·vue.js·学习
测试人社区—66792 小时前
当代码面临道德选择:VR如何为AI伦理决策注入“人性压力”
网络·人工智能·python·microsoft·vr·azure
季明洵3 小时前
二叉树的最小深度、完全二叉树的节点个数、平衡二叉树、路径总和、从中序与后序遍历序列构造二叉树
java·数据结构·算法·leetcode·二叉树
独行soc3 小时前
2026年渗透测试面试题总结-36(题目+回答)
网络·python·安全·web安全·网络安全·渗透测试·安全狮