Skip to content

第11章:聚类分析

聚类分析是无监督学习的重要分支,它在没有标签信息的情况下,根据数据的相似性将样本分组。本章将详细介绍各种聚类算法的原理、实现和应用。

11.1 什么是聚类分析?

聚类分析是一种探索性数据分析技术,目标是将相似的数据点归为一类,不相似的数据点分为不同类。与分类不同,聚类是无监督学习,不需要预先知道类别标签。

11.1.1 聚类的目标

  • 组内相似性最大化:同一簇内的样本尽可能相似
  • 组间相似性最小化:不同簇间的样本尽可能不同
  • 发现数据结构:揭示数据中的隐藏模式

11.1.2 聚类的应用

  • 市场细分:根据消费行为对客户分群
  • 图像分割:将图像分割成不同区域
  • 基因分析:根据表达模式对基因分类
  • 推荐系统:发现用户群体和物品类别
  • 异常检测:识别不属于任何簇的异常点

11.1.3 聚类算法分类

  • 基于距离的聚类:K-means、K-medoids
  • 层次聚类:凝聚聚类、分裂聚类
  • 基于密度的聚类:DBSCAN、OPTICS
  • 基于分布的聚类:高斯混合模型
  • 基于网格的聚类:适用于大数据集

11.2 准备环境和数据

python
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.datasets import make_blobs, make_circles, make_moons, load_iris, load_wine
from sklearn.cluster import KMeans, AgglomerativeClustering, DBSCAN, SpectralClustering
from sklearn.mixture import GaussianMixture
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import adjusted_rand_score, silhouette_score, calinski_harabasz_score
from sklearn.decomposition import PCA
from scipy.cluster.hierarchy import dendrogram, linkage
from scipy.spatial.distance import pdist, squareform
import warnings
warnings.filterwarnings('ignore')

# 设置随机种子
np.random.seed(42)

# 设置图形样式
plt.style.use('seaborn-v0_8')
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False

11.3 K-means聚类

11.3.1 K-means算法原理

K-means是最经典的聚类算法,通过迭代优化来最小化簇内平方和。

python
def demonstrate_kmeans_principle():
    """演示K-means算法的基本原理"""
    
    # 创建简单的聚类数据
    X_demo, y_true = make_blobs(n_samples=300, centers=3, n_features=2, 
                                random_state=42, cluster_std=1.5)
    
    print("K-means算法步骤演示:")
    print("1. 随机初始化K个聚类中心")
    print("2. 将每个点分配给最近的聚类中心")
    print("3. 更新聚类中心为簇内点的均值")
    print("4. 重复步骤2-3直到收敛")
    
    # 可视化数据
    plt.figure(figsize=(15, 10))
    
    # 原始数据
    plt.subplot(2, 3, 1)
    plt.scatter(X_demo[:, 0], X_demo[:, 1], c='gray', alpha=0.6)
    plt.title('原始数据')
    plt.xlabel('特征 1')
    plt.ylabel('特征 2')
    plt.grid(True, alpha=0.3)
    
    # 手动演示K-means迭代过程
    k = 3
    # 随机初始化聚类中心
    centers = np.random.randn(k, 2) * 2
    
    for iteration in range(5):
        if iteration < 4:  # 只显示前4次迭代
            plt.subplot(2, 3, iteration + 2)
            
            # 计算每个点到聚类中心的距离
            distances = np.sqrt(((X_demo - centers[:, np.newaxis])**2).sum(axis=2))
            labels = np.argmin(distances, axis=0)
            
            # 绘制数据点和聚类中心
            colors = ['red', 'blue', 'green']
            for i in range(k):
                mask = labels == i
                plt.scatter(X_demo[mask, 0], X_demo[mask, 1], 
                           c=colors[i], alpha=0.6, s=30)
                plt.scatter(centers[i, 0], centers[i, 1], 
                           c='black', marker='x', s=200, linewidths=3)
            
            plt.title(f'迭代 {iteration + 1}')
            plt.xlabel('特征 1')
            plt.ylabel('特征 2')
            plt.grid(True, alpha=0.3)
            
            # 更新聚类中心
            new_centers = np.array([X_demo[labels == i].mean(axis=0) for i in range(k)])
            centers = new_centers
    
    # 最终结果
    plt.subplot(2, 3, 6)
    for i in range(k):
        mask = labels == i
        plt.scatter(X_demo[mask, 0], X_demo[mask, 1], 
                   c=colors[i], alpha=0.6, s=30)
        plt.scatter(centers[i, 0], centers[i, 1], 
                   c='black', marker='x', s=200, linewidths=3)
    plt.title('最终结果')
    plt.xlabel('特征 1')
    plt.ylabel('特征 2')
    plt.grid(True, alpha=0.3)
    
    plt.tight_layout()
    plt.show()
    
    return X_demo, y_true

X_demo, y_true = demonstrate_kmeans_principle()
```###
 11.3.2 使用Scikit-learn实现K-means

```python
# 使用Scikit-learn的K-means
kmeans = KMeans(n_clusters=3, random_state=42, n_init=10)
y_pred = kmeans.fit_predict(X_demo)

# 获取聚类中心
centers = kmeans.cluster_centers_

print("K-means聚类结果:")
print(f"聚类中心:\n{centers}")
print(f"惯性 (WCSS): {kmeans.inertia_:.2f}")

# 可视化结果
plt.figure(figsize=(15, 5))

# 真实标签
plt.subplot(1, 3, 1)
colors = ['red', 'blue', 'green']
for i in range(3):
    mask = y_true == i
    plt.scatter(X_demo[mask, 0], X_demo[mask, 1], c=colors[i], alpha=0.6, label=f'真实簇 {i}')
plt.title('真实聚类')
plt.xlabel('特征 1')
plt.ylabel('特征 2')
plt.legend()
plt.grid(True, alpha=0.3)

# K-means结果
plt.subplot(1, 3, 2)
for i in range(3):
    mask = y_pred == i
    plt.scatter(X_demo[mask, 0], X_demo[mask, 1], c=colors[i], alpha=0.6, label=f'预测簇 {i}')
plt.scatter(centers[:, 0], centers[:, 1], c='black', marker='x', s=200, linewidths=3, label='聚类中心')
plt.title('K-means聚类结果')
plt.xlabel('特征 1')
plt.ylabel('特征 2')
plt.legend()
plt.grid(True, alpha=0.3)

# 对比
plt.subplot(1, 3, 3)
plt.scatter(X_demo[:, 0], X_demo[:, 1], c=y_pred, cmap='viridis', alpha=0.6)
plt.scatter(centers[:, 0], centers[:, 1], c='red', marker='x', s=200, linewidths=3)
plt.title('聚类结果 (颜色编码)')
plt.xlabel('特征 1')
plt.ylabel('特征 2')
plt.colorbar()
plt.grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

11.3.3 选择最优的K值

python
def find_optimal_k(X, k_range):
    """使用肘部法则和轮廓系数选择最优K值"""
    
    inertias = []
    silhouette_scores = []
    
    print("不同K值的聚类性能:")
    print("K值\t惯性(WCSS)\t轮廓系数")
    print("-" * 35)
    
    for k in k_range:
        kmeans = KMeans(n_clusters=k, random_state=42, n_init=10)
        y_pred = kmeans.fit_predict(X)
        
        inertia = kmeans.inertia_
        if k > 1:  # 轮廓系数需要至少2个簇
            silhouette = silhouette_score(X, y_pred)
        else:
            silhouette = 0
        
        inertias.append(inertia)
        silhouette_scores.append(silhouette)
        
        print(f"{k}\t{inertia:.2f}\t\t{silhouette:.4f}")
    
    # 可视化
    fig, axes = plt.subplots(1, 2, figsize=(15, 6))
    
    # 肘部法则
    axes[0].plot(k_range, inertias, 'bo-', linewidth=2, markersize=8)
    axes[0].set_xlabel('K值')
    axes[0].set_ylabel('惯性 (WCSS)')
    axes[0].set_title('肘部法则')
    axes[0].grid(True, alpha=0.3)
    
    # 轮廓系数
    axes[1].plot(k_range[1:], silhouette_scores[1:], 'ro-', linewidth=2, markersize=8)
    axes[1].set_xlabel('K值')
    axes[1].set_ylabel('轮廓系数')
    axes[1].set_title('轮廓系数法')
    axes[1].grid(True, alpha=0.3)
    
    plt.tight_layout()
    plt.show()
    
    # 找出最佳K值
    best_k_silhouette = k_range[1:][np.argmax(silhouette_scores[1:])]
    print(f"\n基于轮廓系数的最佳K值: {best_k_silhouette}")
    
    return inertias, silhouette_scores

# 寻找最优K值
k_range = range(1, 11)
inertias, silhouette_scores = find_optimal_k(X_demo, k_range)

11.3.4 K-means的变体

python
# 比较不同的K-means变体
from sklearn.cluster import MiniBatchKMeans

def compare_kmeans_variants():
    """比较不同的K-means变体"""
    
    # 创建较大的数据集
    X_large, y_large = make_blobs(n_samples=2000, centers=4, n_features=2, 
                                  random_state=42, cluster_std=2.0)
    
    # 不同的K-means算法
    algorithms = {
        'K-means': KMeans(n_clusters=4, random_state=42),
        'Mini-batch K-means': MiniBatchKMeans(n_clusters=4, random_state=42, batch_size=100)
    }
    
    results = {}
    
    print("K-means变体性能比较:")
    print("算法\t\t\t训练时间\t惯性\t\t轮廓系数")
    print("-" * 60)
    
    import time
    
    fig, axes = plt.subplots(1, 2, figsize=(15, 6))
    
    for i, (name, algorithm) in enumerate(algorithms.items()):
        # 训练时间
        start_time = time.time()
        y_pred = algorithm.fit_predict(X_large)
        train_time = time.time() - start_time
        
        # 性能指标
        inertia = algorithm.inertia_
        silhouette = silhouette_score(X_large, y_pred)
        
        results[name] = {
            'time': train_time,
            'inertia': inertia,
            'silhouette': silhouette
        }
        
        print(f"{name}\t{train_time:.4f}s\t\t{inertia:.2f}\t\t{silhouette:.4f}")
        
        # 可视化
        axes[i].scatter(X_large[:, 0], X_large[:, 1], c=y_pred, cmap='viridis', alpha=0.6, s=10)
        axes[i].scatter(algorithm.cluster_centers_[:, 0], algorithm.cluster_centers_[:, 1], 
                       c='red', marker='x', s=200, linewidths=3)
        axes[i].set_title(f'{name}\n轮廓系数: {silhouette:.3f}')
        axes[i].set_xlabel('特征 1')
        axes[i].set_ylabel('特征 2')
        axes[i].grid(True, alpha=0.3)
    
    plt.tight_layout()
    plt.show()
    
    return results

kmeans_results = compare_kmeans_variants()

11.4 层次聚类

11.4.1 凝聚层次聚类

python
def demonstrate_hierarchical_clustering():
    """演示层次聚类"""
    
    # 创建小规模数据用于演示
    X_hier, y_hier = make_blobs(n_samples=50, centers=3, n_features=2, 
                                random_state=42, cluster_std=1.0)
    
    # 凝聚层次聚类
    hierarchical = AgglomerativeClustering(n_clusters=3, linkage='ward')
    y_pred_hier = hierarchical.fit_predict(X_hier)
    
    print("层次聚类结果:")
    print(f"聚类数: {len(np.unique(y_pred_hier))}")
    
    # 可视化结果
    plt.figure(figsize=(15, 5))
    
    # 原始数据
    plt.subplot(1, 3, 1)
    plt.scatter(X_hier[:, 0], X_hier[:, 1], c='gray', alpha=0.6)
    plt.title('原始数据')
    plt.xlabel('特征 1')
    plt.ylabel('特征 2')
    plt.grid(True, alpha=0.3)
    
    # 层次聚类结果
    plt.subplot(1, 3, 2)
    colors = ['red', 'blue', 'green']
    for i in range(3):
        mask = y_pred_hier == i
        plt.scatter(X_hier[mask, 0], X_hier[mask, 1], c=colors[i], alpha=0.6, label=f'簇 {i}')
    plt.title('层次聚类结果')
    plt.xlabel('特征 1')
    plt.ylabel('特征 2')
    plt.legend()
    plt.grid(True, alpha=0.3)
    
    # 树状图
    plt.subplot(1, 3, 3)
    # 计算链接矩阵
    linkage_matrix = linkage(X_hier, method='ward')
    dendrogram(linkage_matrix, truncate_mode='level', p=5)
    plt.title('树状图')
    plt.xlabel('样本索引')
    plt.ylabel('距离')
    
    plt.tight_layout()
    plt.show()
    
    return X_hier, y_pred_hier

X_hier, y_pred_hier = demonstrate_hierarchical_clustering()

11.4.2 不同链接方法的比较

