1、背景介紹
密度峰值算法(Clustering by fast search and find of density peaks)由Alex Rodriguez和Alessandro Laio於2014年提出,並將論文發表在Science上。Science上的這篇文章《Clustering by fast search and find of density peaks》主要講的是一種基於密度的聚類方法,基於密度的聚類方法的主要思想是尋找被低密度區域分離的高密度區域。 密度峰值算法(DPCA)基於這樣的假設:(1)類簇中心點的密度大於周圍鄰居點的密度;(2)類簇中心點與更高密度點之間的距離相對較大。因此,DPCA主要有兩個需要計算的量:第一,局部密度;第二,與高密度點之間的距離。
2、局部密度
數據對象的局部密度
定義為:
其中,表示截斷距離
,這個公式的含義是說找到與第
個數據點之間的距離小於截斷距離
的數據點的個數,並將其作為第i個數據點真的密度。
3、定義聚類中心距離
密度峰聚類算法的巧妙之處:就是在於聚類中心距離 δi的選定。根據局部密度的定義,我們可以計算出上圖中每個點的密度,依照密度確定聚類中心距離 δi。
1.首先將每個點的密度從大到小排列: ρi > ρj > ρk > ….;密度最大的點的聚類中心距離與其他點的聚類中心距離的確定方法是不一樣的;
2.先確定密度最大的點的聚類中心距離–i點是密度最大的點,它的聚類中心距離δiδi等於與i點最遠的那個點n到點i的直線距離 d(i,n);
3. 再確定其他點的聚類中心距離——其他點的聚類中心距離是等於在密度大於該點的點集合中,與該點距離最小的的那個距離。例如i、j、k的密度都比n點的密度大,且j點離n點最近,則n點的聚類中心距離等於d(j,n);
4. 依次確定所有的聚類中心距離δ
4、聚類效果
將所有點的聚類中心密度都統計出來后,將其值按 δi和pi作為坐標軸作圖可以得到如圖所示結果。可以看到圖中1,10兩個聚類中心同時遠離坐標軸。普通點則是靠近p軸,異常點靠近 δ軸。
5、基於python的實現:
python代碼如下,其中要引入numpy等一些包,pycharm中引入包還是比較簡單的。
# -*- coding:utf-8 -*- # -*- python3.5 import numpy as np import matplotlib.pyplot as plt import sklearn.datasets as ds import matplotlib.colors min_distance = 4.6 # 鄰域半徑 points_number = 40 # 隨機點個數 # 計算各點間距離、各點點密度(局部密度)大小 def get_point_density(datas,labers,min_distance,points_number): # 將numpy.ndarray格式轉為list格式,並定義元組大小 data = datas.tolist() laber = labers.tolist() distance_all = np.random.rand(points_number,points_number) point_density = np.random.rand(points_number) # 計算得到各點間距離 for i in range(points_number): for n in range(points_number): distance_all[i][n] = np.sqrt(np.square(data[i][0]-data[n][0])+np.square(data[i][1]-data[n][1])) print('距離數組:\n',distance_all,'\n') # 計算得到各點的點密度 for i in range(points_number): x = 0 for n in range(points_number): if distance_all[i][n] > 0 and distance_all[i][n]< min_distance: x = x+1 point_density[i] = x print('點密度數組:', point_density, '\n') return distance_all, point_density # 計算點密度最大的點的聚類中心距離 def get_max_distance(distance_all,point_density,laber): point_density = point_density.tolist() a = int(max(point_density)) # print('最大點密度',a,type(a)) b = laber[point_density.index(a)] # print("最大點密度對應的索引:",b,type(b)) c = max(distance_all[b]) # print("最大點密度對應的聚類中心距離",c,type(c)) return c # 計算得到各點的聚類中心距離 def get_each_distance(distance_all,point_density,data,laber): nn = [] for i in range(len(point_density)): aa = [] for n in range(len(point_density)): if point_density[i] < point_density[n]: aa.append(n) # print("大於自身點密度的索引",aa,type(aa)) ll = get_min_distance(aa,i,distance_all, point_density,data,laber) nn.append(ll) return nn # 獲得:到點密度大於自身的最近點的距離 def get_min_distance(aa,i,distance_all, point_density,data,laber): min_distance = [] """ 如果傳入的aa為空,說明該點是點密度最大的點,該點的聚類中心距離計算方法與其他不同 """ if aa != []: for k in aa: min_distance.append(distance_all[i][k]) # print('與上各點距離',min_distance,type(nn)) # print("最小距離:",min(min_distance),type(min(min_distance)),'\n') return min(min_distance) else: max_distance = get_max_distance(distance_all, point_density, laber) return max_distance def get_picture(data,laber,points_number,point_density,nn): # 創建Figure fig = plt.figure() # 用來正常顯示中文標簽 matplotlib.rcParams['font.sans-serif'] = [u'SimHei'] # 用來正常顯示負號 matplotlib.rcParams['axes.unicode_minus'] = False # 原始點的分布 ax1 = fig.add_subplot(211) plt.scatter(data[:,0],data[:,1],c=laber) plt.title(u'原始數據分布') plt.sca(ax1) for i in range(points_number): plt.text(data[:,0][i],data[:,1][i],laber[i]) # 聚類后分布 ax2 = fig.add_subplot(212) plt.scatter(point_density.tolist(),nn,c=laber) plt.title(u'聚類后數據分布') plt.sca(ax2) for i in range(points_number): plt.text(point_density[i],nn[i],laber[i]) plt.show() def main(): # 隨機生成點坐標 data, laber = ds.make_blobs(points_number, centers=points_number, random_state=0) print('各點坐標:\n', data) print('各點索引:', laber, '\n') # 計算各點間距離、各點點密度(局部密度)大小 distance_all, point_density = get_point_density(data, laber, min_distance, points_number) # 得到各點的聚類中心距離 nn = get_each_distance(distance_all, point_density, data, laber) print('最后的各點點密度:', point_density.tolist()) print('最后的各點中心距離:', nn) # 畫圖 get_picture(data, laber, points_number, point_density, nn) """ 距離歸一化:就把上面的nn改為:nn/max(nn) """ if __name__ == '__main__': main()
代碼運行效果如下圖: