跟我一起學scikit-learn22:K-均值算法

K-均值(K-means)算法是一種典型的無監督機器學習算法,用來解決聚類問題(Clustering)。由于數據標記需要耗費巨大的人力物力,無監督或者半監督學習算法不需要對數據進行標記,可以大大減少工作量。

1.K-均值算法原理

我們需要注意聚類問題和分類問題的區別。針對監督式學習算法,如K-近鄰算法,其輸入數據是已經標記了的(x^{(1)},y^{(1)})(x^{(2)},y^{(2)}),...,(x^{(m)},y^{(m)}),目標是找出分類邊界,然后對新的數據進行分類。而無監督式學習算法,如K-均值算法,只給出一組無標記的數據集x^{(1)}x^{(2)},...,x^{(m)},目標是找出這組數據的模式特征,如哪些數據是同一種類型的,哪些數據是另外一種類型。典型的無監督式學習包括市場細分,即通過分析用戶數據,把一個產品的市場進行細分,找出細分人群。另外一個是社交網絡分析,分析社交網絡中參與人員的不同特點,根據特點區分出不同群體。這些都是無監督式學習里的聚類(Clustering)問題。

K-均值算法包含以下兩個步驟:
(1)給聚類中心分配點。計算所有的訓練樣本,把每個訓練樣例分配到距離其最近的聚類中心所在的類別里。
(2)移動聚類中心。新的聚類中心移動到這個聚類所有的點的平均值處。

一直重復上面的動作,直到聚類中心不再移動為止,這時就探索出了數據集的結構了。

我們也可以用數學方式來描述K-均值算法。算法有兩個輸入:一個是K,表示選取的聚類的個數;另外一個是訓練數據集x^{(1)}x^{(2)},...,x^{(m)}
(1)隨機選擇K個初始聚類中心u_1u_2,...,u_k
(2)計算數據集中的每個點x^{(i)}分別到這K個聚類中心u_1u_2,...,u_k的距離,記錄距離最短的聚類中心u_j (1 \leq j \leq k),然后把x^{(i)}歸入這個類,即令x^{(i)}的類別標記c^{(i)}=j。這里一般使用||x^{(i)}-u_j||來計算距離。
(3)重新計算聚類中心,移動聚類中心到這個聚類的均值處。即u_j=\frac{1}{c}(\sum_{d=1}^{c}x^{(d)}),其中c表示該聚類樣本點個數,x^{(d)}表示該聚類第d個樣本點。
(4)重復上述過程,直到所有聚類中心不再移動為止。

1.K-均值算法的成本函數

根據成本函數的定義,成本即模型預測值與實際值的誤差,據此不難得出K-均值算法的成本函數:
J=\frac{1}{m}\sum_{i=1}^{m}||x^{(i)}-u_{c^{(i)}}||^2

其中,c^{(i)}是訓練樣例x^{(i)}分配的聚類序號;u_{c^{(i)}}x^{(i)}所屬聚類的中心。K-均值算法的成本函數的物理意義就是,訓練樣本到其所屬的聚類中心的平均距離。

2.隨機初始化聚類中心點

假設K是聚類的個數,m是訓練樣本的個數,那么必定有K<m。在隨機初始化時,隨機從m個訓練樣本中選擇K個樣本作為聚類中心點。這是正式推薦的隨機初始化聚類中心的做法。

在實際解決問題時,最終的聚類結果會和隨機初始化的聚類中心點有關。即不同的隨機初始化的聚類中心點可能得到不同的最終聚類結果。因為成本函數可能會收斂在一個局部最優解,而不是全局最優解上。有一個解決辦法就是多做幾次隨機初始化的動作,然后訓練出不同的聚類中心及聚類節點分配方案,然后使用這些值算出成本函數,從中選擇成本最小的那個方案。

假設我們做100次K-均值算法,每次都執行如下步驟:
(1)隨機選擇K個聚類中心點
(2)運行K-均值算法,算出c^{(1)}c^{(2)},...,c^{(m)}u_1u_2,...,u_k
(3)使用c^{(1)}c^{(2)},...,c^{(m)}u_1u_2,...,u_k算出最終的成本值。
找出成本最小的方案。這樣就可以適當加大運算次數,從而求出全局最優解。

3.選擇聚類的個數

怎樣選擇合適的聚類個數呢?實際上聚類個數和業務有緊密的關聯,例如我們要對運動鞋的尺碼大小進行聚類分析,那么是分成5個尺寸等級好還是分成10個尺寸等級好呢?這是個業務問題而非技術問題。5個尺寸等級可以給生產和銷售帶來便利,但客戶體驗可能不好;10個尺寸等級客戶體驗好了,可能會給生產和庫存造成不便。

從技術角度來講,也有一些方法可以用來做一些判斷的。比如,我們可以把聚類個數作為橫坐標,成本函數作為縱坐標,把成本和聚類個數的關系畫出來。大體的趨勢是隨著K值越來越大,成本會越來越低。我們找出一個拐點,即在這個拐點之前成本下降比較快,在這個拐點之后成本下降比較慢,那么很可能這個拐點所在的K值就是要尋求的最優解。

當然,這個技術并不總是有效的,因為很可能會得到一個沒有拐點的曲線,這樣,就必須和業務結合以便選擇合適的聚類個數。

2.scikit-learn里的K-均值算法

scikit-learn里的K-均值算法由sklearn.cluster.KMeans類實現。下面通過一個簡單的例子,來學習怎樣在scikit-learn里使用K-均值算法。

我們生成一組包含兩個特征的200個樣本:

from sklearn.datasets import make_blobs
X,y = make_blobs(n_samples=200,
                n_features=2,
                centers=4,
                cluster_std=1,
                center_box=(-10.0,10.0),
                shuffle=True,
                random_state=1)

然后把樣本畫在二維坐標系上,以便直觀地觀察:

import matplotlib.pyplot as plt
plt.figure(figsize=(6,4),dpi=144)
plt.xticks(())
plt.yticks(())
plt.scatter(X[:,0],X[:,1],s=20,marker='o')

結果如圖所示:


image.png

接著使用KMeans模型來擬合。我們設置類別個數為3,并計算出其擬合后的成本。

from sklearn.cluster import KMeans
n_clusters = 3
kmeans = KMeans(n_clusters=n_clusters)
kmeans.fit(X)
print("kmeans: k = {}, cost = {}".format(n_clusters,int(kmeans.score(X))))

輸出如下:

kmeans: k = 3, cost = -668

KMeans.score()函數計算K-均值算法擬合后的成本,用負數表示,其絕對值越大,說明成本越高。前面介紹過,K-均值算法成本的物理意義為訓練樣本到其所屬的聚類中心的距離平均值,在scikit-learn里,其計算成本的方法略有不同,它是計算訓練樣本到其所屬的聚類中心的距離的總和。

當然我們還可以把分類后的樣本及其所屬的聚類中心都畫出來,這樣可以更直觀地觀察算法的擬合效果。

labels = kmean.labels_
centers = kmean.cluster_centers_
markers = ['o', '^', '*']
colors = ['r', 'b', 'y']

plt.figure(figsize=(6,4), dpi=144)
plt.xticks(())
plt.yticks(())

# 畫樣本
for c in range(n_clusters):
    cluster = X[labels == c]
    plt.scatter(cluster[:, 0], cluster[:, 1], 
                marker=markers[c], s=20, c=colors[c])
# 畫出中心點
plt.scatter(centers[:, 0], centers[:, 1],
            marker='o', c="white", alpha=0.9, s=300)
for i, c in enumerate(centers):
    plt.scatter(c[0], c[1], marker='$%d$' % i, s=50, c=colors[i])

輸出結果如圖所示:


image.png

前面說過,K-均值算法的一個關鍵參數是K,即聚類個數。從技術角度來講,K值越大,算法成本越低,這個很容易理解。但從業務角度來看,不是K值越大越好。針對本節的例子,分別選擇K=[2,3,4]這三種不同的聚類個數,來觀察一下K-均值算法最終擬合的結果及其成本。

我們可以把畫出K-均值聚類結果的代碼稍微改造一下,變成一個函數。這個函數會使用K-均值算法來進行聚類擬合,同時會畫出按照這個聚類個數擬合后的分類情況:

def fit_plot_kmean_model(n_clusters, X):
    plt.xticks(())
    plt.yticks(())

    # 使用 k-均值算法進行擬合
    kmean = KMeans(n_clusters=n_clusters)
    kmean.fit_predict(X)

    labels = kmean.labels_
    centers = kmean.cluster_centers_
    markers = ['o', '^', '*', 's']
    colors = ['r', 'b', 'y', 'k']

    # 計算成本
    score = kmean.score(X)
    plt.title("k={}, score={}".format(n_clusters, (int)(score)))

    # 畫樣本
    for c in range(n_clusters):
        cluster = X[labels == c]
        plt.scatter(cluster[:, 0], cluster[:, 1], 
                    marker=markers[c], s=20, c=colors[c])
    # 畫出中心點
    plt.scatter(centers[:, 0], centers[:, 1],
                marker='o', c="white", alpha=0.9, s=300)
    for i, c in enumerate(centers):
        plt.scatter(c[0], c[1], marker='$%d$' % i, s=50, c=colors[i])

函數代碼略微有點長,但通過解釋應該不難理解函數的意圖。函數接受兩個參數,一個是聚類個數,即K的值,另一個是數據樣本。有了這個函數,接下來就簡單了,可以很容易分別對[2,3,4]這三種不同的K值情況進行聚類分析,并把聚類結果可視化。

from sklearn.cluster import KMeans

n_clusters = [2, 3, 4]

plt.figure(figsize=(10, 3), dpi=144)
for i, c in enumerate(n_clusters):
    plt.subplot(1, 3, i + 1)
    fit_plot_kmean_model(c, X)

輸出圖形如下所示:


image.png

3.使用K-均值對文檔進行聚類分析

本節介紹如何使用K-均值算法對文檔進行聚類分析。假設有一個博客平臺,用戶在平臺上發布博客,我們如何對博客進行聚類分析,以方便展示不同類別下的熱門文章呢?

1.準備數據集

為了簡化問題,避免進行中文分詞,我們仍然使用之前介紹樸素貝葉斯算法時使用的數據集:即mlcomp.org上的20news-18828(這個數據集是分好詞的,單詞之間以空格分隔)。并且這里只選擇語料庫里的部分內容來進行聚類分析。假設選擇sci.crypt、sci.electronics、sci.med和sci.space這4個類別的文檔進行聚類分析。到mlcomp原始語料庫里的raw文件夾下,復制對應的文件夾到datasets/clustering/data目錄下。

2.加載數據集

準備好數據集后,我們的任務就是把datasets/clustering/data目錄下的文檔進行聚類分析。你可能有疑問:這些文檔不是按照文件夾已經分好類了嗎?是的,這是人工標記了的數據。有了人工標記的數據,就可以檢驗K-均值算法的性能。

首先需要導入數據:

from time import time
from sklearn.datasets import load_files
print("loading documents ...")
t = time()
docs = load_files('datasets/clustering/data')
print("summary: {0} documents in {1} categories.".format(
    len(docs.data), len(docs.target_names)))
print("done in {0} seconds".format(time() - t))

輸出如下:

loading documents ...
summary: 3949 documents in 4 categories.
done in 26.920000076293945 seconds

總共有3949篇文章,人工標記在4個類別里。接著把文檔轉化為TF-IDF向量:

from sklearn.feature_extraction.text import TfidfVectorizer

max_features = 20000
print("vectorizing documents ...")
t = time()
vectorizer = TfidfVectorizer(max_df=0.4, 
                             min_df=2, 
                             max_features=max_features, 
                             encoding='latin-1')
X = vectorizer.fit_transform((d for d in docs.data))
print("n_samples: %d, n_features: %d" % X.shape)
print("number of non-zero features in sample [{0}]: {1}".format(
    docs.filenames[0], X[0].getnnz()))
print("done in {0} seconds".format(time() - t))

這里需要注意TfidfVectorizer的幾個參數的選擇。max_df=0.4表示如果一個單詞在40%的文檔里都出現過,則認為是一個高頻詞,對文檔聚類沒有幫助,在生成詞典時就會剔除這個詞。min_df=2表示,如果一個單詞的詞頻太低,小于等于2個,則也把這個單詞從詞典里剔除。max_features可以進一步過濾詞典的大小,它會根據TF-IDF權重從高到低進行排序,然后取前面權重高的單詞構成詞典。輸出如下:

vectorizing documents ...
n_samples: 3949, n_features: 20000
number of non-zero features in sample [datasets/clustering/data\sci.electronics\11902-54322]: 56
done in 1.9150002002716064 seconds

從輸出可知,每篇文章構成的向量都是一個稀疏向量,其大部分元素都為0。這也容易理解,我們的詞典大小為20000個詞,而示例文章中不重復的單詞卻只有56個。

3.文本聚類分析

接著使用KMeans算法對文檔進行聚類分析:

from sklearn.cluster import KMeans

print("clustering documents ...")
t = time()
n_clusters = 4
kmean = KMeans(n_clusters=n_clusters, 
               max_iter=100,
               tol=0.01,
               verbose=1,
               n_init=3)
kmean.fit(X);
print("kmean: k={}, cost={}".format(n_clusters, int(kmean.inertia_)))
print("done in {0} seconds".format(time() - t))

選擇聚類個數為4個。max_iter=100表示最多進行100次K-均值迭代。tol=0.1表示中心點移動距離小于0.1時就認為算法已經收斂,停止迭代。verbose=1表示輸出迭代過程的詳細信息。n_init=3表示進行3遍K-均值運算后求平均值。前面介紹過,在算法剛開始迭代時,會隨機選擇聚類中心點,不同的中心點可能導致不同的收斂效果,因此多次運算求平均值的方法可以提高算法的穩定性。由于開啟了迭代過程信息顯示,輸出了較多的信息:

clustering documents ...
Initialization complete
Iteration  0, inertia 7488.362
Iteration  1, inertia 3845.708
Iteration  2, inertia 3835.369
Iteration  3, inertia 3828.959
Iteration  4, inertia 3824.555
Iteration  5, inertia 3820.932
Iteration  6, inertia 3818.555
Iteration  7, inertia 3817.377
Iteration  8, inertia 3816.317
Iteration  9, inertia 3815.570
Iteration 10, inertia 3815.351
Iteration 11, inertia 3815.234
Iteration 12, inertia 3815.181
Iteration 13, inertia 3815.151
Iteration 14, inertia 3815.136
Iteration 15, inertia 3815.120
Iteration 16, inertia 3815.113
Iteration 17, inertia 3815.106
Iteration 18, inertia 3815.104
Converged at iteration 18: center shift 0.000000e+00 within tolerance 4.896692e-07
Initialization complete
Iteration  0, inertia 7494.329
Iteration  1, inertia 3843.474
Iteration  2, inertia 3835.570
Iteration  3, inertia 3828.511
Iteration  4, inertia 3823.826
Iteration  5, inertia 3819.972
Iteration  6, inertia 3817.714
Iteration  7, inertia 3816.666
Iteration  8, inertia 3816.032
Iteration  9, inertia 3815.778
Iteration 10, inertia 3815.652
Iteration 11, inertia 3815.548
Iteration 12, inertia 3815.462
Iteration 13, inertia 3815.424
Iteration 14, inertia 3815.411
Iteration 15, inertia 3815.404
Iteration 16, inertia 3815.402
Converged at iteration 16: center shift 0.000000e+00 within tolerance 4.896692e-07
Initialization complete
Iteration  0, inertia 7538.349
Iteration  1, inertia 3844.796
Iteration  2, inertia 3828.820
Iteration  3, inertia 3822.973
Iteration  4, inertia 3821.341
Iteration  5, inertia 3820.164
Iteration  6, inertia 3819.181
Iteration  7, inertia 3818.546
Iteration  8, inertia 3818.167
Iteration  9, inertia 3817.975
Iteration 10, inertia 3817.862
Iteration 11, inertia 3817.770
Iteration 12, inertia 3817.723
Iteration 13, inertia 3817.681
Iteration 14, inertia 3817.654
Iteration 15, inertia 3817.628
Iteration 16, inertia 3817.607
Iteration 17, inertia 3817.593
Iteration 18, inertia 3817.585
Iteration 19, inertia 3817.580
Converged at iteration 19: center shift 0.000000e+00 within tolerance 4.896692e-07
kmean: k=4, cost=3815
done in 39.484999895095825 seconds

