【SciKit-Learn學習筆記】8:k-均值演算法做文字聚類,聚類演算法效能評估
學習《scikit-learn機器學習》時的一些實踐。
原理見K-means和K-means++的演算法原理及sklearn庫中引數解釋、選擇。
sklearn中的KMeans
from sklearn.datasets import make_blobs
from matplotlib import pyplot as plt
from sklearn.cluster import KMeans
def show_sample():
"""展示樣本點"""
plt.figure(figsize=(6, 4), dpi=100)
plt.xticks( [])
plt.yticks([])
plt.scatter(X[:, 0], X[:, 1], s=20, marker='o')
plt.show()
def fit_plot_kmeans_model(k, X):
"""使樣本X聚k類,並繪製圖像"""
kmeans = KMeans(n_clusters=k)
kmeans.fit(X)
# 這裡score得到的是一個負數,其絕對值越大表示成本越高
# sklearn中對該成本的計算為:樣例到其所屬的聚類中心點的距離總和(而不是平均值)
plt.title("k={},成本={}" .format(k, kmeans.score(X)))
# 聚類得到的類別標籤,這裡都用從0開始的自然數表示
labels = kmeans.labels_
assert len(labels) == len(X)
# 聚類中心
centers = kmeans.cluster_centers_
assert len(centers) == k
markers = ['o', '^', '*', 's']
colors = ['r', 'b', 'y', 'k']
# 對每一個類別
for i in range(k) :
# 繪製該類對應的樣本
cluster = X[labels == i]
plt.scatter(cluster[:, 0], cluster[:, 1], marker=markers[i], s=20, c=colors[i])
# 繪製聚類中心點
plt.scatter(centers[:, 0], centers[:, 1], marker='o', c='white', alpha=0.9, s=300)
# 在中心點大白點(位置cnt)上繪製類別號i
for i, cnt in enumerate(centers):
plt.scatter(cnt[0], cnt[1], marker="$%d$" % i, s=50, c=colors[i])
if __name__ == '__main__':
# 生成標準差為1的200個聚4類的2維樣本點,聚類中心隨機生成且每個維度都在-10到10的範圍,最終將生成的兩樣本打亂
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)
# 聚類的類別數
n_clusters = [2, 3, 4]
plt.figure(figsize=(10, 3), dpi=100)
# plt.xticks([])
# plt.yticks(())
for i, k in enumerate(n_clusters):
plt.subplot(1, len(n_clusters), i + 1)
fit_plot_kmeans_model(k, X)
plt.show()
k-均值演算法做文字聚類
from sklearn.datasets import load_files
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.cluster import KMeans
from sklearn import metrics
# 讀取資料
docs = load_files("E:\Data\code\datasets\clustering\data")
data = docs.data
target_names = docs.target_names
print("summary: {} documents in {} categories.".format(len(data), len(target_names)))
summary: 3949 documents in 4 categories.
# 生成詞典並將文件轉化為TF-IDF向量
# 在生成詞典時,過濾超過max_df比例(或數目)或者min_df比例(或數目)的詞,最大保留20000個特徵,編碼ISO-8859-1
vectorizer = TfidfVectorizer(max_df=0.4, min_df=2, max_features=20000, encoding='latin-1')
X = vectorizer.fit_transform(data)
print("n_samples:{},n_features:{}".format(*X.shape))
print("0號樣本中的非零特徵數目:", X[0].getnnz())
n_samples:3949,n_features:20000
0號樣本中的非零特徵數目: 56
k = 4
# 引數n_init即是多次選取不同的初始化聚類中心,而最終輸出的是score最大(成本最小)的聚類
# 引數max_iter指定一次KMeans過程中最大的迴圈次數,即便聚類中心還可以移動,到達這個最大次數也結束
# 引數tol決定中心點移動距離小於多少時認定演算法已經收斂
kmeans = KMeans(n_clusters=k, max_iter=100, tol=0.01, verbose=0, n_init=3)
kmeans.fit(X)
print("k={},cost={}".format(k, "%.3f" % kmeans.inertia_))
k=4,cost=3816.220
# 檢視1000~1009這10個文件的聚類結果及其本來的檔名,可以看一下目錄一樣的也就是同一類的
print(kmeans.labels_[1000:1010])
print(docs.filenames[1000:1010])
[1 1 1 2 0 2 2 1 2 2]
[‘E:\Data\code\datasets\clustering\data\sci.crypt\10888-15289’
‘E:\Data\code\datasets\clustering\data\sci.crypt\11490-15880’
‘E:\Data\code\datasets\clustering\data\sci.crypt\11270-15346’
‘E:\Data\code\datasets\clustering\data\sci.electronics\12383-53525’
‘E:\Data\code\datasets\clustering\data\sci.space\13826-60862’
‘E:\Data\code\datasets\clustering\data\sci.electronics\11631-54106’
‘E:\Data\code\datasets\clustering\data\sci.space\14235-61437’
‘E:\Data\code\datasets\clustering\data\sci.crypt\11508-15928’
‘E:\Data\code\datasets\clustering\data\sci.space\13593-60824’
‘E:\Data\code\datasets\clustering\data\sci.electronics\12304-52801’]
# 檢視每種類別文件中,影響最大(即那個維度數值最大)的10個單詞
# 對得到的每個聚類中心點進行排序得到排序索引,這裡"::-1"使其按照從大到小排序
order_centroids = kmeans.cluster_centers_.argsort()[:, ::-1]
# 顯然越排在前面的對應的索引值所對應的單詞影響越大
# 取出詞典中的詞
terms = vectorizer.get_feature_names()
# 對每個聚類結果i
for i in range(k):
print("Cluster %d" % i, end='')
# 取出第i行(也就是第i個聚類中心點)前10重要的詞的索引
for ind in order_centroids[i, :10]:
# 在詞典term中可以按這個索引拿到對應的詞
print(" %s" % terms[ind], end='')
print()
Cluster 0 henry toronto zoo spencer hst zoology mission utzoo orbit space
Cluster 1 key clipper chip encryption government keys will escrow we algorithm
Cluster 2 space by any my will know like some nasa we
Cluster 3 my she msg pitt he your has do her gordon
# 評價聚類表現
label_true = docs.target # 標記的類別
label_pred = kmeans.labels_ # 聚類得到的類別
print("齊次性: %.3f" % metrics.homogeneity_score(label_true, label_pred))
print("完整性: %.3f" % metrics.completeness_score(label_true, label_pred))
print("V-measure: %.3f" % metrics.v_measure_score(label_true, label_pred))
print("Adjust Rand Index: %.3f" % metrics.adjusted_rand_score(label_true, label_pred))
print("輪廓係數: %.3f" % metrics.silhouette_score(X, label_pred, sample_size=1000))
齊次性: 0.352
完整性: 0.481
V-measure: 0.406
Adjust Rand Index: 0.250
輪廓係數: 0.005
聚類演算法效能評估
聚類因為得到的類別和標註類別未必有什麼關係(也許有一定程度的對應關係,如上面的文字聚類和實際標籤的比較),不能用分類的MSE損失等方法來對其評估。
而sklearn裡聚類模型的score值也沒有一個確切的範圍,不像分類模型中總是從0到1之間,所以單純的看這個score值也沒有太大用處。
以下方法中,只有輪廓係數是不基於標註標籤對聚類效能做評估的方法,其它方法都使用了標註標籤。
Adjust Rand Index
該方法可以衡量兩個序列相似性,針對兩個結構相同的序列其值接近1,主要優點是對類別標籤不敏感。
from sklearn import metrics
import numpy as np
# 隨機序列
label_true = np.random.randint(1, 4, 6)
label_pred = np.random.randint(1, 4, 6)
print("Adjust Rand Index for 隨機序列: %.3f" % metrics.adjusted_rand_score(label_true, label_pred))
Adjust Rand Index for 隨機序列: -0.296
# 結構相同,這個例子裡能看出"對類別標籤不敏感"
label_true = [1, 1, 3, 3, 2, 2]
label_pred = [3, 3, 2, 2, 1, 1]
print("Adjust Rand Index for 結構相同的序列: %.3f" % metrics.adjusted_rand_score(label_true, label_pred))
Adjust Rand Index for 結構相同的序列: 1.000
Homogeneity(齊次性)
Homogeneity表徵每個聚類類別中的元素只由同種標註標籤的元素組成的程度。
from sklearn import metrics
import numpy as np
label_true = [1, 1, 2, 2]
label_pred = [2, 2, 1, 1]
print("齊次性值 for 結構相同: %.3f" % metrics.homogeneity_score(label_true, label_pred))
齊次性值 for 結構相同: 1.000
label_true = [1, 1, 2, 2]
label_pred = [0, 1, 2, 3]
print("齊次性值 for 每個類別內只由一種原類別元素組成: %.3f" % metrics.homogeneity_score(label_true, label_pred))
齊次性值 for 每個類別內只由一種原類別元素組成: 1.000
label_true = [1, 1, 2, 2]
label_pred = [1, 2, 1, 2]
print("齊次性值 for 每個類別內不由一種原類別元素組成: %.3f" % metrics.homogeneity_score(label_true, label_pred))
齊次性值 for 每個類別內不由一種原類別元素組成: 0.000
label_true = np.random.randint(1, 4, 6)
label_pred = np.random.randint(1, 4, 6)
print("齊次性值 for 隨機序列: %.3f" % metrics.homogeneity_score(label_true, label_pred))
齊次性值 for 隨機序列: 0.685
Completeness(完整性)
Completeness表徵同種標註標籤的元素全部分配到同種聚類類別中的程度。
from sklearn import metrics
import numpy as np
label_true = [1, 1, 2, 2]
label_pred = [2, 2, 1, 1]
print("完整性值 for 結構相同: %.3f" % metrics.completeness_score(label_true, label_pred))
完整性值 for 結構相同: 1.000
label_true = [0, 1, 2, 2]
label_pred = [2, 0, 1, 1]
print("完整性值 for 原類別相同的都分到一個聚類中: %.3f" % metrics.completeness_score(label_true, label_pred))
完整性值 for 原類別相同的都分到一個聚類中: 1.000
label_true = [0, 1, 2, 2]
label_pred = [2, 0, 1, 2]
print("完整性值 for 原類別相同的未分到一個聚類中: %.3f" % metrics.completeness_score(label_true, label_pred))
完整性值 for 原類別相同的未分到一個聚類中: 0.667
label_true = np.random.randint(1, 4, 6)
label_pred = np.random.randint(1, 4, 6)
print("完整性值 for 隨機序列: %.3f" % metrics.completeness_score(label_true, label_pred))
完整性值 for 隨機序列: 0.457
V-measure
V-measure結合了Homogeneity和Completeness這一組互為補充的評價指標。若將聚類結果和標註標籤反轉,得到的V-measure值是相同的。
from sklearn import metrics
import numpy as np
label_true = [1, 1, 2, 2]
label_pred = [2, 2, 1, 1]
print("V-measure for 結構相同: %.3f" % metrics.v_measure_score(label_true, label_pred))
V-measure for 結構相同: 1.000
label_true = [0, 1, 2, 3]
label_pred = [1, 1, 2, 2]
print("V-measure for 不齊次,但完整: %.3f" % metrics.v_measure_score(label_true, label_pred))
print("V-measure for 齊次,但不完整: %.3f" % metrics.v_measure_score(label_pred, label_true))
V-measure for 不齊次,但完整: 0.667
V-measure for 齊次,但不完整: 0.667
label_true = [1, 1, 2, 2]
label_pred = [1, 2, 1, 2]
print("V-measure for 既不齊次,也不完整: %.3f" % metrics.v_measure_score(label_true, label_pred))
V-measure for 既不齊次,也不完整: 0.000
label_true = np.random.randint(1, 4, 6)
label_pred = np.random.randint(1, 4, 6)
print("V-measure for 隨機序列: %.3f" % metrics.v_measure_score(label_true, label_pred))
V-measure for 隨機序列: 0.457
輪廓係數
使用metrics.silhouette_score(樣本集,聚類標籤,)
計算。