《數據分析實戰-托馬茲.卓巴斯》讀書筆記第4章-聚類技巧(K均值、BIRCH、DBSCAN)

python學習筆記-目錄索引html

 

第4章 解釋了多種聚類模型;從最多見的k均值算法開始,一直到高級的BIRCH算法和DBSCAN算法。python

本章會介紹一些技術,幫助對一個銀行營銷電話的數據進行聚類。將學習如下主題:
·評估聚類方法的表現
·用k均值算法聚類數據
·爲k均值算法找到最優的聚類數
·使用mean shift聚類模型發現聚類
·使用c均值構建模糊聚類模型
·使用層次模型聚類數據
·使用DBSCAN和BIRCH算法發現潛在的訂閱者

4.1導論

分類問題中,咱們知道每一個觀測值的歸類(常常稱做監督學習或有老師的學習),聚類問題則否則,不須要標籤就能在數據中發現模式(稱做無監督學習)。
聚類方法根據觀測值之間的類似度將其歸到不一樣的桶中。這種分析在探索階段頗有用,這個階段分析師想知道數據中是否自然存在某種模式。git

 4.2評估聚類方法的表現

沒有真實的標籤,咱們沒法使用前一章介紹的指標。在本技巧中,咱們會介紹三種幫咱們評估聚類方法有效性的指標:Davis-Bouldin、僞F(有時也稱做Calinsk-Harabasz)和Silhouette值是內部評價指標。若是知道真實的標籤,咱們就可使用不少指標,好比調整後的Rand指數、同質性或是完整性。
參考scikit文檔中關於聚類方法的部分,對聚類方法的多種外部評價指標有一個更深的瞭解:github

http://scikit-learn.org/stable/modules/clustering.html#clustering-performance-evaluationweb

內部聚類驗證方法的列表,參考http://datamining.rutgers.edu/publication/internalmeasures.pdf
本示例須要裝好pandasNumPyScikit

前面提到的三個內部評價指標,Scikit只實現了Silhouette值;爲了寫這本書,原做者實現了另兩個。要評估你的聚類模型的表現,你可使用helper.py文件中的printCluster Summary(...)方法:算法

def printClustersSummary(data, labels, centroids):
    '''
        Helper method to automate models assessment
    '''
    print('Pseudo_F: ', pseudo_F(data, labels, centroids))
    print('Davis-Bouldin: ',
        davis_bouldin(data, labels, centroids))
    print('Silhouette score: ',
        mt.silhouette_score(data, np.array(labels),
            metric='euclidean'))

原理:咱們介紹第一個指標,僞F分值。Calinski-Harabasz指標是在模型上啓發式地獲得聚類的數目,其最大值表明最優的聚類方式。
僞F指數計算方式,是用每一個聚類的中心到整個數據集的幾何中心的平方距離,與聚類數減1的比值,再乘以每一個聚類中觀測值的數目。這個數字再除以一個比值,這個比值是用每一個點和聚類中心的平方距離,除以全部觀測值數與聚類數之差獲得的。app

 1 def pseudo_F(X, labels, centroids):
 2     '''
 3         The pseudo F statistic :
 4         pseudo F = [( [(T - PG)/(G - 1)])/( [(PG)/(n - G)])]
 5         The pseudo F statistic was suggested by
 6         Calinski and Harabasz (1974) in
 7         Calinski, T. and J. Harabasz. 1974.
 8             A dendrite method for cluster analysis.
 9             Commun. Stat. 3: 1-27.
10             http://dx.doi.org/10.1080/03610927408827101
11 
12         We borrowed this code from
13         https://github.com/scampion/scikit-learn/blob/master/
14         scikits/learn/cluster/__init__.py
15 
16         However, it had an error so we altered how B is
17         calculated.
18     '''
19     center = np.mean(X,axis=0)
20     u, count = np.unique(labels, return_counts=True)
21 
22     B = np.sum([count[i] * ((cluster - center)**2)
23         for i, cluster in enumerate(centroids)])
24 
25     X = X.as_matrix()
26     W = np.sum([(x - centroids[labels[i]])**2
27                  for i, x in enumerate(X)])
28 
29     k = len(centroids)
30     n = len(X)
31 
32     return (B / (k-1)) / (W / (n-k))

