聚類算法——K-Means

聚類

一、聚類任務

在無監督的學習中,訓練樣本的標記信息是未知的,目標是通過對無標記訓練樣本的學習來揭露數據的內在性質及規律,爲進一步的數據的分析提供基礎,此類學習任務中研究最多、應用最廣泛的是聚類。

聚類試圖將數據集中的樣本劃分爲若干個通常是不相交的子集,每個子集稱爲一個簇,通過這樣的劃分,每個簇可能對應於一些潛在的類別。聚類過程僅能自動形成簇結構,簇對應的概念語義需由使用者來把握和命名。

下面討論聚類算法涉及的兩個基本問題——性能度量和距離計算

二、性能度量

性能度量也稱爲聚類的 “有效性指標”,在聚類結果中,我們需通過某種性能度量來評估其好壞,另一方面,若明確了最終將要使用的性能度量,則可直接將其作爲聚類過程的優化目標,從而得到符合要求的聚類結果。聚類將樣本集 D 劃分爲若干互不相交的子集,即樣本簇,我們希望同一簇的樣本儘可能彼此相似,不同簇的樣本儘可能不同。

聚類性能指標大致分爲兩類——外部指標、內部指標

1.外部指標

指將聚類結果與某個 “參考模型” 進行比較。

則外部指標主要有:

  • Jaccard 係數(簡稱 JC )

                                              JC=\frac{a}{a+b+c}

  • FM 指數(簡稱 FMI )

                                           FMI=\sqrt{\frac{a}{a+b}\cdot \frac{a}{a+c}}

  • Rand 指數(簡稱 RI )

                                                RI=\frac{2(a+b)}{m(m-1)}

上述性能度量的結果值均在 [0,1] 區間,值越大越好。

2.內部指標

則內部指標主要有:

  • DB 指數(簡稱 DBI )

                                           

  • Dunn 指數(簡稱 DI )

                             

DBI 的值越小越好,DI 的值越大越好。

三、距離計算

距離度量需要滿足一些基本性質:

  • 非負性
  • 同一性(兩點相同時,距離爲0)
  • 對稱性
  • 直遞性(也稱爲三角不等式)

我們常將屬性劃分爲 ”連續屬性“ 和 ”離散屬性“,前者在定義域有無窮多個取值,後者在定義域有有限個取值。在討論距離計算時,可以再屬性上計算距離的爲 ”有序屬性“,例如定義域 {1,2,3} 的離散屬性和連續屬性的性質更接近一些,可以直接計算。而定義域 {飛機、火車、輪船} 這樣的離散屬性不能直接計算距離,稱爲 ”無序屬性“。 

 常用的有序屬性:

  • 閔可夫斯基距離

                                                   

  • p=2 時,歐氏距離

                                                  dist_{ed}(x_{i},x_{j})=\left \| x_{i}-x_{j} \right \|_{2}=\sqrt{\sum_{u=1}^{n}\left | x_{iu}-x_{ju} \right |^{2}}

  • p=1 時,曼哈頓距離

                                                dist_{man}(x_{i},x_{j})=\left \| x_{i}-x_{j} \right \|_{1}=\sum_{u=1}^{n}\left | x_{iu}-x_{ju} \right |

常用的無序屬性:

  • m_{u,a} 表示在屬性 u 上取值爲 a 的樣本數,m_{u,a,i} 表示在第 i 個樣本簇中在屬性 u 上取值爲 a 的樣本數,k 爲樣本簇數,則屬性u上兩個離散值 a 與 b 的 VDM 距離爲:

                                                         

  • 將閔可夫斯基距離和 VDM 結合即可處理混合屬性,假定有 n_{c} 個有序屬性,n-n_{c} 個無序屬性,不失一般性,令有序屬性排在無序屬性之前,則:

                                        

  • 當樣本空間中不同屬性的重要性不同時, 可使用 ” 加權距離“ ,以加權閔夫斯基距離爲例:

                                                    dist_{wmk}\left ( x_{i},x_{j} \right )=(w_{1}\cdot \left | x_{i1}-x_{j1} \right |^{p}+...+w_{n}\cdot \left | x_{in}-x_{jn} \right |^{p})^{\frac{1}{p}}

K-Means算法 

一、K-Means 算法的基本原理及步驟

K-Means 算法是基於數據劃分的無監督聚類算法, 首先定義常數 k , 常數 k 表示最終的聚類的類別數。 在確定類別數 k 後, 隨機初始化 k 個類的聚類中心, 通過計算每一個樣本與聚類中心之間的相似度, 將樣本劃分到相似的類別中。

K-Means 算法的步驟:

1. 初始化常數k, 隨機初始化 k 個聚類中心;

2. 重複計算以下過程, 直到聚類中心不再改變; 

  • 計算每個樣本與每個聚類中心之間的相似度, 將樣本劃分到最相似的類別中;
  • 計算劃分到每個類別中的所用樣本特徵的均值, 並將該均值作爲每個類新的聚類中心。

3. 輸出最終的聚類中心以及每個樣本所屬的類別。

二、K-Means 算法與矩陣分解

在 K-Means 算法中,假設訓練數據集 X 有 m 個樣本 \left \{ X^{(1)},X^{(2)} ,...,X^{(m)}\right \},其中,每一個樣本 Xi 爲 n 維的向量。此時的樣本爲一個 m×n 的矩陣:

                                  

k-Means 算法通過歐式距離的度量方法計算每一個樣本 Xi 到質心之間的距離,並將其劃分到較近的質心所屬的類別中並重新計算質心,重複以上的過程,直到質心不再改變爲止。

