[Python] [機器學習] 基礎聚類算法(K-means、AHC、DBSCAN)簡介及可視化代碼

之前寫的入門級介紹,有點久遠有些ref找不着了

簡介

  • 根據在數據中發現的描述對象及其關係的信息,將數據對象分組。
  • 對大量未知標註的數據集,按數據的內在相似性將數據集劃分爲多個類別,使類別內的數據相似度極大而類別間的數據相似度極小。

目標

  • 組內的對象相互之間是相似的,不同組中的對象是不同的。

作用

  1. 易於理解

    🌰生物學:界門綱目科屬種

    🌰信息檢索:搜索引擎返回結果分類

    🌰商業:對客戶進行分類

  2. 更加實用

    🌰降維:樣本數的降維、特徵數的降維

    🌰發現最近鄰:減少計算量

類型

分類 解釋
層次的
vsvs
劃分的
層次的: 嵌套的,樹狀,可在不同層次上得到不同數目的類別。
劃分的: 非嵌套的。每個樣本恰在一個子集中。
互斥的
vsvs
重疊的
vsvs
模糊的
互斥的: 每個樣本僅在一個類中。
重疊的: 一個樣本可同時屬於多個類。
模糊的: 每個樣本以0(絕對不屬於)和1(絕對屬於)之間的權值屬於每個類。
完全的
vsvs
部分的
完全的: 每個樣本至少屬於一個類。
部分的: 部分樣本不屬於任何一個類,如噪聲、離羣點等。

聚類vs分類

import random
import matplotlib.pyplot as plt
import pandas as pd
from sklearn import datasets
from pandas import DataFrame

# 造數據
# 圈圈
noisy_circles=datasets.make_circles(n_samples=1000,factor=.5,noise=.05)
circle = DataFrame()
circle['x1'] = noisy_circles[0][:,0]
circle['x2'] = noisy_circles[0][:,1]
circle['label'] = noisy_circles[1]
# 堆堆
def random_sample(num, mean, st):
    '''
    num:個數
    mean:均值
    st:標準差
    '''
    sample = []
    i = 0
    while i < num:
        sample.append(random.gauss(mean, st))
        i += 1
    return sample
    
num = 300
sample_0 = pd.DataFrame({'x1':random_sample(num, 4, 0.5), 'x2': random_sample(num, 4, 0.5), 'y': [0 for i in range(num)]})
sample_1 = pd.DataFrame({'x1':random_sample(num, 2, 0.5), 'x2': random_sample(num, 2, 0.5), 'y': [1 for i in range(num)]})
sample = pd.concat([sample_0, sample_1])

# 聚類vs分類
plt.figure(figsize=(16,8))
plt.subplot(1,2,1)
plt.scatter(sample_0['x1'], sample_0['x2'], marker = 'o', c = 'k')
plt.scatter(sample_1['x1'], sample_1['x2'], marker = 'o', c = 'k')
plt.title('聚類(無監督)', fontsize =25)

plt.subplot(1,2,2)
plt.scatter(sample_0['x1'], sample_0['x2'], marker = 'o', c = 'b')
plt.scatter(sample_1['x1'], sample_1['x2'], marker = 'o', c = 'r')
plt.title('分類(有監督)', fontsize =25)
plt.show()

在這裏插入圖片描述

K-means

[K-means] 僞代碼

#####################################

  1. 選擇kk個點作爲初始質心。
  2. repeat
  3. ​ 將每個點指派到最近的質心,形成kk個簇。
  4. ​ 重新計算每個簇的質心。
  5. until 質心不發生變化。

#####################################

[K-means] 過程詳解

ref: GitHub: snippet-code

# 修改
data = sample
repeat = 20

# ================開始================
# 導入數據
points = [[data.iloc[i].x1, data.iloc[i].x2] for i in range(len(data))]

# 初始質心
currentCenter1 = [data.x1.min() + (data.x1.max()-data.x1.min())*random.random(), data.x1.min() + (data.x2.max()-data.x2.min())*random.random()]
currentCenter2 = [data.x1.min() + (data.x1.max()-data.x1.min())*random.random(), data.x1.min() + (data.x2.max()-data.x2.min())*random.random()]