首先,咱們計算整個數據集中心的幾何座標。從代數上講,這不過是計算每一列的平均值。使用NumPy的.unique(...),而後統計每一個聚類中觀測值的數目。
往.unique(...)方法傳return_counts=True時,不只會返回標籤向量中的去重值列表u,也會返回去重後每一個值的個數c。
而後,咱們計算每一個聚類的中心到數據集中心的平方距離。使用.enumerate(...)方法遍歷中心列表中的每一個元素,計算每一個和數據集中心的平方差,再乘以聚類中觀測值的個數:c[i]。如名字所示,NumPy的.sum(...)方法,計算列表中全部元素的和。這個方法返回的是Calinski-Harabasz指數。
與僞F指數相反,Davis-Bouldin指標衡量的是最壞狀況下聚類之間的異質性與聚類內部的同質性。也就是說,目標要找出使得這個指標最小的聚類數目。在後面的4.4節,咱們會開發一種方法,經過最小化Davis-Bouldin指標來找到最優的聚類數目。
參考MathWorks(Matlab開發商)關於Davis-Bouldin指標的講解:http://au.mathworks.com/help/stats/clustering.evaluation.daviesbouldinevaluation-class.htmlide

 1  def davis_bouldin(X, labels, centroids):
 2     '''
 3         The Davis-Bouldin statistic is an internal evaluation
 4         scheme for evaluating clustering algorithms. It
 5         encompasses the inter-cluster heterogeneity and
 6         intra-cluster homogeneity in one metric.
 7 
 8         The measure was introduced by
 9         Davis, D.L. and Bouldin, D.W. in 1979.
10             A Cluster Separation Measure
11             IEEE Transactions on Pattern Analysis and
12             Machine Intelligence, PAMI-1: 2, 224--227
13 
14             http://dx.doi.org/10.1109/TPAMI.1979.4766909
15     '''
16     distance = np.array([
17         np.sqrt(np.sum((x - centroids[labels[i]])**2))
18         for i, x in enumerate(X.as_matrix())])
19 
20     u, count = np.unique(labels, return_counts=True)
21 
22     Si = []
23 
24     for i, group in enumerate(u):
25         Si.append(distance[labels == group].sum() / count[i])
26 
27     Mij = []
28 
29     for centroid in centroids:
30         Mij.append([
31             np.sqrt(np.sum((centroid - x)**2))
32             for x in centroids])
33 
34     Rij = []
35     for i in range(len(centroids)):
36         Rij.append([
37             0 if i == j
38             else (Si[i] + Si[j]) / Mij[i][j]
39             for j in range(len(centroids))])
40 
41     Di = [np.max(elem) for elem in Rij]
42 
43     return np.array(Di).sum() / len(centroids)

首先,咱們計算每一個觀測值和所屬聚類的中心之間的幾何距離,並統計每一個聚類中觀測值的數目。
Si衡量的是聚類內部的同質性,便是每一個觀測值到聚類中心的平均距離。Mij計算各個聚類中心之間的幾何距離,量化了聚類之間的異質性。Rij衡量的是兩個聚類之間切分得好很差,Di選取了最差狀況下的切分。Davis-Bouldin指標是Di的平均值。
Silhouette分值(指數)是又一個聚類內部評價指標。咱們沒有實現計算程序(由於Scikit已經提供了實現),但咱們簡要描述了它的計算過程與衡量目標。一個聚類,其Silhouette分值衡量的是聚類中每一個觀測值與其餘觀測值之間的平均距離,將這個值與每一個聚類內點與點之間的平均距離相比。理論上,這個指標的取值範圍在-1到1之間;-1意味着全部的觀測值都在不合適的聚類中,實際上,更合適的聚類可能就是臨近的某個。儘管理論上可行,但實際使用中,你更可能徹底不考慮負的Silhouette分值。光譜的另外一端,1表明完美的切分,全部的觀測值都落在合適的聚類中。這個值若是是0,表明着聚類之間一個完美重疊,意味着每一個聚類都有應該歸屬於其餘聚類的觀測值,而且這些觀測值的數目是相等的。
參考:對這些算法(以及其餘算法)更深層次的描述,參考https://cran.r-project.org/web/packages/clusterCrit/vignettes/clusterCrit.pdf

4.3用k均值算法聚類數據
k均值聚類算法多是在聚類向量化數據方面最爲知名的數據挖掘技術了。它基於觀測察值之間的類似度,將它們分到各個聚類;決定因素就是觀測值和最近一個聚類中心的歐幾里得距離。
裝好pandas和Scikit。
Scikit在其聚類子模塊中提供了多種聚類模型。這裏,咱們使用.KMeans(...)來估算聚類模型(clustering_kmeans.py文件):函數

def findClusters_kmeans(data):
    '''
        Cluster data using k-means
    '''
    # create the classifier object
    kmeans = cl.KMeans(
        n_clusters=4,
        n_jobs=-1,
        verbose=0,
        n_init=30
    )

    # fit the data
    return kmeans.fit(da */ta)    

原理:和前一章的技巧同樣,咱們從讀取數據開始,選擇咱們想用來區別觀測值的特徵:oop

# the file name of the dataset
r_filename = '../../Data/Chapter04/bank_contacts.csv'

# read the data
csv_read = pd.read_csv(r_filename)

# select variables
selected = csv_read[['n_duration','n_nr_employed',
        'prev_ctc_outcome_success','n_euribor3m',
        'n_cons_conf_idx','n_age','month_oct',
        'n_cons_price_idx','edu_university_degree','n_pdays',
        'dow_mon','job_student','job_technician',
        'job_housemaid','edu_basic_6y']]

Tips:

/* The method findClusters_kmeans took 2.32 sec to run.
D:\Java2018\practicalDataAnalysis\helper.py:142: FutureWarning: Method .as_matrix will be removed in a future version. Use .values instead.
  X = X.as_matrix()
Pseudo_F:  11515.72135543927
D:\Java2018\practicalDataAnalysis\helper.py:168: FutureWarning: Method .as_matrix will be removed in a future version. Use .values instead.
  for i, x in enumerate(X.as_matrix())])
Davis-Bouldin:  1.2627990546726073
Silhouette score:  0.3198322783627837
 */

解決方案:修改X.as_matrix()爲X.values

