K-均值(K-means)算法是一種典型的無(wú)監(jiān)督機(jī)器學(xué)習(xí)算法,用來(lái)解決聚類(lèi)問(wèn)題(Clustering)拓售。由于數(shù)據(jù)標(biāo)記需要耗費(fèi)巨大的人力物力签孔,無(wú)監(jiān)督或者半監(jiān)督學(xué)習(xí)算法不需要對(duì)數(shù)據(jù)進(jìn)行標(biāo)記瑰艘,可以大大減少工作量。
1.K-均值算法原理
我們需要注意聚類(lèi)問(wèn)題和分類(lèi)問(wèn)題的區(qū)別僧须。針對(duì)監(jiān)督式學(xué)習(xí)算法纲刀,如K-近鄰算法,其輸入數(shù)據(jù)是已經(jīng)標(biāo)記了的担平,
示绊,...锭部,
,目標(biāo)是找出分類(lèi)邊界面褐,然后對(duì)新的數(shù)據(jù)進(jìn)行分類(lèi)拌禾。而無(wú)監(jiān)督式學(xué)習(xí)算法,如K-均值算法展哭,只給出一組無(wú)標(biāo)記的數(shù)據(jù)集
湃窍,
,...匪傍,
您市,目標(biāo)是找出這組數(shù)據(jù)的模式特征,如哪些數(shù)據(jù)是同一種類(lèi)型的析恢,哪些數(shù)據(jù)是另外一種類(lèi)型墨坚。典型的無(wú)監(jiān)督式學(xué)習(xí)包括市場(chǎng)細(xì)分,即通過(guò)分析用戶數(shù)據(jù)映挂,把一個(gè)產(chǎn)品的市場(chǎng)進(jìn)行細(xì)分泽篮,找出細(xì)分人群。另外一個(gè)是社交網(wǎng)絡(luò)分析柑船,分析社交網(wǎng)絡(luò)中參與人員的不同特點(diǎn)帽撑,根據(jù)特點(diǎn)區(qū)分出不同群體。這些都是無(wú)監(jiān)督式學(xué)習(xí)里的聚類(lèi)(Clustering)問(wèn)題鞍时。
K-均值算法包含以下兩個(gè)步驟:
(1)給聚類(lèi)中心分配點(diǎn)亏拉。計(jì)算所有的訓(xùn)練樣本,把每個(gè)訓(xùn)練樣例分配到距離其最近的聚類(lèi)中心所在的類(lèi)別里逆巍。
(2)移動(dòng)聚類(lèi)中心及塘。新的聚類(lèi)中心移動(dòng)到這個(gè)聚類(lèi)所有的點(diǎn)的平均值處。
一直重復(fù)上面的動(dòng)作锐极,直到聚類(lèi)中心不再移動(dòng)為止笙僚,這時(shí)就探索出了數(shù)據(jù)集的結(jié)構(gòu)了。
我們也可以用數(shù)學(xué)方式來(lái)描述K-均值算法灵再。算法有兩個(gè)輸入:一個(gè)是K肋层,表示選取的聚類(lèi)的個(gè)數(shù);另外一個(gè)是訓(xùn)練數(shù)據(jù)集翎迁,
栋猖,...,
汪榔。
(1)隨機(jī)選擇K個(gè)初始聚類(lèi)中心蒲拉,
,...,
全陨。
(2)計(jì)算數(shù)據(jù)集中的每個(gè)點(diǎn)分別到這K個(gè)聚類(lèi)中心
爆班,
,...辱姨,
的距離柿菩,記錄距離最短的聚類(lèi)中心
,然后把
歸入這個(gè)類(lèi)雨涛,即令
的類(lèi)別標(biāo)記
枢舶。這里一般使用
來(lái)計(jì)算距離。
(3)重新計(jì)算聚類(lèi)中心替久,移動(dòng)聚類(lèi)中心到這個(gè)聚類(lèi)的均值處凉泄。即,其中c表示該聚類(lèi)樣本點(diǎn)個(gè)數(shù)蚯根,
表示該聚類(lèi)第d個(gè)樣本點(diǎn)后众。
(4)重復(fù)上述過(guò)程,直到所有聚類(lèi)中心不再移動(dòng)為止颅拦。
1.K-均值算法的成本函數(shù)
根據(jù)成本函數(shù)的定義蒂誉,成本即模型預(yù)測(cè)值與實(shí)際值的誤差,據(jù)此不難得出K-均值算法的成本函數(shù):
其中距帅,是訓(xùn)練樣例
分配的聚類(lèi)序號(hào)右锨;
是
所屬聚類(lèi)的中心。K-均值算法的成本函數(shù)的物理意義就是碌秸,訓(xùn)練樣本到其所屬的聚類(lèi)中心的平均距離绍移。
2.隨機(jī)初始化聚類(lèi)中心點(diǎn)
假設(shè)K是聚類(lèi)的個(gè)數(shù),m是訓(xùn)練樣本的個(gè)數(shù)讥电,那么必定有K<m蹂窖。在隨機(jī)初始化時(shí),隨機(jī)從m個(gè)訓(xùn)練樣本中選擇K個(gè)樣本作為聚類(lèi)中心點(diǎn)恩敌。這是正式推薦的隨機(jī)初始化聚類(lèi)中心的做法瞬测。
在實(shí)際解決問(wèn)題時(shí),最終的聚類(lèi)結(jié)果會(huì)和隨機(jī)初始化的聚類(lèi)中心點(diǎn)有關(guān)潮剪。即不同的隨機(jī)初始化的聚類(lèi)中心點(diǎn)可能得到不同的最終聚類(lèi)結(jié)果。因?yàn)槌杀竞瘮?shù)可能會(huì)收斂在一個(gè)局部最優(yōu)解分唾,而不是全局最優(yōu)解上抗碰。有一個(gè)解決辦法就是多做幾次隨機(jī)初始化的動(dòng)作,然后訓(xùn)練出不同的聚類(lèi)中心及聚類(lèi)節(jié)點(diǎn)分配方案绽乔,然后使用這些值算出成本函數(shù)弧蝇,從中選擇成本最小的那個(gè)方案。
假設(shè)我們做100次K-均值算法,每次都執(zhí)行如下步驟:
(1)隨機(jī)選擇K個(gè)聚類(lèi)中心點(diǎn)
(2)運(yùn)行K-均值算法看疗,算出沙峻,
,...两芳,
和
摔寨,
,...怖辆,
是复。
(3)使用,
竖螃,...淑廊,
和
,
特咆,...季惩,
算出最終的成本值。
找出成本最小的方案腻格。這樣就可以適當(dāng)加大運(yùn)算次數(shù)画拾,從而求出全局最優(yōu)解。
3.選擇聚類(lèi)的個(gè)數(shù)
怎樣選擇合適的聚類(lèi)個(gè)數(shù)呢荒叶?實(shí)際上聚類(lèi)個(gè)數(shù)和業(yè)務(wù)有緊密的關(guān)聯(lián)碾阁,例如我們要對(duì)運(yùn)動(dòng)鞋的尺碼大小進(jìn)行聚類(lèi)分析,那么是分成5個(gè)尺寸等級(jí)好還是分成10個(gè)尺寸等級(jí)好呢些楣?這是個(gè)業(yè)務(wù)問(wèn)題而非技術(shù)問(wèn)題脂凶。5個(gè)尺寸等級(jí)可以給生產(chǎn)和銷(xiāo)售帶來(lái)便利,但客戶體驗(yàn)可能不好愁茁;10個(gè)尺寸等級(jí)客戶體驗(yàn)好了蚕钦,可能會(huì)給生產(chǎn)和庫(kù)存造成不便。
從技術(shù)角度來(lái)講鹅很,也有一些方法可以用來(lái)做一些判斷的嘶居。比如,我們可以把聚類(lèi)個(gè)數(shù)作為橫坐標(biāo)促煮,成本函數(shù)作為縱坐標(biāo)邮屁,把成本和聚類(lèi)個(gè)數(shù)的關(guān)系畫(huà)出來(lái)。大體的趨勢(shì)是隨著K值越來(lái)越大菠齿,成本會(huì)越來(lái)越低佑吝。我們找出一個(gè)拐點(diǎn),即在這個(gè)拐點(diǎn)之前成本下降比較快绳匀,在這個(gè)拐點(diǎn)之后成本下降比較慢芋忿,那么很可能這個(gè)拐點(diǎn)所在的K值就是要尋求的最優(yōu)解炸客。
當(dāng)然,這個(gè)技術(shù)并不總是有效的戈钢,因?yàn)楹芸赡軙?huì)得到一個(gè)沒(méi)有拐點(diǎn)的曲線痹仙,這樣,就必須和業(yè)務(wù)結(jié)合以便選擇合適的聚類(lèi)個(gè)數(shù)殉了。
2.scikit-learn里的K-均值算法
scikit-learn里的K-均值算法由sklearn.cluster.KMeans類(lèi)實(shí)現(xiàn)开仰。下面通過(guò)一個(gè)簡(jiǎn)單的例子,來(lái)學(xué)習(xí)怎樣在scikit-learn里使用K-均值算法宣渗。
我們生成一組包含兩個(gè)特征的200個(gè)樣本:
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)
然后把樣本畫(huà)在二維坐標(biāo)系上抖所,以便直觀地觀察:
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')
結(jié)果如圖所示:
接著使用KMeans模型來(lái)擬合。我們?cè)O(shè)置類(lèi)別個(gè)數(shù)為3痕囱,并計(jì)算出其擬合后的成本田轧。
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()函數(shù)計(jì)算K-均值算法擬合后的成本,用負(fù)數(shù)表示鞍恢,其絕對(duì)值越大傻粘,說(shuō)明成本越高。前面介紹過(guò)帮掉,K-均值算法成本的物理意義為訓(xùn)練樣本到其所屬的聚類(lèi)中心的距離平均值族扰,在scikit-learn里琐旁,其計(jì)算成本的方法略有不同惊完,它是計(jì)算訓(xùn)練樣本到其所屬的聚類(lèi)中心的距離的總和膜蛔。
當(dāng)然我們還可以把分類(lèi)后的樣本及其所屬的聚類(lèi)中心都畫(huà)出來(lái),這樣可以更直觀地觀察算法的擬合效果涩搓。
labels = kmean.labels_
centers = kmean.cluster_centers_
markers = ['o', '^', '*']
colors = ['r', 'b', 'y']
plt.figure(figsize=(6,4), dpi=144)
plt.xticks(())
plt.yticks(())
# 畫(huà)樣本
for c in range(n_clusters):
cluster = X[labels == c]
plt.scatter(cluster[:, 0], cluster[:, 1],
marker=markers[c], s=20, c=colors[c])
# 畫(huà)出中心點(diǎn)
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])
輸出結(jié)果如圖所示:
前面說(shuō)過(guò)污秆,K-均值算法的一個(gè)關(guān)鍵參數(shù)是K,即聚類(lèi)個(gè)數(shù)昧甘。從技術(shù)角度來(lái)講良拼,K值越大,算法成本越低充边,這個(gè)很容易理解庸推。但從業(yè)務(wù)角度來(lái)看,不是K值越大越好浇冰。針對(duì)本節(jié)的例子贬媒,分別選擇K=[2,3,4]這三種不同的聚類(lèi)個(gè)數(shù),來(lái)觀察一下K-均值算法最終擬合的結(jié)果及其成本肘习。
我們可以把畫(huà)出K-均值聚類(lèi)結(jié)果的代碼稍微改造一下际乘,變成一個(gè)函數(shù)。這個(gè)函數(shù)會(huì)使用K-均值算法來(lái)進(jìn)行聚類(lèi)擬合井厌,同時(shí)會(huì)畫(huà)出按照這個(gè)聚類(lèi)個(gè)數(shù)擬合后的分類(lèi)情況:
def fit_plot_kmean_model(n_clusters, X):
plt.xticks(())
plt.yticks(())
# 使用 k-均值算法進(jìn)行擬合
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']
# 計(jì)算成本
score = kmean.score(X)
plt.title("k={}, score={}".format(n_clusters, (int)(score)))
# 畫(huà)樣本
for c in range(n_clusters):
cluster = X[labels == c]
plt.scatter(cluster[:, 0], cluster[:, 1],
marker=markers[c], s=20, c=colors[c])
# 畫(huà)出中心點(diǎn)
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])
函數(shù)代碼略微有點(diǎn)長(zhǎng)蚓庭,但通過(guò)解釋?xiě)?yīng)該不難理解函數(shù)的意圖。函數(shù)接受兩個(gè)參數(shù)仅仆,一個(gè)是聚類(lèi)個(gè)數(shù)器赞,即K的值,另一個(gè)是數(shù)據(jù)樣本墓拜。有了這個(gè)函數(shù)港柜,接下來(lái)就簡(jiǎn)單了,可以很容易分別對(duì)[2,3,4]這三種不同的K值情況進(jìn)行聚類(lèi)分析咳榜,并把聚類(lèi)結(jié)果可視化夏醉。
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)
輸出圖形如下所示:
3.使用K-均值對(duì)文檔進(jìn)行聚類(lèi)分析
本節(jié)介紹如何使用K-均值算法對(duì)文檔進(jìn)行聚類(lèi)分析。假設(shè)有一個(gè)博客平臺(tái)涌韩,用戶在平臺(tái)上發(fā)布博客畔柔,我們?nèi)绾螌?duì)博客進(jìn)行聚類(lèi)分析,以方便展示不同類(lèi)別下的熱門(mén)文章呢臣樱?
1.準(zhǔn)備數(shù)據(jù)集
為了簡(jiǎn)化問(wèn)題靶擦,避免進(jìn)行中文分詞,我們?nèi)匀皇褂弥敖榻B樸素貝葉斯算法時(shí)使用的數(shù)據(jù)集:即mlcomp.org上的20news-18828(這個(gè)數(shù)據(jù)集是分好詞的雇毫,單詞之間以空格分隔)玄捕。并且這里只選擇語(yǔ)料庫(kù)里的部分內(nèi)容來(lái)進(jìn)行聚類(lèi)分析。假設(shè)選擇sci.crypt棚放、sci.electronics枚粘、sci.med和sci.space這4個(gè)類(lèi)別的文檔進(jìn)行聚類(lèi)分析。到mlcomp原始語(yǔ)料庫(kù)里的raw文件夾下飘蚯,復(fù)制對(duì)應(yīng)的文件夾到datasets/clustering/data目錄下馍迄。
2.加載數(shù)據(jù)集
準(zhǔn)備好數(shù)據(jù)集后,我們的任務(wù)就是把datasets/clustering/data目錄下的文檔進(jìn)行聚類(lèi)分析孝冒。你可能有疑問(wèn):這些文檔不是按照文件夾已經(jīng)分好類(lèi)了嗎柬姚?是的,這是人工標(biāo)記了的數(shù)據(jù)庄涡。有了人工標(biāo)記的數(shù)據(jù)量承,就可以檢驗(yàn)K-均值算法的性能。
首先需要導(dǎo)入數(shù)據(jù):
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篇文章穴店,人工標(biāo)記在4個(gè)類(lèi)別里撕捍。接著把文檔轉(zhuǎn)化為T(mén)F-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的幾個(gè)參數(shù)的選擇。max_df=0.4表示如果一個(gè)單詞在40%的文檔里都出現(xiàn)過(guò)泣洞,則認(rèn)為是一個(gè)高頻詞忧风,對(duì)文檔聚類(lèi)沒(méi)有幫助,在生成詞典時(shí)就會(huì)剔除這個(gè)詞球凰。min_df=2表示狮腿,如果一個(gè)單詞的詞頻太低腿宰,小于等于2個(gè),則也把這個(gè)單詞從詞典里剔除缘厢。max_features可以進(jìn)一步過(guò)濾詞典的大小吃度,它會(huì)根據(jù)TF-IDF權(quán)重從高到低進(jìn)行排序,然后取前面權(quán)重高的單詞構(gòu)成詞典贴硫。輸出如下:
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
從輸出可知椿每,每篇文章構(gòu)成的向量都是一個(gè)稀疏向量,其大部分元素都為0英遭。這也容易理解间护,我們的詞典大小為20000個(gè)詞,而示例文章中不重復(fù)的單詞卻只有56個(gè)挖诸。
3.文本聚類(lèi)分析
接著使用KMeans算法對(duì)文檔進(jìn)行聚類(lèi)分析:
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))
選擇聚類(lèi)個(gè)數(shù)為4個(gè)汁尺。max_iter=100表示最多進(jìn)行100次K-均值迭代。tol=0.1表示中心點(diǎn)移動(dòng)距離小于0.1時(shí)就認(rèn)為算法已經(jīng)收斂多律,停止迭代均函。verbose=1表示輸出迭代過(guò)程的詳細(xì)信息。n_init=3表示進(jìn)行3遍K-均值運(yùn)算后求平均值菱涤。前面介紹過(guò)苞也,在算法剛開(kāi)始迭代時(shí),會(huì)隨機(jī)選擇聚類(lèi)中心點(diǎn)粘秆,不同的中心點(diǎn)可能導(dǎo)致不同的收斂效果如迟,因此多次運(yùn)算求平均值的方法可以提高算法的穩(wěn)定性。由于開(kāi)啟了迭代過(guò)程信息顯示攻走,輸出了較多的信息:
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
從輸出信息中可以看到殷勘,總共進(jìn)行了3次K-均值聚類(lèi)分析,分別作了18,16,19次迭代后收斂昔搂。這樣就把3949個(gè)文檔進(jìn)行自動(dòng)分類(lèi)了玲销。kmean.labels_里保存的就是這些文檔的類(lèi)別信息。如我們所料摘符,len(kmean.labels_)的值是3949贤斜。
len(kmean.labels_)
輸出如下:
3949
查看1000到1010這10個(gè)文檔的聚類(lèi)情況及其對(duì)應(yīng)的文件名:
kmean.labels_[1000:1010]
輸出如下:
array([2, 2, 2, 1, 0, 1, 0, 2, 1, 1])
接著查看對(duì)應(yīng)的文件名:
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')
對(duì)比兩個(gè)輸出可以看到,這10個(gè)文檔基本上正確地歸類(lèi)了逛裤。需要說(shuō)明的是瘩绒,這里類(lèi)別1表示sci.crypt,但這不是必然的對(duì)應(yīng)關(guān)系带族。重新進(jìn)行一次聚類(lèi)分析可能就不是這個(gè)對(duì)應(yīng)關(guān)系了锁荔。我們還可以選擇K為3或者2進(jìn)行聚類(lèi)分析,從而徹底打亂原來(lái)標(biāo)記的類(lèi)別關(guān)系蝙砌。
我們好奇的是:在進(jìn)行聚類(lèi)分析的過(guò)程中阳堕,哪些單詞的權(quán)重最高跋理,從而較容易地決定一個(gè)文章的類(lèi)別?我們可以查看每種類(lèi)別文檔中恬总,其權(quán)重最高的10個(gè)單詞分別是什么薪介?
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()
理解這段代碼的關(guān)鍵在于argsort()函數(shù),它的作用是把一個(gè)Numpy數(shù)組進(jìn)行升序排列越驻,返回的是排序后的索引。例如下面的示例代碼:
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]運(yùn)算是把升序變?yōu)榻敌虿⑽。琣.argsort()[::-1]的輸出為array([3,1,2,0])。
回到我們的代碼里换途,由于kmean.cluster_centers_是二維數(shù)組懊渡,因此kmean.cluster_centers_.argsort()[:,::-1]語(yǔ)句的含義就是把聚類(lèi)中心點(diǎn)的不同分量,按照從大到小的順序進(jìn)行排序军拟,并且把排序后的元素索引保存在二維數(shù)組order_centroids里剃执。vectorizer.get_feature_names()將得到我們的詞典單詞,根據(jù)索引即可得到每個(gè)類(lèi)別里權(quán)重最高的那些單詞了懈息。輸出如下:
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.聚類(lèi)算法性能評(píng)估
聚類(lèi)性能評(píng)估比較復(fù)雜肾档,不像分類(lèi)那樣直觀。針對(duì)分類(lèi)問(wèn)題辫继,我們可以直接計(jì)算被錯(cuò)誤分類(lèi)的樣本數(shù)量怒见,這樣可以直接算出分類(lèi)算法的準(zhǔn)確率。聚類(lèi)問(wèn)題不能使用絕對(duì)數(shù)量的方法進(jìn)行性能評(píng)估姑宽,原因是遣耍,聚類(lèi)分析后的類(lèi)別與原來(lái)已標(biāo)記的類(lèi)別之間不存在必然的一一對(duì)應(yīng)關(guān)系。更典型的炮车,針對(duì)K-均值算法舵变,我們可以選擇K的數(shù)值不等于已標(biāo)記的類(lèi)別個(gè)數(shù)。
前面介紹決策樹(shù)的時(shí)候簡(jiǎn)單介紹過(guò)“熵”的概念瘦穆,它是信息論中最重要的基礎(chǔ)概念棋傍。熵表示一個(gè)系統(tǒng)的有序程度,而聚類(lèi)問(wèn)題的性能評(píng)估难审,就是對(duì)比經(jīng)過(guò)聚類(lèi)算法處理后的數(shù)據(jù)的有序程度瘫拣,與人工標(biāo)記的有序程度之間的差異。下面介紹幾個(gè)常用的聚類(lèi)算法性能評(píng)估指標(biāo)告喊。
1.Adjust Rand Index
Adjust Rand Index是一種衡量?jī)蓚€(gè)序列相似性的算法麸拄。它的優(yōu)點(diǎn)是派昧,針對(duì)兩個(gè)隨機(jī)序列,它的值為負(fù)數(shù)或接近0拢切。而針對(duì)兩個(gè)結(jié)構(gòu)相同的序列蒂萎,它的值接近1。而且對(duì)類(lèi)別標(biāo)簽不敏感淮椰。下面來(lái)看一個(gè)簡(jiǎn)單的例子五慈。
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.齊次性和完整性
根據(jù)條件熵分析,可以得到另外兩個(gè)衡量聚類(lèi)算法性能的指標(biāo)主穗,分別是齊次性(homogeneity)和完整性(completeness)泻拦。齊次性表示一個(gè)聚類(lèi)元素只由一種類(lèi)別的元素組成。完整性表示給定的已標(biāo)記的類(lèi)別忽媒,全部分配到一個(gè)聚類(lèi)里争拐。它們的值均介于[0,1]之間。下面通過(guò)一個(gè)簡(jiǎn)單的例子來(lái)解釋這兩個(gè)概念晦雨。
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
針對(duì)第一組序列架曹,其結(jié)構(gòu)相同,因此其齊次性輸出為1闹瞧,表示完全一致绑雄。奇怪的事情來(lái)了,第二組樣本[1,1,2,2]和[0,1,2,3]為什么也輸出1呢奥邮?答案就是齊次性的定義上绳慎,聚類(lèi)元素只由一種已標(biāo)記的類(lèi)別元素組成時(shí),其值為1漠烧。在我們的例子里杏愤,已標(biāo)記為2個(gè)類(lèi)別,而輸出了4個(gè)聚類(lèi)已脓,這樣就滿足每個(gè)聚類(lèi)元素均來(lái)自一種已標(biāo)記的類(lèi)別這一條件珊楼。同樣的道理,針對(duì)第三組樣本度液,由于每個(gè)聚類(lèi)元素都來(lái)自2個(gè)類(lèi)別的元素厕宗,因此其值為0;而針對(duì)隨機(jī)的元素序列堕担,它不為0已慢,這是與Adjust Rand Index不同的地方。
接下來(lái)看一組完整性的例子:
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
針對(duì)第一組序列霹购,其結(jié)構(gòu)相同佑惠,輸出為1。針對(duì)第二組序列,由于符合完整性的定義膜楷,即每個(gè)類(lèi)別的元素都被分配進(jìn)了同一個(gè)聚類(lèi)里旭咽,因此其完整性也為1。針對(duì)第三組序列赌厅,每個(gè)類(lèi)別的元素都被分配進(jìn)了兩個(gè)不同的聚類(lèi)里穷绵,因此其完整性為0。和齊次性一樣特愿,它對(duì)隨機(jī)類(lèi)別的判斷能力也比較弱仲墨。
從上面的例子中可以看出,齊次性和完整性是一組互補(bǔ)的關(guān)系揍障,我們可以把兩個(gè)指標(biāo)綜合起來(lái)目养,稱為V-measure分?jǐn)?shù)。下面來(lái)看一個(gè)簡(jiǎn)單的例子:
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
針對(duì)第一組序列亚兄,其結(jié)構(gòu)相同,V-measure輸出的值也為1采驻,表示同時(shí)滿足齊次性和完整性审胚。第二行和第三行的輸出,表明V-measure符合對(duì)稱性法則礼旅。
3.輪廓系數(shù)
上面介紹的聚類(lèi)性能評(píng)估方法都需要有已標(biāo)記的類(lèi)別數(shù)據(jù)膳叨,這個(gè)在實(shí)踐中是很難做到的。如果已經(jīng)標(biāo)記了數(shù)據(jù)痘系,就會(huì)直接使用有監(jiān)督的學(xué)習(xí)算法菲嘴,而無(wú)監(jiān)督學(xué)習(xí)算法的最大優(yōu)點(diǎn)就是不需要對(duì)數(shù)據(jù)集進(jìn)行標(biāo)記。輪廓系數(shù)可以在不需要已標(biāo)記的數(shù)據(jù)集的前提下汰翠,對(duì)聚類(lèi)算法的性能進(jìn)行評(píng)估龄坪。
輪廓系數(shù)由以下兩個(gè)指標(biāo)構(gòu)成:
- a:一個(gè)樣本與其所在相同聚類(lèi)的點(diǎn)的平均距離;
- b:一個(gè)樣本與其距離最近的下一個(gè)聚類(lèi)里的點(diǎn)的平均距離复唤。
針對(duì)這個(gè)樣本健田,其輪廓系數(shù)s的值為:
針對(duì)一個(gè)數(shù)據(jù)集,其輪廓系數(shù)s為其所有樣本的輪廓系數(shù)的平均值佛纫。輪廓系數(shù)的數(shù)值介于[-1,1]之間妓局,-1表示完全錯(cuò)誤的聚類(lèi),1表示完美的聚類(lèi)呈宇,0表示聚類(lèi)重疊好爬。
針對(duì)前面的例子,可以分別計(jì)算本節(jié)介紹的幾個(gè)聚類(lèi)算法性能評(píng)估指標(biāo)甥啄,綜合來(lái)看聚類(lèi)算法的性能:
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
可以看到模型性能很一般存炮。可能的一個(gè)原因是數(shù)據(jù)集質(zhì)量不高,當(dāng)然我們也可以閱讀原始的語(yǔ)料庫(kù)僵蛛,檢驗(yàn)一下如果通過(guò)人工標(biāo)記尚蝌,是否能夠標(biāo)記出這些文章的正確分類(lèi)。另外充尉,針對(duì)my飘言、any、me驼侠、by姿鸿、know、your倒源、some苛预、do、so笋熬、has热某,這些都是沒(méi)有特征的單詞,即使人工標(biāo)記胳螟,也無(wú)法判斷這些單詞應(yīng)該屬于哪種類(lèi)別的文章昔馋。