【机器学习】层次聚类

写在篇前

  层次聚类(hierarchical clustering)是一种通用的聚类算法之一,它通过自下而上合并或自上而下拆分来构建嵌套聚类。这种簇的层次结构表示为树(或树状图),树的根汇聚所有样本,树的叶子是各个样本。本篇博客会简述层次聚类的原理,重点是使用sklearn、scipy、seaborn等实现层次聚类并可视化结果。

原理简述

  看到一篇详细讲层次聚类原理的文章层次聚类算法的原理及实现Hierarchical Clustering,讲的通俗易懂,一看便知,这里主要讲一下Metrics(请保证sklearn >=0.20):

  1. Ward:minimizes所有聚类中的平方差和,它是一种方差最小化方法,在这个意义上类似于kmeans
    E = ∑ i = 1 k ∑ x ∈ C i ∣ ∣ x − μ i ∣ ∣ 2 2 E = \sum\limits_{i=1}^k\sum\limits_{x \in C_i} ||x-\mu_i||_2^2 E=i=1kxCixμi22

  2. complete linkage:minimizes两个簇的样本对之间距离的最小值
    d m i n ( C i , C j ) = min ⁡ x ⃗ i ∈ C i , x ⃗ j ∈ C j d i s t a n c e ( x ⃗ i , x ⃗ j ) d_{min}(C_i,C_j)=\min_{\vec{x}_i\in C_i,\vec{x}_j\in C_j}distance(\vec{x}_i,\vec{x}_j) dmin(Ci,Cj)=x iCi,x jCjmindistance(x i,x j)

  3. Average linkage:minimizes两个簇的样本对之间距离的平均值
    d a v g ( C i , C j ) = 1 ∣ C i ∣ ∣ C j ∣ ∑ x ⃗ i ∈ C i ∑ x ⃗ j ∈ C j d i s t a n c e ( x ⃗ i , x ⃗ j ) d_{avg}(C_i,C_j)=\frac{1}{|C_i||C_j|}\sum_{\vec{x}_i\in C_i}\sum_{\vec{x}_j\in C_j}distance(\vec{x}_i,\vec{x}_j) davg(Ci,Cj)=CiCj1x iCix jCjdistance(x i,x j)

  4. Single linkage: minimizes两个簇的样本对之间距离的最大值
    d m a x ( C i , C j ) = max ⁡ x ⃗ i ∈ C i , x ⃗ j ∈ C j d i s t a n c e ( x ⃗ i , x ⃗ j ) d_{max}(C_i,C_j)=\max_{\vec{x}_i\in C_i,\vec{x}_j\in C_j}distance(\vec{x}_i,\vec{x}_j) dmax(Ci,Cj)=x iCi,x jCjmaxdistance(x i,x j)

算法实现

  sklearn中实现的层次聚类实际上是自下而上合并的方式,通过from sklearn.cluster import AgglomerativeClustering导入层次聚类的封装类。

from sklearn.cluster import AgglomerativeClustering

clu = AgglomerativeClustering(n_clusters=2,
                              affinity='euclidean',
                              linkage='ward', compute_full_tree=False)
# --------------------------属性--------------------------

#   基础(类)属性
print('n_clusters:', clu.n_clusters)
print('affinity:', clu.affinity)
print('memory:', clu.memory)
print('connectivity:', clu.connectivity)
print('compute_full_tree:', clu.compute_full_tree)
print('linkage:', clu.linkage)
print('pooling_func:', clu.pooling_func)

#   重要属性
print('labels_:', clu.labels_)  # 样本聚类结果标签
print('children_:', clu.children_)  # 每一个非叶子结点的孩子数
print('n_components_:', clu.n_components_)  # 连接图中连通分量的估计值
print('n_leaves_:', clu.n_leaves_)  # 层次聚类树中叶子数目

# --------------------------函数--------------------------
print('get_params:', clu.get_params())  # 与set_params()相对应

# fit()、fit_predict()函数放到案例里面讲解

案例

基础案例

   官方给了一个很好的案例,我们可以看一下(scikit-learn 0.20 or later):

#! /usr/bin/python
# _*_ coding: utf-8 _*_
__author__ = 'Jeffery'
__date__ = '2018/11/12 17:45'


import time
import warnings
import numpy as np
import matplotlib.pyplot as plt

from sklearn import cluster, datasets
from sklearn.preprocessing import StandardScaler
from itertools import cycle, islice

np.random.seed(0)

######################################################################
# Generate datasets. We choose the size big enough to see the scalability
# of the algorithms, but not too big to avoid too long running times