/*
The method findClusters_kmeans took 2.32 sec to run.
Pseudo_F:  11349.586839983096
Davis-Bouldin:  1.2608899499519972
 */


咱們使用了與前一章相同的數據集,並只使用在分類過程當中最具描述力的特徵。另外,咱們仍然使用@hlp.timeit裝飾器來衡量估算模型的速度。
Scikit的.KMeans(...)方法有多個參數。參數n_clusters定義了期待數據中要有多少聚類,以及要返回多少聚類。在本書4.4節中,咱們將寫一個循環方法,爲k均值算法找出最優的聚類數。
n_jobs參數是你機器上並行執行的任務數;指定-1意味着將並行執行的數目指定爲處理器核的數目。你能夠顯式傳入一個整數,好比8,來指定任務數目。
verbose參數控制着估算階段你能看到多少信息;設置爲1會打印出全部關於估算的細節。
n_init參數控制着要估算多少模型。k均值算法的每一次執行,會隨機選擇每一個聚類的中心,循環調整這些中心,直到模型聚類之間的隔離程度和聚類內部的類似程度達到最好。.KMeans(...)方法以不一樣的初始條件(爲各中心隨機化初始種子),構建n_init參數指定的數目的模型,而後選擇準則表現最好的一個。
準則衡量的是聚類內部的差別。它是聚類內部的平方和。咱們將聚類內部的平方和與平方總和的比率稱爲解釋準則。

Scikit固然不是估算k均值模型的惟一方法;咱們也可使用SciPy(clustering_kmeans_alternative.py文件):

 1 def findClusters_kmeans(data):
 2     '''
 3         Cluster data using k-means
 4     '''
 5     # whiten the observations
 6     data_w = vq.whiten(data)
 7 
 8     # create the classifier object
 9     kmeans, labels = vq.kmeans2(
10         data_w,
11         k=4,
12         iter=30
13     )
14 
15     # fit the data
16     return kmeans, labels
17 
18 # the file name of the dataset
19 r_filename = '../../Data/Chapter04/bank_contacts.csv'
20 
21 # read the data
22 csv_read = pd.read_csv(r_filename)
23 
24 # select variables
25 selected = csv_read[['n_duration','n_nr_employed',
26         'prev_ctc_outcome_success','n_euribor3m',
27         'n_cons_conf_idx','n_age','month_oct',
28         'n_cons_price_idx','edu_university_degree','n_pdays',
29         'dow_mon','job_student','job_technician',
30         'job_housemaid','edu_basic_6y']]
31 
32 # cluster the data
33 #centroids, labels = findClusters_kmeans(selected.as_matrix())
34 centroids, labels = findClusters_kmeans(selected.values)
35 
36 hlp.printClustersSummary(selected, labels, centroids)

與使用Scikit時不一樣,使用SciPy時,咱們須要先洗白數據。洗白相似於將數據標準化(參考本書1.14節),只不過它不刪除均值;.whiten(...)將全部特徵的方差統一爲1。
傳給.kmeans2(...)方法的第一個參數是已清洗的數據集。k參數指定了將數據應用到多少個聚類上。咱們的例子中,.kmeans2(...)最多運行30個循環(iter參數);若是那時方法尚未收斂,其也會中止,並返回最後一次的估算結果。
參考,MLPy也提供了一個估算k均值模型的方法:http://mlpy.sourceforge.net/docs/3.5/cluster.html#k-means

4.4爲k均值算法找到最優的聚類數

不少時候,你不知道數據集中該有多少聚類。對於二維或三維數據,你能夠畫一畫數據集,肉眼識別出聚類。然而,對於高維數據,在一張圖表中繪製都不太可能,因此這種方法就不太可行了。
在本技巧中,咱們會展現如何爲k均值模型找到最優的聚類數。咱們將使用Davis-Bouldin指標來評估不一樣聚類數下k均值模型的表現。目標是當這個指標達到一個最小值時中止。
需裝好pandas、NumPy和Scikit。

爲了找到最優的聚類數,咱們編寫了findOptimalClusterNumber(...)方法。整體而言,估算k均值模型的算法並無改變——只不過用findOptimalClusterNumber(...)方法替換了前面使用的findClusters_kmeans(...)方法(clustering_kmeans_search.py文件):

# find the optimal number of clusters; that is, the number of
# clusters that minimizes the Davis-Bouldin criterion
optimal_n_clusters = findOptimalClusterNumber(selected)

