完整代碼及其數據,請移步小編的GitHub
傳送門:請點擊我
如果點擊有誤:https://github.com/LeBron-Jian/MachineLearningNote
K-Means算法
K-Means 算法是無監督的聚類算法,它實現起來比較簡單,聚類效果也不錯,因此應用很廣泛。K-Means 算法有大量的變體,本文就從最傳統的K-Means算法學起,在其基礎上學習K-Means的優化變體方法。包括初始化優化K-Means++, 距離計算優化 elkan K-Means 算法和大數據情況下的優化 Mini Batch K-Means算法。
聚類問題的一些概念:
- 無監督問題:我們的手里沒有標簽了
- 聚類:就是將相似的東西分到一組
- 聚類問題的難點:如何評估,如何調參
1,K-Means原理
K-Means 算法的思想很簡單,對於給定的樣本集,按照樣本之間的距離大小,將樣本集划分為K個簇。讓簇內的點盡量緊密的連在一起,而讓簇間的距離盡量的大。
如果用數據表達式表示,假設簇划分為(C1, C2, ....Ck),則我們的目標是最小化平方誤差E:
其中μi 是簇Ci的均值向量,有時也稱為質心,表達式為:
如果我們想直接求上式的最小值並不容易,這是一個 NP 難的問題,因此只能采用啟發式的迭代方法。
K-Means采用的啟發式方法很簡單,用下面一組圖就可以形象的描述。
上圖a表達了初始的數據集,假設k=2,在圖b中,我們隨機選擇了兩個 k 類所對應的類別質心,即圖中的紅色質心和藍色質心,然后分別求樣本中所有點到這兩個質心的距離,並標記每個樣本的類別為和該樣本距離最小的質心的類別,如圖C所示,經過計算樣本和紅色質心和藍色質心的距離,我們得到了所有樣本點的第一輪迭代后的類別。此時我們對我們當前標記為紅色和藍色的點分別求其新的質心,如圖d所示,新的紅色質心和藍色質心的位置已經發生了變動。圖e和圖f 重復了我們在圖c和圖d的過程,即將所有點的類別標記為距離最近的質心的類別並求新的質心,最終我們得到了兩個類別如圖f。
當然在實際K-Means算法中,我們一般會多次運行圖c和圖d,才能達到最終的比較優的類別。
2, 傳統的K-Means算法流程
在上一節我們對K-Means的原理做了初步的探討,這里我們對K-Means的算法做一個總結。
首先我們看看K-Means算法的一些要點:
- 1)對於K-Means算法,首先要注意的是 k 值的選擇,一般來說,我們會根據對數據的先驗經驗選擇一個合適的 k 值,如果沒有什么先驗知識,則可以通過交叉驗證選擇一個合適的 k 值。
- 2)在確定了 k 的個數后,我們需要選擇 k 個初始化的質心,就像上圖 b 中的隨機質心。由於我們是啟發式方法,k個初始化的質心的位置選擇對最后的聚類結果和運行時間都有很大的影響,因此需要選擇合適的 k個質心,最后這些質心不能太近。
下面我們來總結一下傳統的K-Means算法流程:
輸入:樣本集D = {x1, x2, ... xm} 聚類的簇樹k,最大迭代次數 N
輸出:簇划分 C = {C1, C2, ....Ck}
1) 從數據集D中隨機選擇 k 個樣本作為初始的 k 個質心向量:{ μ1, μ2, ...μk }
2)對於 n=1, 2, ,,,, N
a) 將簇划分C初始化為Ct = Ø t=1,2,....k
b)對於i=1, 2, ....m,計算樣本 xi 和各個質心向量 μj(j = 1, 2, ... k)的距離:dij = || xi - μj ||22 ,將 xi 標記最小的為 dij 所對應的類別 λi 。此時更新 Cλi = Cλi υ {xi}
c)對於 j=1,2,...k,對Cj中所有的樣本點重新計算新的質心 :
d)如果所有的k個質心向量都沒有發生變換,則轉到步驟3。
3)輸出簇划分 C = {C1, C2, ... Ck}
3,K-Means 初始化優化 K-Means ++
2007年由D.Arthur等人提出的K-Means++針對上面第一步做了改進:http://ilpubs.stanford.edu:8090/778/1/2006-13.pdf
上面我們提到,k個初始化的質心的位置選擇對最后的聚類結果和運行時間都有很大的影響,因此需要選擇合適的 k 個質心。如果僅僅是完全隨機的選擇,有可能導致算法收斂很慢。K-Means ++ 算法就是對K-Means隨機初始化質心的方法的優化。
K-Means ++ 的對於初始化質心的優化策略也很簡單,如下:
- a) 從輸入的數據點集合中隨機選擇一個點作為第一個聚類中心 μ1
- b) 對於數據集中的每一個點 xi,計算它與已選擇的聚類中心中最近聚類中心的距離
其中 r=1,2,....Kselected
- c)選擇一個新的數據點作為新的聚類中心,選擇的原則是:D(x) 較大的點,被選取作為聚類中心的概率較大
- d)重復b和c 直到選擇出k個聚類質心
- e)利用這k個質心來作為初始化質心去運行標准的 K-Means算法
下面結合一個簡單的例子說明K-Means++是如何選取初始聚類中心的。數據集中共有8個樣本,分布以及對應序號如下圖所示:
假設經過K-Means++算法的步驟1后,6號點被選擇為第一個初始聚類中心,那在進行步驟2時每個樣本的D(x) 和被選擇為第二個聚類中心的概率如下表所示:
其中P(x)就是每個樣本被選為下一個聚類中心的概率。最后一行的Sum是概率P(x) 的累加和。用於輪盤法選擇出第二個聚類中心。方法是隨機產生出一個0~1之間的隨機數,判斷他屬於哪個區間,那么該區間對應的序號就是被選擇出來的第二個聚類中心了。例如1號點的區間為[0, 0.2] ,2號點的區間為[0.2, 0.525]。
從上表可以直觀的看出第二個初始聚類中心時1號,2號,3號,4號中的一個的概率為0.9。而這四個點正好是離第一個初始聚類中心6號點較遠的四個點。這也驗證了K-Means的改進思想:即離當前已有聚類中心較遠的點有更大的概率被選為下一個聚類中心。可以看出,該例的K值取2是比較合適的。當K值大於2時,每個樣本會有多個距離,需要取最小的那個距離作為D(x)。
4,K-Means 距離計算優化 elkan K-Means
在傳統的K-Means算法中,我們在每輪迭代時,要計算所有的樣本點到所有質心的距離,這樣會比較的耗時。那么,對於距離的計算有沒有能夠簡化的地方呢?elkan K-Means算法就是從這塊入手加以改進。它的目標是減少不必要的距離的計算。那么那些距離不需要計算呢?
elkan K-Means 利用了兩邊之和大於第三邊,以及兩邊之差小於第三邊的三角形性質,來減少距離的計算。
第一種規律是對於一個樣本點 x 和兩個質心 μj1, μj2。如果我們預先計算出了這兩個質心之間的距離D(j1, j2),則如果計算發現 2D(x, j1) <= D(j1, j2),我們立即就可以知道 D(x, j1) <= D(x, j2)。此時我們不需要計算D(x, j2),也就是說省了一步距離計算。
第二種規律是對於一個樣本點 x 和兩個質心 μj1, μj2。我們可以計算得到 D(x, j2) >= max {0, D(x, j1) - D(j1, j2)}。這個從三角形的性質也很容易得到。
如果利用上面的兩個規律,elkan K-Means 比起傳統的K-Means迭代速度有很大的提升。但是如果我們的樣本的特征是稀疏的,有缺失值的話,這個方法就不使用了,此時某些距離無法計算,則不能使用該算法。
5,大樣本優化 Mini Batch K-Means
在傳統的K-Means算法中,要計算所有的樣本點到所有的質心的距離。如果樣本量非常大。比如達到10萬以上,特征由100以上,此時用傳統的K-Means算法非常的耗時,就算加上 elkan K-Means 優化也是如此。在大數據時代,這樣的場景越來越多。此時Mini Batch K-Means 應運而生。
顧名思義,Mini Batch,也就是用樣本集中的一部分樣本來做傳統的K-Means,這樣可以避免樣本量太大時的計算難題,算法收斂速度大大加快。當然此時的代價就是我們聚類的精確度也會有一些降低。一般來說這個降低的幅度在可以接受的范圍之內。
在Mini Batch K-Means中,我們會選擇一個合適的批樣本大小 batch size,我們僅僅用 batch size 個樣本來做K-means聚類。那么這 batch size 個樣本怎么來的呢?一般是通過無放回的隨機采樣得到的。
為了增加算法的准確性,我們一般會多跑幾次 Mini Batch K-Means算法,用得到不同的隨機采樣集來得到聚類簇,選擇其中最優的聚類簇。
6,K-Means與KNN的對比
我們很容易將K-Means與KNN搞混,但是兩者差別還是很大的。
K-Means是無監督學習的聚類算法,沒有樣本輸出;而KNN則是監督學習的分類算法,有對應的類別輸出。KNN基本不需要訓練,對測試集里面的點,只需要找到在訓練集中最近的k個點,用這最近的k個點的類別來決定測試點的類別。而K-Means則有明顯的訓練過程,找到k個類別的最佳質心,從而決定樣本的簇類別。
當然,兩者也有一些相似點,兩個算法都包含一個過程,即找出和某一個點最近的點,兩者都利用了最近鄰(nearest neighbors)的思想。
而KNN算法可以參考我的博客:
Python機器學習筆記:K-近鄰(KNN)算法
7,K-Means小結
K-Means是個簡單實用的聚類算法,這里對K-Means的優缺點做一個總結。
7.1 K-Means的主要優點:
- 1,原理比較簡單,實現也是很容易,收斂速度快
- 2,聚類效果較優(依賴K的選擇)
- 3,算法的可解釋度比較強
- 4,主要需要調參的參數僅僅是簇數 k
7.2 K-Means的主要缺點:
- 1,K值的選取不好把握
- 2,對於不是凸的數據集比較難收斂
- 3,如果各隱含類別的數據不平衡,比如各隱含類別的數據量嚴重失衡,或者各隱含類別的方差不同,則聚類效果不佳
- 4,采用迭代方法,得到的結果只能保證局部最優,不一定是全局最優(與K的個數及初值選取有關)
- 5,對噪音和異常點比較的敏感(中心點易偏移)
sklearn實現K-Means算法
1,K-Means類概述
在scikit-learn中,包括兩個K-Means的算法,一個是傳統的K-Means算法,對應的類時K-Means。另一個是基於采樣的 Mini Batch K-Means算法,對應的類是 MiniBatchKMeans。一般來說,使用K-Means的算法調參是比較簡單的。
用K-Means類的話,一般要注意的僅僅就是 k 值的選擇,即參數 n_clusters:如果是用MiniBatch K-Means 的話,也僅僅多了需要注意調參的參數 batch_size,即我們的 Mini Batch 的大小。
當然K-Means類和MiniBatch K-Means類可以選擇的參數還有不少,但是大多不需要怎么去調參。下面看看K-Means類和 MiniBatch K-Means類的一些主要參數。
2,K-Means類的主要參數
參數源碼如下:
1) n_clusters: 即我們的k值,一般需要多試一些值以獲得較好的聚類效果。k值好壞的評估標准在下面會講。
2)max_iter: 最大的迭代次數,一般如果是凸數據集的話可以不管這個值,如果數據集不是凸的,可能很難收斂,此時可以指定最大的迭代次數讓算法可以及時退出循環。
3)n_init:用不同的初始化質心運行算法的次數。由於K-Means是結果受初始值影響的局部最優的迭代算法,因此需要多跑幾次以選擇一個較好的聚類效果,默認是10,一般不需要改。如果你的k值較大,則可以適當增大這個值。
4)init: 即初始值選擇的方式,可以為完全隨機選擇'random',優化過的'k-means++'或者自己指定初始化的k個質心。一般建議使用默認的'k-means++'。
5)algorithm:有“auto”, “full” or “elkan”三種選擇。"full"就是我們傳統的K-Means算法, “elkan”是我們原理篇講的elkan K-Means算法。默認的"auto"則會根據數據值是否是稀疏的,來決定如何選擇"full"和“elkan”。一般數據是稠密的,那么就是 “elkan”,否則就是"full"。一般來說建議直接用默認的"auto"
3,MiniBatch K-Means類的主要參數
參數源碼如下:
1) n_clusters: 即我們的k值,和KMeans類的n_clusters意義一樣。
2)max_iter:最大的迭代次數, 和KMeans類的max_iter意義一樣。
3)n_init:用不同的初始化質心運行算法的次數。這里和KMeans類意義稍有不同,KMeans類里的n_init是用同樣的訓練集數據來跑不同的初始化質心從而運行算法。而MiniBatchKMeans類的n_init則是每次用不一樣的采樣數據集來跑不同的初始化質心運行算法。
4)batch_size:即用來跑Mini Batch KMeans算法的采樣集的大小,默認是100.如果發現數據集的類別較多或者噪音點較多,需要增加這個值以達到較好的聚類效果。
5)init: 即初始值選擇的方式,和KMeans類的init意義一樣。
6)init_size: 用來做質心初始值候選的樣本個數,默認是batch_size的3倍,一般用默認值就可以了。
7)reassignment_ratio: 某個類別質心被重新賦值的最大次數比例,這個和max_iter一樣是為了控制算法運行時間的。這個比例是占樣本總數的比例,乘以樣本總數就得到了每個類別質心可以重新賦值的次數。如果取值較高的話算法收斂時間可能會增加,尤其是那些暫時擁有樣本數較少的質心。默認是0.01。如果數據量不是超大的話,比如1w以下,建議使用默認值。如果數據量超過1w,類別又比較多,可能需要適當減少這個比例值。具體要根據訓練集來決定。
8)max_no_improvement:即連續多少個Mini Batch沒有改善聚類效果的話,就停止算法, 和reassignment_ratio, max_iter一樣是為了控制算法運行時間的。默認是10.一般用默認值就足夠了。
4,K值的評估標准
不像監督學習的分類問題和回歸問題,我們的無監督聚類沒有樣本輸出,也就沒有比較直接的聚類評估方法。但是我們可以從簇內的稠密程度和簇間的離散程度來評估聚類的效果。常見的方法有輪廓稀疏Silhouette Coefficient和 Calinski Harabasz Index。個人比較喜歡Calinski-Harabasz Index,這個計算簡單直接,得到的Calinski-Harabasz分數值 s 越大則聚類效果越好。
Calinski-Harabasz 分數值 s 的數學計算公式是:
其中 m 為訓練集樣本數,k為類別數。Bk為類別之間的協方差矩陣,Wk為類別內部數據的協方差矩陣。tr為矩陣的跡。
也就是說,類別內部數據的協方差越小越好,類別之間的協方差越大越好,這樣的 Calinski-Harabasz 分數會高。在scikit-learn 中, Calinski-Harabasz Index對應的方法就是 metrics.calinski_harabaz_score。
5,K-Means 應用實例
下面用一個示例來學習KMeans類和MiniBatchKMeans 類來聚類。我們觀察在不同的k值下Calinski-Harabasz 分數。
首先我們需要隨機創建一些二維數據作為訓練集,選擇二維特征數據,主要是方便可視化,如下:
import numpy as np import matplotlib.pyplot as plt from sklearn.datasets.samples_generator import make_blobs # sklearn 中的make_blobs方法常被用來生成聚類算法的測試數據 # X為樣本特征,Y為樣本簇類別,共1000個樣本,每個樣本2個特征,共4個簇 # 簇中心在[-1,-1], [0,0],[1,1], [2,2], 簇方差分別為[0.4, 0.2, 0.2] # n_samples表示產生多少個數據 n_features表示數據是幾維的 # centers表示數據點中心,可以輸入整數,代表有幾個中心,也可以輸入幾個坐標 # cluster_std表示分布的標准差 X, y = make_blobs(n_samples=1000, n_features=2, centers=[[-1, -1], [0, 0], [1, 1], [2, 2]], cluster_std=[0.4, 0.2, 0.2, 0.2], random_state=9) plt.scatter(X[:, 0], X[:, 1], marker='o') plt.show()
生成的圖如下:
下面我們用K-Means聚類方法來做聚類,首先選擇 k=2,代碼如下:
import numpy as np import matplotlib.pyplot as plt from sklearn.datasets.samples_generator import make_blobs from sklearn.cluster import KMeans clf = KMeans(n_clusters=2, random_state=9) y_pred = clf.fit_predict(X) plt.scatter(X[:, 0], X[:, 1], c=y_pred, marker='o') plt.show()
k=2聚類的效果圖輸出如下:
下面使用Calinski-Harabasz Index 評估的聚類分數:
from sklearn import metrics metrics.calinski_harabaz_score(X, y_pred) # 3116.1706763322227
下面再來看看 k=3的聚類效果,代碼微調即可:
Calinski-Harabaz Index評估的 k=3 時聚類分數為:
2931.625030199556
最后看看 K=4的聚類效果:
Calinski-Harabaz Index評估的 k=4 時聚類分數為:
5924.050613480169
這個分數是這樣的,當聚類分數越大,則說明效果越好,我們可以看到上面k=3的聚類分數比k=2的差,但是k=4的聚類分數比k=2和k=3的都要高,而我們開始設定的隨機數據集就是四個簇,所以這符合我們的預期,當特征維度大於2,我們無法直接可視化聚類效果來肉眼觀察時,用Calinski-Harabaz Index評估是一個很實用的方法。
下面我們再看看用 MiniBatch K-Means 的效果,代碼如下:
import numpy as np import matplotlib.pyplot as plt from sklearn.datasets.samples_generator import make_blobs from sklearn.cluster import KMeans, MiniBatchKMeans from sklearn import metrics X, y = make_blobs(n_samples=1000, n_features=2, centers=[[-1, -1], [0, 0], [1, 1], [2, 2]], cluster_std=[0.4, 0.2, 0.2, 0.2], random_state=9) for index, k in enumerate((2, 3, 4, 5)): plt.subplot(2, 2, index + 1) clf = MiniBatchKMeans(n_clusters=k, batch_size=200, random_state=9) y_pred = clf.fit_predict(X) score = metrics.calinski_harabaz_score(X, y_pred) plt.scatter(X[:, 0], X[:, 1], c=y_pred) plt.text(.99, .01, ('k=%d, score: %.2f' % (k, score)), transform=plt.gca().transAxes, size=10, horizontalalignment='right') plt.show()
對於k=2,3,4,5 對應的輸出圖為:
可見使用MiniBatchKMeans的聚類效果也不錯,當然由於使用Mini Batch的原因,同樣是k=4最優,KMeans類的Calinski-Harabasz Index分數為5924.05,而MiniBatchKMeans的分數稍微低一些,為5921.45。這個差異損耗並不大。
DBSCAN密度聚類算法
DBSCAN(Density-Based Spatial Clustering of Appliactions with Noise,具有噪音的基於密度的聚類方法)是一種很典型的密度聚類算法,是數據挖掘中最經典基於密度的聚類算法,和K-Means,BIRCH這些一般只適用於凸樣本集的聚類相比,DBSCAN既可以適用於凸樣本集,也可以適用於非凸樣本集。下面學習一下DBSCAN算法的原理
1,密度聚集原理
基於密度的聚類尋找被低密度區域分離的高密度區域。DBSCAN是一種簡單,有效的基於密度的聚類算法,它解釋了基於密度的聚類方法的許多重要概念,在這里我們僅關注DBSCAN。
DBSCAN是一種基於密度的聚類算法,這類密度聚類算法一般假定類別可以通過樣本分布的緊密程度決定。同一類別的樣本,他們之間是緊密相連的,也就是說,在該類別任意樣本周圍不遠處一定有同類別的樣本存在。
通過將緊密相連的樣本划為一類,這樣就得到了一個聚類類別。通過將所有各組緊密相連的樣本划為各個不同的類別,則我們就得到了最終的所有聚類類別結果。
如圖所示:A為核心對象 ; BC為邊界點 ; N為離群點; 圓圈代表 ε-鄰域
DBSCAN算法的本質是一個發現類簇並不斷擴展類簇的過程。對於任意一點q,若他是核心點,則在該點為中心,r為半徑可以形成一個類簇 c。而擴展的方法就是,遍歷類簇c內所有點,判斷每個點是否是核心點,若是,則將該點的 ε-鄰域也划入類簇c,遞歸執行,知道不能太擴展類簇C。
假設上圖中 minPts為3,r為圖中圓圈的半徑,算法從A開始,經計算其為核心點,則將點A及其鄰域內的所有點(共四個)歸為類Q,接着嘗試擴展類Q。查詢可知類Q內所有的點均為核心點(紅點),故皆具有擴展能力,點C也被划入類Q。在遞歸擴展的過程中,查詢得知C不是核心點,類Q不能從點C出擴充,稱C為邊界點。邊界點被定義為屬於某一個類的非核心點。在若干次擴展以后類Q不能再擴張,此時形成的類為圖中除N外的所有的點,點N則稱為噪聲點。即不屬於任何一個類簇的點,等價的可以定義為從任何一個核心點出發都是密度不可達的。在上圖中數據點只能聚成一個類,在實際使用中往往會有多個類,即在某一類擴展完成后另外選擇一個未被歸類的核心點形成一個新的類簇並擴展,算法結束的標志是所有的點都已被划入某一類或噪聲,且所有的類都是不可再擴展。
上面提到了幾個概念,下面細說一下:
核心點(core point):這些點在基於密度的簇內部。點的鄰域由距離函數和用戶指定的距離參數 Eps 決定。核心點的定義是,如果該點的給定鄰域內的點的個數超過給定的閾值MinPts,其中MinPts也是一個用戶指定的參數。
邊界點(border point):邊界點不是核心點,但它落在某個核心點的鄰域內。如上圖B點。
噪聲點(noise point):噪聲點是即非核心點也是非邊界點的任何點。如上圖N點。
2,DBSCAN密度定義
在上面我們定性描述了密度聚類的基本思想,下面我們看看DBSCAN是如何描述密度聚類的。DBSCAN是基於一組鄰域來描述樣本集的緊密程度的,參數(ε,MinPts)用來描述鄰域的樣本分布緊密程度。其中 ε 描述了某一樣本的鄰域距離閾值,MinPts 描述了某一樣本的距離為 ε的鄰域中樣本個數的閾值。
假設我的樣本集是D=(x1, x2,...,Xm) 則DBSCAN 具體的密度描述定義如下:
1) ε-鄰域:對於xj € D,其 ε- 鄰域包含樣本集D中與 xj 的距離不大於 ε 的子樣本集,即Nε(xj)={xi € D| distance(xi, xj) <= ε},這個子樣本集的個數記為 | Nε(xj)|
2)核心對象:對於任一樣本xj € D ,如果其 ε- 鄰域對應的 Nε(xj) 至少包含 MinPts個樣本,即如果 | Nε(xj)| >= MinPts,則xj是核心對象。簡單來說就是若某個點的密度達到算法設定的閾值則其為核心點
3)密度直達:如果 xi 位於 xj 的 ε- 鄰域中,且 xj是核心對象,則稱 xi 由 xj 密度直達。注意反之不一定成立,即此時不能說 xj 由 xi 密度直達,除非且 xi 也是核心對象
4)密度可達:對於 xi 和 xj ,如果存在樣本序列 p1, p2, ...pT,滿足p1=xi, pT=xj,且pt+1由 pt 密度直達,則稱xj 由 xi 密度可達。也就是說,密度可達滿足傳遞性。此時序列中的傳遞樣本 p1, p2,.....pT-1均為核心對象,因為只有核心對象才能使其他樣本密度直達。注意密度可達也不滿足對稱性,這個可以由密度直達的不對稱性得出。
5)密度相連:對於xi和xj ,如果存在核心對象樣本 xk,使 xi 和 xj 均由 xk 密度可達,則稱 xi 和 xj 密度相連。注意密度相連關系是滿足對稱性的。
從下圖可以很容易理解上述定義,圖中 MinPts=5,紅色的點都是核心對象,因為其 ε- 鄰域至少有5個樣本。黑色的樣本是非核心對象。所有核心對象密度直達的樣本在以紅色核心對象為中心的超球體內,如果不在超球體內,則不能密度直達。圖中用綠色箭頭連起來的核心對象組成了密度可達的樣本序列。在這些密度可達的樣本序列 ε-鄰域內所有的樣本相互都是密度相連的。
這是一個老師的PPT:
參數選擇:
3,DBSCAN密度聚類思想
DBSCAN的聚類定義很簡單:由密度可達關系導出的最大密度相連的樣本集合,即為我們最終聚類的一個類別,或者說一個簇。
這個DBSCAN的簇里面可以有一個或者多個核心對象。如果只有一個核心對象,則簇里其他的非核心對象樣本都在這個核心對象的 ε- 鄰域里;如果有多個核心對象,則簇里的任意一個核心對象的 ε- 鄰域中一定有一個其他的核心對象。否則這兩個核心對象無法密度可達。這些核心對象的 ε- 鄰域里所有的樣本的集合組成的一個 DBSCAN聚類簇。
那么怎么才能找到這樣的簇樣本集合呢?DBSCAN使用方法很簡單,它任意選擇一個沒有類別的核心對象作為種子,然后找到所有這個核心對象能夠密度可達的樣本集合,即為一個聚類簇。接着繼續選擇另一個沒有類別的核心對象去尋找密度可達的樣本集合,這樣就得到了另一個聚類簇。一直運行到所有核心對象都有類別為止。
基本上這就是DBSCAN算法的主要內容了,但是我們還有三個問題沒有考慮。
第一是一些異常樣本點或者說少量游離於簇外的樣本點,這些點不在任何一個核心對象周圍,在DBSCAN中,我們一般將這些樣本點標記為噪音點。
第二是距離的度量問題,即如何計算某樣本和核心對象樣本的距離。在DBSCAN中,一般采用最近鄰思想,采用某一種距離度量來衡量樣本距離,比如歐氏距離。這種和KNN分類算法的最近鄰思想完全相同。對應少量的樣本,尋找最近鄰可以直接去計算所有樣本的距離,如果樣本量較大,則一半采用KD樹或者球樹來快速的搜索最近鄰。如果對最近鄰的思想不熟悉,可以看KNN算法。
第三種問題比較特殊,某些樣本可能到兩個核心對象的距離都小於 ε ,但是這兩個核心對象由於不是密度直達,又不屬於同一個聚類簇,那么如果界定這個樣本的類別呢?一般來說,此時DBSCAN采用先來后到,先進行聚類的類別簇會標記這個樣本為它的類別。也就是說DBSCAN算法不是完全穩定的算法。
4,DBSCAN聚類算法
下面對DBSCAN聚類算法的流程做一個總結:
輸入:樣本集D=(x1, x2, ...xm),鄰域參數(ε, MinPts),樣本距離度量方法
輸出:簇划分C
1)初始化核心對象集合 Ω = Φ ,初始化聚類簇數 k=0,初始化未訪問樣本集合 Γ = D,簇划分 C = Φ
2) 對於j=1,2,...m,按下面的步驟找出所有的核心對象:
a)通過距離度量方法,找到樣本 xj 的 ε-鄰域子樣本集 Nε(xj)
b)如果子樣本集樣本個數滿足 | Nε(xj)| >= MinPts,將樣本 xj 加入到核心對象樣本集合:Ω = Ω υ {xj}
3)如果核心對象集合 Ω = Φ,則算法結束,否則轉入步驟4.
4)在核心對象集合Ω 中,隨機選擇一個核心對象 o, 初始化當前簇核心對象隊列 Ωcur ={o},初始化類別序號 k=k+1,初始化當前簇樣本集合 Ck={o},更新未訪問樣本集合 Γ = Γ - {o}
5)如果當前簇核心對象隊列 Ωcur = Φ,則當前聚類簇Ck 生成完畢,更新簇划分 C={C1, C2, ...Ck},更新核心對象集合 Ω = Ω - Ck,轉入步驟3,否則更新核心對象集合 Ω = Ω - Ck
6)在當前簇核心對象隊列 Ω cur 中取出一個核心對象 o',通過鄰域距離閾值 ε 找出所欲的 ε-鄰域子樣本集 Nε(o'),令 Δ = Nε(o') n Γ ,更新當前簇樣本集合Ck = Ck υ Δ ,更新未訪問樣本集合 Γ = Γ - Δ,更新 Ωcur = Ωcur υ (Δ n Ω) - o',轉入步驟5
輸出結果為:簇划分C={C1,C2,...Ck}
如果上面解釋太復雜,我們可以看下面DBSCAN的步驟:
- 1,將所有點標記為核心點,邊界點或噪聲點
- 2,刪除噪聲點
- 3,為距離在Eps內的所有核心點之間賦予一條邊
- 4,每組連通的核心點形成一個簇
- 5,將每個邊界點指派到一個與之無關的核心點的簇中
5,DBSCAN 小結
和傳統的K-Means算法相比,DBSCAN最大的不同激素不需要輸入類別k,當然它最大的優勢是可以發現任意形狀的聚類簇,而不是像K-Means,一般僅僅使用於凸的樣本集聚類,同時它在聚類的同時還可以找出異常點,這點和BIRCH算法類似。
那么我們什么時候需要用DBSCAN來聚類呢?一般來說,如果數據集是稠密的,並且數據集不是凸的,那么用DBSCAN會比K-Means聚類效果好很多。如果數據集不是稠密的,則不推薦使用DBSCAN來聚類。
下面對DBSCAN算法的優缺點做一個總結:
5.1 DBSCAN優點:
1) 可以對任意形狀的稠密數據集進行聚類,相對的,K-Means之類的算法一般只適用於凸數據集。
2) 可以在聚類的同時發現異常點,對數據集中的異常點不敏感。
3)聚類結果沒有偏倚,相對的K,K-Means之類的聚類算法那初始值對聚類結果有很大的影響。
5.2 DBSCAN缺點:
1)如果樣本集的密度不均勻,聚類間距離相差很大時,聚類質量較差,這時用DBSCAN聚類一般不適合。
2)如果樣本集較大時,聚類收斂時間較長,此時可以對搜索最近鄰時建立的KD樹或者球樹進行規模限制來改進。
3)調參相對於傳統的K-Means之類的聚類算法稍復雜,主要需要對距離閾值 ε ,鄰域樣本數閾值MinPts聯合調參,不同的參數組合對最后的聚類效果有較大影響。
5.3 DBSCAN和K-Means算法的區別:
- 1,可以不需要事先指定cluster的個數
- 2,可以找出不規則形狀的cluster
sklearn實現DBSCAN算法
在sklearn中,DBSCAN算法類為sklearn.cluster.DBSCAN。要熟練的掌握用DBSCAN類來聚類,除了對DBSCAN本身的原理有較深的理解之外,還要對最近鄰的思想有一定的理解。結合這兩者,就可以熟練應用DBSCAN。
1,DBSCAN類重要參數
參數部分源碼:
1)eps:DBSCAN算法參數,即我們的 ε- 鄰域的距離閾值,和樣本距離超過ε- 的樣本點不在ε- 鄰域內,默認值是0.5。一般需要通過在多組值里面選擇一個合適的閾值,eps過大,則更多的點會落在核心對象的ε- 鄰域,此時我們的類別數可能會減少,本來不應該是一類的樣本也會被划分為一類。反之則類別數可能會增大,本來是一類的樣本卻被划分開。
2)min_samples:DBSCAN算法參數,即樣本點要成為核心對象所需要的ε- 鄰域的樣本數閾值,默認是5。一般需要通過在多組值里面選擇一個合適的閾值。通常和eps一起調參。在eps一定的情況下,min_smaples過大,則核心對象會過少,此時簇內部分本來是一類的樣本可能會被標為噪音點,類別數也會變多。反之 min_smaples過小的話,則會產生大量的核心對象,可能會導致類別數過少。
3)metric:最近鄰距離度量參數,可以使用的距離度量較多,一般來說DBSCAN使用默認的歐式距離(即 p=2 的閔可夫斯基距離)就可以滿足我們的需求。可以使用的距離度量參數有:歐式距離,曼哈頓距離,切比雪夫距離,閔可夫斯基距離,馬氏距離等等。
4)algorithm:最近鄰搜索算法參數,算法一共有三種,第一種是蠻力實現,第二種是KD樹實現,第三種是球樹實現,對於這個參數,一共有4種可選輸入,‘brute’對應第一種蠻力實現,‘kd_tree’對應第二種KD樹實現,‘ball_tree’對應第三種的球樹實現, ‘auto’則會在上面三種算法中做權衡,選擇一個擬合最好的最優算法。需要注意的是,如果輸入樣本特征是稀疏的時候,無論我們選擇哪種算法,最后scikit-learn都會去用蠻力實現‘brute’。個人的經驗,一般情況使用默認的 ‘auto’就夠了。 如果數據量很大或者特征也很多,用"auto"建樹時間可能會很長,效率不高,建議選擇KD樹實現‘kd_tree’,此時如果發現‘kd_tree’速度比較慢或者已經知道樣本分布不是很均勻時,可以嘗試用‘ball_tree’。而如果輸入樣本是稀疏的,無論你選擇哪個算法最后實際運行的都是‘brute’。
5)leaf_size: 最近鄰搜索算法參數,為使用KD樹或者球樹時,停止建子樹的葉子節點數量的閾值。這個值越小,則生成的KD樹或者球樹就越大,層數越深,建樹時間越長,反之,則生成的KD樹或者球樹會小,層數較淺,建樹時間較短。默認是30,因為這個值一般只影響算法的運行速度和使用內存大小,因此一般情況可以不管它。
6)p:最近鄰距離度量參數。只用於閔可夫斯基距離和帶權值閔可夫斯基距離中 p 值的選擇, p=1為曼哈頓距離,p=2為歐式距離,如果使用默認的歐式距離就不需要管這個參數。
以上就是DBSCAN類的主要參數介紹,其實需要調參的就是兩個參數eps和min_samples,這兩個值的組合對最終的聚類效果有很大的影響。
2,Sklearn DBSCAN聚類實例
首先,我們隨機生成一組數據,為了體現DBSCAN在非凸數據的聚類有點,我們生成了兩簇數據,然后再將這兩簇結合起來,代碼如下:
import numpy as np import matplotlib.pyplot as plt from sklearn.datasets import make_blobs, make_circles # 下面我們生成三組數據 X1, y1 = make_circles(n_samples=5000, factor=0.6, noise=0.05) X2, y2 = make_blobs(n_samples=1000, n_features=2, centers=[[1.2, 1.2]], cluster_std=[[0.1]], random_state=9) X = np.concatenate((X1, X2)) plt.scatter(X[:, 0], X[:, 1], marker='o') plt.show()
將這兩簇數據結合起來,圖如下:
下面我們先看看K-Means的聚類效果,代碼如下:
from sklearn.cluster import KMeans
y_pred = KMeans(n_clusters=3, random_state=9).fit_predict(X) plt.scatter(X[:, 0], X[:, 1], c=y_pred) plt.show()
結果圖如下:
從上面代碼輸出的聚類效果圖可以明顯看出:K-Means對於非凸數據集的聚類表現不好。
那么如果使用DBSCAN效果如何呢?我們先不調參,直接使用默認參數,看看聚類效果,代碼如下:
from sklearn.cluster import DBSCAN
y_pred = DBSCAN().fit_predict(X) plt.scatter(X[:, 0], X[:, 1], c=y_pred) plt.show()
結果圖如下:
我們發現輸出結果不好,DBSCAN居然認為所有的數據都是一類。
下面我們嘗試調參,我們需要對DBSCAN的兩個關鍵的參數eps和min_samples進行調參!從上圖我們可以發現,類別數太少,我們需要增加類別數,那么我們可以減少 ε- 鄰域的大小,默認是0.5,我們減少到0.1看看效果,代碼如下:
y_pred = DBSCAN(eps = 0.1).fit_predict(X) plt.scatter(X[:, 0], X[:, 1], c=y_pred) plt.show()
對應的聚類效果圖如下:
可以看到聚類效果有了很大的改進。此時我們可以繼續調參增加類別,有兩個方向都是可以的,一個是繼續減少eps,另一個是增加min_samples。我們現在將min_samples從默認的5增加到10,代碼如下:
y_pred = DBSCAN(eps = 0.1, min_samples = 10).fit_predict(X) plt.scatter(X[:, 0], X[:, 1], c=y_pred) plt.show()
效果圖如下:
出現了一個異常點。。。。不過我們可以使用不調參的模型,聚類效果比較好。
上面這個例子只是幫大家理解DBSCAN調參的一個基本思路,在實際運用中可能要考慮很多問題,以及更多的參數組合,希望這個例子可以給大家一些啟發。
聚類案例實戰
1,聚類算法評估方法:輪廓系數(Silhouette Coefficient)
Silhouette 系數是對聚類結果有效性的解釋和驗證,由Peter J. Rousseeuw 於 1986年提出。
- 計算樣本 i 到同簇其他樣本的平均距離ai,ai越小,說明樣本 i 越應該被聚類到該簇,將 ai 稱為樣本 i 的簇內不相似度。
- 計算樣本 i 到其他某簇 Cj 的所有樣本的平均距離 bij,稱為樣本 i 與簇 Cj 的不相似度。定義為樣本 i 的簇間不相似度:bi = min{bi1, bi2, ...bik}
- si 接近1,則說明樣本 i 聚類合理
- si 接近 -1 ,則說明樣本 i 更應該分類到另外的簇
- 若 si 近似為0,則說明樣本 i 在兩個簇的邊界上
2,四種聚類算法的對比圖
這里使用了BIRCH,DBSCAN,K-Means,MEAN-SHIFT四種算法那,橫軸表示各個特征,宗軸表示預測特征的准確率。而且橫軸對數據特征進行了增強。
總體的趨勢表示了算法的差異,也就是說DBSCAN的能力比其他算法普遍都好。它可以讓我們的准確率更高,其他的三個算法都差不多。所以一般情況最好使用DBSCAN算法。
3,聚類算法實戰
數據去我的GitHub上拿,GitHub地址:https://github.com/LeBron-Jian/MachineLearningNote
數據是簡單的啤酒數據,包含了四個特征分別是卡路里,鈉,酒精,價格。包含了20種不同的啤酒品牌。
我們的目的是使用sklearn演示聚類算法如何實現,下面對數據做簡單的分析
# beer dataset from my github import pandas as pd beer = pd.read_csv(filename, sep=' ') print(beer)
我們首先可以看看數據,數據只有一點點:
我們拿出特征,也就是上面提到的四個變量:
X = beer[['calories', 'sodium', 'alcohol', 'cost']]
因為聚類算法是無監督的算法,所以不需要lables,只要特征即可。我們拿到特征后,就可以進行K-Means算法訓練。sklearn中的聚類算法都在cluster類里面。下面分別用兩個堆,三個堆做聚類。
# beer dataset from my github import pandas as pd from sklearn.cluster import KMeans, DBSCAN km1 = KMeans(n_clusters=3) clf1 = km1.fit(features) km2 = KMeans(n_clusters=2) clf2 = km2.fit(features) print(clf1.labels_) print(clf2.labels_)
我們可以先查看一下其預測出來的標簽,下面lables表明數據屬於那個類別,如果是兩類,則為0,1;如果是三類則為0, 1, 2,最后預測結果如下:
[0 0 0 0 0 0 0 0 1 1 0 1 0 0 0 2 0 0 2 1] [0 0 0 0 0 0 0 0 1 1 0 1 0 0 0 1 0 0 1 1]
我們也可以把分好的類,加入到源數據中查看分類結果(因為數據比較小,所以可以這樣做)
beer['cluster1'] = clf1.labels_ beer['cluster2'] = clf2.labels_ res = beer.sort_values('cluster1') print(res)
結果如下:
下面我們找聚類的中心,可以使用groupby查看其均值,看看不同含量的差異性。
from pandas.plotting import scatter_matrix cluster_centers1 = clf1.cluster_centers_ cluster_centers2 = clf2.cluster_centers_ group_res1 = beer.groupby('cluster1').mean() group_res2 = beer.groupby('cluster2').mean() print(group_res1) print(group_res2)
結果如下:
下面我們畫圖看:
centers1 = group_res1.reset_index() plt.rcParams['font.size'] = 14 colors = np.array(['red', 'green', 'blue', 'yellow']) plt.scatter(beer['calories'], beer['alcohol'], c=colors[beer['cluster1']]) plt.scatter(centers1.calories, centers1.alcohol, linewidths=3, marker='+', s=300, c='black') plt.xlabel('Calories') plt.ylabel('Alcohol')
圖如下:
上圖是聚類的效果,左邊綠色的是一簇,中間藍色的是一簇,右邊紅色的是一簇。
下面我們可以對這四個特征,兩兩進行比較,看看每兩個指標在二維圖上的聚類效果,代碼如下:
scatter_matrix(beer[['calories', 'sodium', 'alcohol', 'cost']], s=100, alpha=1, c=colors[beer['cluster1']], figsize=(10, 10)) plt.title('With 3 centroids initialized') scatter_matrix(beer[['calories', 'sodium', 'alcohol', 'cost']], s=100, alpha=1, c=colors[beer['cluster2']], figsize=(10, 10)) plt.title('With 3 centroids initialized')
圖如下:(第一個是簇為3的,第二個是簇為2的)
從圖中可以看出,簇為三的效果更好一些。
下面我們對數據進行預處理,一般情況下我們都會對數據先做處理,因為這里數據量太小,所以數據預處理之前和處理之后的效果可能不明顯,但是我們還是走一遍過程演示一下。
from sklearn.preprocessing import StandardScaler scaler = StandardScaler() X_scaled = scaler.fit_transform(features)
這里我們將數據歸一化,基本數據都在-1到1之間,我們看看部分數據的效果:
下面我們也看看他歸一化后的簇:
km1 = KMeans(n_clusters=3) clf1 = km1.fit(features) beer['scaled_cluster'] = clf1.labels_ sort_res = beer.sort_values('scaled_cluster') print(sort_res)
如下:
下面我們看看歸一化后的每個簇的聚類中心:
group_res3 = beer.groupby('scaled_cluster').mean() print(group_res3)
結果如下:
注意:有些特征本來就比較重要,而有些特征本來就不重要,所以歸一化的效果不是一定很好的,假如特征本來就沒有用,即使做了數據預處理效果也不會有提升的。
下面看看畫圖代碼(兩個代碼效果一樣):
scatter_matrix(beer[['calories', 'sodium', 'alcohol', 'cost']], s=100, alpha=1, c=colors[beer['scaled_cluster']], figsize=(10, 10)) # scatter_matrix(features, # s=100, alpha=1, c=colors[beer.scaled_cluster], figsize=(10, 10))
結果如下:
我們聚類做完了,那么如何評估那個參數的效果好呢?上面也提到了聚類的評估方法:輪廓系數。輪廓系數在sklearn中也有。
下面看看評估系數的代碼:
score_scaled = metrics.silhouette_score(features, beer.scaled_cluster) score1 = metrics.silhouette_score(features, beer.cluster1) score2 = metrics.silhouette_score(features, beer.cluster2) print(score_scaled, score1, score2) 結果如下: 0.45777415910909475 0.45777415910909475 0.33071506469818307
我們可以看到,做歸一化,和不做歸一化的效果一樣,而且分為3簇效果比分為2簇的效果更好。
我們還可以調參,看看n_clusters為幾的時候,效果最好。
下面我們可以看看不歸一化數據,我們的簇選擇從2到20,調參看看結果:
scores = [] for k in range(2, 20): clf = KMeans(n_clusters=k).fit(features) lables = clf.labels_ score = metrics.silhouette_score(features, lables) scores.append(score) print(scores)
結果如下:
[0.33071506469818307, 0.45777415910909475, 0.47546412252215964, 0.44406582701642483, 0.3653901114020658, 0.3390763170371786, 0.29766539203489123, 0.2935513325548086, 0.26720480054972856, 0.25495898824119195, 0.2681342271735249, 0.2580624498560809, 0.19510367184524083, 0.1839776940060746, 0.12332923155351708, 0.13272517919240542, 0.09795391063666276, 0.053572348458556004]
我們畫圖展示:
plt.plot(list(range(2, 20)), scores) plt.xlabel('Number of Clusters Initialized') plt.ylabel('Sihouette Score')
結果如下:
從圖中,顯而易見可以看出其效果。我們也直接能得出clusters的最佳值。
下面看看DBSCAN 聚類。
from sklearn.cluster import DBSCAN db = DBSCAN(eps=10, min_samples=2).fit(features) labels = db.labels_ beer['cluster_db'] = labels sort_res = beer.sort_values('cluster_db') print(sort_res)
結果如下(上圖是未歸一化的數據的分類效果,下圖是歸一化的數據的分類效果):
我們看看其均值:
group_res = beer.groupby('cluster_db').mean() print(group_res)
結果如下:
畫圖,對特征兩兩比較,代碼如下:
plt.rcParams['font.size'] = 14 colors = np.array(['red', 'green', 'blue', 'yellow']) scatter_matrix(beer[['calories', 'sodium', 'alcohol', 'cost']], s=100, alpha=1, c=colors[beer['cluster_db']], figsize=(10, 10))
圖如下:
下面使用歸一化后的數據,看看圖效果:
很明顯歸一化后的效果不好。
既然歸一化的效果不好,所以我們只需要評估未歸一化的訓練結果,下面使用輪廓稀疏進行評估:
score1 = metrics.silhouette_score(features, beer.cluster_db) print(score1) # 0.49530955296776086
效果還可以,下面我們可以和K-Means一樣,調參選擇最佳參數。
這里我們固定一個參數,調另一個參數,當然也可以兩兩組合,只不過稍占內存。
scores = [] for k in range(5, 10, 1): clf = DBSCAN(eps=10, min_samples=k).fit(features) lables = clf.labels_ score = metrics.silhouette_score(features, lables) scores.append(score) print(scores) # [0.4767963143919395, 0.4767963143919395, 0.390839395721598, 0.390839395721598, 0.21891774205673578]
可視化工具:https://www.naftaliharris.com/blog/visualizing-dbscan-clustering/
PS:這個是參考學習劉建平老師的博客,做了筆記,目的是學習K-Means算法和DBSCAN算法。
參考文獻:
https://www.cnblogs.com/pinard/p/6217852.html
https://www.cnblogs.com/pinard/p/6208966.html
https://www.cnblogs.com/pinard/p/6169370.html
https://www.cnblogs.com/pinard/p/6164214.html
https://blog.csdn.net/sinat_30353259/article/details/80887779