基于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这个神器最终解决了我的问题。






你可能感兴趣的:(scipy)