findOptimalClusterNumber(...)方法定義以下:

 1 def findOptimalClusterNumber(
 2         data, 
 3         keep_going = 1, 
 4         max_iter = 30
 5     ):
 6     '''
 7         A method that iteratively searches for the 
 8         number of clusters that minimizes the Davis-Bouldin
 9         criterion
10     '''
11     # the object to hold measures 
12     measures = [666]
13 
14     # starting point
15     n_clusters = 2 
16     
17     # counter for the number of iterations past the local 
18     # minimum
19     #超過局部最小值的循環次數的計數器
20     keep_going_cnt = 0
21     stop = False   # flag for the algorithm stop
22     
23     def checkMinimum(keep_going):
24         '''
25             A method to check if minimum found
26         '''
27         global keep_going_cnt # access global counter
28 
29         # if the new measure is greater than for one of the 
30         # previous runs
31         if measures[-1] > np.min(measures[:-1]):
32             # increase the counter
33             keep_going_cnt += 1
34 
35             # if the counter is bigger than allowed 
36             if keep_going_cnt > keep_going:
37                 # the minimum is found
38                 return True
39         # else, reset the counter and return False
40         else:
41             keep_going_cnt = 0
42 
43         return False
44 
45     # main loop 
46     # loop until minimum found or maximum iterations(循環) reached
47     while not stop and n_clusters < (max_iter + 2):
48         # cluster the data
49         cluster = findClusters_kmeans(data, n_clusters)
50 
51         # assess the clusters effectiveness
52         labels = cluster.labels_
53         centroids = cluster.cluster_centers_
54 
55         # store the measures(指標)   
56         measures.append(
57             hlp.davis_bouldin(data,labels, centroids)
58         )
59 
60         # check if minimum found
61         stop = checkMinimum(keep_going)
62 
63         # increase the iteration
64         n_clusters += 1
65 
66     # once found -- return the index of the minimum
67     return measures.index(np.min(measures)) + 1

原理:使用findOptimalClusterNumber(...)方法時,傳入的第一個參數是數據集:顯然,沒有數據時,什麼模型也估算不出。keep_going參數指的是,若是當前模型的Davis-Bouldin指標比已有的最小值要大,那還要尋找多少個循環。經過這種方式,咱們能夠(必定程度上)避免找到局部最小值而非全局最小值的狀況:
邀月工做室

max_iter參數指定了最多構建多少個模型;咱們的方法以n_clusters=2構建k均值模型開始,而後循環執行,直到找到一個全局最小值或是達到了最大的循環次數。
咱們的findOptimalClusterNumber(...)方法以定義運行的參數開始。
measures列表用於存儲估算模型的Davis-Bouldin指標;因爲咱們的目標是找到最小的Davis-Bouldin指標,咱們指定了一個大數,以避免這個值成了咱們的最小值。
n_clusters定義了起始點:第一個k均值模型將數據分紅兩個聚類。
keep_going_cnt用來指定Davis-Bouldin指標比以前的最小值要大時,還要走多少個循環。之前面展現的圖爲例,咱們的方法不會終止在某個局部最小值;儘管循環10和循環13的Davis-Bouldin指標(分別)比循環9和循環12小,可是聚類數爲11和14的模型有更低的值。在這個例子中,咱們在聚類數爲15和16時終止,此時Davis-Bouldin指標更大。
標識變量stop用於控制主循環的執行。咱們的while循環直到找到最小值或者達到了最大循環次數。
一我的讀代碼時,看到當不爲stop而且n_clusters<(max_iter+2)時while循環不會終止,他能輕鬆地將代碼翻譯成天然語言,也能體會到Python語法之美。我以爲,這提升了代碼的可讀性與可維護性,同時也使代碼更準確。
循環以估算預約義聚類數的模型開始:

# cluster the data
  cluster = findClusters_kmeans(data, n_clusters)

估算出模型後,咱們獲得估算的標籤和中心,使用.davis_bouldin(...)方法計算Davis-Bouldin指標,如本章第一個技巧中那樣。這個指標經過append(...)方法附到指標列表。
如今,咱們要檢查新估算的模型,其Davis-Bouldin指標是否比以前估算的模型都小。咱們使用checkMinimum(...)方法。這個方法只能由findOptimalClusterNumber(...)方法訪問:

 1 def checkMinimum(keep_going):
 2     '''
 3         A method to check if minimum found
 4     '''
 5     global keep_going_cnt # access global counter
 6 
 7     # if the new measure is greater than for one of the 
 8     # previous runs
 9     if measures[-1] > np.min(measures[:-1]):
10         # increase the counter
11         keep_going_cnt += 1
12 
13         # if the counter is bigger than allowed 
14         if keep_going_cnt > keep_going:
15             # the minimum is found
16             return True
17     # else, reset the counter and return False
18     else:
19         keep_going_cnt = 0
20 
21     return False

首先,咱們將keep_going_cnt定義爲一個全局變量。這樣咱們不須要將變量傳入check Minimum(...)方法,而且能夠全局追蹤計數器。在我看來,這樣有益於代碼的可讀性。
而後,咱們將新近估算的模型與已有的最小值比較;咱們使用NumPy的.min(...)方法獲得最小值。若是當前的Davis-Bouldin指標比已有的最小值大,咱們就給keep_going_cnt計數器增1,並檢查是否超過了keep_going這個循環次數限制——超過了就返回True。返回True意味着咱們找到了全局最小值(在keep_going限制的前提下)。然而,若是新估算的Davis-Bouldin指標更小,咱們先將keep_going_cnt計數器重置爲0,返回False。若是沒有超過keep_going的限制,咱們也返回False。
知道了咱們找沒找到全局最小值以後,咱們將n_clusters增1。
當checkMinimum(...)方法返回True或者n_clusters超過max_iter+2時循環停止。咱們的算法一開始用兩個聚類來估算k均值模型,因此max_iter要加上2。
一旦停止while循環,咱們返回找到的最小值的索引加1這個值。要加上一個1,由於n_
一旦停止while循環,咱們返回找到的最小值的索引加1這個值。要加上一個1,由於n_cluster=2時Davis-Bouldin指標的索引是1。
列表的索引值從0開始。
如今咱們能夠用最優的聚類數來估算模型,並打印指標:

