K-Means聚类+PCA降维实战教程:从原理到Python实现

83 阅读9分钟

在数据分析和机器学习领域,K-Means聚类是最经典的无监督分类算法,而PCA(主成分分析)则是高维数据降维的核心手段。将两者结合,既能解决高维数据聚类的“维度灾难”问题,又能提升聚类效率和可解释性。本文从原理拆解、代码实战到场景优化,全方位讲解K-Means聚类与PCA降维的使用方法,适合数据分析初学者和算法应用开发者学习。

一、核心概念:先搞懂K-Means与PCA的底层逻辑

1. K-Means聚类:无监督的“物以类聚”

K-Means是基于距离的聚类算法,核心目标是将数据划分为K个簇,使得同一簇内数据点距离尽可能近,不同簇间距离尽可能远。

核心步骤:

  1. 初始化:随机选择K个数据点作为初始聚类中心;
  2. 分配簇:计算每个数据点到K个中心的欧氏距离,将其分配到距离最近的簇;
  3. 更新中心:重新计算每个簇的均值,作为新的聚类中心;
  4. 迭代收敛:重复“分配簇-更新中心”,直到聚类中心不再变化或达到迭代次数上限。

关键特点:

  • 优点:简单高效、易于实现,适合处理大规模数据;
  • 缺点:对初始聚类中心敏感,需提前指定K值,对异常值和非球形簇效果差。

2. PCA降维:高维数据的“瘦身术”

PCA通过线性变换,将高维数据映射到低维空间,同时保留数据的主要信息(方差最大的方向)。比如将100维的特征压缩到2-3维,既简化计算,又能可视化数据分布。

核心步骤:

  1. 数据标准化:将各特征缩放到同一尺度(避免量纲影响);
  2. 计算协方差矩阵:反映特征间的线性相关程度;
  3. 求解特征值和特征向量:特征值越大,对应特征向量方向的方差越大;
  4. 选择主成分:选取前N个最大特征值对应的特征向量,构建投影矩阵;
  5. 数据投影:将原始数据乘以投影矩阵,得到降维后的数据。

关键特点:

  • 优点:无监督、无参数限制,能有效降低数据维度;
  • 缺点:解释性弱(主成分是原始特征的线性组合),对非线性数据降维效果差。

二、前置准备:环境搭建与数据准备

1. 安装必备库

本文使用numpy处理数值计算、pandas加载数据、sklearn实现算法、matplotlib可视化结果,安装命令:

# 一键安装
pip install numpy pandas scikit-learn matplotlib

# 指定版本(推荐稳定版)
pip install numpy==1.24.3 pandas==2.0.3 scikit-learn==1.2.2 matplotlib==3.7.1

2. 准备测试数据

我们使用sklearn内置的鸢尾花(Iris)数据集(4维特征,3类样本),既适合演示PCA降维,也能验证K-Means聚类效果:

import pandas as pd
from sklearn.datasets import load_iris

# 加载数据集
iris = load_iris()
# 转为DataFrame,方便查看
df = pd.DataFrame(
    data=iris.data,
    columns=['花萼长度', '花萼宽度', '花瓣长度', '花瓣宽度']
)
# 添加真实标签(用于后续验证聚类效果)
df['真实标签'] = iris.target
print("数据集前5行:")
print(df.head())
print(f"数据集形状:{df.shape}")  # (150, 5),150个样本,4个特征+1个标签

三、实战1:PCA降维实现(从4维到2维)

1. 完整PCA降维代码

import numpy as np
import matplotlib.pyplot as plt
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA

# 步骤1:提取特征数据并标准化
X = df[['花萼长度', '花萼宽度', '花瓣长度', '花瓣宽度']].values
# 标准化(PCA对量纲敏感,必须做)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

# 步骤2:构建PCA模型,降维到2维
pca = PCA(n_components=2)  # 指定降维后的维度
X_pca = pca.fit_transform(X_scaled)

# 步骤3:查看降维结果
print(f"原始数据维度:{X.shape}")
print(f"降维后数据维度:{X_pca.shape}")  # (150, 2)

# 步骤4:查看主成分解释方差(评估降维效果)
explained_variance = pca.explained_variance_ratio_
print(f"各主成分解释方差占比:{explained_variance}")
print(f"累计解释方差占比:{np.sum(explained_variance):.2f}")  # 鸢尾花数据约97%,说明2维保留了绝大部分信息

