python中dbscan和kmeans_聚类——K-Means与DBSCAN原理和应用(附Python代码)

1 K-Means原理

K-Means算法是一种基于距离的聚类算法,K-Means算法的思想很简单,对于给定的样本集,按照样本之间的距离大小,将样本集划分为K个簇。让簇内的点尽量紧密的连在一起,而让簇间的距离尽量的大。假设有训练样本

,每个

,但没有类标签

,K-Means算法描述如下:K-Means算法描述

2 K-Means中 K值的选择

“手肘法”是利用误差平方和(sum of the squared errors,SSE)的变化趋势来作为选择K值的指标。

其中

是第i个簇,p是

中的样本点,

的质心,SSE是所有样本的聚类误差,可以表示聚类效果的好坏。举一个例子:

# 手肘法选择K值

# 生成随机点数据

X, y = make_blobs(n_samples=400, centers=4, random_state=23, cluster_std=0.8)

SSE = [] #存放每次的误差平方和

for k in range(1,9):

estimator = KMeans(n_clusters=k)

estimator.fit(X)

SSE.append(estimator.inertia_)

x = range(1,9)

plt.figure(figsize=(5,5))

plt.xlabel('k')

plt.ylabel('SSE')

plt.plot(x,SSE,'o-')

plt.show()“手肘法”例子

随着聚类数K的增大,样本划分会更加精细,每个簇的聚合程度会逐渐提高,那么误差平方和SSE自然会逐渐变小。并且,当K小于真实聚类数时,由于K的增大会大幅增加每个簇的聚合程度,故SSE的下降幅度会很大,而当K到达真实聚类数时,再增加K所得到的聚合程度回报会迅速变小,所以SSE的下降幅度会骤减,然后随着K值的继续增大而趋于平缓,也就是说SSE和K的关系图是一个手肘的形状,这也是该方法被称为手肘法的原因,而这个肘部对应的K值就是数据的真实聚类数,这里可以看出K等于4的时候最好。聚类效果如下:

3 DBSCAN原理

基于距离的聚类算法比如K-Means有一个缺陷,就是对噪声特别敏感,而且往往聚类簇的形状是球状簇。而DBSCAN(Density-Based Spatial Clustering of Application with Noise)算法是一种基于密度的算法,它可以发现任意形状的聚类,这对于带有噪音点的数据起着重要的作用。

该算法涉及以下几个概念: -邻域:对

-邻域,包含样本集D中与

的距离不大于

的样本,即

核心对象:若

-邻域至少包括MinPts个样本,即

,则

为一个核心对象。

密度直达:若

位于

-邻域中,且

是核心对象,则称

密度直达。

密度可达:对

若存在样本序列

,其中

密度直达,则称

密度可达。

密度相连:对

若存在

使得

,均由

密度可达,则称

密度相连。

基于这些概念,DBSCAN将“簇”定义为:由密度可达关系导出的最大的密度相连的样本集合。形式化地说,给定邻域参数

,簇

是满足以下性质的非空样本集:连接性:

密度相连。

最大性:

密度可达

4 二维数据聚类

4.1 K-means聚类实例

实验中,采用随机函数生成了4簇高斯样本点,共400个,样本中心分别为[-7,-2.5],[-8,5],[0,7],[5,0],使用K-Means进行聚类,当数据标准差为0.8时,得到的结果如图1所示,聚类评价如表1所示,聚类评价如表1。图1 样本点标准差为0.8的聚类效果图表1 样本点标准差为0.8的聚类评价

Python代码:

centers = np.array([[-7,-2.5],[-8,5],[0,7],[5,0]])

X, y = make_blobs(n_samples=400, centers=centers, random_state=23, cluster_std=0.8)

estimator = KMeans(init='random',n_clusters=4, algorithm='full')

y_pred = estimator.fit_predict(X)

clusters_centers = estimator.cluster_centers_

# 校正标签

y_pred = unify_label(clusters_centers, centers, centers.shape[0], y_pred)

performance_evaluation(X, y, y_pred)

plt.figure(figsize=(10,5))

plt.subplot(121)

plt.scatter(X[:, 0], X[:, 1], s=20, c=y, marker='o')

plt.title("origin clusters (cluster_std:{})".format(str(cluster_std)))

plt.subplot(122)

plt.scatter(X[:, 0], X[:, 1], s=20, c=y_pred, marker='o')

plt.scatter(clusters_centers[:, 0], clusters_centers[:, 1], s=90, c='r', marker='*')

plt.title("KMeans clusters")

plt.show()

4.2 噪声对聚类的影响

K-Means一个很大的缺陷就是对噪声或者离群点比较敏感,它无法区分出哪些是噪声或者离群点,只能给每一个数据点都判断出一个类别来,这样就会导致样本质心偏移,导致误判或者聚类紧密程度降低。于是本小节实验中,在标准差为1的4簇高斯样本点(400个)中添加了20个噪声点。实验对比了有噪声和无噪声情况下聚类结果,结果如图2所示,聚类评价如表2所示。图2 有、无噪声点下聚类效果图表2 有、无噪声下的聚类评价

DBSCAN是一种基于密度的算法,它能够将噪声点找出来,所以在抗干扰能力上具有一定优势,其聚类结果如图3所示,其中紫色的点被标为噪声点:图3 噪声情况下DBSCAN分类效果图

小结:结果很明显,加了噪声以后,噪声点也被加上了标签,导致聚类评价下降。一方面表现在,噪声点的标签是错的,另一方面表现在现有簇的紧密程度很低,这也是聚类效果不好的一种表现。DBSCAN由于其基于密度的算法的优势,对于噪声点不那么敏感。