# cluster the data
cluster = findClusters_kmeans(selected, optimal_n_clusters)

# assess the clusters effectiveness評估聚類的有效性
labels = cluster.labels_
centroids = cluster.cluster_centers_

hlp.printClustersSummary(selected, labels, centroids)
/* 

The method findClusters_kmeans took 1.97 sec to run.
The method findClusters_kmeans took 0.56 sec to run.
The method findClusters_kmeans took 0.76 sec to run.
The method findClusters_kmeans took 0.90 sec to run.
The method findClusters_kmeans took 1.01 sec to run.
The method findClusters_kmeans took 1.16 sec to run.
The method findClusters_kmeans took 1.31 sec to run.
The method findClusters_kmeans took 1.34 sec to run.
The method findClusters_kmeans took 1.55 sec to run.
The method findClusters_kmeans took 1.69 sec to run.
The method findClusters_kmeans took 1.82 sec to run.
The method findClusters_kmeans took 2.08 sec to run.
The method findClusters_kmeans took 2.11 sec to run.
The method findClusters_kmeans took 2.40 sec to run.
The method findClusters_kmeans took 2.49 sec to run.
The method findClusters_kmeans took 3.03 sec to run.
The method findClusters_kmeans took 2.98 sec to run.
The method findClusters_kmeans took 3.05 sec to run.
Optimal number of clusters: 17
The method findClusters_kmeans took 2.96 sec to run.
Pseudo_F:  11493.774263351304
Davis-Bouldin:  0.9516597767023216
*/


以前提到,要繪製數據的聚類並不容易,不過,探索數據可視化時,畫成對的圖有時會頗有幫助(clustering_kmeans_search_alternative.py文件):

 1 def plotInteractions(data, n_clusters):
 2     '''
 3         Plot the interactions between variables
 4     '''
 5     # cluster the data
 6     cluster = findClusters_kmeans(data, n_clusters)
 7 
 8     # append the labels to the dataset for ease of plotting
 9     data['clus'] = cluster.labels_
10 
11     # prepare the plot
12     ax = sns.pairplot(selected, hue='clus')
13 
14     # and save the figure
15     ax.savefig(
16         '../../Data/Chapter04/k_means_{0}_clusters.png' \
17         .format(n_clusters)
18     )

咱們使用Seaborn和Matplotlib繪製互動。首先,咱們用預先定義的n_clusters估算模型。而後,咱們將聚類加到數據集中。使用Seaborn的.pairplot(...)方法,咱們建立了一張圖表,遍歷每一個特徵,畫出響應的互動。最後,咱們保存這張圖表。
14個聚類的結果(爲了可讀性,限定3個特徵),看起來相似這樣:
邀月工做室
Tips:

/*
The method findClusters_kmeans took 3.43 sec to run.
D:\Java2018\practicalDataAnalysis\Codes\Chapter04\clustering_kmeans_search_alternative.py:37: SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  data['clus'] = cluster.labels_
D:\tools\Python37\lib\site-packages\statsmodels\nonparametric\kde.py:487: RuntimeWarning: invalid value encountered in true_divide
  binned = fast_linbin(X, a, b, gridsize) / (delta * nobs)
D:\tools\Python37\lib\site-packages\statsmodels\nonparametric\kdetools.py:34: RuntimeWarning: invalid value encountered in double_scalars
  FAC1 = 2*(np.pi*bw/RANGE)**2
   */
   

關於DataFrame的loc方法:

/*    
   Compare these two access methods:
In [340]: dfmi['one']['second']
Out[340]:
0    b
1    f
2    j
3    n
Name: second, dtype: object
In [341]: dfmi.loc[:, ('one', 'second')]
Out[341]:
0    b
1    f
2    j
3    n
Name: (one, second), dtype: object
 */

即便維度很低,經過肉眼要看出數據集中應該有多少聚類也不容易。對於其餘數據,建立這樣一張圖表也許會有好處。這個方法的問題在處理離散或類別的變量時會特別明顯。

使用mean shift聚類模型發現聚類

mean shift模型是一個相似於尋找中心(或最大密度)的方法。與k均值不一樣,這個方法不要求指定聚類數——這個模型根據數據中找到的密度中心的數目返回聚類的數目。
需裝好pandas和Scikit。
咱們估算的打開方式相似以前的模型——從讀入數據集和限制特徵數開始。而後咱們用findClusters_meanShift(...)方法估算模型(clustering_meanShift.py文件):

 1 def findClusters_meanShift(data):
 2     '''
 3         Cluster data using Mean Shift method
 4         使用Mean Shift方法聚類數據
 5     '''
 6     bandwidth = cl.estimate_bandwidth(data,
 7         quantile=0.25, n_samples=500)
 8 
 9     # create the classifier object
10     meanShift = cl.MeanShift(
11         bandwidth=bandwidth,
12         bin_seeding=True
13     )
14 
15     # fit the data
16     return meanShift.fit(data)
/*
The method findClusters_meanShift took 20.53 sec to run.
Pseudo_F:  1512.7080324697126
Davis-Bouldin:  1.736359716302394
Silhouette score:  0.2172934930829158
*/

