机器学习入门 --- K-means、DBSCAN聚类算法(概念、图解、代码示例)

聚类概念

聚类是把相似的东西分到一组,它是一个无监督问题,没有标签使用

难点:
对于有标签的有监督学习问题,标签可以便于我们来评估模型,无监督学习问题在评估上比较难一点
对于不同的参数组合,得到的学习结果,因为比较难对模型做评估,所以不能通过一个精确度的好坏来选择参数组合

K-MEANS算法

K-MEANS算法是聚类问题中,最简单,也是最实用的一个算法

基本概念

一个数据放进来,需要指定K值,来声明要得到簇的个数

质心:一个簇的数据均值,即向量各维取平均即可(迭代时使用)

距离的度量:常用欧几里得距离和余弦相似度(数据需先标准化)

优化目标

通过目标函数进行不断地优化、求解

m i n ∑ i = 1 K ∑ x ∈ C i d i s t ( c i , x ) 2 min\sum_{i=1}^{K}\sum_{x \in C_i}dist(c_i,x)^2 mini=1KxCidist(ci,x)2

∑ i = 1 K \sum_{i=1}^{K} i=1K 表示一共有多少个簇,对 K K K 个簇优化

∑ x ∈ C i d i s t ( c i , x ) 2 \sum_{x \in C_i}dist(c_i,x)^2 xCidist(ci,x)2 表示每个簇的值到其中心点的距离

工作流程

在这里插入图片描述
对于以上这个例子,我们设置 K = 2 K = 2 K=2 ,首先要初始化质心(随机),如 b b b 图所示,通过计算数据中所有的点到分别到这两个质心的距离,哪个距离短,就归于哪一簇,c图为初步结果,划分出了红色、蓝色的两簇,但效果不是很好,需要进一步优化,接下来根据所有的红、蓝点,重新计算质心,进行质心的更新,得到 d d d 图所示质心位置,更新质心后,再次迭代,计算所有的点到分别到这两个质心的距离,哪个距离短,就归于哪一簇,则得到 e e e 图的结果,再次更新质心,进行迭代… 直到所有样本点都不再发生变化了,更新基本结束了

优势:

  • 算法的理解、操作简单
  • 使用快速,适合常规数据集

劣势:

  • 对于一个新的样本,没有标签很难知道要去分多少个簇,K值难确定,通常情况下要设置多组参数实验看效果

  • 质点的初始值影响大

  • 复杂度与样本呈线性关系,数据量庞大时,计算量会很大

  • 因为数据的簇是不确定的,很难发现任意形状的簇,聚类效果不是很好。

比如下图这种,K-means聚类算法会根据初始化质心划分成这样四个簇,但实际上应该是里面三个簇,外面再环绕着一个簇。
在这里插入图片描述

K-means 算法迭代可视化展示

https://www.naftaliharris.com/blog/visualizing-k-means-clustering/

DBSCAN算法

基本概念:(Density-Based Spatial Clustering of Applications with Noise)
基于密度的噪声应用空间聚类

  • 核心对象:若某个点的密度达到算法设定的阈值则其为核心点(即 r 邻域内点的数量不小于 minPts
  • ϵ-邻域的距离阈值:设定的半径 r r r
  • 直接密度可达:若某点 p p p 在点 q q q r r r 邻域内,且 q q q 是核心点则 p − q p-q pq 直接密度可达
  • 密度可达:若有一个点的序列 q 0 、 q 1 、 … q k q_0、q_1、…q_k q0q1qk,对任意 q i − q i − 1 q_i-q_{i-1} qiqi1 是直接密度可达的,则称从 q 0 q_0 q0 q k q_k qk 密度可达,这实际上是直接密度可达的“传播”
  • 密度相连:若从某核心点 p p p 出发,点 q q q 和点 k k k 都是密度可达的,则称点 q q q 和点 k k k 是密度相连的
  • 边界点:属于某一个类的非核心点,在往某个方向扩展时,到了没有边界点的地方,就停止了这个方向(即密度不可达)
  • 噪声点:不属于任何一个类簇的点,从任何一个核心点出发都是密度不可达的

各点描述:
假设 A 为核心对象,画了一个圈,圈中了 A ′ 、 A ′ ′ 、 A ′ ′ ′ A'、A''、A''' AAA 三个点,并以 A ′ 、 A ′ ′ 、 A ′ ′ ′ A'、A''、A''' AAA 为核心对象继续向外扩展,最终到了 B 、 C B、C BC 两点时无法再进行扩展,即 B 、 C B、C BC 两点为边界点, N N N 点没有一个圈能涉及到它,它不属于任何一个类簇的点,是离群点
在这里插入图片描述
因为此算法可检测离群点,所以说 DBSCAN算法还可以做异常检测等任务

工作流程

三个参数:

  • 参数D:输入数据集
  • 参数ϵ:指定半径
  • MinPts:密度阈值

流程:

 1. 标记所有对象为 unvisited(初始时,所有数据都是未访问的)
 2. Do
 3. 随机选择-一个 uvisited 对象 p
 4. 标记 p 为 visited
 5. if p 的 ϵ- 领域至少有 MinPts 个对象
 6. 	创建一个新族 C, 并把 p 添加到 C
 7. 	令 N 为 p 的 ϵ- 领域中的对象集合
 8. 	For N 中每个点 p'
 9. 		if p' 是 umvisited
10. 		标记 p' 为 visited
11. 		if p' 的 ϵ- 领域至少有 MinPs 个对象,把这些对象添加到 N
12. 		if p' 还不是任何簇的成员,把 p' 添加到C
13. 	End for
14. 	输出C:
15. Else 标记p为噪声;
16. Until 没有标记为uwvisited的对象:
参数选择:
  • 半径 ϵ:可以根据 K 距离来设定:找突变点
    K距离:给定数据集P={p(i); i=0,1,…n},计算点P(i)到集合D的子集S中所有点之间的距离,距离按照从小到大的顺序排序,d(k) 就被称为 K距离,d(k) 集中数据突变的位置即为突变点,根据这里来设定半径 ϵ
  • MinPts: K距离中 K 的值,一般取的小一些,多次尝试

优势:

  • 不需要指定簇个数
  • 擅长找到离群点(检测任务)
  • 可以发现任意形状的簇
  • 两个参数就够了

劣势:

  • 高维数据有些困难(可能会报内存溢出,可以做降维)
  • Sklearn中效率很慢(数据削减策略)
  • 参数难以选择(参数对结果的影响非常大)

相比于 K-MEANS算法,DBSCAN算法可以很好的把这个笑脸进行份簇
在这里插入图片描述

DBSCAN 算法迭代可视化展示

https://www.naftaliharris.com/blog/visualizing-dbscan-clustering/

聚类实践

在这个里使用一个啤酒相关的数据进行聚类演示

# beer dataset
import pandas as pd
beer = pd.read_csv('data.txt', sep=' ')
beer.head()

在这里插入图片描述
构建聚类的数据输入

X = beer[["calories","sodium","alcohol","cost"]]
K-means 算法
from sklearn.cluster import KMeans
# 使用3个簇做聚类
km = KMeans(n_clusters=3).fit(X)
# 使用2个簇做聚类
km2 = KMeans(n_clusters=2).fit(X)
# 将聚类结果整合到数据中
beer['cluster'] = km.labels_
beer['cluster2'] = km2.labels_
# 根据 cluster 排序后展示
beer.sort_values('cluster').head(10)

在这里插入图片描述

基于每个簇,查看各项平均值:

beer.groupby("cluster").mean()

在这里插入图片描述

beer.groupby("cluster2").mean()

在这里插入图片描述
通过图像看一下3簇的聚类情况

import numpy as np
import matplotlib.pyplot as plt
# 拿到中心点
centers = beer.groupby("cluster").mean().reset_index()
# 大小
plt.rcParams['font.size'] = 14
# 颜色
colors = np.array(['red', 'green', 'blue', 'yellow'])
# 画数据点
plt.scatter(beer["calories"], beer["alcohol"],c=colors[beer["cluster"]])
# 画中心点
plt.scatter(centers.calories, centers.alcohol, linewidths=3, marker='+', s=300, c='black')

plt.xlabel("Calories")
plt.ylabel("Alcohol")

在这里插入图片描述
这张图展示出的只是在 Calories 和 Alcohol 两个维度上的关系,但我们的数据是4个维度,下面再看一下这4个维度的两两关系

# 2簇
scatter_matrix(beer[["calories","sodium","alcohol","cost"]],s=100, alpha=1, c=colors[beer["cluster2"]], figsize=(10,10))
plt.suptitle("With 3 centroids initialized")

在这里插入图片描述

# 3簇
scatter_matrix(beer[["calories","sodium","alcohol","cost"]],s=100, alpha=1, c=colors[beer["cluster"]], figsize=(10,10))
plt.suptitle("With 3 centroids initialized")

在这里插入图片描述

缩放数据

使用 sklearn 的 StandardScaler 进行数据标准化操作

# 标准化
from sklearn.preprocessing import StandardScaler
# 实例化
scaler = StandardScaler()
# 执行
X_scaled = scaler.fit_transform(X)
X_scaled
array([[ 0.38791334,  0.00779468,  0.43380786, -0.45682969],
       [ 0.6250656 ,  0.63136906,  0.62241997, -0.45682969],
       [ 0.82833896,  0.00779468, -3.14982226, -0.10269815],
       [ 1.26876459, -1.23935408,  0.90533814,  1.66795955],
       [ 0.65894449, -0.6157797 ,  0.71672602,  1.95126478],
       [ 0.42179223,  1.25494344,  0.3395018 , -1.5192243 ],
       [ 1.43815906,  1.41083704,  1.1882563 , -0.66930861],
       [ 0.55730781,  1.87851782,  0.43380786, -0.52765599],
       [-1.1366369 , -0.7716733 ,  0.05658363, -0.45682969],
       [-0.66233238, -1.08346049, -0.5092527 , -0.66930861],
       [ 0.25239776,  0.47547547,  0.3395018 , -0.38600338],
       [-1.03500022,  0.00779468, -0.13202848, -0.24435076],
       [ 0.08300329, -0.6157797 , -0.03772242,  0.03895447],
       [ 0.59118671,  0.63136906,  0.43380786,  1.88043848],
       [ 0.55730781, -1.39524768,  0.71672602,  2.0929174 ],
       [-2.18688263,  0.00779468, -1.82953748, -0.81096123],
       [ 0.21851887,  0.63136906,  0.15088969, -0.45682969],
       [ 0.38791334,  1.41083704,  0.62241997, -0.45682969],
       [-2.05136705, -1.39524768, -1.26370115, -0.24435076],
       [-1.20439469, -1.23935408, -0.03772242, -0.17352445]])

做完数据标准化后,再进行 K-eams 聚类

km = KMeans(n_clusters=3).fit(X_scaled)
beer["scaled_cluster"] = km.labels_
beer.sort_values("scaled_cluster").head(10)

在这里插入图片描述

DBSCAN算法
from sklearn.cluster import DBSCAN
# 实例化、执行
db = DBSCAN(eps=10, min_samples=2).fit(X)
# 得到聚类结果
labels = db.labels_
# 并入数据
beer['cluster_db'] = labels
beer.sort_values('cluster_db').head(10)

在这里插入图片描述
查看各个特征之间的聚类效果

pd.scatter_matrix(X, c=colors[beer.cluster_db], figsize=(10,10), s=100)

在这里插入图片描述

聚类评估:轮廓系数(Silhouette Coefficient )

s ( i ) = b ( i ) − a ( i ) m a x { a ( i ) , b ( i ) } s(i)=\frac{b(i)-a(i)}{max\{a(i),b(i)\}} s(i)=max{a(i),b(i)}b(i)a(i)

s ( i ) = { 1 − a ( i ) b ( i ) , a ( i ) < b ( i ) 0 , a ( i ) = b ( i ) b ( i ) a ( i ) − 1 , a ( i ) > b ( i ) s(i)= \left\{\begin{matrix} 1-\frac{a(i)}{b(i)}, & a(i)<b(i) \\ 0, & a(i)=b(i) \\ \frac{b(i)}{a(i)}-1, & a(i)>b(i) \end{matrix}\right. s(i)=1b(i)a(i),0,a(i)b(i)1,a(i)<b(i)a(i)=b(i)a(i)>b(i)

  • 计算样本i到同簇其他样本的平均距离ai。ai 越小,说明样本i越应该被聚类到该簇。将ai 称为样本i的簇内不相似度。

  • 计算样本i到其他某簇Cj 的所有样本的平均距离bij,称为样本i与簇Cj 的不相似度。定义为样本i的簇间不相似度:bi =min{bi1, bi2, …, bik}

  • Si接近1,则说明样本i聚类合理

  • Si接近-1,则说明样本i更应该分类到另外的簇

  • 若Si 近似为0,则说明样本i在两个簇的边界上

K-means聚类算法结果评估
from sklearn import metrics
# 计算得分值(标准化数据)
score_scaled = metrics.silhouette_score(X,beer.scaled_cluster)
# 计算得分值(未做标准化数据)
score = metrics.silhouette_score(X,beer.cluster)
# 打印比较
print(score_scaled, score)
0.179780680894 0.673177504646

看起来聚类效果并不是很好,而且做完标准化后的数据在聚类任务上表现得并不好,原因可能就是在原始数据中不同的特征对聚类的影响度不同,在标准化后打破了各个特征的权重,所以在对数据预处理上,要根据数据的实际情况再进行操作

查看一下不同的 K 系数对聚类效果的影响:

# 算轮廓系数得分
scores = []
for k in range(2,20):
    labels = KMeans(n_clusters=k).fit(X).labels_
    score = metrics.silhouette_score(X, labels)
    scores.append(score)

scores
[0.69176560340794857,
 0.67317750464557957,
 0.58570407211277953,
 0.42254873351720201,
 0.4559182167013377,
 0.43776116697963124,
 0.38946337473125997,
 0.39746405172426014,
 0.33061511213823314,
 0.34131096180393328,
 0.34597752371272478,
 0.31221439248428434,
 0.30707782144770296,
 0.31834561839139497,
 0.28495140011748982,
 0.23498077333071996,
 0.15880910174962809,
 0.084230513801511767]

对输出的得分进行可视化展示

plt.plot(list(range(2,20)), scores)
plt.xlabel("Number of Clusters Initialized")
plt.ylabel("Sihouette Score")

在这里插入图片描述

DBSCAN聚类算法结果评估
# 算轮廓系数得分
labels_ = DBSCAN(eps=10, min_samples=2).fit(X).labels_
score = metrics.silhouette_score(X,labels_)
print(score)
0.49530955296776086
  • 8
    点赞
  • 66
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值