DBSCAN(Density-Based Spatial Clustering of Applications with Noise)聚类算法,是一种基于高密度连通区域的、基于密度的聚类算法,能够将具有足够高密度的区域划分为簇(Cluster),并在具有噪声的数据中发现任意形状的簇。DBSCAN算法通过距离定义出一个密度函数,计算出每个样本附近的密度,从而根据每个样本附近的密度值来找出那些样本相对比较集中的区域,这些区域就是我们要找的簇。
DBSCAN算法的基本原理其它聚类方法大都是基于对象之间的距离进行聚类,聚类结果是球状的簇。DBSCAN 算法利用簇的高密度连通性,寻找被低密度区域分离的高密度区域,可以发现任意形状的簇,其基本思想是:对于一个簇中的每个对象,在其给定半径的领域中包含的对象不能少于某一给定的最小数目。DBSCAN算法中有两个重要参数:ε表示定义密度时的邻域半径,M 表示定义核心点时的阈值。考虑数据集合X={x(1),x(2),…,x(n)}X={x(1),x(2),…,x(n)}
,引入以下概念与记号。
-
ε邻域 设x∈X,称Nε(x)={y∈X:d(y,x)≤ε}为X的ε邻域。显然,x∈Nε(x)。 为了简单起见,节点x(i)与其下标i一一对应,引入记号
Nε(i)={j:d(y(j),x(i))≤ε;y(j),x(i)∈X} -
密度 设x∈X,称ρ(x)=|Nε(x)|为x的密度。密度是一个整数,且依赖于半径ε。
-
核心点 设x∈X,若ρ(x)≥M,则称x为X的核心点。记由X中所有核心点构成的集合为Xc,并记Xnc=X−Xc表示X中所有非核心点构成的集合。
-
边界点 若x∈Xncx∈Xnc4. 边界点 若x∈Xnc,且∃y∈X∃,满足y∈Nε(x)⋂Xc,即X的非核心点x的ε邻域中存在核心点,则称x为X的边界点。记由X中所有边界点构成的集合为Xbd
。 此外,边界点也可以这么定义,若x∈Xnc,且x落在某个核心点的ε邻域内,则称x为X
的边界点。一个边界点可能同时落入一个或多个核心点的ε邻域内。 -
噪声点 记Xnoi=X−(Xc⋃Xbd),若x∈Xnoi,则称x为噪音点。 至此,我们严格给出了核心点、边界点和噪音点的数学定义,且满足X=Xc⋃Xbd⋃Xnoi
核心点、边界点和噪声点直观地说,核心点对应稠密区域内部的点,边界点对应稠密区域边缘的点,而噪音点对应稀疏区域中的点。数据集通过聚类形成的子集是簇。核心点位于簇的内部,它确定无误地属于某个特定的簇;噪音点是数据集中的干扰数据,它不属于任何一个簇;边界点是一类特殊的点,它位于一个或几个簇的边缘地带,可能属于一个簇,也可能属于另外一个簇,其归属并不明确。
- 直接密度可达设x,y∈X. 若满足x∈Xc,则称y是x从直接密度可达的。
- 密度可达设p(1),p(2),…,p(m)∈X,其中m≥2。若它们满足:p(i+1)是从p(i)直接密度可达的,其中i=1,2,…,m−1,则称p(m)是从p(1)中密度可达的。
- 当m=2,密度可达即为直接密度可达。实际上,密度可达是直接密度可达的传递闭包。
密度可达关系不具有对称性。若p(m)是从p(1)密度可达的,那么p(1)不一定是从p(m)
密度可达的。根据上述定义可知,p(1),p(2),…,p(m−1)必须为核心点,而p(m)可以是核心点,也可以是边界点。当p(m)是边界点时,p(1)一定不是从p(m)密度可达的。 - 密度相连设x,y,z∈X,若y和z均是从x密度可达的,则称y和z是密度相连的。显然,密度相连具有对称性。
- 簇(cluster) 非空集合C⊂X,如果C满足:对于x,y∈X,若x∈C,且y是从x密度可达的,则y∈C,若x∈C,y∈C,则x,y是密度相连的。 则称C是X的一个簇。
1、 DBSCAN 算法基于以下一个基本事实:对于任一核心点xx,数据集X中所有从x 密度可达的数据点可以构成一个完整的簇C,且x∈C。其核心思想描述如下:从某个选定的核心点出发,不断向密度可达的区域扩张,从而得到一个包含核心点和边界点的最大化区域,区域中任意两点密度相连。
2. DBSCAN算法的实现《数据挖掘概念与技术》给出的算法伪代码如下:考虑数据集合X={x(1),x(2),…,x(n)}。DBSCAN算法的目标是将数据集合X分成K个簇及噪声点集合,其中K也是由算法得到,为此,引入簇的标记数组mi={j,−1,若x(i)属于第j个簇;若x(i)为噪声点mi={j,若x(i)属于第j个簇;−1,若x(i)为噪声点。
为了大家更直观的看懂原理:附一个视频连接https://www.naftaliharris.com/blog/visualizing-dbscan-clustering/
DBSCAN算法的目标就是生成标记数组mi,i=1,…,n. 为了保证可以更有效地实现算法1中第3句随机选择一个unvisited对象p,设计了一个数据结构visitlist,其中包含两个列表visitedlist和unvisitedlist,分别用于存储已访问的点和未访问的点,每次从unvisitedlist 中取点可以保证每次取到的点都是未访问过的点,实现代码如下:代码1:visitlist数据结构
# visitlist类用于记录访问列表
# unvisitedlist记录未访问过的点
# visitedlist记录已访问过的点
# unvisitednum记录访问过的点数量
class Visitlist:
def __init__(self, count=0):
self.unvisitedlist=[i for i in range(count)]
self.visitedlist=list()
self.unvisitednum=count
def visit(self, pointId):
self.visitedlist.append(pointId)
self.unvisitedlist.remove(pointId)
self.unvisitednum -= 1
import numpy as np
import matplotlib.pyplot as plt
import math
import random
def dist(a, b):
# 计算a,b两个元组的欧几里得距离
return math.sqrt(np.power(a - b, 2).sum())
def my_dbscanl(dataSet, eps, minPts):
# numpy.ndarray的 shape属性表示矩阵的行数与列数
nPoints = dataSet.shape[0]
# (1)标记所有对象为unvisited
# 在这里用一个类vPoints进行买现
vPoints = Visitlist(count = nPoints)
# 初始化簇标记列表C,簇标记为 k
k = -1
C = [-1 for i in range(nPoints)]
while (vPoints.unvisitednum > 0):
# (3)随机上选择一个unvisited对象p
p = random.choice(vPoints.unvisitedlist)
# (4)标记p为visited
vPoints.visit(p)
# (5)if p的$\varepsilon$-邻域至少有MinPts个对象
# N是p的$\varepsilon$-邻域点列表
N = [i for i in range(nPoints) if dist(dataSet[i], dataSet[p]) <= eps]
if len(N) >= minPts:
# (6)创建个新簇C,并把p添加到C
# 这里的C是一个标记列表,直接对第p个结点进行赋植
k += 1
C[p] = k
# (7)令N为p的ε-邻域中的对象的集合
# N是p的$\varepsilon$-邻域点集合
# (8) for N中的每个点p'
for p1 in N:
# (9) if p'是unvisited
if p1 in vPoints.unvisitedlist:
# (10)标记p’为visited
vPoints.visit(p1)
# (11) if p'的$\varepsilon$-邻域至少有MinPts个点,把这些点添加到N
# 找出p'的$\varepsilon$-邻域点,并将这些点去重添加到N
M = [i for i in range(nPoints) if dist(dataSet[i],dataSet[p1]) <= eps]
if len(M) >= minPts:
for i in M:
if i not in N:
N.append(i)
# (12) if p'还不是任何簇的成员,把P'添加到C
# C是标记列表,直接把p'分到对应的簇里即可
if C[p1] == -1:
C[p1] = k
# (15)else标记p为噪声
else:
C[p] = -1
# (16)until没有标t己为unvisitedl内对象
return C
代码
import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
plt.rcParams['font.sans-serif'] = [u'SimHei']
plt.rcParams['axes.unicode_minus'] = False
#X1, Y1 = datasets.make_circles(n_samples=2000, factor=0.6, noise=0.05,random_state=1)
X1, Y1 = datasets.make_blobs(n_samples=50, n_features=2, centers=[[5,5]],
cluster_std=[[0.1]], random_state=5)
X2, Y2 = datasets.make_blobs(n_samples=50, n_features=2, centers=[[5.5,5.5]],
cluster_std=[[0.1]], random_state=5)
X = np.concatenate((X1, X2))
plt.figure(figsize=(12, 9), dpi=80)
plt.scatter(X[:,0], X[:,1], marker='.')
plt.title("原始数据集")
plt.show()
算法的思想与实现进行了简略摘要,是学习算法的一个过程。算法的学习还比较粗劣和浅层,在实践应用中上述代码并不实用。如果需要使用DBSCAN的算法求解聚类问题,建议使用sklearn自带的DBSCAN函数。以代码3中生成数据为例: 1 # DBSCAN eps = 0.1, MinPts = 10
import time
start = time.time()
C1 = my_dbscanl(X, 0.1, 10)
end = time.time()
print ("`运行时间`:", end - start)
plt.scatter(X[:, 0], X[:, 1], c=C1, marker='.')
plt.title("DSCAN算法聚类")
plt.show()