一:DBSCAN算法
(1)快速入门BCSCAN算法
如下是一组未被聚类的数据
可以用肉眼进行大致划分
这种非凸数据是无法用K-Means算法进行区分的,如果使用则效果会是这样
而本节的DBSCAN算法可以解决这个问题。DBSCAN算法通过密度来识别簇
核心点是DBSCAN算法中非常重要的点,一个点是否能够成为核心点关键在于该点周围的临近点个数是否大于等于某个
r
r
r值(称为半径,需要自己提前设定)
- 例如下图,定义这个
r
r
r值为4,那么第1-4个点可以作为核心点,但第5-6个点不可以
接着,我们挑选这组数据的所有核心点和非核心点
然后在核心点中随机选取一个并标记为绿色,然后把该绿色点周围的所有核心点都加入到该簇中,也标记为绿色
然后对其他绿色点执行重复操作
在处理时,如果同时喷到了核心点和非核心点,则暂时只把核心点加入其中
依据这个策略,进行扩展
然后处理非核心点。如下对于这个非核心点,其周围存在属于第一个簇的核心点,所以他可以并入
但是该非核心点上面的那个点现在就不可以并入了,或者说这就是它的边界
按照这个规则把所有非核心点并入,第一个簇完毕
(2)算法简介
A:基本概念
基本概念:DBSCAN全称为Density-Based Spatial Clustering of Applications with Noise,也即基于密度的带有噪声点的聚类方法。该算法将具有足够密度的区域划分为簇,并在具有噪声的空间数据中发现任意形状的簇,它将簇定义为密度相连的点的最大集合;在DBSCAN算法中将数据点分为以下3类
- 核心点: 如果一个对象在其半径
Eps 内含有超过MinPts 数目的点,则该对象为核心点 - 边界点: 如果一个对象在其半径
Eps 内含有点的数量小于MinPts ,但是该对象落在核心点的邻域内,则该对象为边界点 - 噪声点: 如果一个对象既不是核心点也不是边界点,则该对象为噪声点
其中Eps 和MinPtS 是DBSCAN算法中的两个重要参数
Eps :定义密度时的邻域半径MinPts :定义核心点时的阈值
如下图所示,假设MinPts 设置为5,箭头表示Eps 范围,则:
-
A
A
A是核心点:因为在其Eps邻域内有7个点
-
B
B
B是边界点:因为它落在了
A
A
A的Eps邻域内
-
C
C
C是噪声点:没有落在任何核心点的邻域内
B:直接密度可达、密度可达、密度相连
直接密度可达:如果点
p
p
p在核心点
q
q
q的
E
p
s
Eps
Eps邻域内,则称数据对象
p
p
p从数据对象
q
q
q出发是直接密度可达的
- 注意是
p
p
p直接可达
q
q
q,但不能是
q
q
q直接可达
p
p
p,除非
q
q
q也是核心点
密度可达:如果存在数据对象链(
p
1
p_{1}
p1?,
p
2
p_{2}
p2?, … ,
p
n
p_{n}
pn? ,
p
n
+
1
p_{n+1}
pn+1?),其中
p
i
+
1
p_{i+1}
pi+1?是从
p
i
p_{i}
pi?关于
E
p
s
Eps
Eps和
M
i
n
P
t
s
MinPts
MinPts直接密度可达的,则称数据对象
p
n
p_{n}
pn?是从数据对象
p
1
p_{1}
p1?关于
E
p
s
Eps
Eps和
M
i
n
P
t
s
MinPts
MinPts密度可达的
密度相连:若从某核心点
p
p
p出发,点
q
q
q和点
k
k
k都是密度可达的,那么
q
q
q和
k
k
k就是密度相连的
如下图:
- 左图:点
a
a
a为核心点,点
b
b
b为边界点,所以
a
a
a直接密度可达
b
b
b(但
b
b
b不直接可达
a
a
a,
b
b
b不是核心点)
- 右图:
c
c
c直接密度可达
a
a
a,
a
a
a直接密度可达
b
b
b,所以
c
c
c密度可达
b
b
b
(3)算法流程
A:流程
DBSCAN输入为
- 参数
D
D
D:输入的数据集
- 参数
ε
\varepsilon
ε:邻域半径
- 参数
M
i
n
P
t
s
MinPts
MinPts:密度阈值
其中
ε
\varepsilon
ε和
M
i
n
P
t
s
MinPts
MinPts这两个参数的选择非常重要,选择时可以参考如下方法,但是在实际使用时通常时多次尝试即可
-
ε
\varepsilon
ε:可以根据距
K
K
K距离来设定,找出 突变点(
K
K
K距离:对于给定的数据集
P
P
P=
{
p
1
,
p
2
,
.
.
.
,
p
n
}
\{p_{1}, p_{2} , ... , p_{n}\}
{p1?,p2?,...,pn?},计算点
p
(
i
)
p(i)
p(i)到数据集
D
D
D的子集
S
S
S中所有点之间的距离,距离按从小到大的顺序排序,则
d
(
k
)
d(k)
d(k)就称之为
K
距
离
K距离
K距离)
-
M
i
n
P
t
s
MinPts
MinPts:一般情况下小一点合适
算法流程如下,整体来说还是很容易理解的
B:演示
现在以下面的样本数据为例,演示DBSCAN算法工作流程
-
ε
\varepsilon
ε= 3
-
M
i
n
P
t
s
MinPts
MinPts = 3
步骤①: 顺序扫描数据集样本点,首先取到
p
1
(
1
,
2
)
p1(1, 2)
p1(1,2)
- 计算每一点到
p
1
p1
p1的距离,例如
d
(
p
1
,
p
2
)
=
1.414
d(p1, p2)=1.414
d(p1,p2)=1.414
- 根据每个样本点到
p
1
p1
p1的距离和所设邻域
ε
\varepsilon
ε= 3,得到
p
1
p1
p1的
E
p
s
Eps
Eps邻域为
{
p
1
,
p
2
,
p
3
,
p
13
}
\{p1, p2, p3, p13\}
{p1,p2,p3,p13}
- 所设
M
i
n
p
t
s
Minpts
Minpts=3,而
p
1
p1
p1的
E
p
s
Eps
Eps邻域有4个点,所以
p
1
p1
p1为核心点
- 因此以
p
1
p1
p1为核心点建立簇
C
1
C_{1}
C1?,找出所有从
p
1
p1
p1密度可达的点(
p
1
p1
p1邻域内的点都是
p
1
p1
p1直接密度可达的点,所以都属于
C
1
C_{1}
C1?)
-
p
2
p2
p2的邻域为
(
p
1
,
p
2
,
p
3
,
p
4
,
p
13
)
(p1, p2, p3, p4, p13)
(p1,p2,p3,p4,p13),且
p
1
p1
p1直接密度可达
p
2
p2
p2、
p
2
p2
p2直接密度可达
p
4
p4
p4,所以
p
1
p1
p1密度可达
p
4
p4
p4,因此
p
4
p4
p4也属于
C
1
C_{1}
C1?
-
p
3
p3
p3、
p
4
p4
p4、
p
13
p13
p13也是核心点,其其邻域的点已经都在
C
1
C_{1}
C1?中了
- 于是,以
p
1
p1
p1为核心点出发的那些密度可达的对象全部处理完毕,得到簇
C
1
C_{1}
C1?,包含对象
(
p
1
,
p
2
,
p
3
,
p
4
,
p
13
)
(p1, p2, p3, p4, p13)
(p1,p2,p3,p4,p13)
步骤②: 继续顺序扫描数据样本点,取到
p
5
(
5
,
8
)
p5(5, 8)
p5(5,8)
- 计算
p
5
p5
p5邻域,其
E
p
s
Eps
Eps邻域为
(
p
5
,
p
6
,
p
7
,
p
8
)
(p5, p6, p7, p8)
(p5,p6,p7,p8),由于有4个点,所以
p
5
p5
p5是核心点
- 以
p
5
p5
p5为核心点建立簇
C
2
C_{2}
C2?,找出所有从
p
5
p5
p5密度可达的点,得到簇
C
2
C_{2}
C2?,包含点
(
p
5
,
p
6
,
p
7
,
p
8
)
(p5, p6, p7, p8)
(p5,p6,p7,p8)
步骤③: 继续顺序扫描数据集样本点取到
p
9
(
9
,
5
)
p9(9, 5)
p9(9,5)
-
p
9
p9
p9其
E
p
s
Eps
Eps邻域内只有一个点,所以不是核心点
步骤④: 继续顺序扫描数据集样本点取到
p
10
(
1
,
12
)
p10(1, 12)
p10(1,12)
(4)Python实现
A:算法代码
import random
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
def dbscan(data_set, eps, min_pts):
examples_nus = np.shape(data_set)[0]
unvisited = [i for i in range(examples_nus)]
visited = []
cluster = [-1 for i in range(examples_nus)]
k = - 1
while len(unvisited) > 0:
p = random.choice(unvisited)
unvisited.remove(p)
visited.append(p)
nighbor = []
for i in range(examples_nus):
if i != p and np.sqrt(np.sum(np.power(data_set[i, :]-data_set[p, :], 2))) <= eps:
nighbor.append(i)
if len(nighbor) >= min_pts:
k = k+1
cluster[p] = k
for pi in nighbor:
if pi in unvisited:
unvisited.remove(pi)
visited.append(pi)
nighbor_pi = []
for j in range(examples_nus):
if np.sqrt(np.sum(np.power(data_set[j]-data_set[pi], 2))) <= eps and j != pi:
nighbor_pi.append(j)
if len(nighbor_pi) >= min_pts:
for t in nighbor_pi:
if t not in nighbor:
nighbor.append(t)
if cluster[pi] == -1:
cluster[pi] = k
else:
cluster[p] = -1
return cluster
B:可视化输出代码
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
from DBSCAN import dbscan
data_types = [0.0000000e+00, 1.0000000e+00, 2.0000000e+00, 3.00E+00]
raw_data = pd.read_csv('./threecircles.csv', header=None)
raw_data.columns = ['X', 'Y', 'Type']
x_axis = 'X'
y_axis = 'Y'
examples_num = raw_data.shape[0]
train_data = raw_data[[x_axis, y_axis]].values.reshape(examples_num, 2)
def select_minpts(data_set, k):
k_dist = []
for i in range(data_set.shape[0]):
dist = (((data_set[i] - data_set)**2).sum(axis=1)**0.5)
dist.sort()
k_dist.append(dist[k])
return np.array(k_dist)
eps = 0.07
min_pts = 4
cluster = dbscan(train_data, eps, min_pts)
print(cluster)
plt.figure(figsize=(15, 7), dpi=80)
plt.subplot(1, 2, 1)
for data_types_index in data_types:
plt.scatter(raw_data[x_axis][raw_data['Type'] == data_types_index], raw_data[y_axis][raw_data['Type'] == data_types_index], label=str(data_types_index))
plt.title('label know')
plt.legend()
plt.subplot(1, 2, 2)
class1_X = []
class1_Y = []
class2_X =[]
class2_Y =[]
class3_X =[]
class3_Y =[]
noise_X = []
noise_Y = []
for index, value in enumerate(cluster):
if value == 0:
class1_X.append(raw_data[x_axis][index])
class1_Y.append(raw_data[y_axis][index])
elif value == 1:
class2_X.append(raw_data[x_axis][index])
class2_Y.append(raw_data[y_axis][index])
elif value == 2:
class3_X.append(raw_data[x_axis][index])
class3_Y.append(raw_data[y_axis][index])
elif value == -1:
noise_X.append(raw_data[x_axis][index])
noise_Y.append(raw_data[y_axis][index])
plt.scatter(class1_X, class1_Y, c='g', label='class1')
plt.scatter(class2_X, class2_Y, c='r', label='class2')
plt.scatter(class3_X, class3_Y, c='blue', label='class3')
plt.scatter(noise_X, noise_Y, c='black', label='noise')
plt.title('DBSCAN')
plt.legend()
plt.show()
(5)聚类效果展示
①:鸢尾花数据集
②:三个圆圈数据集(K-Means实现效果差)
③:笑脸数据集(K-Means实现效果差)
④:人造数据集
- 438个数据,分为三类(提前不知道标签)
eps = 1.8min_pts = 4
⑤:西瓜数据集
二:DBSCAN算法探讨
(1)算法优缺点
A:优点
①:可以对任意形状的稠密数据集进行聚类,相对的,K-Means之类的聚类算法一般只适用于凸数据集
②:可以在聚类的同时发现异常点,对数据集中的异常点不敏感
③:不需要指定簇数,并且多次实验结果往往是相同的
B:缺点
①:如果样本集的密度不均匀、聚类间距差相差很大时,聚类质量较差,这时用DBSCAN聚类一般不适合
②:调参相较于K-Means复杂一点,不同参数组合对聚类效果有很多影响
(2)参数选择
A:参数选择原则(快速)
min_pts :一般选择维度+1或者维度×2即可eps :如果所分的类少了,那就把eps调小一点;如果所分的类多了,那就把eps调大一点
B:调参确定
前面说过,eps 可以根据K-dist 图(K距离图)确定。这里的K-dist 是指,从某个点
P
P
P到第
K
K
K个与它最近的点的距离的值,然后将这些距离从小到大排序后绘图,称为K-dist图,其中拐点位置即为eps 的值
调参步骤如下
- 确定K值,一般取2*维度-1
- 绘制
K-dist 图 - 拐点位置(波动最大的地方)为
eps (可以取多个拐点尝试) min_pts 可以取K+1
k = 3
k_dist = select_minpts(train_data, k)
k_dist.sort()
plt.plot(np.arange(k_dist.shape[0]), k_dist[::-1])
plt.show()
如下eps 建议取0.0581
|