# 步骤5:可视化降维后的数据
plt.figure(figsize=(8, 6))
# 按真实标签着色
colors = ['red', 'green', 'blue']
labels = ['山鸢尾', '变色鸢尾', '维吉尼亚鸢尾']
for i in range(3):
    plt.scatter(
        X_pca[df['真实标签']==i, 0],  # 第一主成分
        X_pca[df['真实标签']==i, 1],  # 第二主成分
        c=colors[i],
        label=labels[i],
        alpha=0.8
    )
plt.xlabel('第一主成分')
plt.ylabel('第二主成分')
plt.title('PCA降维后鸢尾花数据分布')
plt.legend()
plt.show()

2. 关键参数说明

  • n_components:可指定降维后的维度(如2),也可指定解释方差占比(如0.95,表示保留95%的信息):
    # 保留95%的信息,自动确定维度
    pca = PCA(n_components=0.95)
    X_pca = pca.fit_transform(X_scaled)
    print(f"自动选择的维度数:{pca.n_components_}")
    
  • explained_variance_ratio_:每个主成分解释的方差占比,累计值越高,降维保留的信息越多。

四、实战2:K-Means聚类实现(基于降维后数据)

1. 完整K-Means聚类代码

from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score

# 步骤1:使用PCA降维后的数据进行聚类
# 确定K值:鸢尾花数据真实类别数为3,这里指定K=3
kmeans = KMeans(
    n_clusters=3,  # 聚类数
    init='k-means++',  # 优化初始中心选择,避免随机初始化的弊端
    max_iter=300,  # 最大迭代次数
    random_state=42  # 固定随机种子,保证结果可复现
)
# 训练模型并预测聚类标签
y_pred = kmeans.fit_predict(X_pca)

# 步骤2:评估聚类效果
# 1. 轮廓系数(范围[-1,1],越接近1聚类效果越好)
sil_score = silhouette_score(X_pca, y_pred)
print(f"聚类轮廓系数:{sil_score:.2f}")  # 鸢尾花数据约0.55,聚类效果较好

# 2. 查看聚类中心
print("聚类中心(降维后空间):")
print(kmeans.cluster_centers_)

# 步骤3:可视化聚类结果
plt.figure(figsize=(8, 6))
# 按聚类标签着色
for i in range(3):
    plt.scatter(
        X_pca[y_pred==i, 0],
        X_pca[y_pred==i, 1],
        c=colors[i],
        label=f'聚类{i+1}',
        alpha=0.8
    )
# 绘制聚类中心
plt.scatter(
    kmeans.cluster_centers_[:, 0],
    kmeans.cluster_centers_[:, 1],
    c='black',
    marker='*',
    s=200,  # 大小
    label='聚类中心'
)
plt.xlabel('第一主成分')
plt.ylabel('第二主成分')
plt.title('K-Means聚类结果(PCA降维后)')
plt.legend()
plt.show()

2. 如何选择最优K值?

实际场景中无法提前知道K值,常用“肘部法则”和轮廓系数结合选择:

# 肘部法则:计算不同K值的平方和误差(SSE)
sse = []
sil_scores = []
k_range = range(2, 10)  # 测试K=2到9
for k in k_range:
    kmeans_temp = KMeans(n_clusters=k, init='k-means++', random_state=42)
    kmeans_temp.fit(X_pca)
    sse.append(kmeans_temp.inertia_)  # 平方和误差
    sil_scores.append(silhouette_score(X_pca, kmeans_temp.labels_))

# 可视化肘部法则曲线
plt.figure(figsize=(12, 5))
# 子图1:SSE曲线
plt.subplot(1, 2, 1)
plt.plot(k_range, sse, 'o-')
plt.xlabel('K值')
plt.ylabel('平方和误差(SSE)')
plt.title('肘部法则选择最优K值')
plt.axvline(x=3, color='red', linestyle='--')  # 肘部位置

# 子图2:轮廓系数曲线
plt.subplot(1, 2, 2)
plt.plot(k_range, sil_scores, 'o-', color='green')
plt.xlabel('K值')
plt.ylabel('轮廓系数')
plt.title('轮廓系数选择最优K值')
plt.axvline(x=3, color='red', linestyle='--')
plt.show()
  • 肘部法则:SSE随K增大逐渐下降,下降速度骤减的点即为最优K(鸢尾花数据K=3);
  • 轮廓系数:选择轮廓系数最大的K值。

