机器学习 — python(sklearn / scipy) 实现层次聚类,precomputed自定义距离矩阵

机器学习 — python(sklearn / scipy) 实现层次聚类,precomputed自定义距离矩阵

关于层次聚类的原理,可以参考我的另一篇博客:层次聚类原理。本博客主要讲解如何简单直接使用 python 来实现层次聚类。

本篇博客包含的内容:sklearn / scipy 两种方式实现层次聚类,以及在 sklearn 中通过 precomputed 参数实现自定义距离矩阵

一. scipy实现

(一) 函数说明

主要是两个函数:linkage,fcluster

1. linkage
def linkage(y, method='single', metric='euclidean', optimal_ordering=False):

函数功能解释:执行层次/聚集聚类。

  1. 参数(Parameters):

    • y:输入y可以是 1 维凝聚距离矩阵(距离向量)或 2 维观测矢量数组(坐标点的矩阵)。
    • method:method是指计算类间距离的方法,有如下几种方法:single,complete,average,weighted,centroid,ward
      • single:最近邻,把类与类间距离最近的作为类间距
        从簇 u 和 簇 v 中找到一对距离最近的点的距离作为簇 u 和簇 v 的距离:在这里插入图片描述
        在这里插入图片描述
      • complete:最远邻,把类与类间距离最远的作为类间距
        从簇 u 和 簇 v 中找到一对距离最远的点的距离作为簇 u 和簇 v 的距离:在这里插入图片描述在这里插入图片描述
      • average:平均距离,类与类间所有pairs距离的平均
        如簇 u 中有若干个点 i,簇 v 中有若干个 j 点,簇 u 和簇 v的距离为:在这里插入图片描述 在这里插入图片描述
      • weighted:在压缩距离矩阵上执行加权/ WPGMA链接
        如簇u由簇 s 和簇 t 组成,那么簇 u 到 簇 v 的距离为:在这里插入图片描述
        在这里插入图片描述
      • centroid:质心距离,把类与类中的质心间距离作为类间距在这里插入图片描述
      • ward:将要合并的群集的方差最小化。
  2. 返回值(Returns):

    • Z(ndarray):层次聚类编码为一个linkage矩阵。

      Z共有四列组成,第 1 字段与第 2 字段分别为聚类簇的编号,在初始距离前每个初始值被从0~n-1进行标识,每生成一个新的聚类簇就在此基础上增加一对新的聚类簇进行标识,第 3 个字段表示前两个聚类簇之间的距离,第 4 个字段表示新生成聚类簇所包含的元素的个数。

  3. 参考资料:https://docs.scipy.org/doc/scipy/reference/generated/scipy.cluster.hierarchy.linkage.html#scipy.cluster.hierarchy.linkage


2. fcluster
def fcluster(Z, t, criterion='inconsistent', depth=2, R=None, monocrit=None):

函数功能解释:从给定链接矩阵定义的层次聚类中形成平面聚类。

  1. 参数(Parameters):
    • Z:Z是linkage得到的矩阵,记录了层次聚类的层次信息;
    • t:是一个聚类的阈值
    • criterionstr:形成扁平簇的准则
      • inconsistent:
      • distance:以簇之间的距离作为划分准则
  2. 返回值(Returns):
    fcluster(ndarray):长度为n的数组。 T [i]是原始观测值 i 所属的平面簇数。
  3. 详细参考:https://docs.scipy.org/doc/scipy/reference/generated/scipy.cluster.hierarchy.fcluster.html#scipy.cluster.hierarchy.fcluster

(二) 示例含完整算法

如对以下 5 个点进行凝聚层次聚类。

xy
点012
点123
点2-33
点3-2-1
点45-1

在坐标轴上的位置:在这里插入图片描述层次聚类结果的聚类树为:在这里插入图片描述

完整算法如下:

#!/usr/bin/env python
# encoding: utf-8
'''
@Author  : pentiumCM
@Email   : 842679178@qq.com
@Software: PyCharm
@File    : sci_cluster.py
@Time    : 2020/4/15 22:21
@desc	 : scipy实现层次聚类
'''

import numpy as np
from scipy.cluster.hierarchy import dendrogram, linkage, fcluster

from matplotlib import pyplot as plt

data = np.array([[1, 2], [2, 3], [-3, 3], [-2, -1], [5, -1]])

# 画点
plt.scatter(x=data[:, 0:1], y=data[:, 1:2], marker='.', color='red')
n = np.arange(data.shape[0])
for i, txt in enumerate(n):
    plt.annotate(txt, (data[i:i + 1, 0:1], data[i:i + 1, 1:2]))
