密度聚类算法

基于密度的聚类算法假设样本结构能够通过样本分布的紧密程度而决定,以数据集在空间内分布的稠密程度为依据进行聚类,即只要一个区域中的样本密度大于某个阈值,就把它划入与之相近的簇中。

密度聚类可以克服K-means,BIRCH算法只适用于凸样本的缺点,密度聚类算法既可以适用于凸样本集也可以用于非凸样本集。

常见的密度聚类算法有:DBSCAN、MDCA、OPTICS、DENCLUE等。

密度聚类算法的主要特点:

  1. 对噪声数据不敏感
  2. 发现任意形状的簇
  3. 一次扫描
  4. 需要密度参数来作为算法停止的条件
  5. 计算量大、复杂度高

密度空间(epsilon-neighborhood)

有半径为e且含有n个点的neighborhood,密度等于包含点的个数/空间的大小,也就是

瞎聊机器学习——DBSCAN算法_聚类算法

(当然这个区域也可以是其他形状),

通过计算某一小区域内的密度,经过横向对比可以得知整个区域的密度分布,由此相近的点可以聚类到同一区域中。

DBSCAN算法(Density-Based Spatial Clustering of Applications with Noise)

DBSCAN算法是基于一组邻域参数(

瞎聊机器学习——DBSCAN算法_邻域_02

,MinPts)来描述样本分布的紧密程度(

瞎聊机器学习——DBSCAN算法_邻域_02

描述了某一样本的邻域距离的阈值,MinPts描述了某一样本的距离为

瞎聊机器学习——DBSCAN算法_邻域_02

的邻域中样本个数的阈值),相对于基于划分的聚类算法和层次聚类算法,DBSCAN算法将簇定义为密度相连的样本的最大集合,能够将密度足够高的区域划分为簇,不需要给定簇的数量,并可以在有噪声的空间数据集中发现任意形状的簇。

基本概念

     给定数据集

瞎聊机器学习——DBSCAN算法_DBSCAN_05

  1. -邻域(Eps):对,其-邻域包含D中与距离不大于的样本子集,即,这个样本的子集个数记为
  2. MinPts:-邻域内样本个数的最小值。
  3. 核心对象:若的-邻域至少包含MinPts个样本,即,则为一个核心对象。
  4. 密度直达:如果位于的-邻域中,且是核心对象,则称由密度直达。
  5. 密度可达:对于和,若存在样本序列p1,p2,...,pn,其中p1=,pn=,p1,p2,...,pn-1均为核心对象且pi+1从pi密度直达,则称由密度可达。密度可达关系满足直递性,但不满足对称性。
  6. 密度相连:对与,若存在使得与均由密度可达,则称与密度相连。密度相连关系满足对称性。

从下图可以很容易看出理解上述定义,图中MinPts=5,红色的点都是核心对象,因为其ϵ-邻域至少有5个样本。黑色的样本是非核心对象。所有核心对象密度直达的样本在以红色核心对象为中心的超球体内,如果不在超球体内,则不能密度直达。图中用绿色箭头连起来的核心对象组成了密度可达的样本序列。在这些密度可达的样本序列的ϵϵ-邻域内所有的样本相互都是密度相连的。

瞎聊机器学习——DBSCAN算法_聚类算法_06

算法思想

上一节中我们给出了有关DBSCAN的一些基本概念,本节来讲述一下该算法的主要思想。

DBSCAN的聚类定义很简单:由密度可达关系导出的最大密度相连的样本集合,即为我们最终聚类的一个类别,或者说一个簇。

这个DBSCAN的簇里面可以有一个或者多个核心对象。如果只有一个核心对象,则簇里其他的非核心对象样本都在这个核心对象的ϵϵ-邻域里;如果有多个核心对象,则簇里的任意一个核心对象的ϵϵ-邻域中一定有一个其他的核心对象,否则这两个核心对象无法密度可达。这些核心对象的ϵϵ-邻域里所有的样本的集合组成的一个DBSCAN聚类簇。

那么怎么才能找到这样的簇样本集合呢?DBSCAN使用的方法很简单,它任意选择一个没有类别的核心对象作为种子,然后找到所有这个核心对象能够密度可达的样本集合,即为一个聚类簇。接着继续选择另一个没有类别的核心对象去寻找密度可达的样本集合,这样就得到另一个聚类簇。一直运行到所有核心对象都有类别为止。

基本上这就是DBSCAN算法的主要内容了,是不是很简单?但是我们还是有三个问题没有考虑。

第一个是一些异常样本点或者说少量游离于簇外的样本点,这些点不在任何一个核心对象周围,在DBSCAN中,我们一般将这些样本点标记为噪音点。

第二个是距离的度量问题,即如何计算某样本和核心对象样本的距离。在DBSCAN中,一般采用最近邻思想,采用某一种距离度量来衡量样本距离,比如欧式距离。这和KNN分类算法的最近邻思想完全相同。对应少量的样本,寻找最近邻可以直接去计算所有样本的距离,如果样本量较大,则一般采用KD树或者球树来快速的搜索最近邻。