# 記錄每次迭代後每個簇的質心的更新軌跡
center1 = [currentCenter1]; center2 = [currentCenter2]

# 丟點圖
plt.figure(figsize=(18,18))
ax = plt.subplot(3,3, 1)
change_ax(ax)
plt.title('step 0', fontsize = 15)
plt.plot([eachpoint[0] for eachpoint in points], [eachpoint[1] for eachpoint in points], '.', color='grey')
plt.scatter([eachcenter[0] for eachcenter in center1], [eachcenter[1] for eachcenter in center1], marker='x', c='b', s=100)
plt.scatter([eachcenter[0] for eachcenter in center2], [eachcenter[1] for eachcenter in center2], marker='x', c='g', s=100)

# 兩個簇
group1 = []; group2 = []

for runtime in range(repeat):
    group1 = []; group2 = []
    for eachpoint in points:
        # 計算每個點到質心的距離
        distance1 = pow(abs(eachpoint[0]-currentCenter1[0]),2) + pow(abs(eachpoint[1]-currentCenter1[1]),2)
        distance2 = pow(abs(eachpoint[0]-currentCenter2[0]),2) + pow(abs(eachpoint[1]-currentCenter2[1]),2)
        
        # 將該點指派到離它最近的質心所在的簇
        mindis = min(distance1,distance2)
        if(mindis == distance1):
            group1.append(eachpoint)
        else:
            group2.append(eachpoint)
            
    if runtime < 5 or runtime > (repeat - 4): 
    	# 只可視化前五次最後三次
        if runtime < 5:
            ax = plt.subplot(3,3, runtime+2)
            change_ax(ax)
            plt.title('step %d'%(runtime+1), fontsize = 15)
        else:
            ax = plt.subplot(3,3, 10-repeat+runtime)
            change_ax(ax)
            plt.title('step %d'%(runtime+1), fontsize = 15)

        # 打印所有的點,用顏色標識該點所屬的簇
        plt.plot([eachpoint[0] for eachpoint in group1], [eachpoint[1] for eachpoint in group1], '.', color='lightsteelblue')#, linewidth=1)
        plt.plot([eachpoint[0] for eachpoint in group2], [eachpoint[1] for eachpoint in group2], '.', color='lightgreen')#, linewidth=0.3)

        # 畫質心
        plt.plot([eachcenter[0] for eachcenter in center1], [eachcenter[1] for eachcenter in center1],'bx')
        plt.plot([eachcenter[0] for eachcenter in center1], [eachcenter[1] for eachcenter in center1], 'b--', linewidth=0.5)

        plt.plot([eachcenter[0] for eachcenter in center2], [eachcenter[1] for eachcenter in center2],'gx')
        plt.plot([eachcenter[0] for eachcenter in center2], [eachcenter[1] for eachcenter in center2], 'g--', linewidth=0.5)

    # 指派完所有的點後,更新每個簇的質心
    currentCenter1 = [sum([eachpoint[0] for eachpoint in group1])/len(group1),sum([eachpoint[1] for eachpoint in group1])/len(group1)]
    currentCenter2 = [sum([eachpoint[0] for eachpoint in group2])/len(group2),sum([eachpoint[1] for eachpoint in group2])/len(group2)]

     # 記錄該次對質心的更新
    center1.append(currentCenter1)
    center2.append(currentCenter2)
    

plt.show()

在這裏插入圖片描述

[K-means] 初始點的選擇

  1. 隨機選取

  2. kmeans++kmeans++

    ​ 在選取第一個聚類中心(n=1)(n=1)時同樣通過隨機的方法。

    ​ 假設已經選取了nn個初始聚類中心(0&lt;n&lt;k)(0&lt;n&lt;k),則在選取第n+1n+1個聚類中心時:距離當前nn個聚類中心越遠的點會有更高的概率被選爲第n+1n+1個聚類中心。

  3. 結合其他算法

    ​ 選用層次聚類或CanopyCanopy算法進行初始聚類,然後從kk個類別中分別隨機選取kk個點,來作爲kmeanskmeans的初始聚類中心點。