plt.show()

# 1. 层次聚类
# linkage方法用于计算两个聚类簇s和t之间的距离d(s,t)
# 层次聚类编码为一个linkage矩阵。
Z = linkage(data, 'average')
print("聚类过程:", Z)

# 从给定链接矩阵定义的层次聚类中形成平面聚类。
# distance:以距离为划分距离的准则
f = fcluster(Z, 4, 'distance')
print("平面聚类结果:", f)

fig = plt.figure(figsize=(5, 3))
# 将层级聚类结果以树状图表示出来
dn = dendrogram(Z)
plt.show()

算法运行结果:

聚类过程: [[0.         1.         1.41421356 2.        ]
 [2.         3.         4.12310563 2.        ]
 [5.         6.         4.75565014 4.        ]
 [4.         7.         6.48606798 5.        ]]
平面聚类结果: [1 1 2 3 4]

聚类的主要信息在 Z = linkage(data, ‘average’)中。
Z共有四列组成,第 1 字段与第 2 字段分别为聚类簇的编号,在初始距离前每个初始值被从0~n-1进行标识,每生成一个新的聚类簇就在此基础上增加一对新的聚类簇进行标识,第 3 个字段表示前两个聚类簇之间的距离,第 4 个字段表示新生成聚类簇所包含的元素的个数。


层次聚类可以一次性聚类出所有的情况,当生成出聚类树的结果时,可以通过在聚类树上画水平线(例如在上面算法中,水平线是以簇之间距离为准则)来选择聚成几类的结果。如用户需要聚成两类:在这里插入图片描述只需要从上往下画出如上图的水平线来判断聚成两类的结果,可以看出两类的结果,一类为 4,另一类为0、1、2、3。
如果需要聚成3类,只需要将水平线往下平移即可知道聚成三类的结果。


二、sklearn实现

(一) 函数说明

sklearn库下的层次聚类是在sklearn.cluster的 AgglomerativeClustering中:

def __init__(self, n_clusters=2, affinity="euclidean",
             memory=None,
             connectivity=None, compute_full_tree='auto',
             linkage='ward', distance_threshold=None):

AgglomerativeClustering 类的构造函数的参数有 簇的个数 n_clusters,连接方法 linkage,连接度量选项 affinity 三个重要参数:

  • n_clusters:用户指定需要聚成几类。

  • linkage:选择计算簇与簇之间距离的策略,包含:ward,complete,average,single

    • ward:将要合并的群集的方差最小化。
    • complete:完全距离/最大距离,使用两组中所有观察值之间的最大距离。
    • single:最小距离,使用两组中所有观测值之间的最小距离。
    • average:平均距离,使用两组的每个观测值的距离平均值。
  • affinity:是一个簇间距离的计算方法。 可以是 “ euclidean(欧式距离)”,“ l1”,“ l2”,“manhattan(曼哈顿距离)”,“cosine(余弦)” 或 “precomputed(预先计算)”。

    • 如果链接为“ward”,则仅接受“欧式距离”。

    • 如果 “precomputed(预先计算)”,则需要距离矩阵作为拟合方法的输入。

      距离矩阵 的生成方法:假设用户有 n 个观测点,那么先依次构造这 n 个点两两间的距离列表,即长度为 n*(n-1)/2 的距离列表,然后通过 scipy.spatial.distance 的 dist 库的 squareform 函数就可以构造距离矩阵了。这种方式的好处是用户可以使用自己定义的方法计算任意两个观测点的距离,然后再进行聚类。后面也会给出基于自定义的距离矩阵进行的聚类算法。

参考资料:源码文档


(二) 完整算法

#!/usr/bin/env python
# encoding: utf-8
'''
@Author  : pentiumCM
@Email   : 842679178@qq.com
@Software: PyCharm
@File    : sklearn_hierarchical_cluster.py
@Time    : 2020/4/23 15:00
@desc	 : sklearn的层次聚类
'''

import numpy as np
from matplotlib import pyplot as plt

from sklearn.cluster import AgglomerativeClustering

data = np.array([[1, 2], [2, 3], [-3, 3], [-2, -1], [5, -1]])

# 画点
plt.scatter(x=data[:, 0:1], y=data[:, 1:2], marker='.', color='red')
n = np.arange(data.shape[0])
for i, txt in enumerate(n):
    plt.annotate(txt, (data[i:i + 1, 0:1], data[i:i + 1, 1:2]))
plt.show()

