K-Means算法和 Mean Shift算法都是基于距离的聚类算法,基于距离的聚类算法的聚类结果是球状的簇,当数据集中的聚类结果是非球状结构时,基于距离的聚类算法的聚类效果并不好。
与基于距离的聚类算法不同的是,基于密度的聚类算法可以发现任意形状的聚类。在基于密度的聚类算法中,通过在数据集中寻找被低密度区域分离的高密度区域,将分离出的高密度区域作为一个独立的类别。DBSCAN(Density-Based Spatial Clustering of Application with Noise)是一种典型的基于密度的聚类算法。
DBSCAN(Density-Based Spatial Clustering of Applications with Noise,具有噪声的基于密度的聚类方法)是一种基于密度的空间聚类算法。该算法将具有足够密度的区域划分为簇,并在具有噪声的空间数据库中发现任意形状的簇,它将簇定义为密度相连的点的最大集合。
在DBSCAN算法中将数据点分为三类:
在这里有两个量,一个是半径Eps( ),另一个是指定的数目MinPts。
在DBSCAN算法中,还定义了如下一些概念:
基于密度的聚类算法通过寻找被低密度区域分离的高密度区域,并将高密度区域作为一个聚类的“簇”。在DBSCAN算法中,聚类“簇”定义为:由密度可达关系导出的最大的密度连接样本的集合。
在DBSCAN算法中,有核心对象出发,找到与该核心对象密度可达的所有样本形成“簇”。DBSCAN算法的流程为:
伪代码:
(1) 首先将数据集D中的所有对象标记为未处理状态 (2) for(数据集D中每个对象p) do (3) if (p已经归入某个簇或标记为噪声) then (4) continue; (5) else (6) 检查对象p的Eps邻域 NEps(p) ; (7) if (NEps(p)包含的对象数小于MinPts) then (8) 标记对象p为边界点或噪声点; (9) else (10) 标记对象p为核心点,并建立新簇C, 并将p邻域内所有点加入C (11) for (NEps(p)中所有尚未被处理的对象q) do (12) 检查其Eps邻域NEps(q),若NEps(q)包含至少MinPts个对象,则将NEps(q)中未归入任何一个簇的对象加入C; (13) end for (14) end if (15) end if (16) end for
Python实现:
# -*- coding: utf-8 -*- import numpy as np def distance(data): '''计算样本点之间的距离 :param data(mat):样本 :return:dis(mat):样本点之间的距离 ''' m, n = np.shape(data) dis = np.mat(np.zeros((m, m))) for i in range(m): for j in range(i, m): # 计算i和j之间的欧式距离 tmp = 0 for k in range(n): tmp += (data[i, k] - data[j, k]) * (data[i, k] - data[j, k]) dis[i, j] = np.sqrt(tmp) dis[j, i] = dis[i, j] return dis def find_eps(distance_D, eps): '''找到距离≤eps的样本的索引 :param distance_D(mat):样本i与其他样本之间的距离 :param eps(float):半径的大小 :return: ind(list):与样本i之间的距离≤eps的样本的索引 ''' ind = [] n = np.shape(distance_D)[1] for j in range(n): if distance_D[0, j] <= eps: ind.append(j) return ind def dbscan(data, eps, MinPts): '''DBSCAN算法 :param data(mat):需要聚类的数据集 :param eps(float):半径 :param MinPts(int):半径内最少的数据点数 :return: types(mat):每个样本的类型:核心点、边界点、噪音点 sub_class(mat):每个样本所属的类别 ''' m = np.shape(data)[0] # 在types中,1为核心点,0为边界点,-1为噪音点 types = np.mat(np.zeros((1, m))) sub_class = np.mat(np.zeros((1, m))) # 用于判断该点是否处理过,0表示未处理过 dealt = np.mat(np.zeros((m, 1))) # 计算每个数据点之间的距离 dis = distance(data) # 用于标记类别 number = 1 # 对每一个点进行处理 for i in range(m): # 找到未处理的点 if dealt[i, 0] == 0: # 找到第i个点到其他所有点的距离 D = dis[i,] # 找到半径eps内的所有点 ind = find_eps(D, eps) # 区分点的类型 # 边界点 if len(ind) > 1 and len(ind) < MinPts + 1: types[0, i] = 0 sub_class[0, i] = 0 # 噪音点 if len(ind) == 1: types[0, i] = -1 sub_class[0, i] = -1 dealt[i, 0] = 1 # 核心点 if len(ind) >= MinPts + 1: types[0, i] = 1 for x in ind: sub_class[0, x] = number # 判断核心点是否密度可达 while len(ind) > 0: dealt[ind[0], 0] = 1 D = dis[ind[0],] tmp = ind[0] del ind[0] ind_1 = find_eps(D, eps) if len(ind_1) > 1: # 处理非噪音点 for x1 in ind_1: sub_class[0, x1] = number if len(ind_1) >= MinPts + 1: types[0, tmp] = 1 else: types[0, tmp] = 0 for j in range(len(ind_1)): if dealt[ind_1[j], 0] == 0: dealt[ind_1[j], 0] = 1 ind.append(ind_1[j]) sub_class[0, ind_1[j]] = number number += 1 # 最后处理所有未分类的点为噪音点 ind_2 = ((sub_class == 0).nonzero())[1] for x in ind_2: sub_class[0, x] = -1 types[0, x] = -1 return types, sub_class
MinPts
这个参数建议根据数据量及具体的业务进行自行设定
Eps
《Python机器学习算法》这本书上给出了一个计算公式,但是没有解释中间的原因,并不清楚理论依据是什么,算法如下:
def epsilon(data, MinPts): '''计算最佳半径 input: data(mat):训练数据 MinPts(int):半径内的数据点的个数 output: eps(float):半径 ''' m, n = np.shape(data) xMax = np.max(data, 0) xMin = np.min(data, 0) eps = ((np.prod(xMax - xMin) * MinPts * math.gamma(0.5 * n + 1)) / (m * math.sqrt(math.pi ** n))) ** (1.0 / n) return eps
其他参考资料:
主要函数介绍:
DBSCAN(eps=0.5, min_samples=5, metric='euclidean', algorithm='auto', leaf_size=30, p=None, n_jobs=1)
核心参数:
其他参数:
属性:
使用示例:
import numpy as np from sklearn.cluster import DBSCAN import matplotlib.pyplot as plt from math import radians, sin, cos, asin, sqrt def haversine(latlon1, latlon2): """ 计算两经纬度之间的距离 """ if (latlon1 - latlon2).all(): lat1, lon1 = latlon1 lat2, lon2 = latlon2 lon1, lat1, lon2, lat2 = map(radians, [lon1, lat1, lon2, lat2]) dlon = lon2 - lon1 dlat = lat2 - lat1 a = sin(dlat / 2) ** 2 + cos(lat1) * cos(lat2) * sin(dlon / 2) ** 2 c = 2 * asin(sqrt(a)) r = 6370996.81 # 地球半径 distance = c * r else: distance = 0 return distance if __name__ == "__main__": data = [] f = open("k_means_sample_data.txt", 'r') for line in f: data.append([float(line.split(',')[0]), float(line.split(',')[1])]) data = np.array(data) MinPts = int(data.shape[0] / 100) eps = 2000 db = DBSCAN(eps=eps, min_samples=MinPts, metric=haversine).fit(data) core_samples_mask = np.zeros_like(db.labels_, dtype=bool) core_samples_mask[db.core_sample_indices_] = True labels = db.labels_ n_clusters_ = len(set(labels)) - (1 if -1 in labels else 0) unique_labels = set(labels) colors = ['r', 'b', 'g', 'y', 'c', 'm', 'orange'] for k, col in zip(unique_labels, colors): if k == -1: col = 'k' class_member_mask = (labels == k) xy = data[class_member_mask & core_samples_mask] plt.plot(xy[:, 0], xy[:, 1], 'o', markerfacecolor=col, markeredgecolor='w', markersize=10) xy = data[class_member_mask & ~core_samples_mask] plt.plot(xy[:, 0], xy[:, 1], 'o', markerfacecolor=col, markeredgecolor='w', markersize=3) plt.title('Estimated number of clusters: %d' % n_clusters_) plt.show()
执行结果:
优点:
缺点:
The post 聚类算法之DBSCAN appeared first on 标点符.
Related posts: