本阶段将开启 无监督机器学习 的旅程。对于无监督机器学习问题,主要有两种:聚类、降维
聚类 Clustering
降维 Dimensionality Reduction
小贴士:
① 聚类就是分组(归堆);降维类似于换个角度去审视原来的数据。
② 由于维度越多,速度越慢。所以,为提高模型运行速度,通常会做降维的任务。
什么情况下要用到聚类?
从图中可以看出,不同的聚类算法,对于不同的数据分布场景,其聚类效果有所不同。
本文将针对 K-Means 聚类、层次聚类、密度聚类以及谱聚类展开介绍。
小贴士
- scikit-learn:是针对 Python 编程语言的免费软件机器学习库
- 关于 scikit-learn 的社区地址:外语:scikit-learn.org; 中文:scikit-learn.org.cn
3.1 相似度
对于相似度的判断,站在不同的角度会有不同的结果:
举例:
3.2 数据间的相似度
概念:
欧氏距离、闵mǐn可夫斯基距离:
小贴士
- 这些距离可用 “向量的范数” 进行表示,感兴趣的伙伴可阅读:04人工智能基础-高等数学知识强化(三)线性代数基础之向量 —— 文中的 “向量到原点的范式” 部分
如何计算样本到中心点的距离?通常有两种方式:① 欧氏距离测度、② 余弦距离测度
4.1 欧氏距离测度 Euclidean Distance Measure
公式:(也就是上文中的欧式距离)
e u c l i d e a n ( A , B ) = ∑ i = 1 n ( A i − B i ) 2 euclidean(A,B)=\sqrt{\sum_{i=1}^{n}(A_i -B_i)^{2}} euclidean(A,B)=i=1∑n(Ai−Bi)2
说明:欧氏距离越大,相似度越低
4.2 余弦距离测度 Cosine Similarity Measure
步骤:
公式:(余弦相似度)
s i m i l a r i t y = c o s ( θ ) = A ⋅ B ∥ A ∥ ∥ B ∥ = ∑ i = 1 n A i B i ∑ i = 1 n A i 2 ∑ i = 1 n B i 2 similarity = cos(\theta) = \frac{A\cdot B}{\left \| A \right \|\left \| B \right \|} = \frac{\sum_{i=1}^{n}A_iB_i}{\sqrt{\sum_{i=1}^{n}A_i^{2}}\sqrt{\sum_{i=1}^{n}B_i^{2}}} similarity=cos(θ)=∥A∥∥B∥A⋅B=∑i=1nAi2∑i=1nBi2∑i=1nAiBi
说明:夹角越大,余弦值越小,相似度越低
小贴士
- 余弦距离 = 1 - 余弦相似度
- ∴ 余弦距离的取值范围:[0, 2]
4.3 选择欧氏距离还是余弦距离?
总结:
举例:
小贴士
- 欧氏距离测度是常用的距离测度
- 余弦距离测度更适用于文本:余弦相似度可以评价文章的相似度,从而实现对文章,进行分类
4.4 归一化后欧氏距离与余弦距离在效果上等效
概述:
推导:
说明:
小贴士
- 参考:余弦相似度和余弦距离的推导与理解
❥ K-Means 聚类属于聚类任务的算法之一
为什么叫 K-Means 聚类?
K-Means 必须首先知道要分为几个组(即:K 需要预先设定)
假设 K = 3,这三个类别的位置,开始是不知道的:
问:K-Means 要干的事情是什么?
答:求平均
问:迭代终止条件是什么?
答:没有新的样本点被划分进某组(即:所有样本的分配不再改变;也可以理解为:中心点的位置不再变化)
优点:
缺点:
目标函数
小贴士
- 参考1:K-Means算法详细介绍(SSE、轮廓分析)
- 参考2:参数误差统计:SSE、SSR、SST、R_square、MSE、RMSE
对于聚类数 K 的选择,其中一种方式便是肘部法(elbow method)
肘部法
目标:(找到最合适的点——拐点)
如何判断拐点?
动图-演示:
小贴士
- 没有所谓最好的选择聚类数的方法,通常是需要根据不同的问题,人工进行选择的。
下面介绍一些由 K-Means 所衍生出来的算法,它们针对 K-Means 所暴露出来的不足进行了优化,如:① K-Medoids、② 二分 K-Means、③ K-Means++、④ Mini-batch Kmeans 算法等
算法 | 针对 K-Means 中的不足点 | 优化内容 |
---|---|---|
K-Medoids | K-Means 算法对异常值很敏感 | 从求平均切换为求中位数 |
二分 K-Means | K-Means 算法的初始中心点位置,很大程度上会影响聚类的效果 | 再次划分,重新改变中心点位置 |
K-Means++ | K-Means 选择一个好的初始中心非常重要,初始中心点分布不够均匀往往会导致聚类效果不好 | 改进了 K-Means 算法初始中心点的方式 |
Mini Batch K-Means | K-Means 算法中需要计算所有样本点到所有质心的距离,计算复杂度较高 | 随机选择一部分的数据求均值 |
本节还会介绍一个 Canopy 聚类算法,虽然不是 K-Means 所衍生出来的算法,但它很少单独使用,会结合 K-Means 一起使用。
说明:
步骤:
小贴士
- 参考:机器学习 K-Means 方面的算法
优化:
目标:
初始化中心点的步骤:
K-Means++ 用到的范围比较广,如:
小贴士:K-Means++ 的初始化中心点
- K-Means++ 是一个一个中心点依次选取出来的
- 概率化选择时,其概率为均匀分布,在(0,1)间随机取值,由于将距离转化为概率,而距离远的其在数轴中的长度间隔会更大,所以更有可能被选中
该算法的迭代步骤有两步:
区别:
特点:一次迭代,出 k 个中心点的结果
Canopy 聚类的步骤:
Canopy 聚类的说明:
K-Means 在初始中心点时,使用 Canopy 聚类的好处:
小贴士
- 参考1:Canopy 聚类、层次聚类、密度聚类-DBSCAN
- 参考2:Canopy 聚类算法分析
下面是基于 sklearn 库做的聚类,使用的算法为 K-Means++
代码:( 工具:PyCharm,基于:python3)
各步骤说明:
import numpy as np
import matplotlib.pyplot as plt
import sklearn.datasets as ds # 通过datasets,帮我们创建一些数据集
import matplotlib.colors # 关于颜色的
from sklearn.cluster import KMeans # k-means 聚类
N = 400 # 一共想创建400个数据(样本)
centers = 4 # 告诉函数,要创建4个组(共4个中心点)
# -----下面创建了三组不同的数据,1、第一组:根据高斯分布,创建同方差的数据(400样本,4个组,每组100个);2、第二组:高斯分布的方差不同;3、第三组:同方差,但只选择了175条数据(且各组数据个数不同) ------
# make_blobs 创建数据,括号中的参数:① 创建多少样本,② 每条样本的维度(这里:400行2列),③ 样本分到多少个组中,④ 创建随机种子
data, y = ds.make_blobs(N, n_features=2, centers=centers, random_state=2) # 得到每组中的数据data 及 label标签y(每个样本在哪个组)
# 此处多了个 cluster_std —— 每组对应的方差(此处设置成了不同的),上面那条代码默认同方差的
data2, y2 = ds.make_blobs(N, n_features=2, centers=centers, cluster_std=(1, 2.5, 0.5, 2), random_state=2)
# vstack:竖的堆叠,括号中个参数含义:① 将标签y为0的该组中,所有数据取出(100条);② 组别为1的样本,只取50条;③ 组别为2的样本,只取20条;④ 3的:5条
data3 = np.vstack((data[y == 0][:], data[y == 1][:50], data[y == 2][:20], data[y == 3][:5]))
# 这里的 标签y 需要自己写。说明:[0] * 100 这种 —— 是原生的python - 将列表扩展,得:一个列表中有100个0; 加号代表:将列表元素拼在一起
# 下列代码结果:用列表装数据,前100个为0,50个1,20个2,最后5个为3
y3 = np.array([0] * 100 + [1] * 50 + [2] * 20 + [3] * 5)
# ----- K-means++ 算法 (核心代码:调用 k-means++ 训练和预测,得到相应的 ŷ 结果)-----
cls = KMeans(n_clusters=4, init='k-means++') # 创建k-means对象,括号中参数:① n_clusters:指定K的个数;② init:初始化 k-means 算法的超参数=
y_hat = cls.fit_predict(data) # 对对象进行聚类,得到聚类的结果(fit_predict 返回的是:数据的类别号)
y2_hat = cls.fit_predict(data2)
y3_hat = cls.fit_predict(data3)
# 下面:通过矩阵,将原来的数据做了高维空间的线性变换,将其映射到新的维度中(相当于 做了旋转)
m = np.array(((1, 1), (1, 3))) # 把元组变为了2行2列的矩阵。矩阵能干的事情:高维空间的线性变换
data_r = data.dot(m) # 点乘:用 原始数据data 点乘 矩阵m → 相当于 把数据中的每个向量点,全部进行相对于的旋转,投影到另外一个轴中
y_r_hat = cls.fit_predict(data_r)
def expand(a, b): # 后续画图时会用到的自定义函数
d = (b - a) * 0.1
return a - d, b + d
# ----- 以下是画图的内容,共画了8个子图 ------------------------------
matplotlib.rcParams['font.sans-serif'] = [u'SimHei'] # 可展示中文(黑体)
matplotlib.rcParams['axes.unicode_minus'] = False # 让负号可以正常显示
cm = matplotlib.colors.ListedColormap(list('rgbm'))
plt.figure(figsize=(9, 10), facecolor='w')
plt.subplot(421) # 8个子图中的:第一个子图
plt.title(u'原始数据') # u:表示unicode字符串
plt.scatter(data[:, 0], data[:, 1], c=y, s=30, cmap=cm, edgecolors='none') # 散点图,传真实结果 y
x1_min, x2_min = np.min(data, axis=0)
x1_max, x2_max = np.max(data, axis=0)
x1_min, x1_max = expand(x1_min, x1_max)
x2_min, x2_max = expand(x2_min, x2_max)
plt.xlim((x1_min, x1_max)) # x 轴
plt.ylim((x2_min, x2_max)) # y 轴
plt.grid(True) # 网格
plt.subplot(422)
plt.title(u'KMeans++聚类')
plt.scatter(data[:, 0], data[:, 1], c=y_hat, s=30, cmap=cm, edgecolors='none') # 散点图,传预测结果 ŷ
plt.xlim((x1_min, x1_max))
plt.ylim((x2_min, x2_max))
plt.grid(True)
plt.subplot(423)
plt.title(u'旋转后数据')
plt.scatter(data_r[:, 0], data_r[:, 1], c=y, s=30, cmap=cm, edgecolors='none')
x1_min, x2_min = np.min(data_r, axis=0)
x1_max, x2_max = np.max(data_r, axis=0)
x1_min, x1_max = expand(x1_min, x1_max)
x2_min, x2_max = expand(x2_min, x2_max)
plt.xlim((x1_min, x1_max))
plt.ylim((x2_min, x2_max))
plt.grid(True)
plt.subplot(424)
plt.title(u'旋转后KMeans++聚类')
plt.scatter(data_r[:, 0], data_r[:, 1], c=y_r_hat, s=30, cmap=cm, edgecolors='none')
plt.xlim((x1_min, x1_max))
plt.ylim((x2_min, x2_max))
plt.grid(True)
plt.subplot(425)
plt.title(u'方差不相等数据')
plt.scatter(data2[:, 0], data2[:, 1], c=y2, s=30, cmap=cm, edgecolors='none')
x1_min, x2_min = np.min(data2, axis=0)
x1_max, x2_max = np.max(data2, axis=0)
x1_min, x1_max = expand(x1_min, x1_max)
x2_min, x2_max = expand(x2_min, x2_max)
plt.xlim((x1_min, x1_max))
plt.ylim((x2_min, x2_max))
plt.grid(True)
plt.subplot(426)
plt.title(u'方差不相等KMeans++聚类')
plt.scatter(data2[:, 0], data2[:, 1], c=y2_hat, s=30, cmap=cm, edgecolors='none')
plt.xlim((x1_min, x1_max))
plt.ylim((x2_min, x2_max))
plt.grid(True)
plt.subplot(427)
plt.title(u'数量不相等数据')
plt.scatter(data3[:, 0], data3[:, 1], s=30, c=y3, cmap=cm, edgecolors='none')
x1_min, x2_min = np.min(data3, axis=0)
x1_max, x2_max = np.max(data3, axis=0)
x1_min, x1_max = expand(x1_min, x1_max)
x2_min, x2_max = expand(x2_min, x2_max)
plt.xlim((x1_min, x1_max))
plt.ylim((x2_min, x2_max))
plt.grid(True)
plt.subplot(428)
plt.title(u'数量不相等KMeans++聚类')
plt.scatter(data3[:, 0], data3[:, 1], c=y3_hat, s=30, cmap=cm, edgecolors='none')
plt.xlim((x1_min, x1_max))
plt.ylim((x2_min, x2_max))
plt.grid(True)
plt.suptitle(u'数据分布对KMeans聚类的影响', fontsize=18) # 总标题
plt.tight_layout() # 会自动调整子图参数,使之填充整个图像区域(防止重叠)
plt.savefig('cluster_kmeans') # 将绘图结果保存为png图片
plt.show() # 展示
小贴士
- 若对于 python 中 matplotlib 绘图不太了解的伙伴,可参考文章:AI算法工程师 | 03人工智能基础-Python科学计算和可视化(二)Matplotlib
整体代码:
# !/usr/bin/python
# -*- coding:utf-8 -*-
"""
基于 sklearn 库做聚类:K-Means++
"""
# 导入包
import numpy as np
import matplotlib.pyplot as plt
import sklearn.datasets as ds # 通过datasets,帮我们创建一些数据集
import matplotlib.colors # 关于颜色的
from sklearn.cluster import KMeans # k-means 聚类
def expand(a, b):
d = (b - a) * 0.1
return a - d, b + d
if __name__ == "__main__":
N = 400 # 一共想创建400个数据(样本)
centers = 4 # 告诉函数,要创建4个组(共4个中心点)
# -----下面创建了三组不同的数据,1、第一组:根据高斯分布,创建同方差的数据(400样本,4个组,每组100个);2、第二组:高斯分布的方差不同;3、第三组:同方差,但只选择了175条数据(且各组数据个数不同) ------
# make_blobs 创建数据,括号中的参数:① 创建多少样本,② 每条样本的维度(这里:400行2列),③ 样本分到多少个组中,④ 创建随机种子
data, y = ds.make_blobs(N, n_features=2, centers=centers, random_state=2) # 得到每组中的数据data 及 label标签y(每个样本在哪个组)
# 此处多了个 cluster_std —— 每组对应的方差(此处设置成了不同的),上面那条代码默认同方差的
data2, y2 = ds.make_blobs(N, n_features=2, centers=centers, cluster_std=(1, 2.5, 0.5, 2), random_state=2)
# vstack:竖的堆叠,括号中个参数含义:① 将标签y为0的该组中,所有数据取出(100条);② 组别为1的样本,只取50条;③ 组别为2的样本,只取20条;④ 3的:5条
data3 = np.vstack((data[y == 0][:], data[y == 1][:50], data[y == 2][:20], data[y == 3][:5]))
# 这里的 标签y 需要自己写。说明:[0] * 100 这种 —— 是原生的python - 将列表扩展,得:一个列表中有100个0; 加号代表:将列表元素拼在一起
# 下列代码结果:用列表装数据,前100个为0,50个1,20个2,最后5个为3
y3 = np.array([0] * 100 + [1] * 50 + [2] * 20 + [3] * 5)
# ----- K-means 算法 (核心代码:调用 k-means 训练和预测,得到相应的 ŷ 结果)-----
cls = KMeans(n_clusters=4, init='k-means++') # 创建k-means对象,括号中参数:① n_clusters:指定K的个数;② init:初始化 k-means 算法的超参数
y_hat = cls.fit_predict(data) # 对对象进行聚类,得到聚类的结果(fit_predict 返回的是:数据的类别号)
y2_hat = cls.fit_predict(data2)
y3_hat = cls.fit_predict(data3)
# 下面:通过矩阵,将原来的数据做了高维空间的线性变换,将其映射到新的维度中(相当于 做了旋转)
m = np.array(((1, 1), (1, 3))) # 把元组变为了2行2列的矩阵。矩阵能干的事情:高维空间的线性变换
data_r = data.dot(m) # 点乘:用 原始数据data 点乘 矩阵m → 相当于 把数据中的每个向量点,全部进行相对于的旋转,投影到另外一个轴中
y_r_hat = cls.fit_predict(data_r)
# ----- 以下是画图的内容,共画了8个子图 ------------------------------
matplotlib.rcParams['font.sans-serif'] = [u'SimHei'] # 可展示中文(黑体)
matplotlib.rcParams['axes.unicode_minus'] = False # 让负号可以正常显示
cm = matplotlib.colors.ListedColormap(list('rgbm'))
plt.figure(figsize=(9, 10), facecolor='w')
plt.subplot(421) # 8个子图中的:第一个子图
plt.title(u'原始数据') # u:表示unicode字符串
plt.scatter(data[:, 0], data[:, 1], c=y, s=30, cmap=cm, edgecolors='none') # 散点图,传真实结果 y
x1_min, x2_min = np.min(data, axis=0)
x1_max, x2_max = np.max(data, axis=0)
x1_min, x1_max = expand(x1_min, x1_max)
x2_min, x2_max = expand(x2_min, x2_max)
plt.xlim((x1_min, x1_max)) # x 轴
plt.ylim((x2_min, x2_max)) # y 轴
plt.grid(True) # 网格
plt.subplot(422)
plt.title(u'KMeans++聚类')
plt.scatter(data[:, 0], data[:, 1], c=y_hat, s=30, cmap=cm, edgecolors='none') # 散点图,传预测结果 ŷ
plt.xlim((x1_min, x1_max))
plt.ylim((x2_min, x2_max))
plt.grid(True)
plt.subplot(423)
plt.title(u'旋转后数据')
plt.scatter(data_r[:, 0], data_r[:, 1], c=y, s=30, cmap=cm, edgecolors='none')
x1_min, x2_min = np.min(data_r, axis=0)
x1_max, x2_max = np.max(data_r, axis=0)
x1_min, x1_max = expand(x1_min, x1_max)
x2_min, x2_max = expand(x2_min, x2_max)
plt.xlim((x1_min, x1_max))
plt.ylim((x2_min, x2_max))
plt.grid(True)
plt.subplot(424)
plt.title(u'旋转后KMeans++聚类')
plt.scatter(data_r[:, 0], data_r[:, 1], c=y_r_hat, s=30, cmap=cm, edgecolors='none')
plt.xlim((x1_min, x1_max))
plt.ylim((x2_min, x2_max))
plt.grid(True)
plt.subplot(425)
plt.title(u'方差不相等数据')
plt.scatter(data2[:, 0], data2[:, 1], c=y2, s=30, cmap=cm, edgecolors='none')
x1_min, x2_min = np.min(data2, axis=0)
x1_max, x2_max = np.max(data2, axis=0)
x1_min, x1_max = expand(x1_min, x1_max)
x2_min, x2_max = expand(x2_min, x2_max)
plt.xlim((x1_min, x1_max))
plt.ylim((x2_min, x2_max))
plt.grid(True)
plt.subplot(426)
plt.title(u'方差不相等KMeans++聚类')
plt.scatter(data2[:, 0], data2[:, 1], c=y2_hat, s=30, cmap=cm, edgecolors='none')
plt.xlim((x1_min, x1_max))
plt.ylim((x2_min, x2_max))
plt.grid(True)
plt.subplot(427)
plt.title(u'数量不相等数据')
plt.scatter(data3[:, 0], data3[:, 1], s=30, c=y3, cmap=cm, edgecolors='none')
x1_min, x2_min = np.min(data3, axis=0)
x1_max, x2_max = np.max(data3, axis=0)
x1_min, x1_max = expand(x1_min, x1_max)
x2_min, x2_max = expand(x2_min, x2_max)
plt.xlim((x1_min, x1_max))
plt.ylim((x2_min, x2_max))
plt.grid(True)
plt.subplot(428)
plt.title(u'数量不相等KMeans++聚类')
plt.scatter(data3[:, 0], data3[:, 1], c=y3_hat, s=30, cmap=cm, edgecolors='none')
plt.xlim((x1_min, x1_max))
plt.ylim((x2_min, x2_max))
plt.grid(True)
plt.suptitle(u'数据分布对KMeans聚类的影响', fontsize=18) # 总标题
plt.tight_layout() # 会自动调整子图参数,使之填充整个图像区域(防止重叠)
plt.savefig('cluster_kmeans') # 将绘图结果保存为png图片
plt.show() # 展示
结果展示:
层次聚类解决了 K-Means 中 K 值选择和初始中心点选择的问题。
其聚类方式分为:① 分裂法、② 凝聚法
层次聚类对比 K-Means:
像一棵树一样,不断的分裂
算法步骤:
说明:二分 K-Means 本质上是层次聚类中的分裂法,它通过不断分裂直到达到预设的簇类个数。
原理:
算法步骤:
合并 C1、C2 的方式可以有所差别︰(两个簇之间距离的度量)
与层次聚类的异同:
同:与层次聚类一样无需设置 K 值
异:
密度聚类最参见的算法为:DBSCAN 算法
概述:
什么叫密度相连?
先来了解几个概念:
从上述可知,密度聚类有两个超参数: ε ε ε 、 m m m
密度可达 和 密度相连有什么用?
算法步骤:
DBSCAN 通过检查数据集中每个对象的 ε ε ε 邻域来寻找聚类
步骤:(下面是更新一个簇的思路)
根据半径( ε ε ε 邻域)、最少点数( m m m )区分核心点、边界点、噪声点:
小贴士
- List item
- 参考1:密度聚类(DBSCAN / MDCA) by loveliuzz
- 参考2:六种常见聚类算法(参考的文中密度聚类部分) by TingXiao-Ul
下面是基于 sklearn 库做的聚类,感受超参数 ε ε ε 、 m m m 不同的情况 DBSCAN 聚类的效果
代码:( 工具:PyCharm,基于:python3)
# ----- 导入模块 -----
import numpy as np
import matplotlib.pyplot as plt
import sklearn.datasets as ds # 通过datasets,帮我们创建一些数据集
import matplotlib.colors # 关于颜色的
from sklearn.cluster import DBSCAN # 密度聚类
from sklearn.preprocessing import StandardScaler
def expand(a, b):
d = (b - a) * 0.1
return a - d, b + d
if __name__ == "__main__":
# ----- 数据准备 ----------------------
N = 1000
centers = [[1, 2], [-1, -1], [1, -1], [-1, 1]] # 指明簇中心的位置
# make_blobs 函数:生成数据集。—— 创建1000个样本点,每个样本点有2个特征,指定了4个簇中心,默认使用正态分布-随机初始化数据,random_state:为了保证程序每次运行都分割一样的训练集和测试集
data, y = ds.make_blobs(N, n_features=2, centers=centers, cluster_std=[0.5, 0.25, 0.7, 0.5], random_state=0)
data = StandardScaler().fit_transform(data) # 进行归一化
# 数据的参数:(epsilon, min_sample) —— 半径,最少的样本的个数。下面创建了六组超参数
params = ((0.2, 5), (0.2, 10), (0.2, 15), (0.3, 5), (0.3, 10), (0.3, 15))
# ----- 以下是画图的内容,画了6个子图(因为设置了 6 组超参数,每次循环取出一组进行密度聚类) ------------------------------
matplotlib.rcParams['font.sans-serif'] = [u'SimHei'] # 可展示中文(黑体)
matplotlib.rcParams['axes.unicode_minus'] = False # 让负号可以正常显示
plt.figure(figsize=(12, 8), facecolor='w') # 设置画布大小和颜色
plt.suptitle(u'DBSCAN聚类', fontsize=20) # 主标题
for i in range(6):
# ----- 下面进行密度聚类(核心代码)--------------
eps, min_samples = params[i] # 取出每组超参数
""" 调用 DBSCAN 密度聚类算法
参数含义:
· eps: 半径,表示以给定点 p 为中心的圆形部域的范围
· min_samples: 以点 p 为中心的邻域内最少点的数量
如果满足 以点 p 为中心,半径为 eps 的领域内,点的个数不少 min_samples ,则称点 p 为【核心点】
"""
model = DBSCAN(eps=eps, min_samples=min_samples) # DBSCAN 密度聚类算法
model.fit(data) # 训练 DBSCAN 模型
y_hat = model.labels_ # 拿到每个样本对应的类别聚类的结果。无论核心点还是边界点,只要是同一个簇的都被赋予同样的label,噪声点为-1.
core_indices = np.zeros_like(y_hat, dtype=bool) # 生成数据类型和形状和 y_hat 一致的初始化为0的数组。dtype=bool:会覆盖原数据类型,∴是一个布尔数组
core_indices[model.core_sample_indices_] = True # model.core_ sample_ 核心点的索引。由于labels_无法区分核心点与边界点,所以要用该索引确定核心点。
y_unique = np.unique(y_hat) # 统计总共有几类,其中为 -1 的:表示未分类样本。unique 函数:去除其中重复的元素
n_clusters = y_unique.size - (1 if -1 in y_hat else 0) # 得到聚类簇的个数
print(y_unique, '聚类簇的个数为:', n_clusters)
plt.subplot(2, 3, i + 1) # 共6张子图(2行3列),绘制第 i+1 张
clrs = plt.cm.Spectral(np.linspace(0, 0.8, y_unique.size)) # plt.cm.Spectral 作用:在画图时为不同类别的样本分别分配不同的颜色
print(clrs)
for k, clr in zip(y_unique, clrs): # zip() 函数:将可迭代的对象作为参数,把对象中对应的元素打包成一个个元组,并返回由这些元组组成的对象
cur = (y_hat == k)
cur = (y_hat == k)
if k == -1:
plt.scatter(data[cur, 0], data[cur, 1], s=20, c='k') # 散点图,用于绘制未分类样本。c='k':黑色
continue
plt.scatter(data[cur, 0], data[cur, 1], s=30, c=clr, edgecolors='k') # 散点图,绘制正常节点
plt.scatter(data[cur & core_indices][:, 0], data[cur & core_indices][:, 1], s=60, c=clr, marker='o',
edgecolors='k') # 绘制边界点
x1_min, x2_min = np.min(data, axis=0) # 分别找到数据的两列中的最小值
x1_max, x2_max = np.max(data, axis=0) # 分别找到数据的两列中的最大值
x1_min, x1_max = expand(x1_min, x1_max)
x2_min, x2_max = expand(x2_min, x2_max)
plt.xlim((x1_min, x1_max)) # 设置 x 轴的数值显示范围
plt.ylim((x2_min, x2_max)) # 设置 y 轴的数值显示范围
plt.grid(True) # 网格
plt.title(u'epsilon = %.1f m = %d,聚类数目:%d' % (eps, min_samples, n_clusters), fontsize=16) # 子图的标题
plt.tight_layout() # 会自动调整子图参数,使之填充整个图像区域(防止重叠)
plt.subplots_adjust(top=0.9)
plt.savefig('cluster_DBSCAN') # 将绘图结果保存为png图片
plt.show() # 展示
结果展示:
从图中可看出,密度聚类存在的一个问题:
离群点为什么聚不进来?
谱聚类的本质:先做降维,再用 K-Means
谱聚类的特点
优点:
缺点:
谱聚类整体思路
一个典型的实现由三个基本步骤组成:
∴ 构图:通过 S S S 相似矩阵 → 得到 W W W 构图矩阵(构图方式有多种) → D D D 对角矩阵 → L L L 拉普拉斯矩阵
步骤一:相似度矩阵 S S S
步骤二:根据构图方式计算 W W W 矩阵(邻接矩阵)
W W W 矩阵:想表达的是——如何构建的
常见构图方式:
① ε-neighborhood( ε ε ε-邻近法)
② k-nearest neighborhood(K 近邻法)
③ fully connected(全连接法)
步骤三:计算 D D D 矩阵和拉普拉斯矩阵
切图的过程,就是聚类的过程
内容:
切图的目的:
思考:
切图的问题:
切图方法 ☞ RatioCut:
下面是基于 sklearn 库做的聚类,感受不同超参数下的谱聚类效果
代码:( 工具:PyCharm,基于:python3)
# 导入模块
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors
from sklearn.cluster import spectral_clustering # 谱聚类
from sklearn.metrics import euclidean_distances # 欧式距离
def expand(a, b):
d = (b - a) * 0.1
return a-d, b+d
if __name__ == "__main__":
# -----创建数据-----
t = np.arange(0, 2*np.pi, 0.1)
data1 = np.vstack((np.cos(t), np.sin(t))).T
data2 = np.vstack((2*np.cos(t), 2*np.sin(t))).T
data3 = np.vstack((3*np.cos(t), 3*np.sin(t))).T
data = np.vstack((data1, data2, data3))
n_clusters = 3
m = euclidean_distances(data, squared=True) # 欧式距离的开根号(矩阵)
sigma = np.median(m) # 中位数
# ------------- 绘图 --------------
matplotlib.rcParams['font.sans-serif'] = [u'SimHei'] # 可展示中文(黑体)
matplotlib.rcParams['axes.unicode_minus'] = False # 让负号可以正常显示
plt.figure(figsize=(12, 8), facecolor='w')
plt.suptitle(u'谱聚类', fontsize=20)
clrs = plt.cm.Spectral(np.linspace(0, 0.8, n_clusters))
for i, s in enumerate(np.logspace(-2, 0, 6)):
print(s)
af = np.exp(-m ** 2 / (s ** 2)) + 1e-6 # 此处 s 用来判断高斯距离;‘+ 1e-6’:是为了防止值为0
y_hat = spectral_clustering(af, n_clusters=n_clusters, assign_labels='kmeans', random_state=1)
plt.subplot(2, 3, i+1)
for k, clr in enumerate(clrs):
cur = (y_hat == k)
plt.scatter(data[cur, 0], data[cur, 1], s=40, c=clr, edgecolors='k')
x1_min, x2_min = np.min(data, axis=0)
x1_max, x2_max = np.max(data, axis=0)
x1_min, x1_max = expand(x1_min, x1_max)
x2_min, x2_max = expand(x2_min, x2_max)
plt.xlim((x1_min, x1_max))
plt.ylim((x2_min, x2_max))
plt.grid(True) # 网格
plt.title(u'sigma = %.2f' % s, fontsize=16) # 子图的标题
plt.tight_layout() # 会自动调整子图参数,使之填充整个图像区域(防止重叠)
plt.subplots_adjust(top=0.9)
plt.savefig('cluster_spectral') # 将绘图结果保存为png图片
plt.show() # 展示
结果展示:
—— 说明:本文写于 2022.9.2 和 9.18~9.28 ,文中内容基于 python3,使用工具 PyCharm
编写的代码