[K-means] 距離的度量

  1. 曼哈頓距離
    d(x,y)=k=1nxkyk d(x,y)=\sum_{k=1}^n|x_k-y_k|

  2. 歐幾里得距離
    d(x,y)=k=1n(xkyk)2 d(x,y)=\sum_{k=1}^n(x_k-y_k)^2

  3. 餘弦相似度
    d(x,y)=cos(x,y)=xyx y d(x,y)=cos(x,y)=\frac{x\cdot y}{||x||\ ||y||}
    其中,"\cdot"表示向量點積,x||x||是向量x的長度。

  4. BregmanBregman散度
    d(x,y)=ϕ(x)ϕ(y)ϕ(y),(xy) d(x,y)=\phi(x)-\phi(y)-\langle\nabla\phi(y),(x-y)\rangle
    其中,ϕ()\phi(\cdot)是一個嚴格的凸函數,ϕ(y)\nabla\phi(y)是在yy上計算的ϕ\phi的梯度,xyx-yxxyy的向量差,而ϕ(y),(xy)\langle\nabla\phi(y),(x-y)\rangle是兩者的內積。

[K-means] 質心的選擇

鄰近度函數 質心
曼哈頓距離 中位數
平方歐幾里得距離 均值
餘弦 均值
BregmanBregman散度 均值

[K-means] 參數確定

K值確定

  1. 根據實際情況確定

    🌰設計新款時確定潛在客戶(新款數已知)

  2. 肘部法則

    ​ 肘部法則會把不同kk值的成本函數值畫出來。隨着kk值的增大,平均畸變程度會減小;每個類包含的樣本數會減少,於是樣本離其質心會更近。

    ​ 但是,隨着kk值繼續增大,平均畸變程度的改善效果會不斷減低。k值增大過程中,畸變程度的改善效果下降幅度最大的位置對應的kk值就是肘部。
    Cost  Function J=i=1kxCid(x,ci) Cost\ \ Function:\ J=\sum_{i=1}^k\sum_{x\in C_i}d(x,c_i)

from sklearn.cluster import KMeans
cri = pd.DataFrame(columns=['k', 'j'])
for i in range(1,11):
    km = KMeans(n_clusters=i)
    km.fit(sample[['x1', 'x2']])
    cri.loc[i] = [i, km.inertia_]
   
plt.figure(figsize=(13,7))
plt.plot(cri['k'], cri['j'])
plt.title("肘部法則", fontsize = 25)
plt.xlabel('k(聚類個數)', fontsize =15)
plt.ylabel('損失函數', fontsize = 15)
plt.show()