# 训练模型
ac = AgglomerativeClustering(n_clusters=3, affinity='euclidean', linkage='average')
clustering = ac.fit(data)

print("每个数据所属的簇编号", clustering.labels_)
print("每个簇的成员", clustering.children_)

算法运行结果:

每个数据所属的簇编号 [2 2 0 0 1]
每个簇的成员 [[0 1]
 			[2 3]
 			[5 6]
 			[4 7]]

借助 scipy 生成的聚类树,我们来理解一下聚类的结果:

在这里插入图片描述
clustering.labels_:表示每个数据所属于哪一个簇。
  [2 2 0 0 1]:表示数据0、1分为一簇,2、3分为一簇,4分为一簇。
clustering.children_:表示每个簇中有哪些元素。
  [[0 1] [2 3] [5 6] [4 7]]:首先将数据初始化为簇 0 ~ n-1,然后簇0、1合并为簇5,簇2、3合并为簇6,簇5、6合并为簇7,最后簇4、7合并。


补充基于预计算(precomputed)的距离矩阵的算法

完整算法如下:

#!/usr/bin/env python
# encoding: utf-8
'''
@Author  : pentiumCM
@Email   : 842679178@qq.com
@Software: PyCharm
@File    : sklearn_hierarchical_cluster.py
@Time    : 2020/4/23 15:00
@desc	 : sklearn的层次聚类
'''

import numpy as np
from matplotlib import pyplot as plt

from sklearn.cluster import AgglomerativeClustering

from sklearn.metrics.pairwise import euclidean_distances
import scipy.spatial.distance as dist
from scipy.cluster.hierarchy import dendrogram, linkage

data = np.array([[1, 2], [2, 3], [-3, 3], [-2, -1], [5, -1]])

# 画点
plt.scatter(x=data[:, 0:1], y=data[:, 1:2], marker='.', color='red')
n = np.arange(data.shape[0])
for i, txt in enumerate(n):
    plt.annotate(txt, (data[i:i + 1, 0:1], data[i:i + 1, 1:2]))
plt.show()

# 聚类方式一
# 训练模型
ac = AgglomerativeClustering(n_clusters=3, affinity='euclidean', linkage='average')
clustering = ac.fit(data)

print("每个数据所属的簇编号:", clustering.labels_)
print("每个簇的成员:", clustering.children_)

# 聚类的方式二
# 自定义距离矩阵
num = data.shape[0]
dist_matrix = np.mat(np.zeros((num, num)))
for i in range(num):
    for j in range(i, num):
        distence = euclidean_distances(data[i:i + 1], data[j:j + 1])
        dist_matrix[i:i + 1, j:j + 1] = distence
        dist_matrix[j:j + 1, i:i + 1] = dist_matrix[i:i + 1, j:j + 1]

# 基于自定义的聚类矩阵进行聚类
model = AgglomerativeClustering(n_clusters=3, affinity='precomputed', linkage='average')
clustering2 = model.fit(dist_matrix)

print("自定义距离矩阵聚类方式:")
print("每个数据所属的簇编号:", clustering2.labels_)
print("每个簇的成员:", clustering2.children_)

# 调整距离矩阵的形状
dist_matrix = dist.squareform(dist_matrix)

# linkage方法用于计算两个聚类簇s和t之间的距离d(s,t)
# 层次聚类编码为一个linkage矩阵。
Z = linkage(dist_matrix, 'average')
print("聚类过程:", Z)

# 将层级聚类结果以树状图表示出来
fig = plt.figure(figsize=(5, 3))
dn = dendrogram(Z)
plt.show()

算法运行结果:

每个数据所属的簇编号: [2 2 0 0 1]
每个簇的成员: [[0 1]
			  [2 3]
			  [5 6]
			  [4 7]]
自定义距离矩阵聚类方式:
每个数据所属的簇编号: [2 2 0 0 1]
每个簇的成员: [[0 1]
			  [2 3]
			  [5 6]
			  [4 7]]
聚类过程: [[0.         1.         1.41421356 2.        ]
		  [2.         3.         4.12310563 2.        ]
		  [5.         6.         4.75565014 4.        ]
		  [4.         7.         6.48606798 5.        ]]

上面的算法包含了sklearn的两种聚类方式,方式二为预计算的方式:算法预先计算了 期初各个数据点之间的距离矩阵dist_matrix,然后在聚类时,affinity=‘precomputed’。


对比之下,对于层次聚类的方便理解的方式,大家可以采用scipy的方式。


参考资料

  • 19
    点赞
  • 118
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值