基於scipy層次聚類的python實現

前段時間的項目中碰到一個分層聚類問題,任務是對語料庫中的高頻詞彙進行分層聚類並刻畫分類結果中的簇內的層次關係。第一想法是到網上去搜搜看看有沒有什麼好用的庫。看了sklearn上的層次聚類的庫函數,尼瑪居然要我指定簇的個數,層次聚類的特點就是無需指定簇的個數嘛!逗我?之後發現scipy下的cluster.hierarchy可以做層次聚類。開幹!單詞的描述用的是word2vec詞向量,挺火的一個工具,google開發的。我用的200維詞向量,訓練方法網上一大堆,訓練的結果就是一個類似於詞向量字典的東西,建立了單詞與向量之間的映射關係。有了單詞的表示,那麼就可以刻畫單詞之間的相似性了。

在分層聚類中,我們要考慮到相似性度量的參數,以及簇間距離的計算方式。採用餘弦相似度作爲參數,介於單連接和全連接之間的average作爲簇間距離的計算方式。我先是得到了語料庫中詞頻最高的5000個單詞,轉化爲word2vec詞向量之後,做分層聚類。

代碼如下:


points = [self.Word2Vec[i] for i in mykeys[0:n]]#mykeys爲得到的詞頻字典的鍵的列表

Z = sch.linkage(points , method='average', metric='cosine')#得到對應的Z矩陣

cluster = sch.fcluster(Z, t=1, criterion='inconsistent')#分層聚類結果

cluster的結果是一個列表,裏面有n個元素,對應原始觀測點的聚類之後所屬的簇的id。

然後我仔細研究了Z矩陣的構成,它一共有n-1行,4列,第一列和第二列的值代表節點id,按照簇出現的順序進行編號。然後第三列是這兩個id之間的相似程度,或者說是距離,這裏餘弦相似度被轉化爲距離了,也就是1-cos(point1, point2)。第四列是按照當前行合併之後得到的節點id。這樣以來,可以得到n個葉子結點id,n-1個內部節點id,一共2*n-1個節點id。現在我要把每個內部節點的子節點的信息保留下來,以便後續操作:

原始的觀測點的簇id就按照順序來好了。

從第一個內部節點開始,就要保存兒子節點的信息了,即保留左右兒子的id。

# 按照bottom-up方式獲取內部節點,存儲了ClusterNode的信息,countdist,id,left,right
#樹的葉子節點列表
nodelist = sch._order_cluster_tree(Z)

#節點字典,鍵爲節點id,值爲對應的子節點的id列表。
node_dict = {}
# 保存葉子節點id,用做初始化。
for item in xrange(n):
    node_dict[item] = item

# 保存內部節點id
# 把每個凝聚點的子節點的信息保留在字典中
for item in nodelist:
    node_dict[item.id] = [node_dict[item.left.id], node_dict[item.right.id]]

# 將字典中每個葉子節點變爲列表形式,方便之後的處理
for item in xrange(n):
    node_dict[item] = [item]

# 獲取各個簇的最近公共父節點
cluster_node = sch.leaders(Z, cluster)

在得到各個簇的最近公共父節點id之後我們就可以得到一種嵌套的列表,然後按照我的另一篇博文的方式就可以提取層次關係了。

http://blog.csdn.net/u012260341/article/details/77989167


這樣就可以了麼?後來一想,5000個單詞是不是小了點。於是乎我加到了10000。滿懷期待的等它出結果。結果,傻眼了,給我報了內存錯誤。那麼就去看看啥錯誤咯,是numpy在申請列表的時候內存報錯,想想,一次性載入這麼多數據,不爆纔怪呢。1w個單詞,需要存儲1w*9999/2個數據,python的列表表示很不開心,容納不下。那怎麼辦,改源碼?改源碼還是需要勇氣的。我選擇了尊重源碼。那這個問題就到此爲止了麼?不行,這樣子沒法交差啊!於是乎我想到了數學運算工具matlab。數學運算神器哦。我怕它再次內存報錯,先產生了10000個隨機數進行測試,哇,居然沒報錯,那我繼續,加到20000,居然還沒報錯!我來勁了,直接上100000!這次報錯了,說我沒有足夠的內存容納30多G的數據。想想也是,我的電腦也才8G內存。matlab都沒法處理這麼大量的數據了。本來啊,分層聚類本來就不適合海量數據嘛。不過對20000個數據進行聚類的能力還是要有的。那麼我就研究,python怎麼調用matlab的庫。原來是要裝個mlab的庫,用pip install mlab就好了。然後就是導入matlab的相應模塊了。這裏要注意,我之前內存報錯的原因在於計算10000個點兩兩之間的餘弦相似度,而我要得到的是一個Z的矩陣,它包含了點之間的距離等信息,因此我只需要調用matlab庫得到Z就可以了,之後還是按照原來的方法即可。這種兩階段方法也是挺有意思的。誰讓scipy沒法一次性處理這麼多數據嘛!

導入mlab的相應模塊

from mlab.releases import latest_release as matlab

假設要對points中的點進行分層聚類。
points = [[1, 1], [1, 2], [1, 3], [1, 4], [1, 5], [1, 6]]

然後直接調用matlab下的linkage函數就好了。這樣就完成了20000個單詞的分層聚類任務。

#得到matlab下的Z
Z_matlab = matlab.linkage(points, 'average', 'cosine')

#matlab下的Z轉換成scipy可用的Z
Z = sch.from_mlab_linkage(Z_matlab)

# 根據linkage matrix Z得到聚類結果:
cluster = sch.fcluster(Z, t=1, criterion='inconsistent')

總結:在這次分層聚類任務過程中,我閱讀了大量庫的源碼,包括函數參數的意義,輸入輸出的過程,這個過程還是略有點痛苦,特別是後來報個MemoryError簡直桑心透了。還好找到了解決方案,用matlab這個神器最終解決了我的問題。






發佈了23 篇原創文章 · 獲贊 14 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章