從輸出信息中可以看到,總共進行了3次K-均值聚類分析,分別作了18,16,19次迭代后收斂。這樣就把3949個文檔進行自動分類了。kmean.labels_里保存的就是這些文檔的類別信息。如我們所料,len(kmean.labels_)的值是3949。

len(kmean.labels_)

輸出如下:

3949

查看1000到1010這10個文檔的聚類情況及其對應的文件名:

kmean.labels_[1000:1010]

輸出如下:

array([2, 2, 2, 1, 0, 1, 0, 2, 1, 1])

接著查看對應的文件名:

docs.filenames[1000:1010]

輸出如下:

array(['datasets/clustering/data\\sci.crypt\\10888-15289',
       'datasets/clustering/data\\sci.crypt\\11490-15880',
       'datasets/clustering/data\\sci.crypt\\11270-15346',
       'datasets/clustering/data\\sci.electronics\\12383-53525',
       'datasets/clustering/data\\sci.space\\13826-60862',
       'datasets/clustering/data\\sci.electronics\\11631-54106',
       'datasets/clustering/data\\sci.space\\14235-61437',
       'datasets/clustering/data\\sci.crypt\\11508-15928',
       'datasets/clustering/data\\sci.space\\13593-60824',
       'datasets/clustering/data\\sci.electronics\\12304-52801'],
      dtype='<U52')

對比兩個輸出可以看到,這10個文檔基本上正確地歸類了。需要說明的是,這里類別1表示sci.crypt,但這不是必然的對應關系。重新進行一次聚類分析可能就不是這個對應關系了。我們還可以選擇K為3或者2進行聚類分析,從而徹底打亂原來標記的類別關系。

我們好奇的是:在進行聚類分析的過程中,哪些單詞的權重最高,從而較容易地決定一個文章的類別?我們可以查看每種類別文檔中,其權重最高的10個單詞分別是什么?

from __future__ import print_function
print("Top terms per cluster:")
order_centroids = kmean.cluster_centers_.argsort()[:, ::-1]
terms = vectorizer.get_feature_names()
for i in range(n_clusters):
    print("Cluster %d:" % i, end='')
    for ind in order_centroids[i, :10]:
        print(' %s' % terms[ind], end='')
    print()

理解這段代碼的關鍵在于argsort()函數,它的作用是把一個Numpy數組進行升序排列,返回的是排序后的索引。例如下面的示例代碼:

import numpy as np
a = np.array([10, 30, 20, 40])
a.argsort()

輸出如下:

array([0, 2, 1, 3], dtype=int32)

即索引為0的元素(10)最小,其次是索引為2的元素(20),再次是索引為1的元素(30),最大的是索引為3的元素(40)。又比如:

a = np.array([10, 30, 20, 40])
a.argsort()[::-1]

輸出如下:

array([3, 1, 2, 0], dtype=int32)

[::-1]運算是把升序變為降序,a.argsort()[::-1]的輸出為array([3,1,2,0])。

回到我們的代碼里,由于kmean.cluster_centers_是二維數組,因此kmean.cluster_centers_.argsort()[:,::-1]語句的含義就是把聚類中心點的不同分量,按照從大到小的順序進行排序,并且把排序后的元素索引保存在二維數組order_centroids里。vectorizer.get_feature_names()將得到我們的詞典單詞,根據索引即可得到每個類別里權重最高的那些單詞了。輸出如下:

Top terms per cluster:
Cluster 0: space henry nasa toronto moon pat zoo shuttle gov orbit
Cluster 1: my any me by know your some do so has
Cluster 2: key clipper encryption chip government will keys escrow we nsa
Cluster 3: geb pitt banks gordon shameful dsl n3jxp chastity cadre surrender

4.聚類算法性能評估

聚類性能評估比較復雜,不像分類那樣直觀。針對分類問題,我們可以直接計算被錯誤分類的樣本數量,這樣可以直接算出分類算法的準確率。聚類問題不能使用絕對數量的方法進行性能評估,原因是,聚類分析后的類別與原來已標記的類別之間不存在必然的一一對應關系。更典型的,針對K-均值算法,我們可以選擇K的數值不等于已標記的類別個數。

