無監督學習(1) 聚類算法簡述與Python實現

距離度量

好的我們開始學習聚類,聚類是一種無監督手段,嘗試僅僅依靠自變量的分佈把數據進行分類。很多聚類算法都基於向量的歐式距離進行,所以正確處理各特徵的尺度以及確定各特徵之間的權重比是聚類是否有效的關鍵。

最常用的距離度量方法是“閔可夫斯基距離”(Minkowski distance):
在這裏插入圖片描述
當p=1時,閔可夫斯基距離即曼哈頓距離(Manhattan distance):
在這裏插入圖片描述
當p=2時,閔可夫斯基距離即歐氏距離(Euclidean distance)

在這裏插入圖片描述
對於無序屬性,我們一般採用VDM進行距離的計算,例如:對於離散屬性的兩個取值a和b,定義:
在這裏插入圖片描述

K-means

K-Means的思想十分簡單,首先隨機指定類中心,根據樣本與類中心的遠近劃分類簇,接着重新計算類中心,迭代直至收斂。但是其中迭代的過程並不是主觀地想象得出,事實上,若將樣本的類別看做爲“隱變量”(latent variable),類中心看作樣本的分佈參數,這一過程正是通過EM算法的兩步走策略而計算出,其根本的目的是爲了最小化平方誤差函數E

在這裏插入圖片描述
K-Means的算法流程如下所示:

在這裏插入圖片描述
簡單實現,並用於鳶尾花聚類。

import numpy as np
import random
from copy import deepcopy
import matplotlib
from matplotlib import pyplot as plt 
from sklearn.metrics import accuracy_score
import warnings
from sklearn.datasets import load_iris

warnings.filterwarnings('ignore')
iris = load_iris()
descr = iris['DESCR']
data = iris['data']
feature_names = iris['feature_names']
target = iris['target']
target_names = iris['target_names']
def Dist(x1,x2):
    #Minkowski distance
    p = len(x1)
    return np.sum((x1-x2)**p)**(1/p)
def Kmeans(data,k):
    data = list(data)
    us = random.sample(data,k)
    N = len(data)
    labels = [0 for _ in range(N)]#聚類標籤
    while(1):
        for j in range(N):
            for i in range(k):
                c = labels[j]
                if Dist(us[i],data[j])<Dist(us[c],data[j]):
                    labels[j] = i
        new_us = [np.zeros(len(data[0])) for _ in range(k)]
        counter = [0 for _ in range(k)]
        for j in range(N):
            c = labels[j]
            new_us[c] += data[j]
            counter[c]+=1
        flag = 1
        for i in range(k):
            new_us[i]/=counter[i]
            if (new_us[i]!=us[i]).all():
                flag = 0
        if flag:
            return labels
        us = new_us
    return None
labels = Kmeans(data,3)
plt.figure(figsize=(10,4), dpi=80)
plt.subplot(1,2,1)
plt.scatter(data[:,0].reshape(-1), data[:,1].reshape(-1),edgecolors='black',
            c=target)
plt.xlabel('sepal length (cm)')
plt.ylabel('sepal width (cm)')
plt.title('Iris original distribution')
plt.subplot(1,2,2)
plt.scatter(data[:,0].reshape(-1), data[:,1].reshape(-1),edgecolors='black',
            c=labels)
plt.xlabel('sepal length (cm)')
plt.ylabel('sepal width (cm)')
plt.title('Distribution by Kmeans')

在這裏插入圖片描述

高斯混合聚類

