使用 Jaccard 相似度对分类数据进行聚类

Posted

技术标签:

【中文标题】使用 Jaccard 相似度对分类数据进行聚类【英文标题】:Clustering Categorical data using jaccard similarity 【发布时间】:2015-07-20 07:55:38 【问题描述】:

我正在尝试为分类数据构建聚类算法。

我已经阅读了不同的算法,例如 k-modes、ROCK、LIMBO,但是我想构建自己的一个,并将准确性和成本与其他算法进行比较。

我有 (m) 个训练集和 (n=22) 个特征

方法

我的方法很简单:

第 1 步:计算每个训练数据之间的 Jaccard 相似度,形成一个 (m*m) 相似度矩阵。 第 2 步:然后我执行一些操作以找到最佳质心并使用简单的 k-means 方法找到聚类。

我在步骤 1 中创建的相似度矩阵将在执行 k-means 算法时使用

矩阵创建:

total_columns=22
for i in range(0,data_set):
    for j in range(0,data_set):
        if j>=i:
            # Calculating jaccard similarity between two data rows i and j 
            for column in data_set.columns:    
                if data_orig[column][j]==data_new[column][i]:
                    common_count=common_count+1
            probability=common_count/float(total_columns)    
            fnl_matrix[i][j] =probability  
            fnl_matrix[j][i] =probability

我的fnl_matrix(6 行)的部分快照如下:

问题陈述:

我面临的问题是,当我创建 (m*m) 矩阵时,对于更大的数据集,我的性能会受到影响。即使对于具有 8000 行的较小数据集,相似度矩阵的创建也需要难以忍受的时间。有什么方法可以调整我的代码或对矩阵做一些具有成本效益的事情。

【问题讨论】:

如果您已经看到我的回答,我只是添加了一些显着的改进和进一步的建议。 【参考方案1】:

首先,您计算 Jaccard 的方式似乎效率低下(如果不是错误的话)。您正在使用 for 循环,这可能是在 Python 中做事最慢的方式。我建议您使用 Python 的 set 来存储行。集合提供了快速的交集,因为它们是哈希表,并且所有计算都是在 C/C++ 中执行的,而不是在 Python 本身中。想象一下r1r2 是两行。

r1 = set(some_row1)
r2 = set(some_row2)
intersection_len = len(r1.intersect(r2))
union_len = len(r1) + len(r2) - intersection_len
jaccard = intersection_len / union_len

集合的构建成本很高,因此您最初应该将所有行存储为集合。那么你应该摆脱

for i in range(0,data_set):
    for j in range(0,data_set):

部分也是如此。请改用itertools。假设 data_set 是一个行列表。

for row1, row2 in itertools.combinations(data_set, r=2):
    ...

这个东西运行得更快,不需要if j>=i 检查。这样你就得到了矩阵的上三角形。让我们画出最终算法的草图。 更新:添加numpy

from scipy.spatial import distance
from itertools import combinations
import numpy as np


def jaccard(set1, set2):
    intersection_len = set1.intersection(set2)
    union_len = len(set1) + len(set2) - intersection_len
    return intersection_len / union_len

original_data_set = [row1, row2, row3,..., row_m]
data_set = [set(row) for row in original_data_set]

jaccard_generator = (jaccard(row1, row2) for row1, row2 in combinations(data_set, r=2))
flattened_matrix = np.fromiter(jaccard_generator, dtype=np.float64)

# since flattened_matrix is the flattened upper triangle of the matrix
# we need to expand it.
normal_matrix = distance.squareform(flattened_matrix)
# replacing zeros with ones at the diagonal. 
normal_matrix += np.identity(len(data_set))

就是这样。你有你的矩阵。从这一点开始,您可能会考虑将这段代码移植到 Cython(没有太多工作要做,您只需要以稍微不同的方式定义 jaccard 函数,即为局部变量添加类型声明)。比如:

cpdef double jaccard(set set1, set set2):
    cdef long intersection_len, union_len # or consider int 
    intersection_len = set1.intersection(set2)
    union_len = len(set1) + len(set2) - intersection_len
    return intersection_len / union_len

但我不确定这会正确编译(我的 Cython 经验非常有限)

附: 您可以使用numpy 数组而不是sets,因为它们提供了类似的交集方法并且也可以在 C/C++ 中运行,但是两个数组的交集大约需要 O(n^2) 时间,而两个哈希的交集-tables (set objects) 需要 O(n) 时间,前提是碰撞率接近于零。

【讨论】:

谢谢你的精彩解释。你的代码真的帮了我很多 @user2404193 不客气。我添加了 Cythonized jaccard 函数的草图。 Eli,通过使用上述技术。我所有的对角线都变为 0,但是我想保持对角线为 1。我知道我可以在矩阵形成后将所有对角线更新为 1。我可以即时做同样的事情吗,即在矩阵形成时 @Sam 我不知道有这样的功能。尽管如此,最短的解决方案是将大小为 m 的单位矩阵添加到扩展矩阵中,其中 m 是data_set 中的行数,即normal_matrix += np.identity(m)。这会将所有对角线元素变为 1。性能开销几乎为零。 是的,我明白了,但是当矩阵被创建时,我不能即时做一些事情。因为单位矩阵只有在矩阵准备好后才能添加。【参考方案2】:

解释的 Python 代码很慢。真的很慢。

这就是为什么好的 python 工具包包含大量 Cython 代码甚至 C 和 Fortran 代码(例如 numpy 中的矩阵运算),并且只使用 Python 来驱动整个过程。

如果您尝试尽可能多地使用numpy,您可能会大大加快您的代码速度。或者,如果您改用 Cython。

考虑使用基于距离的聚类算法,而不是对抗质心:

层次凝聚聚类 (HAC),需要距离矩阵 DBSCAN,可用于任意距离。它甚至不需要距离矩阵,只需要某个阈值的相似项目列表。 K-medoids/PAM 当然也值得一试;但通常不会很快。

【讨论】:

事实上,Python 内置和最广泛使用的模块(如itertools)完全是用C/C++ 编写的,因此摆脱for 循环和纯Python 函数就足够了在每一个可能的地方打电话。如果不明智地使用numpy,尽可能多地使用numpy 可能会减慢速度。

以上是关于使用 Jaccard 相似度对分类数据进行聚类的主要内容,如果未能解决你的问题,请参考以下文章

R语言计算杰卡德相似系数(Jaccard Similarity)实战:自定义函数计算Jaccard相似度对字符串向量计算Jaccard相似度将Jaccard相似度转化为Jaccard距离

使用余弦相似度对文档进行分类

为啥 Jaccard 相似度得分与二进制分类中的准确度得分相同?

按相似度对数据进行分组的最佳算法(相同 ID)

聚类算法——DBSCAN算法原理及公式

数据挖掘中的度量方法