dbscan算法python实现_挑子学习笔记:DBSCAN算法的python实现
DBSCAN(Density-Based Spatial Clustering of Applications with Noise)聚類(lèi)算法,是一種基于高密度連通區(qū)域的、基于密度的聚類(lèi)算法,能夠?qū)⒕哂凶銐蚋呙芏鹊膮^(qū)域劃分為簇(Cluster),并在具有噪聲的數(shù)據(jù)中發(fā)現(xiàn)任意形狀的簇。DBSCAN算法通過(guò)距離定義出一個(gè)密度函數(shù),計(jì)算出每個(gè)樣本附近的密度,從而根據(jù)每個(gè)樣本附近的密度值來(lái)找出那些樣本相對(duì)比較集中的區(qū)域,這些區(qū)域就是我們要找的簇。
1. DBSCAN算法的基本原理
其它聚類(lèi)方法大都是基于對(duì)象之間的距離進(jìn)行聚類(lèi),聚類(lèi)結(jié)果是球狀的簇。DBSCAN 算法利用簇的高密度連通性,尋找被低密度區(qū)域分離的高密度區(qū)域,可以發(fā)現(xiàn)任意形狀的簇,其基本思想是:對(duì)于一個(gè)簇中的每個(gè)對(duì)象,在其給定半徑的領(lǐng)域中包含的對(duì)象不能少于某一給定的最小數(shù)目。
DBSCAN算法中有兩個(gè)重要參數(shù):$\varepsilon$表示定義密度時(shí)的鄰域半徑,$M$ 表示定義核心點(diǎn)時(shí)的閾值。
考慮數(shù)據(jù)集合$X= \{x^{(1)}, x^{(2)},...,x^{(n)} \}$,引入以下概念與記號(hào)。
1. $\varepsilon$鄰域
設(shè)$x \in X$,稱(chēng)
\begin{equation*}
N_{\varepsilon}(x) = \{ y \in X: d(y,x) \le \varepsilon \}
\end{equation*}
為$X$的$\varepsilon$鄰域。顯然,$x \in N_{\varepsilon}(x)$。
為了簡(jiǎn)單起見(jiàn),節(jié)點(diǎn)$x^{(i)}$與其下標(biāo)$i$一一對(duì)應(yīng),引入記號(hào)
\begin{equation*}
N_{\varepsilon}(i) = \{ j: d(y^{(j)},x^{(i)}) \le \varepsilon; \; y^{(j)},x^{(i)} \in X \}
\end{equation*}
2. 密度
設(shè)$x \in X$,稱(chēng)$\rho(x) = |N_{\varepsilon}(x)|$為$x$的密度。密度是一個(gè)整數(shù),且依賴(lài)于半徑$\varepsilon$。
3. 核心點(diǎn)
設(shè)$x \in X$,若$\rho(x) \ge M$,則稱(chēng)$x$為$X$的核心點(diǎn)。記由$X$中所有核心點(diǎn)構(gòu)成的集合為$X_c$,并記$X_{nc}=X-X_c$表示$X$中所有非核心點(diǎn)構(gòu)成的集合。
4. 邊界點(diǎn)
若$x \in X_{nc}$,且$\exists y \in X$,滿足$y \in N_{\varepsilon}(x) \bigcap X_c$,即$X$的非核心點(diǎn)$x$的$\varepsilon$鄰域中存在核心點(diǎn),則稱(chēng)$x$ 為$X$的邊界點(diǎn)。記由$X$中所有邊界點(diǎn)構(gòu)成的集合為$X_{bd}$。
此外,邊界點(diǎn)也可以這么定義,若$x \in X_{nc}$,且$x$落在某個(gè)核心點(diǎn)的$\varepsilon$鄰域內(nèi),則稱(chēng)$x$為$X$的邊界點(diǎn)。一個(gè)邊界點(diǎn)可能同時(shí)落入一個(gè)或多個(gè)核心點(diǎn)的$\varepsilon$ 鄰域內(nèi)。
5. 噪聲點(diǎn)
記$X_{noi} = X - (X_c \bigcup X_{bd})$,若$x \in X_{noi}$,則稱(chēng)$x$為噪音點(diǎn)。
至此,我們嚴(yán)格給出了核心點(diǎn)、邊界點(diǎn)和噪音點(diǎn)的數(shù)學(xué)定義,且滿足$X = X_c \bigcup X_{bd} \bigcup X_{noi}$.
圖1:核心點(diǎn)、邊界點(diǎn)和噪聲點(diǎn)
直觀地說(shuō),核心點(diǎn)對(duì)應(yīng)稠密區(qū)域內(nèi)部的點(diǎn),邊界點(diǎn)對(duì)應(yīng)稠密區(qū)域邊緣的點(diǎn),而噪音點(diǎn)對(duì)應(yīng)稀疏區(qū)域中的點(diǎn)。
數(shù)據(jù)集通過(guò)聚類(lèi)形成的子集是簇。核心點(diǎn)位于簇的內(nèi)部,它確定無(wú)誤地屬于某個(gè)特定的簇;噪音點(diǎn)是數(shù)據(jù)集中的干擾數(shù)據(jù),它不屬于任何一個(gè)簇;邊界點(diǎn)是一類(lèi)特殊的點(diǎn),它位于一個(gè)或幾個(gè)簇的邊緣地帶,可能屬于一個(gè)簇,也可能屬于另外一個(gè)簇,其歸屬并不明確。
6. 直接密度可達(dá)
設(shè)$x,y \in X$. 若滿足$x \in X_c$,則稱(chēng)$y$是$x$從直接密度可達(dá)的。
7. 密度可達(dá)
設(shè)$p^{(1)}, p^{(2)},..., p^{(m)} \in X$,其中$m \ge 2$。若它們滿足:$p^ {(i+1)}$ 是從$p^{(i)}$直接密度可達(dá)的,其中$i = 1,2,...,m-1$,則稱(chēng)$p^ {(m)}$ 是從$p^{(1)}$ 中密度可達(dá)的。
7.1. 當(dāng)$m = 2$時(shí),密度可達(dá)即為直接密度可達(dá)。實(shí)際上,密度可達(dá)是直接密度可達(dá)的傳遞閉包。
7.2. 密度可達(dá)關(guān)系不具有對(duì)稱(chēng)性。若$p^{(m)}$是從$p^{(1)}$密度可達(dá)的,那么$p^ {(1)}$ 不一定是從$p^{(m)}$密度可達(dá)的。根據(jù)上述定義可知,$p^{(1)}, p^{(2)}, ..., p^{(m-1)}$必須為核心點(diǎn),而$p^{(m)}$可以是核心點(diǎn),也可以是邊界點(diǎn)。當(dāng)$p^ {(m)}$是邊界點(diǎn)時(shí),$p^{(1)}$一定不是從$p^{(m)}$密度可達(dá)的。
8. 密度相連
設(shè)$x,y,z \in X$,若$y$和$z$均是從$x$密度可達(dá)的,則稱(chēng)$y$和$z$是密度相連的。顯然,密度相連具有對(duì)稱(chēng)性。
9. 簇(cluster)
非空集合$C \subset X$,如果$C$滿足:對(duì)于$x,y \in X$
若$x \in C$,且$y$是從$x$密度可達(dá)的,則$y \in C$,
若$x \in C$,$y \in C$,則$x,y$是密度相連的。
則稱(chēng)$C$是$X$的一個(gè)簇。
DBSCAN 算法基于以下一個(gè)基本事實(shí):對(duì)于任一核心點(diǎn)$x$,數(shù)據(jù)集$X$中所有從$x$ 密度可達(dá)的數(shù)據(jù)點(diǎn)可以構(gòu)成一個(gè)完整的簇$C$,且$x \in C$。其核心思想描述如下:從某個(gè)選定的核心點(diǎn)出發(fā),不斷向密度可達(dá)的區(qū)域擴(kuò)張,從而得到一個(gè)包含核心點(diǎn)和邊界點(diǎn)的最大化區(qū)域,區(qū)域中任意兩點(diǎn)密度相連。
2.?DBSCAN算法的實(shí)現(xiàn)
《數(shù)據(jù)挖掘概念與技術(shù)》給出的算法偽代碼如下:
考慮數(shù)據(jù)集合$X= \{x^{(1)}, x^{(2)},..., x^{(n)} \}$。DBSCAN算法的目標(biāo)是將數(shù)據(jù)集合$X$分成$K$個(gè)簇及噪聲點(diǎn)集合,其中$K$也是由算法得到,為此,引入簇的標(biāo)記數(shù)組
\begin{equation*}
m_i =
\begin{cases}
j, & \text{若}x^{(i)}\text{屬于第}j\text{個(gè)簇}; \\
-1, & \text{若}x^{(i)}\text{為噪聲點(diǎn)}
\end{cases}
\end{equation*}
DBSCAN算法的目標(biāo)就是生成標(biāo)記數(shù)組$m_i, \; i=1,...,n$.
為了保證可以更有效地實(shí)現(xiàn)算法1中第3句隨機(jī)選擇一個(gè)unvisited對(duì)象$p$,設(shè)計(jì)了一個(gè)數(shù)據(jù)結(jié)構(gòu)visitlist,其中包含兩個(gè)列表visitedlist和unvisitedlist,分別用于存儲(chǔ)已訪問(wèn)的點(diǎn)和未訪問(wèn)的點(diǎn),每次從unvisitedlist 中取點(diǎn)可以保證每次取到的點(diǎn)都是未訪問(wèn)過(guò)的點(diǎn),實(shí)現(xiàn)代碼如下:
代碼1:visitlist數(shù)據(jù)結(jié)構(gòu)
1 #visitlist類(lèi)用于記錄訪問(wèn)列表
2 #unvisitedlist記錄未訪問(wèn)過(guò)的點(diǎn)
3 #visitedlist記錄已訪問(wèn)過(guò)的點(diǎn)
4 #unvisitednum記錄訪問(wèn)過(guò)的點(diǎn)數(shù)量
5 classvisitlist:6 def _init_(self, count=0):7 self.unvisitedlist=[i for i inrange(count)]8 self.visitedlist=list()9 self.unvisitednum=count10
11 defvisit(self, pointId):12 self.visitedlist.append(pointId)13 self.unvisitedlist.remove(pointId)14 self.unvisitednum -= 1
DBSCAN算法實(shí)現(xiàn)代碼如下:
代碼2:DBSCAN算法實(shí)現(xiàn)
1 importnumpy as np2 importmatplotlib.pyplot as plt3 importmath4 importrandom5
6 defdist(a, b):7 #計(jì)算a,b兩個(gè)元組的歐幾里得距離
8 return math.sqrt(np.power(a-b, 2).sum())9
10 defmy_dbscanl(dataSet, eps, minPts):11 #numpy.ndarray的 shape屬性表示矩陣的行數(shù)與列數(shù)
12 nPoints =dataSet.shape[0]13 #(1)標(biāo)記所有對(duì)象為unvisited
14 #在這里用一個(gè)類(lèi)vPoints進(jìn)行買(mǎi)現(xiàn)
15 vPoints = visitlist(count=nPoints)16 #初始化簇標(biāo)記列表C,簇標(biāo)記為 k
17 k = -1
18 C = [-1 for i inrange(nPoints)]19 while(vPoints.unvisitednum >0):20 #(3)隨機(jī)上選擇一個(gè)unvisited對(duì)象p
21 P =random.choice(vPoints.unvisitedlist)22 #(4)標(biāo)記p為visited
23 vPoints.visit(p)24 #(5)if p的$\varepsilon$-鄰域至少有MinPts個(gè)對(duì)象
25 #N是p的$\varepsilon$-鄰域點(diǎn)列表
26 N = [i for i in range(nPoints) if dist(dataSet[i], dataSet[p])<=eps]27 if len(N) >=minPts:28 #(6)創(chuàng)建個(gè)新簇C,并把p添加到C
29 #這里的C是一個(gè)標(biāo)記列表,直接對(duì)第p個(gè)結(jié)點(diǎn)進(jìn)行賦植
30 k += 1
31 C[p]=k32 #(7)令N為p的ε-鄰域中的對(duì)象的集合
33 #N是p的$\varepsilon$-鄰域點(diǎn)集合
34 #(8) for N中的每個(gè)點(diǎn)p'
35 for p1 inN:36 #(9) if p'是unvisited
37 if p1 invPoints.unvisitedlist:38 #(10)標(biāo)記p’為visited
39 vPoints.visit(p1)40 #(11) if p'的$\varepsilon$-鄰域至少有MinPts個(gè)點(diǎn),把這些點(diǎn)添加到N
41 #找出p'的$\varepsilon$-鄰域點(diǎn),并將這些點(diǎn)去重添加到N
42 M=[i for i in range(nPoints) ifdist(dataSet[i], \43 dataSet[p1]) <=eps]44 if len(M) >=minPts:45 for i inM:46 if i not inN:47 N.append(i)48 #(12) if p'還不是任何簇的成員,把P'添加到C
49 #C是標(biāo)記列表,直接把p'分到對(duì)應(yīng)的簇里即可
50 if C[p1] == -1:51 C[p1]=k52 #(15)else標(biāo)記p為噪聲
53 else:54 C[p]=-1
55
56 #(16)until沒(méi)有標(biāo)t己為unvisitedl內(nèi)對(duì)象
57 return C
利用sklearn生成數(shù)據(jù)集,共2500條數(shù)據(jù),并利用matplotlib畫(huà)出散點(diǎn)圖,代碼如下:
代碼3:生成數(shù)據(jù)集
1 importnumpy as np2 importmatplotlib.pyplot as plt3 from sklearn importdatasets4
5 X1, Y1 = datasets.make_circles(n_samples=2000, factor=0.6, noise=0.05,6 random_state=1)7 X2, Y2 = datasets.make_blobs(n_samples=500, n_features=2, centers=[[1.5,1.5]],8 cluster_std=[[0.1]], random_state=5)9
10 X =np.concatenate((X1, X2))11 plt.figure(figsize=(12, 9), dpi=80)12 plt.scatter(X[:,0], X[:,1], marker='.')13 plt.show()
圖2:數(shù)據(jù)集散點(diǎn)圖
設(shè)置參數(shù)Eps=0.1, MinPts=10,聚類(lèi)結(jié)果如下圖:
圖3:聚類(lèi)結(jié)果
3.?利用KD樹(shù)進(jìn)行優(yōu)化
KD樹(shù)(K-Dimensional Tree),是一種分割k維數(shù)據(jù)空間的數(shù)據(jù)結(jié)構(gòu),是二叉搜索樹(shù)在多維條件下的推廣。主要應(yīng)用于多維空間關(guān)鍵數(shù)據(jù)的搜索。KD樹(shù)的介紹見(jiàn):https://www.jianshu.com/p/ffe52db3e12b,不贅述。
利用scipy實(shí)現(xiàn)KD樹(shù)的構(gòu)造和查詢(xún),對(duì)代碼2的算法進(jìn)行改進(jìn),代碼如下:
代碼4:DBSCAN算法的優(yōu)化實(shí)現(xiàn)
1 importnumpy as np2 importmatplotlib.pyplot as plt3 importmath4 importrandom5 from scipy.spatial importKDTree6
7 def my-dbscan2(dataSet, eps, minPts):8 #numpy.ndarray的 shape屬性表示矩陣的行數(shù)與列數(shù)
9 #行數(shù)即表小所有點(diǎn)的個(gè)數(shù)
10 nPoints =dataSet.shape[0]11 #(1) 標(biāo)記所有對(duì)象為unvisited
12 #在這里用一個(gè)類(lèi)vPoints進(jìn)行實(shí)現(xiàn)
13 vPoints = visitlist(count=nPoints)14 #初始化簇標(biāo)記列表C,簇標(biāo)記為 k
15 k = -1
16 C = [-1 for i inrange(nPoints)]17 #構(gòu)建KD-Tree,并生成所有距離<=eps的點(diǎn)集合
18 kd =KDTree(X)19 while(vPoints.unvisitednum>0):20 #(3) 隨機(jī)選擇一個(gè)unvisited對(duì)象p
21 p =random.choice(vPoints.unvisitedlist)22 #(4) 標(biāo)t己p為visited
23 vPoints.visit(p)24 #(5) if p 的$\varepsilon$-鄰域至少有MinPts個(gè)對(duì)象
25 #N是p的$\varepsilon$-鄰域點(diǎn)列表
26 N =kd.query_ball_point(dataSet[p], eps)27 if len(N) >=minPts:28 #(6) 創(chuàng)建個(gè)一個(gè)新簇C,并把p添加到C
29 #這里的C是一個(gè)標(biāo)記列表,直接對(duì)第p個(gè)結(jié)點(diǎn)進(jìn)行賦值
30 k += 1
31 C[p] =k32 #(7) 令N為p的$\varepsilon$-鄰域中的對(duì)象的集合
33 #N是p的$\varepsilon$-鄰域點(diǎn)集合
34 #(8) for N中的每個(gè)點(diǎn)p'
35 for p1 inN:36 #(9) if p'是unvisited
37 if p1 invPoints.unvisitedlist:38 #(10) 標(biāo)記p'為visited
39 vPoints.visit(p1)40 #(11) if p'的$\varepsilon$-鄰域至少有MinPts個(gè)點(diǎn),把這些點(diǎn)添加到N
41 #找出p'的$\varepsilon$-鄰域點(diǎn),并將這些點(diǎn)去重新添加到N
42 M =kd.query_ball_point(dataSet[p1], eps)43 if len(M) >=minPts:44 for i inM:45 if i not inN:46 N.append(i)47 #(12) if p'還不是任何簇的成員,把p'添加到c
48 #C是標(biāo)記列表,直接把p'分到對(duì)應(yīng)的簇里即可
49 if C[p1] == -1
50 C[p1] =k51 #(15) else標(biāo)記p為噪聲
52 else:53 C[p1] = -1
54
55 #(16) until沒(méi)有標(biāo)記為unvisited的對(duì)象
56 return C
以代碼3中生成的2500條數(shù)據(jù)作為測(cè)試,比較優(yōu)化前后的算法性能
1 importtime2 start =time.time()3 C1 = my_dbscanl(X, 0.1, 10)4 end =time.time()5 print "`運(yùn)行時(shí)間`:", end -start6 plt.scatter(X[:, 0], X[:, 1], c=C1, marker='.')7 plt.show()8 >>> `運(yùn)行時(shí)間:`29.1249849796
圖4:優(yōu)化前算法結(jié)果
1 importtime2 start =time.time()3 C2 = my_dbscan2(X, 0.1, 10)4 end =time.time()5 print "運(yùn)行時(shí)間:", end -start6 plt.scatter(X[:, 0], X[:, 1], c=C2, marker='.')7 plt.show()8 >>> 運(yùn)行時(shí)間:4.72340583801
圖5:優(yōu)化后算法結(jié)果
可以看到優(yōu)化后的算法運(yùn)行時(shí)間從29.12s降到了4.72s,優(yōu)化的效果非常明顯。
4.?后記
上文僅僅是對(duì)DBSCAN算法的思想與實(shí)現(xiàn)進(jìn)行了簡(jiǎn)略摘要,是學(xué)習(xí)算法的一個(gè)過(guò)程。算法的學(xué)習(xí)還比較粗劣和淺層,在實(shí)踐應(yīng)用中上述代碼并不實(shí)用。如果需要使用DBSCAN的算法求解聚類(lèi)問(wèn)題,建議使用sklearn自帶的DBSCAN函數(shù)。以代碼3中生成數(shù)據(jù)為例:
1 #DBSCAN eps = 0.1, MinPts = 10
2 importtime3 from sklearn.cluster importDBSCAN4 start =time.time()5 C = DBSCAN(eps=0.1, min_pts=10).6 end =time.time()7 print "運(yùn)行時(shí)間:", end -start8 plt.scatter(X[:, 0], X[:, 1], c=C, marker='.')9 plt.show()10 >>> 運(yùn)行時(shí)間:0.0240921974182
總結(jié)
以上是生活随笔為你收集整理的dbscan算法python实现_挑子学习笔记:DBSCAN算法的python实现的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 从权益、年费、额度评测:建行MUSE信用
- 下一篇: python秒转化为时间格式_Pytho