python
def compare_linkage_methods():
    """比较不同的链接方法"""
    
    # 创建具有不同形状的聚类数据
    X_shapes = []
    y_shapes = []
    
    # 圆形聚类
    X_circles, y_circles = make_circles(n_samples=200, noise=0.1, factor=0.3, random_state=42)
    X_shapes.append(('圆形', X_circles, y_circles))
    
    # 月牙形聚类
    X_moons, y_moons = make_moons(n_samples=200, noise=0.1, random_state=42)
    X_shapes.append(('月牙形', X_moons, y_moons))
    
    # 球形聚类
    X_blobs, y_blobs = make_blobs(n_samples=200, centers=3, random_state=42)
    X_shapes.append(('球形', X_blobs, y_blobs))
    
    # 不同的链接方法
    linkage_methods = ['ward', 'complete', 'average', 'single']
    
    fig, axes = plt.subplots(len(X_shapes), len(linkage_methods) + 1, figsize=(20, 12))
    fig.suptitle('不同链接方法的层次聚类比较', fontsize=16)
    
    for i, (shape_name, X, y_true) in enumerate(X_shapes):
        # 原始数据
        axes[i, 0].scatter(X[:, 0], X[:, 1], c=y_true, cmap='viridis', alpha=0.6)
        axes[i, 0].set_title(f'{shape_name}数据')
        axes[i, 0].set_xlabel('特征 1')
        axes[i, 0].set_ylabel('特征 2')
        
        # 不同链接方法
        for j, linkage_method in enumerate(linkage_methods):
            n_clusters = len(np.unique(y_true))
            
            if linkage_method == 'ward':
                # Ward方法只适用于欧几里得距离
                hierarchical = AgglomerativeClustering(n_clusters=n_clusters, linkage=linkage_method)
            else:
                hierarchical = AgglomerativeClustering(n_clusters=n_clusters, linkage=linkage_method)
            
            y_pred = hierarchical.fit_predict(X)
            
            axes[i, j + 1].scatter(X[:, 0], X[:, 1], c=y_pred, cmap='viridis', alpha=0.6)
            axes[i, j + 1].set_title(f'{linkage_method}链接')
            axes[i, j + 1].set_xlabel('特征 1')
            axes[i, j + 1].set_ylabel('特征 2')
    
    plt.tight_layout()
    plt.show()

compare_linkage_methods()
```##
 11.5 基于密度的聚类 (DBSCAN)

### 11.5.1 DBSCAN算法原理

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

```python
def demonstrate_dbscan_principle():
    """演示DBSCAN算法原理"""
    
    print("DBSCAN算法核心概念:")
    print("1. 核心点: 在其ε邻域内至少有min_samples个点")
    print("2. 边界点: 不是核心点但在某个核心点的ε邻域内")
    print("3. 噪声点: 既不是核心点也不是边界点")
    print("4. 密度可达: 通过核心点序列连接的点")
    
    # 创建包含噪声的数据
    X_noise, _ = make_blobs(n_samples=200, centers=3, n_features=2, 
                           random_state=42, cluster_std=1.0)
    
    # 添加噪声点
    noise_points = np.random.uniform(-8, 8, (20, 2))
    X_dbscan = np.vstack([X_noise, noise_points])
    
    # DBSCAN聚类
    dbscan = DBSCAN(eps=1.5, min_samples=5)
    y_pred_dbscan = dbscan.fit_predict(X_dbscan)
    
    # 统计结果
    n_clusters = len(set(y_pred_dbscan)) - (1 if -1 in y_pred_dbscan else 0)
    n_noise = list(y_pred_dbscan).count(-1)
    
    print(f"\nDBSCAN聚类结果:")
    print(f"聚类数: {n_clusters}")
    print(f"噪声点数: {n_noise}")
    print(f"核心点数: {len(dbscan.core_sample_indices_)}")
    
    # 可视化结果
    plt.figure(figsize=(15, 5))
    
    # 原始数据
    plt.subplot(1, 3, 1)
    plt.scatter(X_dbscan[:, 0], X_dbscan[:, 1], c='gray', alpha=0.6)
    plt.title('原始数据(含噪声)')
    plt.xlabel('特征 1')
    plt.ylabel('特征 2')
    plt.grid(True, alpha=0.3)
    
    # DBSCAN结果
    plt.subplot(1, 3, 2)
    unique_labels = set(y_pred_dbscan)
    colors = plt.cm.Spectral(np.linspace(0, 1, len(unique_labels)))
    
    for k, col in zip(unique_labels, colors):
        if k == -1:
            # 噪声点用黑色表示
            col = 'black'
            marker = 'x'
            alpha = 0.5
            label = '噪声点'
        else:
            marker = 'o'
            alpha = 0.7
            label = f'簇 {k}'
        
        class_member_mask = (y_pred_dbscan == k)
        xy = X_dbscan[class_member_mask]
        plt.scatter(xy[:, 0], xy[:, 1], c=[col], marker=marker, alpha=alpha, s=50)
    
    plt.title('DBSCAN聚类结果')
    plt.xlabel('特征 1')
    plt.ylabel('特征 2')
    plt.grid(True, alpha=0.3)
    
    # 核心点标记
    plt.subplot(1, 3, 3)
    # 绘制所有点
    plt.scatter(X_dbscan[:, 0], X_dbscan[:, 1], c=y_pred_dbscan, 
               cmap='Spectral', alpha=0.6, s=50)
    
    # 标记核心点
    core_samples = X_dbscan[dbscan.core_sample_indices_]
    plt.scatter(core_samples[:, 0], core_samples[:, 1], 
               facecolors='none', edgecolors='red', s=100, linewidths=2, label='核心点')
    
    plt.title('核心点标记')
    plt.xlabel('特征 1')
    plt.ylabel('特征 2')
    plt.legend()
    plt.grid(True, alpha=0.3)
    
    plt.tight_layout()
    plt.show()
    
    return X_dbscan, y_pred_dbscan

X_dbscan, y_pred_dbscan = demonstrate_dbscan_principle()

11.5.2 DBSCAN参数调优

python
def tune_dbscan_parameters():
    """调优DBSCAN参数"""
    
    # 使用月牙形数据测试DBSCAN
    X_moons, y_moons = make_moons(n_samples=200, noise=0.1, random_state=42)
    
    # 不同的eps和min_samples组合
    eps_values = [0.1, 0.2, 0.3, 0.5]
    min_samples_values = [3, 5, 10, 15]
    
    fig, axes = plt.subplots(len(eps_values), len(min_samples_values), figsize=(16, 12))
    fig.suptitle('DBSCAN参数调优', fontsize=16)
    
    print("DBSCAN参数调优结果:")
    print("eps\tmin_samples\t聚类数\t噪声点数\t轮廓系数")
    print("-" * 55)
    
    best_score = -1
    best_params = None
    
    for i, eps in enumerate(eps_values):
        for j, min_samples in enumerate(min_samples_values):
            dbscan = DBSCAN(eps=eps, min_samples=min_samples)
            y_pred = dbscan.fit_predict(X_moons)
            
            n_clusters = len(set(y_pred)) - (1 if -1 in y_pred else 0)
            n_noise = list(y_pred).count(-1)
            
            # 计算轮廓系数(需要至少2个簇且不全是噪声)
            if n_clusters >= 2 and len(set(y_pred)) > 1:
                # 排除噪声点计算轮廓系数
                mask = y_pred != -1
                if np.sum(mask) > 0:
                    silhouette = silhouette_score(X_moons[mask], y_pred[mask])
                else:
                    silhouette = -1
            else:
                silhouette = -1
            
            print(f"{eps}\t{min_samples}\t\t{n_clusters}\t{n_noise}\t\t{silhouette:.4f}")
            
            # 记录最佳参数
            if silhouette > best_score:
                best_score = silhouette
                best_params = (eps, min_samples)
            
            # 可视化
            axes[i, j].scatter(X_moons[:, 0], X_moons[:, 1], c=y_pred, 
                             cmap='Spectral', alpha=0.7, s=30)
            axes[i, j].set_title(f'eps={eps}, min_samples={min_samples}\n'
                                f'簇数={n_clusters}, 轮廓系数={silhouette:.3f}')
            axes[i, j].set_xlabel('特征 1')
            axes[i, j].set_ylabel('特征 2')
    
    plt.tight_layout()
    plt.show()
    
    print(f"\n最佳参数: eps={best_params[0]}, min_samples={best_params[1]}")
    print(f"最佳轮廓系数: {best_score:.4f}")
    
    return best_params

best_dbscan_params = tune_dbscan_parameters()

11.6 高斯混合模型 (GMM)

11.6.1 GMM基本原理

python
def demonstrate_gmm():
    """演示高斯混合模型"""
    
    print("高斯混合模型 (GMM) 特点:")
    print("1. 假设数据来自多个高斯分布的混合")
    print("2. 使用EM算法进行参数估计")
    print("3. 提供软聚类(概率分配)")
    print("4. 能够处理椭圆形聚类")
    
    # 创建椭圆形聚类数据
    X_ellipse, y_ellipse = make_blobs(n_samples=300, centers=3, n_features=2, 
                                     random_state=42, cluster_std=2.0)
    
    # 对数据进行变换使其呈椭圆形
    transformation_matrix = np.array([[1, 0.5], [0.5, 1]])
    X_ellipse = X_ellipse.dot(transformation_matrix)
    
    # 高斯混合模型
    gmm = GaussianMixture(n_components=3, random_state=42)
    y_pred_gmm = gmm.fit_predict(X_ellipse)
    y_proba_gmm = gmm.predict_proba(X_ellipse)
    
    print(f"\nGMM聚类结果:")
    print(f"聚类数: {gmm.n_components}")
    print(f"收敛: {gmm.converged_}")
    print(f"迭代次数: {gmm.n_iter_}")
    print(f"对数似然: {gmm.score(X_ellipse):.2f}")
    
    # 可视化结果
    fig, axes = plt.subplots(2, 2, figsize=(12, 10))
    fig.suptitle('高斯混合模型聚类', fontsize=16)
    
    # 原始数据
    axes[0, 0].scatter(X_ellipse[:, 0], X_ellipse[:, 1], c='gray', alpha=0.6)
    axes[0, 0].set_title('原始数据')
    axes[0, 0].set_xlabel('特征 1')
    axes[0, 0].set_ylabel('特征 2')
    axes[0, 0].grid(True, alpha=0.3)
    
    # GMM聚类结果
    colors = ['red', 'blue', 'green']
    for i in range(3):
        mask = y_pred_gmm == i
        axes[0, 1].scatter(X_ellipse[mask, 0], X_ellipse[mask, 1], 
                          c=colors[i], alpha=0.6, label=f'簇 {i}')
    axes[0, 1].set_title('GMM聚类结果')
    axes[0, 1].set_xlabel('特征 1')
    axes[0, 1].set_ylabel('特征 2')
    axes[0, 1].legend()
    axes[0, 1].grid(True, alpha=0.3)
    
    # 概率分配(软聚类)
    # 显示每个点属于第一个簇的概率
    scatter = axes[1, 0].scatter(X_ellipse[:, 0], X_ellipse[:, 1], 
                                c=y_proba_gmm[:, 0], cmap='Reds', alpha=0.7)
    axes[1, 0].set_title('属于簇0的概率')
    axes[1, 0].set_xlabel('特征 1')
    axes[1, 0].set_ylabel('特征 2')
    plt.colorbar(scatter, ax=axes[1, 0])
    
    # 高斯分布等高线
    axes[1, 1].scatter(X_ellipse[:, 0], X_ellipse[:, 1], c=y_pred_gmm, 
                      cmap='viridis', alpha=0.6)
    
    # 绘制高斯分布的等高线
    x_min, x_max = X_ellipse[:, 0].min() - 2, X_ellipse[:, 0].max() + 2
    y_min, y_max = X_ellipse[:, 1].min() - 2, X_ellipse[:, 1].max() + 2
    xx, yy = np.meshgrid(np.linspace(x_min, x_max, 100),
                        np.linspace(y_min, y_max, 100))
    
    mesh_points = np.c_[xx.ravel(), yy.ravel()]
    Z = gmm.score_samples(mesh_points)
    Z = Z.reshape(xx.shape)
    
    axes[1, 1].contour(xx, yy, Z, levels=10, alpha=0.5)
    axes[1, 1].set_title('GMM等高线')
    axes[1, 1].set_xlabel('特征 1')
    axes[1, 1].set_ylabel('特征 2')
    
    plt.tight_layout()
    plt.show()
    
    return X_ellipse, y_pred_gmm, gmm

X_ellipse, y_pred_gmm, gmm = demonstrate_gmm()

11.6.2 模型选择和信息准则

python
def gmm_model_selection():
    """使用信息准则选择GMM的最佳组件数"""
    
    n_components_range = range(1, 11)
    aic_scores = []
    bic_scores = []
    
    print("GMM模型选择:")
    print("组件数\tAIC\t\tBIC")
    print("-" * 30)
    
    for n_components in n_components_range:
        gmm = GaussianMixture(n_components=n_components, random_state=42)
        gmm.fit(X_ellipse)
        
        aic = gmm.aic(X_ellipse)
        bic = gmm.bic(X_ellipse)
        
        aic_scores.append(aic)
        bic_scores.append(bic)
        
        print(f"{n_components}\t{aic:.2f}\t\t{bic:.2f}")
    
    # 可视化信息准则
    plt.figure(figsize=(10, 6))
    plt.plot(n_components_range, aic_scores, 'bo-', label='AIC', linewidth=2, markersize=8)
    plt.plot(n_components_range, bic_scores, 'ro-', label='BIC', linewidth=2, markersize=8)
    
    plt.xlabel('组件数')
    plt.ylabel('信息准则值')
    plt.title('GMM模型选择 - AIC vs BIC')
    plt.legend()
    plt.grid(True, alpha=0.3)
    plt.show()
    
    # 找出最佳组件数
    best_n_aic = n_components_range[np.argmin(aic_scores)]
    best_n_bic = n_components_range[np.argmin(bic_scores)]
    
    print(f"\n基于AIC的最佳组件数: {best_n_aic}")
    print(f"基于BIC的最佳组件数: {best_n_bic}")
    
    return best_n_aic, best_n_bic

best_n_aic, best_n_bic = gmm_model_selection()

11.7 聚类算法比较

11.7.1 不同算法在各种数据上的表现

python
def comprehensive_clustering_comparison():
    """全面比较不同聚类算法"""
    
    # 创建不同类型的数据集
    datasets = []
    
    # 1. 球形聚类
    X_blobs, y_blobs = make_blobs(n_samples=200, centers=3, random_state=42)
    datasets.append(('球形聚类', X_blobs, y_blobs))
    
    # 2. 月牙形
    X_moons, y_moons = make_moons(n_samples=200, noise=0.1, random_state=42)
    datasets.append(('月牙形', X_moons, y_moons))
    
    # 3. 圆形
    X_circles, y_circles = make_circles(n_samples=200, noise=0.1, factor=0.3, random_state=42)
    datasets.append(('圆形', X_circles, y_circles))
    
    # 4. 不同密度
    X_varied, y_varied = make_blobs(n_samples=200, centers=3, 
                                   cluster_std=[1.0, 2.5, 0.5], random_state=42)
    datasets.append(('不同密度', X_varied, y_varied))
    
    # 聚类算法
    algorithms = {
        'K-means': KMeans(n_clusters=2, random_state=42),
        '层次聚类': AgglomerativeClustering(n_clusters=2, linkage='ward'),
        'DBSCAN': DBSCAN(eps=0.3, min_samples=5),
        'GMM': GaussianMixture(n_components=2, random_state=42),
        '谱聚类': SpectralClustering(n_clusters=2, random_state=42)
    }
    
    # 创建比较图
    fig, axes = plt.subplots(len(datasets), len(algorithms) + 1, figsize=(20, 16))
    fig.suptitle('不同聚类算法比较', fontsize=16)
    
    print("聚类算法性能比较:")
    print("数据集\t\t算法\t\t\t轮廓系数\tARI")
    print("-" * 60)
    
    for i, (dataset_name, X, y_true) in enumerate(datasets):
        # 标准化数据
        scaler = StandardScaler()
        X_scaled = scaler.fit_transform(X)
        
        # 原始数据
        axes[i, 0].scatter(X[:, 0], X[:, 1], c=y_true, cmap='viridis', alpha=0.7)
        axes[i, 0].set_title(f'{dataset_name}\n(真实标签)')
        axes[i, 0].set_xlabel('特征 1')
        axes[i, 0].set_ylabel('特征 2')
        
        for j, (alg_name, algorithm) in enumerate(algorithms.items()):
            # 调整聚类数
            if hasattr(algorithm, 'n_clusters'):
                algorithm.n_clusters = len(np.unique(y_true))
            elif hasattr(algorithm, 'n_components'):
                algorithm.n_components = len(np.unique(y_true))
            
            # 对某些算法使用标准化数据
            if alg_name in ['谱聚类']:
                X_input = X_scaled
            else:
                X_input = X
            
            try:
                y_pred = algorithm.fit_predict(X_input)
                
                # 计算评估指标
                if len(set(y_pred)) > 1:
                    silhouette = silhouette_score(X_input, y_pred)
                    ari = adjusted_rand_score(y_true, y_pred)
                else:
                    silhouette = -1
                    ari = -1
                
                print(f"{dataset_name}\t{alg_name}\t\t{silhouette:.4f}\t\t{ari:.4f}")
                
                # 可视化结果
                axes[i, j + 1].scatter(X[:, 0], X[:, 1], c=y_pred, cmap='viridis', alpha=0.7)
                axes[i, j + 1].set_title(f'{alg_name}\n轮廓系数: {silhouette:.3f}')
                axes[i, j + 1].set_xlabel('特征 1')
                axes[i, j + 1].set_ylabel('特征 2')
                
            except Exception as e:
                print(f"{dataset_name}\t{alg_name}\t\t错误: {str(e)[:20]}")
                axes[i, j + 1].text(0.5, 0.5, '算法失败', ha='center', va='center', 
                                   transform=axes[i, j + 1].transAxes)
                axes[i, j + 1].set_title(f'{alg_name}\n失败')
    
    plt.tight_layout()
    plt.show()

comprehensive_clustering_comparison()
```## 11.
8 聚类评估

### 11.8.1 内部评估指标

```python
def clustering_evaluation_metrics():
    """演示聚类评估指标"""
    
    # 使用鸢尾花数据集
    iris = load_iris()
    X_iris = iris.data
    y_true_iris = iris.target
    
    # 标准化数据
    scaler = StandardScaler()
    X_iris_scaled = scaler.fit_transform(X_iris)
    
    # 不同的聚类算法
    algorithms = {
        'K-means': KMeans(n_clusters=3, random_state=42),
        '层次聚类': AgglomerativeClustering(n_clusters=3),
        'GMM': GaussianMixture(n_components=3, random_state=42)
    }
    
    print("聚类评估指标比较:")
    print("算法\t\t轮廓系数\tCalinski-Harabasz\tARI\t\t互信息")
    print("-" * 70)
    
    results = {}
    
    for name, algorithm in algorithms.items():
        # 聚类
        y_pred = algorithm.fit_predict(X_iris_scaled)
        
        # 内部评估指标(不需要真实标签)
        silhouette = silhouette_score(X_iris_scaled, y_pred)
        calinski_harabasz = calinski_harabasz_score(X_iris_scaled, y_pred)
        
        # 外部评估指标(需要真实标签)
        from sklearn.metrics import adjusted_mutual_info_score
        ari = adjusted_rand_score(y_true_iris, y_pred)
        ami = adjusted_mutual_info_score(y_true_iris, y_pred)
        
        results[name] = {
            'silhouette': silhouette,
            'calinski_harabasz': calinski_harabasz,
            'ari': ari,
            'ami': ami,
            'y_pred': y_pred
        }
        
        print(f"{name}\t{silhouette:.4f}\t\t{calinski_harabasz:.2f}\t\t{ari:.4f}\t\t{ami:.4f}")
    
    # 可视化评估结果
    fig, axes = plt.subplots(2, 2, figsize=(15, 12))
    fig.suptitle('聚类评估指标比较', fontsize=16)
    
    metrics = ['silhouette', 'calinski_harabasz', 'ari', 'ami']
    metric_names = ['轮廓系数', 'Calinski-Harabasz指数', '调整兰德指数', '调整互信息']
    
    for i, (metric, metric_name) in enumerate(zip(metrics, metric_names)):
        row = i // 2
        col = i % 2
        
        names = list(results.keys())
        values = [results[name][metric] for name in names]
        
        bars = axes[row, col].bar(names, values, alpha=0.7, 
                                 color=['skyblue', 'lightgreen', 'lightcoral'])
        axes[row, col].set_title(metric_name)
        axes[row, col].set_ylabel('得分')
        axes[row, col].tick_params(axis='x', rotation=45)
        
        # 添加数值标签
        for bar, value in zip(bars, values):
            height = bar.get_height()
            axes[row, col].text(bar.get_x() + bar.get_width()/2., height + height*0.01,
                               f'{value:.3f}', ha='center', va='bottom')
    
    plt.tight_layout()
    plt.show()
    
    return results

evaluation_results = clustering_evaluation_metrics()

11.8.2 聚类稳定性分析

python
def clustering_stability_analysis():
    """分析聚类算法的稳定性"""
    
    # 创建数据集
    X_stability, y_stability = make_blobs(n_samples=300, centers=3, 
                                         n_features=2, random_state=42)
    
    # 多次运行聚类算法
    n_runs = 20
    algorithms = {
        'K-means': KMeans(n_clusters=3),
        '层次聚类': AgglomerativeClustering(n_clusters=3),
        'GMM': GaussianMixture(n_components=3)
    }
    
    stability_results = {}
    
    print("聚类稳定性分析:")
    print("算法\t\t平均轮廓系数\t标准差\t\t变异系数")
    print("-" * 55)
    
    for name, algorithm in algorithms.items():
        silhouette_scores = []
        
        for run in range(n_runs):
            # 为了测试稳定性,每次使用不同的随机种子
            if hasattr(algorithm, 'random_state'):
                algorithm.random_state = run
            
            y_pred = algorithm.fit_predict(X_stability)
            silhouette = silhouette_score(X_stability, y_pred)
            silhouette_scores.append(silhouette)
        
        mean_silhouette = np.mean(silhouette_scores)
        std_silhouette = np.std(silhouette_scores)
        cv_silhouette = std_silhouette / mean_silhouette if mean_silhouette != 0 else 0
        
        stability_results[name] = {
            'scores': silhouette_scores,
            'mean': mean_silhouette,
            'std': std_silhouette,
            'cv': cv_silhouette
        }
        
        print(f"{name}\t{mean_silhouette:.4f}\t\t{std_silhouette:.4f}\t\t{cv_silhouette:.4f}")
    
    # 可视化稳定性
    fig, axes = plt.subplots(1, 2, figsize=(15, 6))
    
    # 箱线图
    data_for_boxplot = [stability_results[name]['scores'] for name in algorithms.keys()]
    axes[0].boxplot(data_for_boxplot, labels=algorithms.keys())
    axes[0].set_title('聚类算法稳定性(箱线图)')
    axes[0].set_ylabel('轮廓系数')
    axes[0].tick_params(axis='x', rotation=45)
    axes[0].grid(True, alpha=0.3)
    
    # 变异系数比较
    names = list(stability_results.keys())
    cvs = [stability_results[name]['cv'] for name in names]
    
    bars = axes[1].bar(names, cvs, alpha=0.7, color=['skyblue', 'lightgreen', 'lightcoral'])
    axes[1].set_title('变异系数比较(越小越稳定)')
    axes[1].set_ylabel('变异系数')
    axes[1].tick_params(axis='x', rotation=45)
    
    # 添加数值标签
    for bar, cv in zip(bars, cvs):
        height = bar.get_height()
        axes[1].text(bar.get_x() + bar.get_width()/2., height + height*0.01,
                    f'{cv:.4f}', ha='center', va='bottom')
    
    plt.tight_layout()
    plt.show()
    
    return stability_results

stability_results = clustering_stability_analysis()

11.9 实际应用案例

11.9.1 客户细分分析

python
def customer_segmentation_case():
    """客户细分案例"""
    
    # 创建模拟客户数据
    np.random.seed(42)
    n_customers = 1000
    
    # 生成客户特征
    age = np.random.normal(40, 15, n_customers)
    annual_income = np.random.exponential(50000, n_customers)
    spending_score = np.random.normal(50, 25, n_customers)
    
    # 添加一些相关性
    # 年轻人倾向于有更高的消费分数
    spending_score += (50 - age) * 0.5 + np.random.normal(0, 10, n_customers)
    # 高收入者倾向于有更高的消费分数
    spending_score += annual_income / 2000 + np.random.normal(0, 5, n_customers)
    
    # 确保数据在合理范围内
    age = np.clip(age, 18, 80)
    annual_income = np.clip(annual_income, 15000, 200000)
    spending_score = np.clip(spending_score, 1, 100)
    
    # 创建DataFrame
    customer_data = pd.DataFrame({
        '年龄': age,
        '年收入': annual_income,
        '消费分数': spending_score
    })
    
    print("客户数据概览:")
    print(customer_data.describe())
    
    # 数据标准化
    scaler = StandardScaler()
    X_customers = scaler.fit_transform(customer_data)
    
    # 使用肘部法则确定最佳聚类数
    k_range = range(1, 11)
    inertias = []
    
    for k in k_range:
        kmeans = KMeans(n_clusters=k, random_state=42, n_init=10)
        kmeans.fit(X_customers)
        inertias.append(kmeans.inertia_)
    
    # 可视化肘部法则
    plt.figure(figsize=(15, 10))
    
    plt.subplot(2, 3, 1)
    plt.plot(k_range, inertias, 'bo-', linewidth=2, markersize=8)
    plt.xlabel('聚类数 K')
    plt.ylabel('惯性 (WCSS)')
    plt.title('肘部法则')
    plt.grid(True, alpha=0.3)
    
    # 选择K=4进行聚类
    optimal_k = 4
    kmeans_final = KMeans(n_clusters=optimal_k, random_state=42, n_init=10)
    customer_segments = kmeans_final.fit_predict(X_customers)
    
    # 添加聚类标签到原始数据
    customer_data['客户群体'] = customer_segments
    
    # 分析每个客户群体的特征
    print(f"\n客户细分结果 (K={optimal_k}):")
    segment_analysis = customer_data.groupby('客户群体').agg({
        '年龄': ['mean', 'std'],
        '年收入': ['mean', 'std'],
        '消费分数': ['mean', 'std']
    }).round(2)
    
    print(segment_analysis)
    
    # 可视化客户细分
    # 年龄 vs 年收入
    plt.subplot(2, 3, 2)
    colors = ['red', 'blue', 'green', 'orange']
    for i in range(optimal_k):
        mask = customer_segments == i
        plt.scatter(customer_data.loc[mask, '年龄'], 
                   customer_data.loc[mask, '年收入'],
                   c=colors[i], alpha=0.6, label=f'群体 {i}')
    plt.xlabel('年龄')
    plt.ylabel('年收入')
    plt.title('年龄 vs 年收入')
    plt.legend()
    plt.grid(True, alpha=0.3)
    
    # 年收入 vs 消费分数
    plt.subplot(2, 3, 3)
    for i in range(optimal_k):
        mask = customer_segments == i
        plt.scatter(customer_data.loc[mask, '年收入'], 
                   customer_data.loc[mask, '消费分数'],
                   c=colors[i], alpha=0.6, label=f'群体 {i}')
    plt.xlabel('年收入')
    plt.ylabel('消费分数')
    plt.title('年收入 vs 消费分数')
    plt.legend()
    plt.grid(True, alpha=0.3)
    
    # 年龄 vs 消费分数
    plt.subplot(2, 3, 4)
    for i in range(optimal_k):
        mask = customer_segments == i
        plt.scatter(customer_data.loc[mask, '年龄'], 
                   customer_data.loc[mask, '消费分数'],
                   c=colors[i], alpha=0.6, label=f'群体 {i}')
    plt.xlabel('年龄')
    plt.ylabel('消费分数')
    plt.title('年龄 vs 消费分数')
    plt.legend()
    plt.grid(True, alpha=0.3)
    
    # 客户群体分布
    plt.subplot(2, 3, 5)
    segment_counts = customer_data['客户群体'].value_counts().sort_index()
    plt.pie(segment_counts.values, labels=[f'群体 {i}' for i in segment_counts.index], 
            autopct='%1.1f%%', colors=colors[:len(segment_counts)])
    plt.title('客户群体分布')
    
    # 3D可视化
    from mpl_toolkits.mplot3d import Axes3D
    ax = plt.subplot(2, 3, 6, projection='3d')
    for i in range(optimal_k):
        mask = customer_segments == i
        ax.scatter(customer_data.loc[mask, '年龄'],
                  customer_data.loc[mask, '年收入'],
                  customer_data.loc[mask, '消费分数'],
                  c=colors[i], alpha=0.6, label=f'群体 {i}')
    ax.set_xlabel('年龄')
    ax.set_ylabel('年收入')
    ax.set_zlabel('消费分数')
    ax.set_title('3D客户细分')
    ax.legend()
    
    plt.tight_layout()
    plt.show()
    
    # 客户群体特征描述
    print("\n客户群体特征分析:")
    for i in range(optimal_k):
        segment_data = customer_data[customer_data['客户群体'] == i]
        print(f"\n群体 {i} ({len(segment_data)} 人, {len(segment_data)/len(customer_data)*100:.1f}%):")
        print(f"  平均年龄: {segment_data['年龄'].mean():.1f} 岁")
        print(f"  平均年收入: ¥{segment_data['年收入'].mean():,.0f}")
        print(f"  平均消费分数: {segment_data['消费分数'].mean():.1f}")
        
        # 群体特征标签
        if segment_data['年龄'].mean() < 35 and segment_data['消费分数'].mean() > 60:
            print("  特征: 年轻高消费群体")
        elif segment_data['年收入'].mean() > 80000 and segment_data['消费分数'].mean() > 60:
            print("  特征: 高收入高消费群体")
        elif segment_data['年龄'].mean() > 50 and segment_data['消费分数'].mean() < 40:
            print("  特征: 中老年保守消费群体")
        else:
            print("  特征: 普通消费群体")
    
    return customer_data, customer_segments

