【问题标题】:Computing Jaccard Similarity in Python在 Python 中计算 Jaccard 相似度
【发布时间】:2017-03-27 12:57:04
【问题描述】:

我有 20,000 个文档要计算其真正的 Jaccard 相似度,以便以后检查 MinWise 散列法对其进行近似的准确度。

每个文档都表示为 numpy 矩阵中的一列,其中每一行是一个出现在文档中的单词 (entry=1) 或不出现在文档中的单词 (entry = 0)。大约有 600 个单词(行)。

因此,例如第 1 列将是 [1 0 0 0 0 0 1 0 0 0 1 0],这意味着其中出现了单词 1,7,11 而没有其他单词。

除了我的元素比较方法之外,还有更有效的方法来计算相似度吗?我看不出如何使用集合来提高速度,因为集合刚刚变成 (0,1),但就目前而言,代码慢得不可思议。

import numpy as np

#load file into python
rawdata = np.loadtxt("myfile.csv",delimiter="\t")
#Convert the documents from rows to columns
rawdata = np.transpose(rawdata)
#compute true jacard similarity
ndocs = rawdata.shape[1]
nwords = rawdata.shape[0]
tru_sim = np.zeros((ndocs,ndocs))

#computes jaccard similarity of 2 documents
def jaccard(c1, c2):
    n11 = sum((c1==1)&(c2==1))
    n00 = sum((c1==0)&(c2==0))
    jac = n11 / (nfeats-n00)
    return (jac)

for i in range(0,ndocs):
    tru_sim[i,i]=1
    for j in range(i+1,ndocs):
        tru_sim[i,j] = jaccard(rawdata[:,i],rawdata[:,j])

【问题讨论】:

标签: python performance numpy vectorization data-mining


【解决方案1】:

这是一种矢量化方法 -

# Get the row, col indices that are to be set in output array        
r,c = np.tril_indices(ndocs,-1)

# Use those indicees to slice out respective columns 
p1 = rawdata[:,c]
p2 = rawdata[:,r]

# Perform n11 and n00 vectorized computations across all indexed columns
n11v = ((p1==1) & (p2==1)).sum(0)
n00v = ((p1==0) & (p2==0)).sum(0)

# Finally, setup output array and set final division computations
out = np.eye(ndocs)
out[c,r] = n11v / (nfeats-n00v)

np.einsum 计算n11vn00v 的替代方法-

n11v = np.einsum('ij,ij->j',(p1==1),(p2==1).astype(int))
n00v = np.einsum('ij,ij->j',(p1==0),(p2==0).astype(int))

如果rawdata 仅由0s1s 组成,获取它们的更简单方法是-

n11v = np.einsum('ij,ij->j',p1,p2)
n00v = np.einsum('ij,ij->j',1-p1,1-p2)

基准测试

函数定义-

def original_app(rawdata, ndocs, nfeats):
    tru_sim = np.zeros((ndocs,ndocs))
    for i in range(0,ndocs):
        tru_sim[i,i]=1
        for j in range(i+1,ndocs):
            tru_sim[i,j] = jaccard(rawdata[:,i],rawdata[:,j])
    return tru_sim

def vectorized_app(rawdata, ndocs, nfeats):
    r,c = np.tril_indices(ndocs,-1)
    p1 = rawdata[:,c]
    p2 = rawdata[:,r]
    n11v = ((p1==1) & (p2==1)).sum(0)
    n00v = ((p1==0) & (p2==0)).sum(0)
    out = np.eye(ndocs)
    out[c,r] = n11v / (nfeats-n00v)
    return out

验证和时间安排 -

In [6]: # Setup inputs
   ...: rawdata = (np.random.rand(20,10000)>0.2).astype(int)
   ...: rawdata = np.transpose(rawdata)
   ...: ndocs = rawdata.shape[1]
   ...: nwords = rawdata.shape[0]
   ...: nfeats = 5
   ...: 

In [7]: # Verify results
   ...: out1 = original_app(rawdata, ndocs, nfeats)
   ...: out2 = vectorized_app(rawdata, ndocs, nfeats)
   ...: print np.allclose(out1,out2)
   ...: 
True

In [8]: %timeit original_app(rawdata, ndocs, nfeats)
1 loops, best of 3: 8.72 s per loop

In [9]: %timeit vectorized_app(rawdata, ndocs, nfeats)
10 loops, best of 3: 27.6 ms per loop

那里有一些神奇的 300x+ 加速!

那么,它为什么这么快?嗯,这涉及到很多因素,最重要的一个是 NumPy 数组是为提高性能而构建的,并针对矢量化计算进行了优化。通过所提议的方法,我们可以很好地利用它,因此可以看到这样的加速。

这里有一个related Q&A 详细讨论了这些性能标准。

【讨论】:

  • 我的数据确实只包含 1 和 0。你能解释一下为什么这比我使用的方法计算效率更高吗?
  • @Magic8ball 添加了运行时测试和一些关于为什么它有效的 cmets。看看吧!
  • 感谢您的反馈。现在我在步骤 p1 = rawdata[:,c] 上得到了一个 MemoryError ,因为它是一个包含大约 2.32 亿个条目的数组,所以我不确定这个特定代码是否可以扩展到我的项目,但想法是有帮助。
  • @Divakar 是的 nfeats = nwords。
  • @Anony-Mousse 是正确的。由于巨大的内存消耗,这不能随功能扩展。
【解决方案2】:

要计算 Jaccard,请使用:

def jaccard(x,y):
  x = np.asarray(x, np.bool) # Not necessary, if you keep your data
  y = np.asarray(y, np.bool) # in a boolean array already!
  return np.double(np.bitwise_and(x, y).sum()) / np.double(np.bitwise_or(x, y).sum())

print jaccard([1,1,0,0,0],[0,1,0,0,1])
>>> 0.33333333333333331

【讨论】:

    猜你喜欢
    • 2022-01-04
    • 1970-01-01
    • 1970-01-01
    • 2018-01-02
    • 1970-01-01
    • 1970-01-01
    • 2019-03-26
    • 2018-01-23
    • 2022-07-21
    相关资源
    最近更新 更多