前面介紹決策樹的時候簡單介紹過“熵”的概念,它是信息論中最重要的基礎概念。熵表示一個系統的有序程度,而聚類問題的性能評估,就是對比經過聚類算法處理后的數據的有序程度,與人工標記的有序程度之間的差異。下面介紹幾個常用的聚類算法性能評估指標

1.Adjust Rand Index

Adjust Rand Index是一種衡量兩個序列相似性的算法。它的優點是,針對兩個隨機序列,它的值為負數或接近0。而針對兩個結構相同的序列,它的值接近1。而且對類別標簽不敏感。下面來看一個簡單的例子。

from sklearn import metrics

label_true = np.random.randint(1, 4, 6)
label_pred = np.random.randint(1, 4, 6)
print("Adjusted Rand-Index for random sample: %.3f"
      % metrics.adjusted_rand_score(label_true, label_pred))
label_true = [1, 1, 3, 3, 2, 2]
label_pred = [3, 3, 2, 2, 1, 1]
print("Adjusted Rand-Index for same structure sample: %.3f"
      % metrics.adjusted_rand_score(label_true, label_pred))

輸出如下:

Adjusted Rand-Index for random sample: -0.239
Adjusted Rand-Index for same structure sample: 1.000
2.齊次性和完整性

根據條件熵分析,可以得到另外兩個衡量聚類算法性能的指標,分別是齊次性(homogeneity)和完整性(completeness)。齊次性表示一個聚類元素只由一種類別的元素組成。完整性表示給定的已標記的類別,全部分配到一個聚類里。它們的值均介于[0,1]之間。下面通過一個簡單的例子來解釋這兩個概念。

from sklearn import metrics
label_true = [1, 1, 2, 2]
label_pred = [2, 2, 1, 1]
print("Homogeneity score for same structure sample: %.3f"
      % metrics.homogeneity_score(label_true, label_pred))
label_true = [1, 1, 2, 2]
label_pred = [0, 1, 2, 3]
print("Homogeneity score for each cluster come from only one class: %.3f"
      % metrics.homogeneity_score(label_true, label_pred))
label_true = [1, 1, 2, 2]
label_pred = [1, 2, 1, 2]
print("Homogeneity score for each cluster come from two class: %.3f"
      % metrics.homogeneity_score(label_true, label_pred))
label_true = np.random.randint(1, 4, 6)
label_pred = np.random.randint(1, 4, 6)
print("Homogeneity score for random sample: %.3f"
      % metrics.homogeneity_score(label_true, label_pred))

輸出如下:

Homogeneity score for same structure sample: 1.000
Homogeneity score for each cluster come from only one class: 1.000
Homogeneity score for each cluster come from two class: 0.000
Homogeneity score for random sample: 0.315

針對第一組序列,其結構相同,因此其齊次性輸出為1,表示完全一致。奇怪的事情來了,第二組樣本[1,1,2,2]和[0,1,2,3]為什么也輸出1呢?答案就是齊次性的定義上,聚類元素只由一種已標記的類別元素組成時,其值為1。在我們的例子里,已標記為2個類別,而輸出了4個聚類,這樣就滿足每個聚類元素均來自一種已標記的類別這一條件。同樣的道理,針對第三組樣本,由于每個聚類元素都來自2個類別的元素,因此其值為0;而針對隨機的元素序列,它不為0,這是與Adjust Rand Index不同的地方。

接下來看一組完整性的例子:

from sklearn import metrics
label_true = [1, 1, 2, 2]
label_pred = [2, 2, 1, 1]
print("Completeness score for same structure sample: %.3f"
      % metrics.completeness_score(label_true, label_pred))
label_true = [0, 1, 2, 3]
label_pred = [1, 1, 2, 2]
print("Completeness score for each class assign to only one cluster: %.3f"
      % metrics.completeness_score(label_true, label_pred))
label_true = [1, 1, 2, 2]
label_pred = [1, 2, 1, 2]
print("Completeness score for each class assign to two class: %.3f"
      % metrics.completeness_score(label_true, label_pred))
label_true = np.random.randint(1, 4, 6)
label_pred = np.random.randint(1, 4, 6)
print("Completeness score for random sample: %.3f"
      % metrics.completeness_score(label_true, label_pred))

輸出如下:

Completeness score for same structure sample: 1.000
Completeness score for each class assign to only one cluster: 1.000
Completeness score for each class assign to two class: 0.000
Completeness score for random sample: 0.457

針對第一組序列,其結構相同,輸出為1。針對第二組序列,由于符合完整性的定義,即每個類別的元素都被分配進了同一個聚類里,因此其完整性也為1。針對第三組序列,每個類別的元素都被分配進了兩個不同的聚類里,因此其完整性為0。和齊次性一樣,它對隨機類別的判斷能力也比較弱。

從上面的例子中可以看出,齊次性和完整性是一組互補的關系,我們可以把兩個指標綜合起來,稱為V-measure分數。下面來看一個簡單的例子:

from sklearn import metrics

label_true = [1, 1, 2, 2]
label_pred = [2, 2, 1, 1]
print("V-measure score for same structure sample: %.3f"
      % metrics.v_measure_score(label_true, label_pred))
label_true = [0, 1, 2, 3]
label_pred = [1, 1, 2, 2]
print("V-measure score for each class assign to only one cluster: %.3f"
      % metrics.v_measure_score(label_true, label_pred))
print("V-measure score for each class assign to only one cluster: %.3f"
      % metrics.v_measure_score(label_pred, label_true))
label_true = [1, 1, 2, 2]
label_pred = [1, 2, 1, 2]
print("V-measure score for each class assign to two class: %.3f"
      % metrics.v_measure_score(label_true, label_pred))

輸出如下:

V-measure score for same structure sample: 1.000
V-measure score for each class assign to only one cluster: 0.667
V-measure score for each class assign to only one cluster: 0.667
V-measure score for each class assign to two class: 0.000

針對第一組序列,其結構相同,V-measure輸出的值也為1,表示同時滿足齊次性和完整性。第二行和第三行的輸出,表明V-measure符合對稱性法則。

3.輪廓系數

上面介紹的聚類性能評估方法都需要有已標記的類別數據,這個在實踐中是很難做到的。如果已經標記了數據,就會直接使用有監督的學習算法,而無監督學習算法的最大優點就是不需要對數據集進行標記。輪廓系數可以在不需要已標記的數據集的前提下,對聚類算法的性能進行評估。

輪廓系數由以下兩個指標構成:

  • a:一個樣本與其所在相同聚類的點的平均距離;
  • b:一個樣本與其距離最近的下一個聚類里的點的平均距離。

針對這個樣本,其輪廓系數s的值為:
s = \frac{b-a}{max(a,b)}

針對一個數據集,其輪廓系數s為其所有樣本的輪廓系數的平均值。輪廓系數的數值介于[-1,1]之間,-1表示完全錯誤的聚類,1表示完美的聚類,0表示聚類重疊。

針對前面的例子,可以分別計算本節介紹的幾個聚類算法性能評估指標,綜合來看聚類算法的性能:

from sklearn import metrics

labels = docs.target
print("Homogeneity: %0.3f" % metrics.homogeneity_score(labels, kmean.labels_))
print("Completeness: %0.3f" % metrics.completeness_score(labels, kmean.labels_))
print("V-measure: %0.3f" % metrics.v_measure_score(labels, kmean.labels_))
print("Adjusted Rand-Index: %.3f"
      % metrics.adjusted_rand_score(labels, kmean.labels_))
print("Silhouette Coefficient: %0.3f"
      % metrics.silhouette_score(X, kmean.labels_, sample_size=1000))

輸出如下:

Homogeneity: 0.459
Completeness: 0.519
V-measure: 0.487
Adjusted Rand-Index: 0.328
Silhouette Coefficient: 0.004

可以看到模型性能很一般。可能的一個原因是數據集質量不高,當然我們也可以閱讀原始的語料庫,檢驗一下如果通過人工標記,是否能夠標記出這些文章的正確分類。另外,針對my、any、me、by、know、your、some、do、so、has,這些都是沒有特征的單詞,即使人工標記,也無法判斷這些單詞應該屬于哪種類別的文章。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,663評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,125評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,506評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,614評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,402評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,934評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,021評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,168評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,690評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,596評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,784評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,288評論 5 357
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,027評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,404評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,662評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,398評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,743評論 2 370

推薦閱讀更多精彩內容