原理:咱們首先估算帶寬(estimate_bandwidth(...))。帶寬會用在這個方法的RBF核中。
咱們使用SVM分類技巧中的徑向基函數:參考本書3.5節。
estimate_bandwidth(...)方法至少要傳入數據做爲參數。
因爲方法中使用的算法,隨着觀測值數目的增加,呈二次方複雜度,因此對於有不少觀測值的樣本,最好限制記錄數目;使用n_samples吧。
quantile參數決定了從什麼地方切分傳入.MeanShift(...)方法的核的樣本。
quantile取0.5就意味着中位數。
如今能夠估算模型了。咱們傳入bandwidth和(可選的)bin_seeding參數,構建模型對象。
若是bin_seeding置爲True,核初始位置就設到了全部數據點的(使用bandwidth)離散化的羣。這個參數置爲True會加快算法,由於要初始化的核種子變少了;隨着觀測值成百上千地增加,這個會帶來顯著的加速。
談到了估算的速度,這個方法比任何k均值都會慢得多(即使有數十個聚類)。在咱們的機器上,估算會比k均值慢5到20秒:平均值在13秒左右。
要是對Mean Shift算法的原理感興趣,做者推薦這篇文檔:http://homepages.inf.ed.ac.uk/rbf/CVonline/LOCAL_COPIES/TUZEL1/MeanShift.pdf
參考本書3.5節。

4.6
使用c均值構建模糊聚類模型

k均值和mean shift聚類算法將觀測值歸到大相徑庭的聚類:一個觀測值能夠且只能被歸到一個類似樣本的聚類中。對於離散可分的數據集,這個也許是對的,但數據若是有重疊,將它們放進同一個桶裏就很困難了。畢竟,咱們的世界並非非黑即白,咱們能看到無數色彩。
c均值聚類模型容許每一個觀測值屬於不止一個聚類,各從屬關係有一個權重:對每一個觀測值來講,它所屬的全部聚類對應的權重之和必須爲1。
須要pandas和Scikit-Fuzzy模塊。Scikit-Fuzzy模塊通常不在Anaconda發行版中,因此你須要自行安裝。
>pip install Scikit-Fuzzy

/* Looking in indexes: https://pypi.tuna.tsinghua.edu.cn/simple
.......
Successfully built Scikit-Fuzzy
Installing collected packages: decorator, networkx, Scikit-Fuzzy
Successfully installed Scikit-Fuzzy-0.4.2 decorator-4.4.1 networkx-2.4
FINISHED
 */

Scikit已經提供估算c均值的算法。調用方法和以前的略有不一樣(clustering_cmeans.py文件):

 1 import skfuzzy.cluster as cl
 2 import numpy as np
 3 
 4 @hlp.timeit
 5 def findClusters_cmeans(data):
 6     '''
 7         Cluster data using fuzzy c-means clustering
 8         algorithm
 9     '''
10     # create the classifier object
11     return cl.cmeans(
12         data,
13         c = 5,          # number of clusters 聚類數
14         m = 2,          # exponentiation factor 指數因子
15         
16         # stopping criteria
17         error = 0.01,
18         maxiter = 300
19     )

原理:如同以前的技巧,咱們首先載入數據,選擇咱們想用來估算模型的列。
而後調用findClusters_cmeans(...)方法估算模型。以前的方法都是建立一個未訓練的模型,而後用.fit(...)方法應用數據,與此不一樣,.cmeans(...)方法是建立時就用傳入的數據(及其餘參數)估算模型了。
c參數指定了要應用多少個聚類,m參數指定了在每次循環時應用到成員關係函數的指數因子。
注意你要指定的聚類數,要是指定得太多,計算指標時會由於有些聚類沒有成員致使錯誤!若是你遇到了IndexError,估算停止,那麼在不丟失精確度的前提下減小聚類數吧。
咱們明確了終止條件。若是當前循環與前一次循環(在成員關係函數上的變化)差別小於0.01,咱們的模型就會終止循環。
這個模型返回一系列對象。centroids是五個聚類的座標。u存着每一個觀測值的成員值;結構以下:

/*
The method findClusters_cmeans took 2.25 sec to run.
[[0.20158559 0.09751069 0.07842359 ... 0.16641284 0.16997486 0.18780077]
 [0.14041632 0.05272408 0.04176255 ... 0.25766411 0.13334598 0.13312636]
 [0.15019893 0.05824498 0.04623189 ... 0.14150986 0.2692789  0.14128872]
 [0.13702899 0.05074051 0.04020164 ... 0.28432326 0.27960719 0.38820815]
 [0.37077018 0.74077974 0.79338032 ... 0.15008993 0.14779308 0.14957599]]
Pseudo_F:  8340.883112129994
Davis-Bouldin:  1.3062853046748828
Silhouette score:  0.35108693925427953
 */

若是將每一行的全部列值加起來,你會發現(果真)一直是1。
剩下的返回對象咱們不用太操心:u0是每一個觀測值的初始成員種子,d是最終的歐幾里得距離矩陣,jm是目標函數的變動歷史,p是估算模型所用的循環數,fpc是模糊劃分係數(fuzzy partition coefficient)。
爲了計算咱們的指標,咱們須要將聚類賦給每一個觀測值。咱們用NumPy的.argmax(...)方法來作這事:

 # assess the clusters effectiveness
