DBSCAN

(Density-Based Spatial Clustering of Applications with Noise)

与划分和层次聚类方法不同,它将簇定义为密度相连的点的最大集合,能够把具有足够高密度的区域划分为簇,并可在噪声的空间数据库中发现任意形状的聚类

算法流程

选取一个点,以eps为半径,画一个圈,看圈有几个临近点 若临近点个数大于某个阈值(min_points) ,则认为该点为某一簇的点; 若小于min_points,则被标记为噪声点,然后处理下一个点 将临近点作为种子点 seeds = [4,7,9,10 ] 遍历所有种子点 ​ 若该点被标记为噪声点 ,则重标为聚类点 ​ 若该点没有被标记过,则标记为聚类点 ​ 并且以该点为圆心,以eps为半径画圈,若圈内点大于min_points,则将圈内点添加到种子点中 ​ seeds = [4,7,9,10,1,7,9,16] 重复步骤2,直到遍历完所有的种子点 seeds = [4,7,9,10, 1,7,9,16] #第一次步骤2,标记4 ​ 7的周围有12,4,小于min_points,seeds不扩展 seeds = [4,7,9,10, 1,7,9,16] #第二次步骤2,标记7 ​ 9的周围有1,4,3,大于min_points,seeds扩展 seeds = [4,7,9,10, 1,7,9,16,1,4,3] #第三次步骤2,标记9 ​ 10的周围有1,6,7,大于min_points,seeds扩展 seeds = [4,7,9,10, 1,7,9,16,1,4,3,1,6,7] #第四次步骤2,标记10 ​ 1已经标记过,继续下一个点 seeds = [4,7,9,10, 1,7,9,16,1,4,3,1,6,7] ​ 7已经标记过,继续下一个点 seeds = [4,7,9,10, 1,7,9,16,1,4,3,1,6,7] ​ 9已经标记过,继续下一个点 seeds = [4,7,9,10, 1,7,9,16,1,4,3,1,6,7] ​ 16的周围有12,4,小于min_points,seeds不扩展 seeds = [4,7,9,10, 1,7,9,16,1,4,3,1,6,7] . . . 重复进行画圈、标记 标记完一簇后

寻找一个未被标记的点,开始新一轮的聚类

​ 如果找到点5,周围点少,标记为NOISE

​ 如果找到点15,周围点少,标记为NOISE

​ 如果找到点19,开始新一轮聚类

.

.

.

最终效果

举例

import numpy as np

import matplotlib.pyplot as plt

from sklearn.datasets import make_blobs

from sklearn.preprocessing import StandardScaler

from sklearn.cluster import DBSCAN

from sklearn import metrics

UNCLASSIFIED = 0 #点未被标记

NOISE = -1 #噪声点标记

# 计算数据点两两之间的距离

def getDistanceMatrix(datas): #datas 是聚类数据

N,D = np.shape(datas) #读取datas的维度,维度是N x D(N指数据个数,D指特征维度) ,shape函数用于获取矩阵的形状

dists = np.zeros([N,N]) #zeros 函数:返回一个给定形状和类型的用0填充的数组,

for i in range(N):

for j in range(N):

vi = datas[i,:] #切片 [开始,结束]

vj = datas[j,:]

dists[i,j]= np.sqrt(np.dot((vi-vj),(vi-vj))) #欧式距离函数,返回点与点之间距离的数组

return dists

# 寻找以点cluster_id 为中心,eps 为半径的圆内的所有点的id

def find_points_in_eps(point_id, eps, dists):

index = dists[point_id] <= eps #dists[point_id] 即:point_id 与 所有点的距离

return np.where(index)[0].tolist() #返回所有符合的点的集合

# 聚类扩展

# dists : 任意数据两两之间的距离 N x N

# labs : 所有数据的标签 labs N,

# cluster_id : 一个簇的标号

# eps : 密度评估半径

# seeds: 用来进行簇扩展的点

# min_points: 半径内最少的点数