第三种问题比较特殊,某些样本可能到两个核心对象的距离都小于ϵϵ,但是这两个核心对象由于不是密度直达,又不属于同一个聚类簇,那么如果界定这个样本的类别呢?一般来说,此时DBSCAN采用先来后到,先进行聚类的类别簇会标记这个样本为它的类别。也就是说DBSCAN的算法不是完全稳定的算法。

DBSCAN的算法流程

在进行DBSCAN算法之前首先我们要确定该算法需要的两个参数:

(1)epsilon:在一个点周围邻近区域的半径
(2)minPts:邻近区域内至少包含点的个数

根据以上两个参数,结合epsilon-neighborhood的特征,可以把样本中的点分成三类:

核点(core point):满足NBHD(p,epsilon)>=minPts,则为核样本点。
边缘点(border point):NBHD(p,epsilon)<minPts,但是该点可由一些核点获得(density-reachable或者directly-reachable)
离群点(Outlier):既不是核点也不是边缘点,则是不属于这一类的点。
(3)任意选择一个点(既没有指定到一个类也没有特定为离群点),计算它的NBHD(p,epsilon)判断是否为核点。如果是,在该点周围建立一个类,否则,设定为外围点。
(4)遍历其他点,直到建立一个类。把directly-reachable的点加入到类中,接着把density-reachable的点也加进来。如果标记为离群点的点被加进来,修改状态为边缘点。
(5)重复步骤3和4,直到所有的点满足在类中(核点或边缘点)或者为离群点。

DBSCAN算法的代码实现

​点击获取数据集​​  

# coding=utf-8
import numpy as np
from scipy.spatial.distance import cdist
import matplotlib.pyplot as plt
import seaborn as sns
sns.set()

from sklearn.cluster import DBSCAN
from sklearn.preprocessing import StandardScaler
import pandas as pd

data = pd.read_csv("C:\\Users\\EDZ\\Desktop\\wholesale.csv")
data.drop(["Channel", "Region"], axis=1, inplace=True)

data = data[["Grocery", "Milk"]]
data = data.as_matrix().astype("float32", copy=False)#convert to array

#数据预处理,特征标准化,每一维是零均值和单位方差
stscaler = StandardScaler().fit(data)
data = stscaler.transform(data)

#画出x和y的散点图
plt.scatter(data[:, 0], data[:, 1])
plt.xlabel("Groceries")
plt.ylabel("Milk")
plt.title("Wholesale Data - Groceries and Milk")
plt.savefig("wholesale.png", format="PNG")

dbsc = DBSCAN(eps=0.5, min_samples=15).fit(data)

labels = dbsc.labels_ #聚类得到每个点的聚类标签 -1表示噪点
#print(labels)
core_samples = np.zeros_like(labels, dtype=bool) #构造和labels一致的零矩阵,值是false
core_samples[dbsc.core_sample_indices_] = True
#print(core_samples)


unique_labels = np.unique(labels)
colors = plt.cm.Spectral(np.linspace(0, 1, len(unique_labels))) #linespace返回在【0,1】之间均匀分布数字是len个,Sepectral生成len个颜色


#print(zip(unique_labels,colors))
for (label, color) in zip(unique_labels, colors):
class_member_mask = (labels == label)
print(class_member_mask&core_samples)
xy = data[class_member_mask & core_samples]
plt.plot(xy[:, 0], xy[:, 1], 'o', markerfacecolor=color, markersize=10)

xy2 = data[class_member_mask & ~core_samples]
plt.plot(xy2[:, 0], xy2[:, 1], 'o', markerfacecolor=color, markersize=5)
plt.title("DBSCAN on Wholsesale data")
plt.xlabel("Grocery (scaled)")
plt.ylabel("Milk (scaled)")
plt.savefig("(0.9,15)dbscan_wholesale.png", format="PNG")

聚类后的效果图如下:

瞎聊机器学习——DBSCAN算法_DBSCAN_07

随着epsilon和Minpts值得不断变化所得到的效果图也有所变化,这里不再一一展示。

DBSCAN的优缺点

  优点:

                不需要事先给定簇的数目k;

                适于稠密的非凸数据集,可以发现任意形状的簇;

                可以在聚类时发现噪音点、对数据集中的异常点不敏感;

                对样本输入顺序不敏感。

        缺点:

                对于高维数据效果不好;

                不适于数据集中样本密度差异很小的情况;

                调参复杂,给定eps选择过大的MinPts会导致核心对象数量减少,使得一些包含对象较少的自然簇被丢弃;

                选择过小的MinPts会导致大量对象被标记为核心对象,从而将噪声归入簇;

                给定MinPts选择过小的eps会导致大量的对象被误标为噪声,一个自然簇被误拆为多个簇;

                选择过大的eps则可能有很多噪声被归入簇,而本应分离的若干自然簇也被合并为一个簇;

                数据量很大时算法收敛的时间较长,可对搜索最近邻时建立的KD-tree或Ball-tree进行规模限制。