n_samples = 1500
noisy_circles = datasets.make_circles(n_samples=n_samples, factor=.5, noise=.05)
noisy_moons = datasets.make_moons(n_samples=n_samples, noise=.05)
blobs = datasets.make_blobs(n_samples=n_samples, random_state=8)
no_structure = np.random.rand(n_samples, 2), None
# Anisotropicly distributed data
random_state = 170
X, y = datasets.make_blobs(n_samples=n_samples, random_state=random_state)
transformation = [[0.6, -0.6], [-0.4, 0.8]]
X_aniso = np.dot(X, transformation)
aniso = (X_aniso, y)
# blobs with varied variances
varied = datasets.make_blobs(n_samples=n_samples,
                             cluster_std=[1.0, 2.5, 0.5],
                             random_state=random_state)
# data shape:
# noisy_circles:(1500, 2)
# noisy_moons: (1500, 2)
# varied(blobs): (1500, 2)
# blobs: (1500, 2)
# aniso: (1500, 2)
# no_structure:(1500, 2)  no labels

######################################################################
# Run the clustering and plot

# Set up cluster parameters
plt.figure(figsize=(9 * 1.3 + 2, 14.5))
plt.subplots_adjust(left=.02, right=.98, bottom=.001, top=.96, wspace=.05,
                    hspace=.01)

plot_num = 1
default_base = {'n_clusters': 3}
datasets = [
    (noisy_circles, {'n_clusters': 2}),
    (noisy_moons, {'n_clusters': 2}),
    (varied, {}),
    (aniso, {}),
    (blobs, {}),
    (no_structure, {})]


for i_dataset, (dataset, algo_params) in enumerate(datasets):
    # update parameters with dataset-specific values
    params = default_base.copy()
    params.update(algo_params)

    X, y = dataset

    # normalize dataset for easier parameter selection
    X = StandardScaler().fit_transform(X)

    # ============
    # Create cluster objects
    # ============
    ward = cluster.AgglomerativeClustering(
        n_clusters=params['n_clusters'], linkage='ward')
    complete = cluster.AgglomerativeClustering(
        n_clusters=params['n_clusters'], linkage='complete')
    average = cluster.AgglomerativeClustering(
        n_clusters=params['n_clusters'], linkage='average')
    single = cluster.AgglomerativeClustering(
        n_clusters=params['n_clusters'], linkage='single')

    clustering_algorithms = (
        ('Single Linkage', single),
        ('Average Linkage', average),
        ('Complete Linkage', complete),
        ('Ward Linkage', ward),
    )

    for name, algorithm in clustering_algorithms:
        t0 = time.time()
        algorithm.fit(X)

        t1 = time.time()
        if hasattr(algorithm, 'labels_'):
            y_pred = algorithm.labels_.astype(np.int)
        else:
            y_pred = algorithm.predict(X)

        plt.subplot(len(datasets), len(clustering_algorithms), plot_num)
        if i_dataset == 0:
            plt.title(name, size=18)

        colors = np.array(list(islice(cycle(['#377eb8', '#ff7f00', '#4daf4a',
                                             '#f781bf', '#a65628', '#984ea3',
                                             '#999999', '#e41a1c', '#dede00']),
                                      int(max(y_pred) + 1))))

        plt.scatter(X[:, 0], X[:, 1], s=10, color=colors[y_pred])
        plt.xlim(-2.5, 2.5)
        plt.ylim(-2.5, 2.5)
        plt.xticks(())
        plt.yticks(())
        plt.text(.99, .01, ('%.2fs' % (t1 - t0)).lstrip('0'),
                 transform=plt.gca().transAxes, size=15,
                 horizontalalignment='right')
        plot_num += 1

plt.show()

在这里插入图片描述
通过以上案例我们应该学习到:

  • single linkage速度快,并且可以在非球状数据上表现良好,但是在存在噪声的情况下它表现不佳;
  • average linkage和complete linkage在‘干净’的球状数据上表现良好;
  • Ward是应对噪声数据最有效的方法;
  • 层次聚类一般不能直接适用于高维数据

进阶案例

树状图

  对于层次聚类,我们一般还会构建聚类树或生成热图,这就是我们下面要探讨的问题。但是其中还会牵涉一些原理,包括natural divisions、dissimilarity(cophenetic correlation coefficient.)、disconsistency等,请参考层次聚类原理解析

import pandas as pd
from scipy.cluster import hierarchy
import matplotlib.pyplot as plt
import numpy as np

my_data = np.random.random(size=[10, 4])
my_data = pd.DataFrame(my_data, index=(1, 2, 3, 4, 5, 6, 7, 8, 9, 10),
                       columns='A B C D'.split())

Z = hierarchy.linkage(my_data,
                      method='ward',
                      metric='euclidean',
                      optimal_ordering=False)
print(type(Z), '\n', Z)  # 这个有必要解释一下
hierarchy.dendrogram(Z)
plt.show()

