【機器學習】--Kmeans從初識到應用

LHBlog發表於2018-01-18

一.前述

Kmeans演算法一般在資料分析前期使用,選取適當的k,將資料分類後,然後分類研究不同聚類下資料的特點。

Kmeans演算法是一種無監督的演算法。 

常用於分組,比如使用者偏好。

 

二.概念及原理

Kmeans原理:

      1 隨機選取k箇中心點

      2 遍歷所有資料,將每個資料劃分到最近的中心點中

      3 計算每個聚類的平均值,並作為新的中心點

      4 重複2-3,直到這k箇中線點不再變化(收斂了),或執行了足夠多的迭代。

樣本點之間的相似度距離計算:

1.歐氏距離相似度(常用!!!

 

2.Jaccard相似度(用於比較樣本之間的相似性,係數值越大表示值越高

 

3.餘弦相似度

 

餘弦相似度值在[-1,1]之間,值越趨近於1代表越相近,越趨近於-1,代表越相反,接近於0,代表兩個向量正交。

常用計算文字相似性!!!將兩個文字根據他們詞,建立兩個向量,計算這兩個向量的餘弦值,就可以知道兩個文字在統計學方法中他們的相似度情況。

4.Pearson相似度

5.相對熵(K-L距離)

 總結:

           對於高維空間點之間的度量,用歐氏距離。

           對於集合度量Jaccard相似度。

     對於自然語言處理用餘弦相似度。

     對於函式度量用Pearson相似度

 

目標函式:

考慮歐幾里得距離的資料,使用誤差平方和(Sum of the Squared Error,SSE)作為聚類的目標函式,兩次執行K均值產生的兩個不同的簇集,我們更喜歡SSE最小的那個。

k表示k個聚類中心,ci表示第幾個中心,dist表示的是歐幾里得距離。
這裡有一個問題就是為什麼,我們更新質心是讓所有的點的平均值,這裡就是SSE所決定的。

通俗來說,每個點計算 到所在分配的中心店的距離,然後加和。

隨著k的增長損失函式,逐漸遞減。肘部法就是計算不同的K,然後計算SSE,求最大的兩組即可。

選擇一開始下降速度快,後來下降速度慢的。

肘部法圖解:

聚類評價方法:

 

常見的聚類評測指標有純度和 F 值,其中 F 值更為常用。

F 值的更普適的應用是資訊檢索的結果,其計算包括了兩個指標:召回率(Recall Rate)和準確率(Precision Rate)。

  • 召回率的定義為:檢索出的相關文件數和文件庫中所有的相關文件數的比率,衡量的是檢索系統的查全率;
  • 準確率的定義為:檢索出相關文件數與檢索出的文件總數的比率,衡量的是檢索系統的查準率;F 值為兩者的調和平均值。

以上評估方法都是基於原始資料事先知道一定的標籤,但這種似乎並不太常用,所以常用的評估模型的指標是輪廓係數,適用於在一開始不知道標籤的情況下。

具體如下:

輪廓係數它結合內聚度和分離度兩種因素。可以用來在相同原始資料的基礎上用來評價不同演算法、或者演算法不同執行方式對聚類結果所產生的影響。

方法:

                  1,計算樣本i到同簇其他樣本的平均距離ai。ai 越小,說明樣本i越應該被聚類到該簇。將ai 稱為樣本i的簇內不相似度

                            簇C中所有樣本的a i 均值稱為簇C的簇不相似度。

                  2,計算樣本i到其他某簇Cj 的所有樣本的平均距離bij,稱為樣本i與簇Cj 的不相似度。定義為樣本i的簇間不相似度:bi =min{bi1, bi2, ..., bik}

                            bi越大,說明樣本i越不屬於其他簇。

                  3,根據樣本i的簇內不相似度a i 和簇間不相似度b i ,定義樣本i的輪廓係數

                 4,判斷:

                            si接近1,則說明樣本i聚類合理;

                            si接近-1,則說明樣本i更應該分類到另外的簇;

                            若si 近似為0,則說明樣本i在兩個簇的邊界上。

結果為負數則可以直接放棄了!!!說明分類的很不好。

二分Kmeans原理:

為了得到k個簇,將所有點的集合分裂成兩個簇,從這些簇中選取一個繼續分裂,如此下去,直到產生k個簇。

比如要分成5個組,第一次分裂產生2個組,然後從這2個組中選一個目標函式產生的誤差比較大的,分裂這個組產生2個,這樣加上開始那1個就有3個組了,然後再從這3個組裡選一個分裂,產生4個組,重複此過程,產生5個組。這算是一中基本求精的思想。二分k均值不太受初始化的困擾,因為它執行了多次二分試驗並選取具有最小誤差的試驗結果,還因為每步只有兩個質心。

 

Kmeans++原理:

k-means++演算法選擇初始seeds的基本思想就是:初始的聚類中心之間的相互距離要儘可能的遠。

  1. 從輸入的資料點集合中隨機選擇一個點作為第一個聚類中心
  2. 對於資料集中的每一個點x,計算它與聚類中心(指已選擇的聚類中心)的距離D(x),然後對於每一個點/總和得出一個概率,則第二個點依據概率進行選擇。第三個點的選擇是其他所有的點到前兩個點的距離然後加和,再計算每一個點/總和的概率,再選擇一箇中心店。以此類推。。。。。
  3. 選擇一個新的資料點作為新的聚類中心,選擇的原則是:D(x)較大的點被選取作為聚類中心的概率較大(概率化選擇)

利用Canopy計算初始類簇中心點。

不需要迭代,比較快

步驟:

1.首先定義兩個距離T1和T2,T1>T2.從初始的點的集合S中隨機移除一個點P,然後對於還在S中的每個點I,計算該點I與點P的距離

2.如果距離小於T1,則將點I加入到點P所代表的Canopy中如果距離小於T2,則將點I從集合S中移除,並將點I加入到點P所代表的Canopy中。

3.迭代完一次之後,重新從集合S中隨機選擇一個點作為新的點P,然後重複執行以上步驟。

圖示:

總結:與中心的距離大於T1時,這些點就不會被歸入到中心所在的這個canopy類中。然當距離小於T1大於T2時,這些點會被歸入到該中心所在的canopy中,但是它們並不會從D中被移除,也就是說,它們將會參與到下一輪的聚類過程中,成為新的canopy類的中心或者成員。亦即,兩個Canopy類中有些成員是重疊的。而當距離小於T2的時候,這些點就會被歸入到該中心的canopy類中,而且會從D中被移除,也就是不會參加下一次的聚類過程了。

 三、程式碼

程式碼一:Kmeans實現

# !/usr/bin/python
# -*- coding:utf-8 -*-

import numpy as np
import matplotlib.pyplot as plt
import sklearn.datasets as ds
import matplotlib.colors
from sklearn.cluster import KMeans


def expand(a, b):
    d = (b - a) * 0.1
    return a-d, b+d


if __name__ == "__main__":
    N = 400#400個資料點
    centers = 4#4箇中心店
    data, y = ds.make_blobs(N, n_features=2, centers=centers, random_state=2)#聚類測試點 建立高沙分佈的點
    data2, y2 = ds.make_blobs(N, n_features=2, centers=centers, cluster_std=(1, 2.5, 0.5, 2), random_state=2)#每個類別的標準差不一樣,u是正態分佈,胖瘦
    data3 = np.vstack((data[y == 0][:], data[y == 1][:50], data[y == 2][:20], data[y == 3][:5]))#垂直放 每個類別生成一百個資料 每個類別拿的資料不一樣。目的是每一個簇裡面的密度不一樣,看看聚類效果。
    y3 = np.array([0] * 100 + [1] * 50 + [2] * 20 + [3] * 5)#自己構建label

    cls = KMeans(n_clusters=4, init='k-means++')
    y_hat = cls.fit_predict(data)
    y2_hat = cls.fit_predict(data2)
    y3_hat = cls.fit_predict(data3)

    m = np.array(((1, 1), (1, 3)))#矩陣點乘 ,相當於旋轉
    data_r = data.dot(m)
    y_r_hat = cls.fit_predict(data_r)

    matplotlib.rcParams['font.sans-serif'] = [u'SimHei']
    matplotlib.rcParams['axes.unicode_minus'] = False
    cm = matplotlib.colors.ListedColormap(list('rgbm'))

    plt.figure(figsize=(9, 10), facecolor='w')
    plt.subplot(421)
    plt.title(u'原始資料')
    plt.scatter(data[:, 0], data[:, 1], c=y, s=30, cmap=cm, edgecolors='none')#座標軸
    x1_min, x2_min = np.min(data, axis=0)
    x1_max, x2_max = np.max(data, axis=0)
    x1_min, x1_max = expand(x1_min, x1_max)
    x2_min, x2_max = expand(x2_min, x2_max)
    plt.xlim((x1_min, x1_max))
    plt.ylim((x2_min, x2_max))
    plt.grid(True)

    plt.subplot(422)
    plt.title(u'KMeans++聚類')
    plt.scatter(data[:, 0], data[:, 1], c=y_hat, s=30, cmap=cm, edgecolors='none')
    plt.xlim((x1_min, x1_max))
    plt.ylim((x2_min, x2_max))
    plt.grid(True)

    plt.subplot(423)
    plt.title(u'旋轉後資料')
    plt.scatter(data_r[:, 0], data_r[:, 1], c=y, s=30, cmap=cm, edgecolors='none')
    x1_min, x2_min = np.min(data_r, axis=0)
    x1_max, x2_max = np.max(data_r, axis=0)
    x1_min, x1_max = expand(x1_min, x1_max)
    x2_min, x2_max = expand(x2_min, x2_max)
    plt.xlim((x1_min, x1_max))
    plt.ylim((x2_min, x2_max))
    plt.grid(True)

    plt.subplot(424)
    plt.title(u'旋轉後KMeans++聚類')
    plt.scatter(data_r[:, 0], data_r[:, 1], c=y_r_hat, s=30, cmap=cm, edgecolors='none')
    plt.xlim((x1_min, x1_max))
    plt.ylim((x2_min, x2_max))
    plt.grid(True)

    plt.subplot(425)
    plt.title(u'方差不相等資料')
    plt.scatter(data2[:, 0], data2[:, 1], c=y2, s=30, cmap=cm, edgecolors='none')
    x1_min, x2_min = np.min(data2, axis=0)
    x1_max, x2_max = np.max(data2, axis=0)
    x1_min, x1_max = expand(x1_min, x1_max)
    x2_min, x2_max = expand(x2_min, x2_max)
    plt.xlim((x1_min, x1_max))
    plt.ylim((x2_min, x2_max))
    plt.grid(True)

    plt.subplot(426)
    plt.title(u'方差不相等KMeans++聚類')
    plt.scatter(data2[:, 0], data2[:, 1], c=y2_hat, s=30, cmap=cm, edgecolors='none')
    plt.xlim((x1_min, x1_max))
    plt.ylim((x2_min, x2_max))
    plt.grid(True)

    plt.subplot(427)
    plt.title(u'數量不相等資料')
    plt.scatter(data3[:, 0], data3[:, 1], s=30, c=y3, cmap=cm, edgecolors='none')
    x1_min, x2_min = np.min(data3, axis=0)
    x1_max, x2_max = np.max(data3, axis=0)
    x1_min, x1_max = expand(x1_min, x1_max)
    x2_min, x2_max = expand(x2_min, x2_max)
    plt.xlim((x1_min, x1_max))
    plt.ylim((x2_min, x2_max))
    plt.grid(True)

    plt.subplot(428)
    plt.title(u'數量不相等KMeans++聚類')
    plt.scatter(data3[:, 0], data3[:, 1], c=y3_hat, s=30, cmap=cm, edgecolors='none')
    plt.xlim((x1_min, x1_max))
    plt.ylim((x2_min, x2_max))
    plt.grid(True)

    plt.tight_layout(2, rect=(0, 0, 1, 0.97))
    plt.suptitle(u'資料分佈對KMeans聚類的影響', fontsize=18)
    # https://github.com/matplotlib/matplotlib/issues/829
    # plt.subplots_adjust(top=0.92)
    # plt.show()
    plt.savefig('cluster_kmeans')

 

  結果:

程式碼二:評估指標

# !/usr/bin/python
# -*- coding:utf-8 -*-

from sklearn import metrics#評估


if __name__ == "__main__":
    y = [0, 0, 0, 1, 1, 1]
    y_hat = [0, 0, 1, 1, 2, 2]
    h = metrics.homogeneity_score(y, y_hat)
    c = metrics.completeness_score(y, y_hat)
    print(u'同一性(Homogeneity):', h)
    print(u'完整性(Completeness):', c)
    v2 = 2 * c * h / (c + h)
    v = metrics.v_measure_score(y, y_hat)
    print(u'V-Measure:', v2, v)

    y = [0, 0, 0, 1, 1, 1]
    y_hat = [0, 0, 1, 3, 3, 3]
    h = metrics.homogeneity_score(y, y_hat)
    c = metrics.completeness_score(y, y_hat)
    v = metrics.v_measure_score(y, y_hat)
    print(u'同一性(Homogeneity):', h)
    print(u'完整性(Completeness):', c)
    print(u'V-Measure:', v)

    # 允許不同值
    y = [0, 0, 0, 1, 1, 1]
    y_hat = [1, 1, 1, 0, 0, 0]
    h = metrics.homogeneity_score(y, y_hat)
    c = metrics.completeness_score(y, y_hat)
    v = metrics.v_measure_score(y, y_hat)
    print(u'同一性(Homogeneity):', h)
    print(u'完整性(Completeness):', c)
    print(u'V-Measure:', v)

    y = [0, 0, 1, 1]
    y_hat = [0, 1, 0, 1]
    ari = metrics.adjusted_rand_score(y, y_hat)
    print(ari)

    y = [0, 0, 0, 1, 1, 1]
    y_hat = [0, 0, 1, 1, 2, 2]
    ari = metrics.adjusted_rand_score(y, y_hat)
    print(ari)

 

均一性,一個簇中只包含一個類別樣本,Precision
完整性,同類別樣本被歸到同一個簇中,Recall

V-Measure:

 

 

相關文章