看出K-Means與LVQ都試圖以類中心作爲原型指導聚類,高斯混合聚類則採用高斯分佈來描述原型。現假設每個類簇中的樣本都服從一個多維高斯分佈,那麼空間中的樣本可以看作由k個多維高斯分佈混合而成。
對於多維高斯分佈,其概率密度函數如下所示:
在這裏插入圖片描述
其中u表示均值向量,∑表示協方差矩陣,可以看出一個多維高斯分佈完全由這兩個參數所確定。接着定義高斯混合分佈爲:
在這裏插入圖片描述
α稱爲混合係數,這樣空間中樣本的採集過程則可以抽象爲:(1)先選擇一個類簇(高斯分佈),(2)再根據對應高斯分佈的密度函數進行採樣,這時候貝葉斯公式又能大展身手了:
在這裏插入圖片描述
此時只需要選擇PM最大時的類簇並將該樣本劃分到其中,看到這裏很容易發現:這和那個傳說中的貝葉斯分類不是神似嗎,都是通過貝葉斯公式展開,然後計算類先驗概率和類條件概率。但遺憾的是:這裏沒有真實類標信息,對於類條件概率,並不能像貝葉斯分類那樣通過最大似然法計算出來,因爲這裏的樣本可能屬於所有的類簇,這裏的似然函數變爲:
在這裏插入圖片描述
可以看出:簡單的最大似然法根本無法求出所有的參數,這樣PM也就沒法計算。這裏就要召喚出之前的EM大法,首先對高斯分佈的參數及混合係數進行隨機初始化,計算出各個PM(即γji,第i個樣本屬於j類),再最大化似然函數(即LL(D)分別對α、u和∑求偏導 ),對參數進行迭代更新。

在這裏插入圖片描述

高斯混合聚類的算法流程如下圖所示:
在這裏插入圖片描述
簡單實現並用於鳶尾花,觀察聚類中心。

#高斯混合聚類,基於EM算法
#高斯混合模型是混合多個高斯分佈得到的
#每個模型包含均值,協方差矩陣以及模型權重
def GMM(data,k,iter_num=20):
    #初始化
    N,d = data.shape
    alpha = np.random.rand(k)
    alpha /= alpha.sum()
    means = np.random.rand(k,d)
    covs = np.empty((k,d,d))
    for i in range(k):
        covs[i] = np.eye(d)*np.random.rand(1)*k
    
    for iteration in range(iter_num):
        gamma = np.zeros((N,k))
        #計算後驗概率
        for j in range(N):
            p = np.zeros(k)
            for i in range(k):
                vec = (data[j]-means[i]).reshape(1,-1)
                num = np.exp(-0.5*vec.dot(np.linalg.pinv(covs[i])).dot(np.transpose(vec)))
                den = (2*np.pi)**d*np.linalg.det(covs[i])
                p[i] = num/den
            s = (p*alpha).sum()
            for i in range(k):
                gamma[j][i] = alpha[i]*p[i]/s
        #最大化期望
        for i in range(k):
            num = 0;den = 0;
            for j in range(N):
                num+=gamma[j][i]*data[j]
                den+=gamma[j][i]
            means[i] = num/den
            num = np.zeros((d,d));
            for j in range(N):
                vec = data[j]-means[i].reshape(1,-1)
                num+=gamma[j][i]*np.transpose(vec).dot(vec)
            covs[i] = num/den
            alpha[i] = den/N
    return alpha,means,covs
alpha,means,covs = GMM(data,3,30)
plt.figure(figsize=(10,6.5), dpi=80)
plt.scatter(data[:,2].reshape(-1), data[:,3].reshape(-1),edgecolors='black',
            c=target)
plt.xlabel('petal length (cm)')
plt.ylabel('petal width (cm)')
plt.title('Means vectors of Gussian mixture model')
plt.scatter(means[:,2].reshape(-1), means[:,3].reshape(-1),marker='*',c='r',linewidth=5)

在這裏插入圖片描述

密度聚類

挺簡單的,設置閾值表示連通性,設置點數表示是不是聚類中心,就可以用密度可達性得到一張連通圖,進而進行聚類。
常用的DBSCAN算法如下
在這裏插入圖片描述

#密度聚類,使用密度可達關係導出連通性
#基於連通性定理把數據分塊
import queue

def DBscan(data,epsilon,minpts):
    core = set()
    L = len(data)
    AM = [[0 for _ in range(L)] for _ in range(L)]#聯通與否使用bool值表示
    #建立初步鄰接矩陣並確定核心對象
    for i in range(L):
        for j in range(L):
            AM[i][j] = (Dist(data[i],data[j])<epsilon)
    #不考慮本身時,密度直達的點的數目大於等於minpts,則該點爲core object
    for i in range(L):
        if sum(AM[i])-1>=minpts:
            core.add(i)
    visited = set()#使用集合表示是否被訪問過
    #實行廣度優先搜索獲得多個簇
    cluster_cnt = 0
    labels = [-1 for _ in range(L)]#-1表示該點不屬於任何一個核心對象的簇,即異常值
    while (len(core)):
        #隨機獲取一個core object
        co = core.pop()
        if co in visited:
            continue
        q = queue.Queue()
        q.put(co)
        while (not q.empty()):
            p = q.get()
            labels[p] = cluster_cnt#置p爲第n個簇
            visited.add(p)
            for i in range(L):
                if AM[p][i] and p!=i and (i not in visited):
                    q.put(i)
        cluster_cnt += 1
    return labels
labels = DBscan(data,0.3,5)
plt.figure(figsize=(10,4), dpi=80)
plt.subplot(1,2,1)
plt.scatter(data[:,0].reshape(-1), data[:,1].reshape(-1),edgecolors='black',
            c=target)
plt.xlabel('sepal length (cm)')
plt.ylabel('sepal width (cm)')
plt.title('Iris original distribution')
plt.subplot(1,2,2)
plt.scatter(data[:,0].reshape(-1), data[:,1].reshape(-1),edgecolors='black',
            c=labels)
plt.xlabel('sepal length (cm)')
plt.ylabel('sepal width (cm)')
plt.title('Distribution by dbscan clustering')

在這裏插入圖片描述

層次聚類

使用的距離度量不是點與點直接的距離,而是簇與簇之間的距離。根據簇之間的相似度不斷進行合併,算法的核心在於“如何度量簇與簇之間的距離。
在這裏插入圖片描述

def synthesis_dist(a, b):
    D = [[Dist(a[i], b[j]) for j in range(len(b))] for i in range(len(a))]
    minz = []
    for z in range(len(b)):
        minz.append(min(D[x][z] for x in range(len(a))))
    minx = []
    for x in range(len(a)):
        minx.append(min(D[x][z] for z in range(len(b))))
    return max(max(minz),max(minx))

def hierarchical_clustering(data, k):
    #初始化n個簇,每個簇只有一個元素
    clusters = [[ele] for ele in data]
    N = len(data)
    M = np.zeros((N,N))
    INF = float("inf")
    #設置簇與自身的dist爲無窮大,因爲簇不可能與自身合併
    for i in range(N):
        M[i][i] = INF
        for j in range(i+1,N):
            M[i][j] = synthesis_dist(clusters[i],clusters[j])
            M[j][i] = M[i][j]
    while(len(clusters)>k):
        #尋找距離最近的兩個簇
        L =len(clusters)
        cur_dist = M[0][0]
        curi = curj = 0
        for i in range(L):
            for j in range(i+1,L):
                if cur_dist>M[i][j]:
                    cur_dist = M[i][j]
                    curi = i
                    curj = j
        #合併i、j兩簇,並刪除clusters和M中的i、j行列
        clusters[curi].extend(deepcopy(clusters[curj]))
        clusters.pop(curj)
        index = np.arange(0,L)
        index = np.delete(index,curj)
        M = M[:,index][index]
        #更新距離矩陣
        for j in range(L-1):
            if curi==j:
                M[curi][j]=INF
                M[j][curi]=INF
            else:
                D = synthesis_dist(clusters[curi],clusters[j])
                M[curi][j] = D
                M[j][curi] = D
    #設計labels
    labels = np.zeros(N)
    dic = {}
    for i in range(k):
        for ele in clusters[i]:
            key = tuple(ele)
            dic[key] = i
    for i in range(N):
        labels[i] = dic[tuple(data[i])]
    return labels
labels = hierarchical_clustering(data,3)
plt.figure(figsize=(10,4), dpi=80)
plt.subplot(1,2,1)
plt.scatter(data[:,0].reshape(-1), data[:,1].reshape(-1),edgecolors='black',
            c=target)
plt.xlabel('sepal length (cm)')
plt.ylabel('sepal width (cm)')
plt.title('Iris original distribution')

plt.subplot(1,2,2)
plt.scatter(data[:,0].reshape(-1), data[:,1].reshape(-1),edgecolors='black',
            c=labels)
plt.xlabel('sepal length (cm)')
plt.ylabel('sepal width (cm)')
plt.title('Distribution by AGNES clustering')

在這裏插入圖片描述

本文代碼均爲原創,轉載請聲明

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章