【無監督學習】3:Density Peaks聚類演算法實現(區域性密度聚類演算法)
前言:密度峰聚類演算法和DBSCAN聚類演算法有相似的地方,兩者都是基於密度的聚類方式。自己是在學習無監督學習過程中,無意間見到介紹這種聚類演算法的文章,感覺密度峰聚類演算法方法很新奇,操作也很簡答,於是自己也動手寫一下了。
–—-—-—-—-—-—-—-—-—-—-—-—–—-—-—-—-—-—-—-——-—-—-—-—-—-—-—-—-—-—-—-—-—-——-
–—-—-—-—-—-—-—-—-—-—-—-—–—-—-—-—-—-—-—-——-—-—-—-—-—-—-—-—-—-—-—-—-—-——-
聚類演算法主要包括哪些演算法?
主要包括:K-means,DBSCAN,Density Peaks聚類(區域性密度聚類),層次聚類,譜聚類。若按照聚類的方式可劃分成三類:第一類是類似於K-means,DBSCAN,Density Peaks聚類(區域性密度聚類)的依據密度
–—-—-—-—-—-—-—-—-—-—-—-—–—-—-—-—-—-—-—-——-—-—-—-—-—-—-—-—-—-—-—-—-—-——-
–—-—-—-—-—-—-—-—-—-—-—-—–—-—-—-—-—-—-—-——-—-—-—-—-—-—-—-—-—-—-—-—-—-——-
什麼是無監督學習?
- 無監督學習也是相對於有監督學習來說的,因為現實中遇到的大部分資料都是未標記的樣本,要想通過有監督的學習就需要事先人為標註好樣本標籤,這個成本消耗、過程用時都很巨大,所以無監督學習就是使用無標籤的樣本找尋資料規律的一種方法
- 聚類演算法就歸屬於機器學習領域下的無監督學習方法。
–—-—-—-—-—-—-—-—-—-—-—-—–—-—-—-—-—-—-—-——-—-—-—-—-—-—-—-—-—-—-—-—-—-——-
無監督學習的目的是什麼呢?
可以從龐大的樣本集合中選出一些具有代表性的樣本子集加以標註,再用於有監督學習-可以從無類別資訊情況下,尋找表達樣本集具有的特徵
–—-—-—-—-—-—-—-—-—-—-—-—–—-—-—-—-—-—-—-——-—-—-—-—-—-—-—-—-—-—-—-—-—-——-
–—-—-—-—-—-—-—-—-—-—-—-—–—-—-—-—-—-—-—-——-—-—-
分類和聚類的區別是什麼呢?
- 對於分類來說,在給定一個數據集,我們是事先已知這個資料集是有多少個種類的。比如一個班級要進行性別分類,我們就下意識清楚分為“男生”、“女生”兩個類;該班又轉入一個同學A,“男ta”就被分入“男生”類;
- 而對於聚類來說,給定一個數據集,我們初始並不知道這個資料集包含多少類,我們需要做的就是將該資料集依照某個“指標”,將相似指標的資料歸納在一起,形成不同的類;
- 分類是一個後續的過程,已知標籤資料,再將測試樣本分入同標籤資料集中;聚類是不知道標籤,將“相似指標”的資料強行“擼”在一起,形成各個類。
–—-—-—-—-—-—-—-—-—-—-—-—–—-—-—-—-—-—-—-——-—-—-—-—-—-—-—-—-—-—-—-—-—-——-
一、基於區域性密度聚類演算法——Density Peaks
1、背景介紹
Density Peaks聚類演算法是在2014年 6 月份,由Alex Rodriguez 和 Alessandro Laio 在 Science 上發表了一篇名為《Clustering by fast search and find of density peaks》的文章,這為聚類演算法的設計提供了一種新的思路。
雖然這個演算法從Science上發表後也受到爭議——部分學者覺得這篇思想簡單、操作方便的聚類演算法還達不到能在Science上發表的水平,這可能只是部分學者的口舌之爭吧。但值得一提的是,寫出Density Peaks聚類演算法的兩位科學家都不是研究數學,也不是研究演算法分析的,而是西班牙研究化學的科學家。論文主體中也引證了很多采用這個聚類演算法實現像人臉識別當前熱火的場景的結果,如有興趣,大家可以自己去細究一下。
2、定義區域性密度大小——
Density Peaks聚類演算法要是用文字描述是有一些費解的,我儘量用圖解釋一下:
下圖是一個樣本空間點的分佈圖,一共分佈著28個點:
- 事先給定一個鄰域半徑,定義任意i、j兩點的距離用表示;
- 表示i點的密度大小,i點的密度大小是如何確定的呢——以i點為圓心,包含在半徑大小為的圓內點的個數即為i點的密度大小(與DBSCAN密度確定方法相似);
- 數學公式如下右所示,即比較兩點距離與領域半徑的大小關係,小於表示在圓內,計數1,大於表示圓外,計數0,最後求和。
可得的結論:越大表示點i的區域性密度越大,越有可能成為聚類中心。
3、定義聚類中心距離——
密度峰聚類演算法的巧妙之處:就是在於聚類中心距離 的選定。
根據區域性密度的定義,我們可以計算出上圖中每個點的密度,依照密度確定聚類中心距離
1.首先將每個點的密度從大到小排列: > > > ….;密度最大的點的聚類中心距離與其他點的聚類中心距離的確定方法是不一樣的;
2.先確定密度最大的點的聚類中心距離–i點是密度最大的點,它的聚類中心距離等於與i點最遠的那個點n到點i的直線距離 ;
3. 再確定其他點的聚類中心距離——其他點的聚類中心距離是等於在密度大於該點的點集合中,與該點距離最小的的那個距離。例如i、j、k的密度都比n點的密度大,且j點離n點最近,則n點的聚類中心距離等於
4. 依次確定所有的聚類中心距離
聚類中心距離的數學式如下:(雖然我覺得這個數學式表達的不是很貼切)
4、決策圖確定聚類簇核心、簇邊緣
Density Peaks聚類演算法就是依據每個點的區域性密度大小、聚類中心距離的數值,組合(,)投射到二維座標系中。先上決策圖:
(這裡聚類中心距離經過歸一化處理,將①號點的聚類中心距離定為1)
從B圖中可以清楚的看出來:
分佈在右上角區域的是聚類的核心點:周圍密度很大,且沒有其他核心點;
分佈在靠近ρ軸的值是屬於正常值:密度雖然大,但是周圍有比它更合適作為核心點的點;
分佈在靠近δ軸的值是屬於噪聲點:周圍密度小,而且離其他點的距離還遠。
現實意義就是:北京聯合天津、廊坊等地構成帝都經濟群,上海聯合無錫、常州、蘇州構成長江三角洲經濟群,廣州深圳形成珠三角經濟群。北京、上海、廣州深圳相當於聚類核心點,引領發展;天津雖然也是現代化城市,但是由於它離北京很近,更多的資源會流向聚類核心點北京,所以天津只能作為正常點。
5、Density Peaks聚類演算法的意義
聚類演算法中最困惑的地方就是選定K值等於多少才算合適,Density Peaks聚類演算法給出了一種比較好的確定K值的方式:定義γi=ρi*δi,得到的乘積比較選取較大的K個點作為聚類中心。
原始碼:
注意:請修改第10、11行的設定引數
# -*- coding:utf-8 -*-
# -*- author:zzZ_CMing CSDN address:https://blog.csdn.net/zzZ_CMing
# -*- 2018/08/22;16:11
# -*- 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 = 5 # 隨機點個數
# 計算各點間距離、各點點密度(區域性密度)大小
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()
結果展示:
1、由於隨機生成的資料大多是分散的,所以得到的實驗效果不是很理想;
2、有這方面需求的夥伴們可以自己做拓展——將隨機資料生成改為引入自己在txt或excel中的資料做測試;
3、高維空間也是可以的,但時間複雜度和記憶體消耗我並沒有估計,改進也留給你們了。