在这里插入图片描述
在这里插入图片描述

# 可以继续对树进行剪枝
# n_clusters和height只能二选其一
label = cluster.hierarchy.cut_tree(Z, n_clusters=2, height=None) 

# label:(可以对比上图验证结果的正确性)
[[0]
 [1]
 [0]
 [0]
 [1]
 [1]
 [1]
 [0]
 [1]
 [0]]

  分割聚类树,将其生成多个不同的cluster还有一种方式:

# 分割cluster
labels = fcluster(Z, t=0.7, criterion='inconsistent')
print(labels)

参数说明:

  1. 当criterion为’inconsistent’时,t值应该在0-1之间波动,t越接近1代表两个数据之间的相关性越大,t越趋于0表明两个数据的相关性越小。
  2. 当criterion为’distance’时,t值代表了绝对的差值,如果小于这个差值,两个数据将会被合并,当大于这个差值,两个数据将会被分开。
  3. 当criterion为’maxclust’时,t代表了最大的聚类的个数。
  4. 当criterion为’monocrit’时,t的选择不是固定的,而是根据一个函数monocrit[j]来确定。
热图

  上面的图呢,形式比较简单,有时候我们更希望我们能够画出更加炫酷的聚类图,这个时候我们就会想到绘制clustermap,这是我们可以用到seaborn.clustermap(),该函数基于上面讲到的scipy.hierarchy模块,封装了一个方便的绘图函数。

import seaborn as sns
import matplotlib.pyplot as plt

sns.set(color_codes=True)
iris = sns.load_dataset("iris")
species = iris.pop("species")  # {'setosa', 'versicolor', 'virginica'}

lut = dict(zip(species.unique(), "rbg"))
row_colors = species.map(lut)
g = sns.clustermap(iris, row_colors=row_colors)

print(g.dendrogram_row.reordered_ind)  # reordered row indices
print(g.dendrogram_col.reordered_ind)  # reordered col indices
print(g.savefig('a.png'))

plt.show()

在这里插入图片描述

  层次聚类本身的原理不难,主要就在于对聚类的method(euclidean、manhattan等)、metrics(single、average等)的理解以及聚类树分割原理的理解。

### 头歌平台中机器学习层次聚类教程实现方法 头歌实验教学平台提供了丰富的 Python 和机器学习相关内容,其中包括如何使用层次聚类算法进行数据分析和建模的任务。以下是基于已有引用内容以及专业知识整理的内容。 #### 1. 层次聚类简介 层次聚类是一种无监督学习算法,其核心在于构建数据点之间的层次结构,并以树形形式展示该结构,称为聚类树或树状图[^2]。它主要包括两种类型:凝聚型(Agglomerative)和分裂型(Divisive)。其中,凝聚型层次聚类更为常见,它是通过不断合并最接近的数据点或簇来逐步形成更大的簇[^4]。 #### 2. 数据预处理与加载 在头歌平台上完成层次聚类任务前,需先准备数据集。例如,在鸢尾花(Iris)数据集中,可以利用 `sklearn` 库加载数据并进行必要的标准化操作: ```python from sklearn.datasets import load_iris import pandas as pd # 加载数据集 data = load_iris() df = pd.DataFrame(data.data, columns=data.feature_names) # 标准化特征值 from sklearn.preprocessing import StandardScaler scaler = StandardScaler() scaled_data = scaler.fit_transform(df) ``` 上述代码展示了如何加载鸢尾花数据集并对特征值进行标准化处理[^3]。 #### 3. 使用 Scipy 或 Sklearn 进行层次聚类 Scipy 提供了完整的层次聚类功能,而 Sklearn 则简化了部分接口调用方式。以下是一个典型的例子,演示如何使用 Scipy 计算层次聚类并绘制树状图: ```python import scipy.cluster.hierarchy as sch import matplotlib.pyplot as plt # 创建链接矩阵 dendrogram = sch.dendrogram(sch.linkage(scaled_data, method='ward')) # 绘制树状图 plt.title('Dendrogram') plt.xlabel('Samples') plt.ylabel('Euclidean distances') plt.show() # 基于树状图选择最佳簇数并执行聚类 from sklearn.cluster import AgglomerativeClustering hc = AgglomerativeClustering(n_clusters=3, affinity='euclidean', linkage='ward') y_hc = hc.fit_predict(scaled_data) ``` 此代码片段实现了层次聚类的核心流程,包括创建链接矩阵、绘制树状图以及最终的聚类分配。 #### 4. 结果评估 为了验证层次聚类的效果,可采用外部指标如调整兰德指数(ARI)、互信息分数(AMI)等对结果进行量化评价。这些工具同样可以通过 `sklearn.metrics` 获取支持。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值