customer_data, customer_segments = customer_segmentation_case()

11.9.2 图像分割应用

python
def image_segmentation_example():
    """图像分割示例"""
    
    # 创建模拟图像数据
    from sklearn.datasets import make_checkerboard
    
    # 生成棋盘图案数据
    np.random.seed(42)
    n_samples = 1000
    
    # 创建三个区域的数据
    # 区域1: 低亮度
    region1 = np.random.normal(50, 10, (n_samples//3, 3))  # RGB值
    
    # 区域2: 中等亮度
    region2 = np.random.normal(128, 15, (n_samples//3, 3))
    
    # 区域3: 高亮度
    region3 = np.random.normal(200, 12, (n_samples//3, 3))
    
    # 合并数据
    image_data = np.vstack([region1, region2, region3])
    
    # 确保RGB值在有效范围内
    image_data = np.clip(image_data, 0, 255)
    
    print("图像分割聚类分析:")
    print(f"像素数据形状: {image_data.shape}")
    print(f"RGB值范围: [{image_data.min():.0f}, {image_data.max():.0f}]")
    
    # 使用不同聚类算法进行图像分割
    algorithms = {
        'K-means': KMeans(n_clusters=3, random_state=42),
        'GMM': GaussianMixture(n_components=3, random_state=42),
        '层次聚类': AgglomerativeClustering(n_clusters=3)
    }
    
    fig, axes = plt.subplots(2, 2, figsize=(12, 10))
    fig.suptitle('图像分割聚类比较', fontsize=16)
    
    # 原始数据分布
    axes[0, 0].scatter(image_data[:, 0], image_data[:, 1], 
                      c=image_data[:, 2], cmap='viridis', alpha=0.6, s=10)
    axes[0, 0].set_title('原始像素分布 (R vs G, 颜色=B)')
    axes[0, 0].set_xlabel('红色通道')
    axes[0, 0].set_ylabel('绿色通道')
    
    for i, (name, algorithm) in enumerate(algorithms.items()):
        row = (i + 1) // 2
        col = (i + 1) % 2
        
        # 聚类
        labels = algorithm.fit_predict(image_data)
        
        # 计算聚类中心
        if hasattr(algorithm, 'cluster_centers_'):
            centers = algorithm.cluster_centers_
        elif hasattr(algorithm, 'means_'):
            centers = algorithm.means_
        else:
            # 对于层次聚类,手动计算中心
            centers = np.array([image_data[labels == k].mean(axis=0) for k in range(3)])
        
        # 可视化分割结果
        axes[row, col].scatter(image_data[:, 0], image_data[:, 1], 
                              c=labels, cmap='viridis', alpha=0.6, s=10)
        axes[row, col].scatter(centers[:, 0], centers[:, 1], 
                              c='red', marker='x', s=200, linewidths=3)
        axes[row, col].set_title(f'{name} 分割结果')
        axes[row, col].set_xlabel('红色通道')
        axes[row, col].set_ylabel('绿色通道')
        
        # 计算轮廓系数
        silhouette = silhouette_score(image_data, labels)
        print(f"{name} 轮廓系数: {silhouette:.4f}")
    
    plt.tight_layout()
    plt.show()
    
    # 分析分割质量
    print("\n分割区域分析:")
    best_algorithm = KMeans(n_clusters=3, random_state=42)
    best_labels = best_algorithm.fit_predict(image_data)
    
    for i in range(3):
        region_pixels = image_data[best_labels == i]
        print(f"区域 {i}:")
        print(f"  像素数量: {len(region_pixels)}")
        print(f"  平均RGB: ({region_pixels[:, 0].mean():.1f}, "
              f"{region_pixels[:, 1].mean():.1f}, {region_pixels[:, 2].mean():.1f})")
        print(f"  RGB标准差: ({region_pixels[:, 0].std():.1f}, "
              f"{region_pixels[:, 1].std():.1f}, {region_pixels[:, 2].std():.1f})")
    
    return image_data, best_labels

image_data, image_labels = image_segmentation_example()
```## 11.1
0 聚类算法选择指南

### 11.10.1 算法特点总结

```python
def clustering_algorithm_guide():
    """聚类算法选择指南"""
    
    print("聚类算法选择指南")
    print("=" * 50)
    
    algorithms_info = {
        'K-means': {
            '适用场景': ['球形聚类', '大小相似的簇', '大数据集'],
            '优点': ['简单快速', '可扩展性好', '结果稳定'],
            '缺点': ['需要预设K值', '对异常值敏感', '假设球形聚类'],
            '参数': ['n_clusters', 'init', 'n_init'],
            '时间复杂度': 'O(n*k*i*d)',
            '最佳使用': '当你知道大概的聚类数量且数据呈球形分布时'
        },
        
        '层次聚类': {
            '适用场景': ['小到中等数据集', '需要聚类层次结构', '不知道聚类数'],
            '优点': ['不需要预设聚类数', '提供层次结构', '确定性结果'],
            '缺点': ['计算复杂度高', '对噪声敏感', '难以处理大数据'],
            '参数': ['n_clusters', 'linkage', 'distance_threshold'],
            '时间复杂度': 'O(n³)',
            '最佳使用': '当你需要理解数据的层次结构或数据集较小时'
        },
        
        'DBSCAN': {
            '适用场景': ['任意形状聚类', '有噪声的数据', '密度不均匀'],
            '优点': ['发现任意形状', '自动确定聚类数', '识别噪声点'],
            '缺点': ['参数敏感', '密度变化大时效果差', '高维数据困难'],
            '参数': ['eps', 'min_samples'],
            '时间复杂度': 'O(n log n)',
            '最佳使用': '当数据有噪声且聚类形状不规则时'
        },
        
        'GMM': {
            '适用场景': ['椭圆形聚类', '需要概率分配', '重叠聚类'],
            '优点': ['软聚类', '处理椭圆形', '概率解释'],
            '缺点': ['需要预设组件数', '对初始化敏感', '计算复杂'],
            '参数': ['n_components', 'covariance_type'],
            '时间复杂度': 'O(n*k*i*d²)',
            '最佳使用': '当你需要概率分配或聚类可能重叠时'
        },
        
        '谱聚类': {
            '适用场景': ['非凸聚类', '流形数据', '图数据'],
            '优点': ['处理非凸形状', '基于图理论', '效果稳定'],
            '缺点': ['计算复杂度高', '内存需求大', '参数选择困难'],
            '参数': ['n_clusters', 'affinity', 'gamma'],
            '时间复杂度': 'O(n³)',
            '最佳使用': '当数据在低维流形上或聚类形状复杂时'
        }
    }
    
    for alg_name, info in algorithms_info.items():
        print(f"\n{alg_name}:")
        print("-" * 30)
        for key, value in info.items():
            if isinstance(value, list):
                print(f"{key}: {', '.join(value)}")
            else:
                print(f"{key}: {value}")
    
    return algorithms_info

algorithm_info = clustering_algorithm_guide()

11.10.2 决策树指南

python
def clustering_decision_tree():
    """聚类算法选择决策树"""
    
    print("\n聚类算法选择决策树:")
    print("=" * 40)
    
    decision_tree = """
    开始

    ├─ 你知道聚类数量吗?
    │  ├─ 是 ──┐
    │  └─ 否 ──┼─ 数据集大小?
    │          ├─ 小(<1000) → 层次聚类
    │          └─ 大(>1000) → DBSCAN

    ├─ 数据有噪声吗?
    │  ├─ 是 → DBSCAN
    │  └─ 否 ──┐

    ├─ 聚类形状?
    │  ├─ 球形 → K-means
    │  ├─ 椭圆形 → GMM
    │  ├─ 任意形状 → DBSCAN 或 谱聚类
    │  └─ 不确定 → 尝试多种算法

    ├─ 需要概率输出吗?
    │  ├─ 是 → GMM
    │  └─ 否 → K-means 或 DBSCAN

    ├─ 数据集大小?
    │  ├─ 小(<1000) → 任何算法
    │  ├─ 中(1000-10000) → K-means, DBSCAN
    │  └─ 大(>10000) → K-means, Mini-batch K-means

    └─ 计算资源限制?
       ├─ 有限 → K-means
       └─ 充足 → 根据数据特点选择
    """
    
    print(decision_tree)
    
    # 创建选择矩阵
    selection_matrix = pd.DataFrame({
        '数据大小': ['小', '中', '大', '小', '中', '大'],
        '聚类形状': ['球形', '球形', '球形', '任意', '任意', '任意'],
        '有噪声': ['否', '否', '否', '是', '是', '是'],
        '推荐算法': ['K-means/层次', 'K-means', 'K-means', 'DBSCAN', 'DBSCAN', 'DBSCAN'],
        '备选算法': ['GMM', 'GMM/DBSCAN', 'Mini-batch K-means', '谱聚类', 'GMM', '谱聚类']
    })
    
    print("\n算法选择矩阵:")
    print(selection_matrix.to_string(index=False))

clustering_decision_tree()

11.11 练习题

练习1:基础聚类

  1. 使用鸢尾花数据集进行K-means聚类
  2. 使用肘部法则和轮廓系数确定最佳K值
  3. 比较聚类结果与真实标签的差异

练习2:算法比较

  1. 创建三种不同形状的数据集(球形、月牙形、圆形)
  2. 对每种数据集应用K-means、DBSCAN和GMM
  3. 分析哪种算法最适合哪种数据形状

练习3:参数调优

  1. 使用make_circles数据集
  2. 调优DBSCAN的eps和min_samples参数
  3. 可视化不同参数组合的聚类效果

练习4:层次聚类

  1. 使用葡萄酒数据集进行层次聚类
  2. 绘制树状图并分析聚类层次
  3. 比较不同链接方法的效果

练习5:实际应用

  1. 创建一个客户数据集(包含年龄、收入、消费习惯等特征)
  2. 进行客户细分分析
  3. 为每个客户群体制定营销策略建议

11.12 小结

在本章中,我们深入学习了聚类分析的各个方面:

核心概念

  • 无监督学习:在没有标签的情况下发现数据模式
  • 相似性度量:基于距离或密度的相似性定义
  • 聚类质量:组内相似性最大化,组间差异最大化

主要算法

  • K-means:基于距离的经典聚类算法
  • 层次聚类:构建聚类层次结构
  • DBSCAN:基于密度的聚类,能处理噪声
  • GMM:基于概率分布的软聚类
  • 谱聚类:基于图理论的聚类方法

实践技能

  • 参数选择:K值选择、参数调优
  • 聚类评估:轮廓系数、ARI、信息准则
  • 算法选择:根据数据特点选择合适算法
  • 实际应用:客户细分、图像分割

关键要点

  • 不同算法适用于不同类型的数据和问题
  • 聚类评估需要结合多种指标
  • 数据预处理对聚类结果有重要影响
  • 领域知识在聚类解释中至关重要

算法选择建议

使用K-means当

  • 数据呈球形分布
  • 聚类大小相似
  • 数据集较大
  • 需要快速结果

使用DBSCAN当

  • 数据有噪声
  • 聚类形状不规则
  • 不知道聚类数量
  • 聚类密度不均匀

使用层次聚类当

  • 需要聚类层次结构
  • 数据集较小
  • 不确定聚类数量
  • 需要确定性结果

使用GMM当

  • 需要概率分配
  • 聚类可能重叠
  • 数据呈椭圆分布
  • 需要软聚类

11.13 下一步

现在你已经掌握了聚类分析这个重要的无监督学习技术!在下一章主成分分析中,我们将学习另一个重要的无监督学习方法——降维技术,了解如何在保持数据主要信息的同时减少特征维度。


章节要点回顾

  • ✅ 理解了聚类分析的基本概念和目标
  • ✅ 掌握了K-means、层次聚类、DBSCAN等主要算法
  • ✅ 学会了聚类评估和参数调优方法
  • ✅ 了解了不同算法的适用场景和选择标准
  • ✅ 掌握了聚类在实际应用中的使用技巧
  • ✅ 能够根据数据特点选择合适的聚类算法

本站内容仅供学习和研究使用。