labels = [
    np.argmax(elem) for elem in u.transpose()
]

這個方法返回列表中最大元素的索引。咱們的u矩陣的初始分佈是u_cluster x n_sample,咱們須要先用.transpose()方法轉置u矩陣,以便按行循環,每一行都是咱們樣本中的觀測值。

4.7
使用層次模型聚類數據

層次聚類模型的目標是構建聚類的分層。從概念上講,你能夠理解成是一棵聚類的決策樹:基於聚類之間的類似度(或相異度),它們聚合(或切分)成更抽象(或更具體)的聚類。聚合的作法常被稱做自底向上,而切分的作法被稱做自頂向下。
需裝好pandas、SciPy和PyLab。
>pip install pylab

/* Looking in indexes: https://pypi.tuna.tsinghua.edu.cn/simple
......
Successfully built pyusb PyVISA
Installing collected packages: pyusb, PyVISA, PyVISA-py, pylab
Successfully installed PyVISA-1.10.1 PyVISA-py-0.3.1 pylab-0.0.2 pyusb-1.0.2
FINISHED */

聚合算法的複雜度是O(n3),對於大規模的數據集來講,層次聚類可能會極慢。估算咱們的模型時,咱們使用的是單連接算法,其算法複雜度更好,是O(n2),不過數據集很大時依然會很慢(clustering_hierarchical.py文件):

1 import pylab as pl
2     def findClusters_link(data):
3     '''
4         Cluster data using single linkage hierarchical
5         clustering
6         使用單連接分層聚類數據
7     '''
8     # return the linkage object
9     return cl.linkage(data, method='single')

 

原理:代碼簡單之極:只需調用SciPy的.linkage(...)方法,傳入數據,指定方法。
詳細說來,.linkage(...)方法是基於選擇的方法,根據特定的距離維度聚合(或切分)聚類;method='single'根據兩個聚類之間每一個點的最小距離(因此是O(n2))聚合聚類。
關於全部指標的列表,參考SciPy文檔:http://docs.scipy.org/doc/scipy/reference/generated/scipy.cluster.hierarchy.linkage.html#scipy.cluster.hierarchy.linkage
咱們能夠將聚合(或拆分)以樹的形式具象化:

 1 ####import pylab as pl
 2 import matplotlib.pylab as pl
 3 
 4 # cluster the data
 5 cluster = findClusters_ward(selected)
 6 
 7 # plot the clusters
 8 fig  = pl.figure(figsize=(16,9))
 9 ax   = fig.add_axes([0.1, 0.1, 0.8, 0.8])
10 dend = cl.dendrogram(cluster, truncate_mode='level', p=20)
11 ax.set_xticks([])
12 ax.set_yticks([])
13 
14 # save the figure
15 fig.savefig(
16     '../../Data/Chapter04/hierarchical_dendrogram.png',
17     dpi=300
18 )

首先,咱們使用PyLab和.add_axes(...)建立一幅圖。傳入函數的列表是圖的座標:[x,y,width,height],數值已調整到0和1之間,1意味着100%。.dendrogram(...)方法輸入全部的連接,聚類並建立圖形。咱們不但願輸出全部的連接,因此咱們在p=20層時切掉了樹。咱們也不須要座標標籤,因此咱們使用.set_{x,y}ticks(...)方法關掉了它們。
對於咱們的數據集,你的樹看起來相似這樣:
邀月工做室
你能夠看出來,越接近頂端,你獲得的聚類越不清晰(發生了更多的聚合)。
咱們也可使用MLPy估算分層聚類模型(clustering_hierarchical_alternative.py文件):

 1 def findClusters_ward(data):
 2     '''
 3         Cluster data using Ward's hierarchical clustering
 4     '''
 5     # create the classifier object
 6     ward = ml.MFastHCluster(
 7         method='ward'
 8     )
 9 
10     # fit the data
11     ward.linkage(data)
12 
13     return ward
14 
15 # the file name of the dataset
16 r_filename = '../../Data/Chapter04/bank_contacts.csv'
17 
18 # read the data
19 csv_read = pd.read_csv(r_filename)
20 
21 # select variables
22 selected = csv_read[['n_duration','n_nr_employed',
23         'prev_ctc_outcome_success','n_euribor3m',
24         'n_cons_conf_idx','n_age','month_oct',
25         'n_cons_price_idx','edu_university_degree','n_pdays',
26         'dow_mon','job_student','job_technician',
27         'job_housemaid','edu_basic_6y']]
28 
29 # cluster the data
30 cluster = findClusters_ward(selected)
31 
32 # assess the clusters effectiveness
33 labels = cluster.cut(20)
34 centroids = hlp.getCentroids(selected, labels)

咱們使用.MFastHCluster(...)方法,傳入ward做爲參數。分層聚類的沃德方法使用的是沃德方差最小化算法。.linkage(...)方法可看做k均值.fit(...)方法的等價物;它將模型用到數據上,找出全部的連接。
估算完成後,咱們使用.cut(...)方法在第20層剪掉這棵樹,以獲得可管理的聚類數。返回的對象包括數據集中每一個觀測值的聚類成員索引。
沃德聚類方法不返回聚類的中心(由於隨着咱們切樹的層次不一樣,中心會變化),爲了能使用評估方法,咱們寫了一個方法,可返回指定標籤的中心:

 1 def getCentroids(data, labels):
 2     '''
 3         Method to get the centroids of clusters in clustering
 4         models that do not return the centroids explicitly
 5     '''
 6     # create a copy of the data
 7     data = data.copy()
 8 
 9     # apply labels