def expand_cluster(dists, labs, cluster_id, seeds, eps, min_points):

i = 0

while i < len(seeds):

Pn = seeds[i] #获取一个点

if labs[Pn] == NOISE: #如果是噪声点,则重新标记

labs[Pn] = cluster_id

elif labs[Pn] == UNCLASSIFIED: #如果未被标记过,则进行标记

labs[Pn] = cluster_id

new_seeds = find_points_in_eps(Pn, eps, dists) # 以新点为圆心再画圈,进行扩展

if len(new_seeds) >= min_points: #如果扩张的圈中数够多,则加入到seeds队列中

seeds = seeds + new_seeds

i += 1

#通过挨个标记和扩展seeds里的数字,实现聚类过程

def dbscan(datas, eps, min_points):

dists = getDistanceMatrix(datas) #获取点与点之间的距离,且以二维数组的形式

# 将所有点的标签初始化为0

n_points = datas.shape[0] #shape[0]指读取读取矩阵第一维的长度

labs = [UNCLASSIFIED] * n_points

cluster_id = 0

# 遍历所有点

for point_id in range(n_points):

if labs[point_id] != UNCLASSIFIED: #如果被标记,则结束此次循环,表示该点已处理过

continue #没有处理过,则计算寻找临近点

seeds = find_points_in_eps(point_id, eps, dists) #符合条件的点存到seeds中

if len(seeds) < min_points: # 如果临近点过少,则标记为噪声点

labs[point_id] = NOISE

else: #否则开启新一轮扩张

cluster_id = cluster_id + 1

labs[point_id] = cluster_id #标记当前点

expand_cluster(dists, labs, cluster_id, seeds, eps, min_points)

return labs, cluster_id

# 绘图

# 数据 聚类结果 聚类个数

def draw_cluster(datas,labs, n_cluster):

plt.cla()

#设计颜色

colors = [plt.cm.Spectral(each)

for each in np.linspace(0, 1,n_cluster)] #(起点,终点,几个元素)

#遍历所有点的坐标

for i,lab in enumerate(labs):

if lab ==NOISE: #如果是噪声点 则为黑色

plt.scatter(datas[i,0],datas[i,1],s=16.,color=(0,0,0))

else: #否则,根据类别的编号,来标记颜色

plt.scatter(datas[i,0],datas[i,1],s=16.,color=colors[lab-1])

plt.show()

if __name__== "__main__":

## 数据1

centers = [[1, 1], [-1, -1], [1, -1]] #三个中心点的坐标

#datas为样本数据集,labels_true为样本数据集的标签

datas, labels_true = make_blobs(n_samples=750, centers=centers, cluster_std=0.4,

random_state=0)

#产生一组随机数datas,中心点是centers,方差是0.4,产生750个点

## 数据2

#file_name = "spiral"

#with open(file_name+".txt","r",encoding="utf-8") as f:

# lines = f.read().splitlines()

#lines = [line.split("\t")[:-1] for line in lines]

#datas = np.array(lines).astype(np.float32)

###

# 数据正则化,让参与的数据减去均值出方差,是临均值,标准差成了1

datas = StandardScaler().fit_transform(datas) #计算训练数据的均值和方差,并基于计算出来的均值和方差来转换训练数据,从而把数据转换成标准的正态分布

eps = 0.3

min_points = 10

#手动实现DBSCAN

#dbscan算法,labs是最终结果,cluster_id是分成了多少类

labs, cluster_id = dbscan(datas, eps=eps, min_points=min_points)

print("labs of my dbscan")

print(labs)

#sklearn里的DBSCAN 算法

#分类器 # 半径 min_points 对datas进行聚类

db = DBSCAN(eps=eps, min_samples=min_points).fit(datas)

skl_labels = db.labels_

print("labs of sk-DBSCAN")

print(skl_labels)

#画出

draw_cluster(datas,labs, cluster_id)

# dbscan 输出,123表示聚类点,-1表示噪声点

# sklearn 输出 012表示聚类点,-1表示噪声点

查看原文