在這裏插入圖片描述

  1. 與層次聚類結合

    ​ 首先採用層次凝聚算法決定結果粗的數目,並找到一個初始聚類,然後用迭代重定位來改進該聚類。

  2. 穩定性方法、系統演化方法、CalinskiHarabaszCalinski-Harabasz準則法、輪廓係數法等(參考從零開始實現Kmeans聚類算法kmeans算法原理以及實踐操作(多種k值確定以及如何選取初始點方法)

[K-means] 優缺點

優點:

  1. 算法快速、簡單
  2. 容易解釋
  3. 適用於高維

缺點:

  1. 對離羣點敏感,對噪聲點和孤立點很敏感
  2. 需要自定義kk
  3. 不同的初始點可能得到的結果完全不同
  4. 不適用於非凸的簇或大小差別很大的簇

AHC

AHCAHC(凝聚層次聚類): AgglomerativeHierarchicalAlgorithmsAgglomerative Hierarchical Algorithms

[AHC] 層次聚類類型

產生層次聚類的基本方法
在這裏插入圖片描述

凝聚的: 從點作爲個體簇開始,每一步合併兩個最接近的簇。

分裂的: 從包含所有點的某個簇開始,每一步分裂一個簇

[AHC] 僞代碼

#####################################

  1. 如果需要,計算鄰近度矩陣。
  2. repeat
  3. ​ 合併最接近的兩個簇。
  4. ​ 更新鄰近性矩陣,以反映新的簇與原來的簇之間的鄰近性。
  5. until 僅剩下一個簇。

#####################################

[AHC] 過程詳解

歐式距離矩陣(組平均):
0123       Step 1:           0123[0604104720]     12A \begin{matrix} 0&amp;1&amp;2&amp;3 \end{matrix}\ \ \ \ \ \ \ \\ Step\ 1: \ \ \ \ \ \ \ \ \ \ \ \begin{matrix} 0 \\1\\2\\3 \end{matrix} \left[ \begin{matrix} 0&amp; \\ 6&amp;0 \\ 4&amp;1&amp;0 \\ 4&amp;7&amp;2&amp;0 \end{matrix} \right] \ \ \ \ \ \Longrightarrow將1和2合併爲A

03A       Step 2:            03A[04054.50]     03B \begin{matrix} 0&amp;3&amp;A \end{matrix}\ \ \ \ \ \ \ \\ Step\ 2: \ \ \ \ \ \ \ \ \ \ \ \ \begin{matrix} 0\\3\\A \end{matrix} \left[ \begin{matrix} 0&amp; \\ 4&amp;0 \\ 5&amp;4.5&amp;0 \end{matrix} \right] \ \ \ \ \ \Longrightarrow將0和3合併爲B

AB         Step 3:             AB[04.750]     ABC \begin{matrix} A&amp;B \end{matrix}\ \ \ \ \ \ \ \ \ \\ Step\ 3: \ \ \ \ \ \ \ \ \ \ \ \ \ \begin{matrix} A\\B \end{matrix} \left[ \begin{matrix} 0&amp; \\ 4.75&amp;0 \\ \end{matrix} \right] \ \ \ \ \ \Longrightarrow將A和B合併爲C

num = 4
dist = pd.DataFrame(squareform(sample))

plt.figure(figsize=(8,5))
row_clusters = linkage(sample, method='average')
row_dendr = dendrogram(row_clusters,labels=list(range(num)))
plt.annotate('A', (9.5,0.6), fontsize=30, color='tomato')
plt.annotate('B', (30,3.6), fontsize=30, color='tomato')
plt.annotate('C', (19,4.35), fontsize=30, color='tomato')
plt.ylabel('距離', fontsize=20)
plt.xlabel('樣本', fontsize=20)
plt.tight_layout()

在這裏插入圖片描述

[AHC] 鄰近度度量

  1. MIN(單鏈): 兩個簇的兩個最近的點之間的距離。
  2. MAX(全鏈): 兩個簇的兩個最遠的點之間的距離。
  3. 組平均: 兩個簇的所有點對鄰近度的平均值。

在這裏插入圖片描述
4. Ward法: 兩個簇合並時導致的平方誤差的增量。
5. 質心法: 兩個簇質心之間的距離。

[AHC] 優缺點

優點:

  1. 不用提前確定聚類個數(想要分多少個簇都可以直接根據樹結構來得到結果)

缺點:

  1. 計算量和存儲量大
  2. 不可撤銷(簇之間不能交換對象)

DBSCAN

DBSCAN: DensityBased Spatial Clustering of Applications with NoiseDBSCAN:\ Density-Based\ Spatial\ Clustering\ of\ Applications\ with\ Noise

[DBSCAN] 僞代碼1

在這裏插入圖片描述

[DBSCAN] 過程詳解

……

[DBSCAN] 三個點

核心點: 以該點爲圓心,EpsEps半徑內有大於等於MinPtsMinPts個樣本。

邊界點: 以該點爲圓心,EpsEps半徑內僅有小於個MinPtsMinPts樣本,但在覈心點的鄰域中。

噪聲點: 以該點爲圓心,EpsEps半徑內僅有小於個MinPtsMinPts樣本,且不在覈心點的鄰域中。

[DBSCAN] 僞代碼2

#####################################

  1. 將所有點標記爲核心點、邊界點或噪聲點。
  2. 刪除噪聲點。
  3. 爲距離在EpsEps之內的所有核心點之間賦予一條邊。
  4. 每組連通的核心點形成一個簇。
  5. 將每個邊界點指派到一個與之關聯的核心點的簇中。

#####################################

[DBSCAN] 參數確定

  1. Eps:

    a. 試…

from sklearn import datasets
from sklearn.cluster import DBSCAN

def lin_dbscan(sample, eps, minpts):
    '''
    單個DBSCAN結果畫圖
    '''
    dbscan = DBSCAN(eps = eps, min_samples = minpts)
    sample['dbscan'] = dbscan.fit_predict(sample[['x1', 'x2']])
    a = pd.DataFrame(sample.dbscan.value_counts())
    clusters = a.index
    
    try:
        clusters = clusters.drop([-1])
        noise = sample[sample['dbscan'] == -1]
        ns = plt.scatter(noise['x1'], noise['x2'], marker='x')
        plt.legend([ns], ['噪聲點'], fontsize = 13, loc='lower right')
    except:
        #print('eps=%g, MinPts=%d 沒有噪聲點'%(dbscan.eps, dbscan.min_samples))
        pass
    for i in clusters:
        temp = sample[sample['dbscan'] == i]
        plt.scatter(temp['x1'], temp['x2'])
    plt.title('Eps=%g, MinPts=%d \n 分爲%d類'%(dbscan.eps, dbscan.min_samples, len(clusters)), fontsize =20)

param = {1: [0.1, 10],
            2: [0.1, 20],
            3: [0.1, 30],
            4: [0.2, 25],
            5: [0.2, 32],
            6: [0.2, 35]}

plt.figure(figsize=(16,10))
plt.subplots_adjust(hspace = 0.3)
for i in param.keys():
    plt.subplot(2,3,i)
    eps = param.get(i)[0]
    minpts = param.get(i)[1]
    lin_dbscan(circle, eps, minpts)

在這裏插入圖片描述

b. kk-距離曲線

​ 給定kk鄰域參數kk,對於數據中的每個點,計算對應的第kk個最近鄰域距離,並將數據集所有點對應的最近鄰域距離按照降序方式排序,稱這幅圖爲排序的kk距離圖,選擇該圖中第一個谷值點位置對應的k距離值設定爲EpsEps

import numpy as np

def cal_dist(a,b):
    '''
    計算兩個變量之間的距離
    '''
    c = a-b
    dist = np.sqrt(np.dot(c, c.transpose()))
    return dist

def single_sample_dist(sample, sample_no, k):
    '''
    計算單個變量的第k近鄰
    '''
    k_sample = []
    a = np.array([sample.iloc[sample_no].x1, sample.iloc[sample_no].x2])
    for i in range(len(sample)):
        b = np.array([sample.iloc[i].x1, sample.iloc[i].x2])
        k_sample.append(cal_dist(a,b))
    k_sample.sort()
    if np.mod(sample_no, 100) == 0:
        print("第%d個變量距離計算完畢!"%(sample_no))
    return k_sample[k]

def sample_dist(sample, k):
    '''
    計算所有變量的第k近鄰
    '''
    dists = []
    for i in range(len(sample)):
        dists.append(single_sample_dist(sample, i, k))
    return dists

def plot_dist(sample, k):
    '''
    畫出k近鄰的距離圖
    '''
    dists = sample_dist(sample, k)
    dists.sort()
    plt.figure(figsize=(15,8))
    plt.plot(dists)
    plt.title('%dth近鄰距離圖'%(k), fontsize = 25)
    plt.savefig('./pic/%dth近鄰距離圖.jpg'%(k))
    plt.show()
    return dists

dists = plot_dist(circle, 10)

在這裏插入圖片描述在這裏插入圖片描述
2. MinPts:MinPts≥dim+1

dimdim表示待聚類數據的維度

[DBSCAN] 優缺點

優點:

  1. 無需指定kk
  2. 可以發現任何形狀的簇
  3. 不受噪聲的影響,且可以找出數據中的噪聲

缺點:

  1. 不同參數聚類效果差異很大,參數確定比較困難
  2. 對密度不均勻、簇間距離相差很大的數據集,聚類效果比較差

不同數據集效果對比

在這裏插入圖片描述

Source: Comparing different clustering algorithms on toy datasets

其他知識點

  1. 分類效果的評價指標
  2. 應用場景
  3. 各算法的時間、空間複雜性
  4. 算法改進
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章