10     data['predicted'] = labels
11 
12     # and return the centroids
13     return np.array(data.groupby('predicted').agg('mean')) 

首先,咱們爲數據建立一份本地的副本(由於咱們不但願修改傳入方法的這個對象),並建立一個新列,叫做predicted。而後爲predicted列去重後的每一層計算平均值,並投射爲np.array(...)。
如今有了中心,咱們能夠評估模型的表現了。
若是你對分層聚類感興趣,我建議閱讀這篇文章:http://www.econ.upf.edu/~michael/stanford/maeb7.pdf

4.8使用DBSCAN和BIRCH算法發現潛在的訂閱者

DBSCAN(Density-based Spatial Clustering of Applications with Noise,基於密度的空間聚類)和BIRCH(Balanced Iterative Reducing and Clustering using Hierarchies,利用層次方法的平衡迭代規約和聚類)是首批能有效處理帶噪音的數據的算法。這裏的噪音能夠理解爲,數據集中與其餘部分相比,所放地方不恰當的點;DBSCAN將這種觀測值放到一個未歸類的桶,而BIRCH認爲它們是離羣值,直接挪走。
需裝好pandas和Scikit。
兩種算法都能在Scikit中找到。要使用DBSCAN,使用clustering_dbscan.py文件中的代碼:

 1 import sklearn.cluster as cl
 2 import numpy as np
 3 
 4 @hlp.timeit
 5 def findClusters_DBSCAN(data):
 6     '''
 7         Cluster data using DBSCAN algorithm 使用DBScan算法 聚類數據
 8     '''
 9     # create the classifier object
10     dbscan = cl.DBSCAN(eps=1.2, min_samples=200)
11 
12     # fit the data
13     return dbscan.fit(data)
14 
15 # cluster the data
16 cluster = findClusters_DBSCAN(selected)
17 
18 # assess the clusters effectiveness
19 labels = cluster.labels_ + 1
20 centroids = hlp.getCentroids(selected, labels)

邀月工做室

至於BIRCH模型,查看clustering_birch.py文件中的程序:

 1 import sklearn.cluster as cl
 2 import numpy as np
 3 
 4 @hlp.timeit
 5 def findClusters_Birch(data):
 6     '''
 7         Cluster data using BIRCH algorithm
 8     '''
 9     # create the classifier object
10     birch = cl.Birch(
11         branching_factor=100,
12         n_clusters=4,
13         compute_labels=True,
14         copy=True
15     )
16 
17     # fit the data
18     return birch.fit(data)
19 
20 
21 # cluster the data
22 cluster = findClusters_Birch(selected)
23 
24 # assess the clusters effectiveness
25 labels = cluster.labels_
26 centroids = hlp.getCentroids(selected, labels)
/* 
The method findClusters_Birch took 0.99 sec to run.
Pseudo_F:  1733.8811847551842
Davis-Bouldin:  1.2605820279777722
*/

原理:要估算DBSCAN,咱們使用Scikit的.DBSCAN(...)方法。這個方法能夠接受不少參數。eps控制的是認爲兩個樣本鄰接的最大距離。min_samples參數控制的是鄰接關係;一旦超過這個閾值,至少有這麼多鄰居的點就被當成一個核心點。
如前所述,DBSCAN中的殘餘值(離羣值)會歸到一個未歸類的桶,返回時標籤爲-1。在能夠評估聚類方法的表現前,咱們須要移動標籤,使它們從0開始(0是未聚類的數據點)。這個方法不返回中心的座標,因此咱們使用前一技巧中的.getCentroids(...)方法。
估算BIRCH也不費什麼勁(就咱們這部分來講)。咱們使用Scikit的.Birch(...)方法。branching_factor參數控制的是樹的父節點中最多有多少子聚類(或觀測值);超過這個值時,聚類(以及子聚類)會遞歸拆分。n_clusters參數告訴方法咱們想將數據聚成多少個類。將compute_label設置成True是讓方法在結束時爲每一個觀測值準備並存儲標籤。
BIRCH算法直接丟棄離羣值。若是你不但願方法修改原始數據,能夠將copy參數設置爲True:這會複製原始數據。
參考:
DBSCAN的論文:https://www.aaai.org/Papers/KDD/1996/KDD96-037.pdf
BIRCH的論文:http://www.cs.sfu.ca/CourseCentral/459/han/papers/zhang96.pdf
建議也閱讀.DBSCAN(...)和.Birch(...)方法的文檔:

http://scikit-learn.org/stable/modules/generated/sklearn.cluster.DBSCAN.html#sklearn.cluster.DBSACN

http://scikit-learn.org/stable/modules/generated/sklearn.cluster.Birch.html#sklearn.cluster.Birch

 

  第4章完。

 python學習筆記-目錄索引

 

隨書源碼官方下載:
http://www.hzcourse.com/web/refbook/detail/7821/92

相關文章
相關標籤/搜索