1. 程式人生 > >使用scikit-learn進行KMeans文字聚類

使用scikit-learn進行KMeans文字聚類

轉載自部落格:https://blog.razrlele.com/p/1614

K-Means 演算法簡介

中文名字叫做K-均值演算法,演算法的目的是將n個向量分別歸屬到K箇中心點裡面去。演算法首先會隨機選擇K箇中心向量,然後通過迭代計算以及重新選擇K箇中心向量,使得n個向量各自被分配到距離最近的K中心點,並且所有向量距離各自中心點的和最小。

在這裡借用Wikipedia上的K-Means條目的圖來說明

步驟一:在輸入資料集裡面隨機選擇三個向量作為初始中心點,這裡的K值為3, 也就是一開始從資料集裡面選擇了三個向量。

K_Means_Example_Step_1

步驟二:將每個向量分配到離各自最近的中心點,從而將資料集分成了K個類。

K_Means_Example_Step_2

步驟三:計算得到上步得到聚類中每一聚類觀測值的圖心,作為新的均值點。

K_Means_Example_Step_3

步驟四:重複步驟三,直至結果收斂,這裡的收斂是指所有點到各自中心點的距離的和收斂。

K_Means_Example_Step_4

K-Means演算法的原理比較簡單,但是值得注意的是,有兩個地方是需要演算法使用者去自己選擇的:第一個就是K的值,簡而言之就是資料集應該被分成多少個類,在K-Means演算法裡面是要求先給出K值的; 第二個就是距離函式,即如何計算向量和向量或者向量和中心點之間的距離,這裡也有很多選擇,最常見的自然是歐式距離,也可以用餘弦相似度來作為距離函式,還有其他的一些方法等等。以上兩個需要注意的選擇必然會對聚類結果產生影響,尤其是K值的選擇,這裡就需要演算法使用者根據自身需要來做出仔細衡量。

scikit-learn

scikit-learn 是一個基於Python的Machine Learning模組,裡面給出了很多Machine Learning相關的演算法實現,其中就包括K-Means演算法。安裝的話建議參考scikit-learn的Github Repo, 從軟體包管理裡面裝的似乎都版本比較低,會少一點功能。

在做K-Means聚類之前,我們首先需要對將文字轉化成向量的形式,轉換文字的第一步,自然是分詞,這裡可以直接使用jieba分詞,分完詞過後再將詞轉換成向量,或者說轉換成Bag-of-words模型,這裡可以採用TF-IDF演算法,TF-IDF簡單來說就是從兩個方面對文字中的詞進行加權:

  1. 詞在當前文字中出現的次數。
  2. 總文字數包含詞的數目。

前者越高越好,後者越低越好,比如說「你」,「我」這些詞,在一個文字中出現的次數很多,總文字包含這些詞的數目也會很多,那麼這些詞自然無法作為當前文字的代表性內容,如果一個文字中某個詞大量出現,但是在總文字中出現的次數又不多,那麼這個詞對於文字來說很有可能是很重要的。比如在A文字中」喬布斯「這個詞出現了很多次,然後「喬布斯」在其他文本里面又很少出現,那麼A文字主題和「喬布斯」相關的可能性就比較大,我們必然得提高「喬布斯」這個詞在A文字向量中的權重。

在scikit-learn裡面,同樣也實現了TF-IDF演算法,我們可以直接呼叫。

kmeans_demo

Python

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

#!/usr/bin/env python

# -*- coding: utf-8 -*-

'''

Author: razrlele

Email: [email protected]

'''

import jieba

from sklearn.feature_extraction.text import  TfidfVectorizer

from sklearn.cluster import KMeans

 

def jieba_tokenize(text):

    return jieba.lcut(text)

 

 

tfidf_vectorizer = TfidfVectorizer(tokenizer=jieba_tokenize, \

lowercase=False)

'''

tokenizer: 指定分詞函式

lowercase: 在分詞之前將所有的文字轉換成小寫,因為涉及到中文文字處理,

所以最好是False

'''

text_list = ["今天天氣真好啊啊啊啊", "小明上了清華大學", \

"我今天拿到了Google的Offer", "清華大學在自然語言處理方面真厲害"]

#需要進行聚類的文字集

tfidf_matrix = tfidf_vectorizer.fit_transform(text_list)

 

num_clusters = 3

km_cluster = KMeans(n_clusters=num_clusters, max_iter=300, n_init=40, \

                    init='k-means++',n_jobs=-1)

'''

n_clusters: 指定K的值

max_iter: 對於單次初始值計算的最大迭代次數

n_init: 重新選擇初始值的次數

init: 制定初始值選擇的演算法

n_jobs: 程序個數,為-1的時候是指預設跑滿CPU

注意,這個對於單個初始值的計算始終只會使用單程序計算,

平行計算只是針對與不同初始值的計算。比如n_init=10,n_jobs=40,

伺服器上面有20個CPU可以開40個程序,最終只會開10個程序

'''

#返回各自文字的所被分配到的類索引

result = km_cluster.fit_predict(tfidf_matrix)

 

print "Predicting result: ", result

 

程式執行結果:

ZSH

1

2

3

4

5

6

python2 kmeans_demo.py

Building prefix dict from the default dictionary ...

Loading model from cache /tmp/jieba.cache

Loading model cost 0.275 seconds.

Prefix dict has been built succesfully.

Predicting result: [2 0 1 0]

 

對擬合結果的持久化

在進行大規模文字聚類的時候,並不是每一次都需要從頭開始計算TF-IDF和K-Means的,有的時候我們只是需要根據之前的資料聚類結果預測新資料,所以我們可以考慮將計算中途的一些資料儲存下來,這樣就可以節省不少的時間,令人感到驚喜的是,我們同樣可以通過scikit-learn的joblib模組來實現持久化這一過程。

如果有看過scikit-learn官方文件的話應該就可以發現,其實fit_transformfit_predict只是將fittransform,以及fitpredicti兩個方法寫在了一起,也就是說在上面程式碼中:

Python

1

2

3

4

5

6

7

8

9

tfidf_matrix = tfidf_vectorizer.fit_transform(text_list)

#上面一行程式碼等價於下面兩行程式碼

tfidf_vectorizer.fit(text_list)

tfidf_matrix = tfidf_vectorizer.transform(text_list)

 

result = km_cluster.fit_predict(tfidf_matrix)

#上面一行程式碼等價於下面兩行程式碼

km_cluster.fit(tfidf_matrix)

result = km_cluster.predict(tfidf_matrix)

每一次fit都是對資料進行擬合操作,所以我們可以直接選擇將擬合結果持久化,然後預測的時候直接載入,進而節省時間。

Python

1

2

3

4

5

6

7

8

from sklearn.externals import joblib

 

joblib.dump(tfidf_vectorizer, 'tfidf_fit_result.pkl')

joblib.dump(km_cluster, 'km_cluster_fit_result.pkl')

 

#程式下一次則可以直接load

tfidf_vectorizer = joblib.load('tfidf_fit_result.pkl')

km_cluster = joblib.load('km_cluster_fit_result.pkl')