Python代码:

# 生成随机点数据

centers = np.array([[-7,-2.5],[-8,5],[0,7],[5,0]])

X, y = make_blobs(n_samples=400, centers=4, random_state=23, cluster_std=1)

# 聚类

estimator = KMeans(init='random',n_clusters=4, algorithm='full')

y_pred = estimator.fit_predict(X)

clusters_centers = estimator.cluster_centers_

# 校正标签

y_pred = unify_label(clusters_centers, centers, centers.shape[0], y_pred)

# 评估

performance_evaluation(X, y, y_pred)

plt.figure(figsize=(15,5))

plt.subplot(131)

plt.scatter(X[:, 0], X[:, 1], s=20, c=y_pred, marker='o')

plt.scatter(clusters_centers[:, 0], clusters_centers[:, 1], s=90, c='r', marker='*')

plt.title("clusters without noise")

# 生成[-5,10)的随机噪声点

noise = 15*np.random.random_sample((20,2))-5

noise_class = np.random.randint(4, size=20)

# 合并数据

X = np.r_[X, noise]

y = np.r_[y, noise_class]

# 聚类

estimator = KMeans(init='random',n_clusters=4, algorithm='full')

y_pred = estimator.fit_predict(X)

clusters_centers = estimator.cluster_centers_

# 校正标签

y_pred = unify_label(clusters_centers, centers, centers.shape[0], y_pred)

# 评估

performance_evaluation(X, y, y_pred)

plt.subplot(132)

plt.scatter(X[:, 0], X[:, 1], s=20, c=y_pred, marker='o')

#plt.scatter(noise[:, 0], noise[:, 1], s=10, c='k', marker='s')

plt.scatter(clusters_centers[:, 0], clusters_centers[:, 1], s=90, c='r', marker='*')

plt.title("clusters with noise")

# DBSCAN算法

X = StandardScaler().fit_transform(X)

db = DBSCAN(eps=0.2).fit(X)

core_samples_mask = np.zeros_like(db.labels_, dtype=bool)

core_samples_mask[db.core_sample_indices_] = True

labels = db.labels_

plt.subplot(133)

plt.scatter(X[:, 0], X[:, 1], s=20, c=labels, marker='o')

plt.title("DBSCAN clusters")

plt.show()

4.3 样本点形状对聚类的影响

K-Means算法对于凸性数据具有良好的效果,能够根据距离来讲数据分为球状类的簇,但对于非凸形状的数据点,就无能为力了,比如环形数据等等,此时基于密度的算法DBSCAN就更令人满意了。图4和图5两个例子。图4 月形数据聚类图5 环形数据聚类

Python代码:

# 生成月亮形状的随机点数据

X, y = make_moons(n_samples=400, noise=0.05, random_state=23)

# 生成圆形的随机点数据

#X, y = make_circles(n_samples=400, noise=0.05, factor=0.5, random_state=23

# 聚类

estimator = KMeans(init='random',n_clusters=2, algorithm='full')

y_pred = estimator.fit_predict(X)

clusters_centers = estimator.cluster_centers_

plt.figure(figsize=(10,5))

plt.subplot(121)

plt.scatter(X[:, 0], X[:, 1], s=20, c=y_pred, marker='o')

plt.title("k-means clusters")

X = StandardScaler().fit_transform(X)

db = DBSCAN().fit(X)

core_samples_mask = np.zeros_like(db.labels_, dtype=bool)

core_samples_mask[db.core_sample_indices_] = True

labels = db.labels_

plt.subplot(122)

plt.scatter(X[:, 0], X[:, 1], s=20, c=labels, marker='o')

plt.title("DBSCAN clusters")

plt.show()

5 图像分割

图像分割是图像处理中的一种方法,图像分割是指将一幅图像分解成若干互不相交区域的集合,其实质可以看成是一种像素的聚类过程。

本实验将图A作为研究对象,在不同的K值下,使用K-Means进行聚类分析。每次聚类以后,都将同一类别下的像素点的RGB值设为该类质心取整后的值,图B-E分别展示了K=2,4,8,12时的聚类效果图。图A 原始图像图B K=2的聚类图像图C K=4的聚类图像图D K=8的聚类图像图E K=12的聚类图像

小结:随着K值的增大,图像分类越来越细致,值得注意的是,图片中左边女孩衣服上的口袋轮廓,直到K=12以后才比较明显。另外,对色彩的表达也更接近原图,但在处理阴影渐变的位置时就不那么精准了,比如女孩裙子下摆的位置等。

Python代码:

# RGB图像聚类

data, row, col = load_data("k-on.jpg")

for k in range(2,15):

t0 = time()

estimator = KMeans(n_clusters=k)

label = estimator.fit_predict(data)

centroids = estimator.cluster_centers_.astype('int')

pic_new = Image.new("RGB",(row, col))

for i in range(row):

for j in range(col):

# 像素点RGB设为质心

pic_new.putpixel((i, j), tuple(centroids[label[col*i+j]]))

print('K-Means: K = {} time: {}s'.format(k, (time()-t0)))

pic_new.save('kmeans_k_{}.jpg'.format(k))

display(Image.open('kmeans_k_{}.jpg'.format(k)))

参考

[1] 李航 《统计学习方法》

你可能感兴趣的:(python中dbscan和kmeans_聚类——K-Means与DBSCAN原理和应用(附Python代码))