K-Means 算法的目標是使每一個樣本 Xi 被劃分到最相似的類別中,利用每個類別中的樣本重新計算聚類中心 ui:

證明如下:

三、K-Means 算法的實踐

# -*- coding: utf-8 -*-
"""
Created on Wed Mar 27 16:18:21 2019

@author: 2018061801
"""
import numpy as np

def load_data(file_path):
    '''導入數據
    input:  file_path(string):文件的存儲位置
    output: data(mat):數據
    '''
    f = open(file_path)
    data = []
    for line in f.readlines():
        row = []  # 記錄每一行
        lines = line.strip().split("\t")
        for x in lines:
            row.append(float(x)) # 將文本中的特徵轉換成浮點數
        data.append(row)
    f.close()
    return np.mat(data)

def distance(vecA, vecB):
    '''計算vecA與vecB之間的歐式距離的平方
    input:  vecA(mat)A點座標
            vecB(mat)B點座標
    output: dist[0, 0](float)A點與B點距離的平方
    '''
    dist = (vecA - vecB) * (vecA - vecB).T
    return dist[0, 0]

def randCent(data, k):
    '''隨機初始化聚類中心
    input:  data(mat):訓練數據
            k(int):類別個數
    output: centroids(mat):聚類中心
    '''
    n = np.shape(data)[1]  # 屬性的個數
    centroids = np.mat(np.zeros((k, n)))  # 初始化k個聚類中心
    for j in range(n):  # 初始化聚類中心每一維的座標
        minJ = np.min(data[:, j])
        rangeJ = np.max(data[:, j]) - minJ
        # 在最大值和最小值之間隨機初始化
        centroids[:, j] = minJ * np.mat(np.ones((k , 1))) \
                        + np.random.rand(k, 1) * rangeJ
    return centroids
 
def kmeans(data, k, centroids):
    '''根據KMeans算法求解聚類中心
    input:  data(mat):訓練數據
            k(int):類別個數
            centroids(mat):隨機初始化的聚類中心
    output: centroids(mat):訓練完成的聚類中心
            subCenter(mat):每一個樣本所屬的類別
    '''
    m, n = np.shape(data) # m:樣本的個數,n:特徵的維度
    subCenter = np.mat(np.zeros((m, 2)))  # 初始化每一個樣本所屬的類別
    change = True  # 判斷是否需要重新計算聚類中心
    while change == True:
        change = False  # 重置
        for i in range(m):
            minDist = np.inf  # 設置樣本與聚類中心之間的最小的距離,初始值爲正無窮
            minIndex = 0  # 所屬的類別
            for j in range(k):
                # 計算i和每個聚類中心之間的距離
                dist = distance(data[i, ], centroids[j, ])
                if dist < minDist:
                    minDist = dist
                    minIndex = j
            # 判斷是否需要改變
            if subCenter[i, 0] < minIndex:  # 需要改變
                change = True
                subCenter[i, ] = np.mat([minIndex, minDist])
        # 重新計算聚類中心
        for j in range(k):
            sum_all = np.mat(np.zeros((1, n)))
            r = 0  # 每個類別中的樣本的個數
            for i in range(m):
                if subCenter[i, 0] == j:  # 計算第j個類別
                    sum_all += data[i, ]
                    r += 1
            for z in range(n):
                try:
                    centroids[j, z] = sum_all[0, z] / r
                except:
                    print (" r is zero")   
    return subCenter

def save_result(file_name, source):
    '''保存source中的結果到file_name文件中
    input:  file_name(string):文件名
            source(mat):需要保存的數據
    output: 
    '''
    m, n = np.shape(source)
    f = open(file_name, "w")
    for i in range(m):
        tmp = []
        for j in range(n):
            tmp.append(str(source[i, j]))
        f.write("\t".join(tmp) + "\n")
    f.close()
   
if __name__ == "__main__":
    k = 4  # 聚類中心的個數
    file_path = "D:/anaconda4.3/spyder_work/data4.txt"
    # 1、導入數據
    print ("---------- 1.load data ------------")
    data = load_data(file_path)
    # 2、隨機初始化k個聚類中心  
    print ("---------- 2.random center ------------")
    centroids = randCent(data, k)
    # 3、聚類計算  
    print ("---------- 3.kmeans ------------")
    subCenter = kmeans(data, k, centroids)  
    # 4、保存所屬的類別文件
    print ("---------- 4.save subCenter ------------")
    save_result("sub", subCenter)
    # 5、保存聚類中心
    print ("---------- 5.save centroids ------------")
    save_result("center", centroids) 

結果:

---------- 1.load data ------------
---------- 2.random center ------------
---------- 3.kmeans ------------
---------- 4.save subCenter ------------
---------- 5.save centroids ------------

四個聚類中心的具體值:(每次運行的結果不一樣?)

A:(-5.232158745136095,-5.3149499495898445)
B:(3.714649607552847,4.85395509083394)
C:(5.257939367323595,-4.624586594288424)
D:(-2.4477179599509813,0.5190348172066764)

由於K-Means 算法存在問題,K-Means++ 算法被提出,下一節將瞭解K-Means++ 算法。

 

訓練數據鏈接:https://github.com/zhaozhiyong19890102/Python-Machine-Learning-Algorithm/blob/master/Chapter_10%20KMeans/data.txt

 

參考文獻:趙志勇《python 機器學習算法》(原理+程序)

周志華《機器學習》(原理)
 

 

 

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