五、实战3:完整流程(原始数据→PCA降维→K-Means聚类)

将上述步骤整合,形成可复用的完整代码:

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score

# 1. 加载并预处理数据
def load_and_preprocess_data():
    iris = load_iris()
    X = iris.data
    # 标准化
    scaler = StandardScaler()
    X_scaled = scaler.fit_transform(X)
    return X_scaled, iris.target

# 2. PCA降维
def pca_dimension_reduction(X_scaled, n_components=2):
    pca = PCA(n_components=n_components, random_state=42)
    X_pca = pca.fit_transform(X_scaled)
    explained_var = np.sum(pca.explained_variance_ratio_)
    print(f"PCA降维后累计解释方差占比:{explained_var:.2f}")
    return X_pca

# 3. K-Means聚类
def kmeans_clustering(X_pca, n_clusters=3):
    kmeans = KMeans(
        n_clusters=n_clusters,
        init='k-means++',
        max_iter=300,
        random_state=42
    )
    y_pred = kmeans.fit_predict(X_pca)
    sil_score = silhouette_score(X_pca, y_pred)
    print(f"K-Means聚类轮廓系数:{sil_score:.2f}")
    return y_pred, kmeans.cluster_centers_

# 4. 可视化结果
def visualize_result(X_pca, y_pred, centers):
    plt.figure(figsize=(8, 6))
    colors = ['red', 'green', 'blue']
    for i in range(3):
        plt.scatter(
            X_pca[y_pred==i, 0],
            X_pca[y_pred==i, 1],
            c=colors[i],
            label=f'聚类{i+1}',
            alpha=0.8
        )
    plt.scatter(
        centers[:, 0],
        centers[:, 1],
        c='black',
        marker='*',
        s=200,
        label='聚类中心'
    )
    plt.xlabel('第一主成分')
    plt.ylabel('第二主成分')
    plt.title('PCA+K-Means聚类结果')
    plt.legend()
    plt.show()

# 主函数
if __name__ == '__main__':
    # 执行完整流程
    X_scaled, true_labels = load_and_preprocess_data()
    X_pca = pca_dimension_reduction(X_scaled, n_components=2)
    y_pred, centers = kmeans_clustering(X_pca, n_clusters=3)
    visualize_result(X_pca, y_pred, centers)

六、避坑指南:常见问题与解决方案

1. PCA降维后效果差?

  • 原因:未做数据标准化,特征量纲差异大;
  • 解决:必须先使用StandardScalerMinMaxScaler标准化数据。

2. K-Means聚类结果不稳定?

  • 原因:初始聚类中心随机选择;
  • 解决:使用init='k-means++'(默认),或设置random_state固定种子,也可增加n_init参数(默认10,多次初始化选最优)。

3. 无法确定最优K值?

  • 解决:结合肘部法则和轮廓系数,避免仅依赖单一指标;对于业务数据,可结合业务场景(如用户分群需几类)确定K值。

4. 聚类结果与真实标签不符?

  • 说明:K-Means是无监督算法,聚类标签仅代表类别划分,不与真实标签一一对应;
  • 验证:可使用adjusted_rand_score评估聚类标签与真实标签的一致性:
    from sklearn.metrics import adjusted_rand_score
    ari = adjusted_rand_score(true_labels, y_pred)
    print(f"调整兰德指数(ARI):{ari:.2f}")  # 越接近1,一致性越高
    

七、应用场景与优化建议

1. 典型应用场景

  • 客户分群:将高维用户行为数据(浏览、购买、点击)经PCA降维后,用K-Means划分客户群体;
  • 图像压缩:将图像像素数据降维后聚类,减少存储和计算成本;
  • 异常检测:聚类后远离所有中心的样本,可判定为异常数据。

2. 优化建议

  • 处理异常值:K-Means对异常值敏感,聚类前用IQR或Z-score剔除异常值;
  • 非线性降维:若PCA效果差,可尝试t-SNE、UMAP等非线性降维算法;
  • 并行计算:大规模数据时,使用sklearn.cluster.MiniBatchKMeans替代K-Means,提升速度;
  • 模型融合:结合多个K值的聚类结果,提升稳定性。