成对的相似性与numpy没有for循环

时间:2018-03-11 21:05:13

标签: python numpy

我正在尝试使用numpy计算每一行之间的相似性。请问如何在没有for循环的情况下完成这项工作?

import numpy as np


x = np.array([[1, 2, 3], [4, 5, 6]])     # input:  2 x 3 matrix
similarity_matrix = np.zeros([2, 2])     # output: 2 x 2 matrix

for i, row1 in enumerate(x):
    for j, row2 in enumerate(x):
        similarity_matrix[i, j] = my_similarity_func(row1, row2) # this func returns a scalar

如果我的输入是n x 1矩阵,那么这是有效的。当输入是n x m矩阵时,有没有办法实现这一点?

x = np.array([1, 2, 3])
similarity_matrix = my_similarity_func(*np.meshgrid(x, x))

*我知道有一些库可以计算sklearnscipy之类的相似之处。还存在一种奇特的线性代数方式。但在这里,我只是想知道是否有可能替换这个for循环。

2 个答案:

答案 0 :(得分:1)

您可以使用itertools替换for循环,这可能更有效(我假设效率是您的实际目标):

from itertools import product, starmap
it = starmap(my_similarity_func, product(x, x))
similarity_matrix = np.fromiter(it, float).reshape((len(x), len(x)))

答案 1 :(得分:0)

有几个选项可以删除for循环。 假设这是由于对效率的担忧,我提供了一些基准。 分析这类事物非常依赖于被调用函数的作用以及数组的大小。 定时这里给出的几个方法(使用np.dot作为相似度函数)给出了非常相似的结果,for循环具有惊人的竞争力。

%timeit tmp=test_using_for_loop(x)
5.88 µs ± 164 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

%timeit array([[my_similarity_func(r1, r2) for r1 in x] for r2 in x])
6.54 µs ± 101 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

%timeit it = starmap(my_similarity_func, product(x, x)); similarity_matrix = np.fromiter(it, float).reshape((len(x), len(x)))
5.34 µs ± 364 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

%timeit dist.cdist(x,x,metric=my_similarity_func)
15 µs ± 136 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

另一方面,给出的数据相当小。 在许多应用中,通常在数百或数千个样本上计算相似性度量。 毕竟,为什么要优化2乘3矩阵? 使用更大的数据

x = np.random.randn(3000, 150)

结果

%timeit tmp=test_using_for_loop(x)
5.69 s ± 54.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%timeit array([[my_similarity_func(r1, r2) for r1 in x] for r2 in x])
5.17 s ± 29.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%timeit it = starmap(my_similarity_func, product(x, x)); similarity_matrix = np.fromiter(it, float).reshape((len(x), len(x)))
3.74 s ± 20.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%timeit dist.cdist(x,x,metric=my_similarity_func)
8.08 s ± 156 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

还有coldspeed和其他几位评论者提出的问题 - 优化相似性函数而不是它的调用方式会更好吗? 自定义相似度函数不会像np.dot那样优化。 所以,使用什么是故意的最坏情况(和绝对无用的)相似性函数

def my_similarity_func(a,b):
    calc1 = a.dot(b)
    calc2 = sqrt(abs(sum(a)+sum(b)))
    calc3 = calc1**2 / calc2 + 1
    return calc3

性能上相当大的差异几乎消失了。 itertools方法和基本循环之间的百分比差异大约为5%或6%(仍然大于预期,但并不多)

%timeit tmp=test_using_for_loop(x)
1min 11s ± 2.02 s per loop (mean ± std. dev. of 7 runs, 1 loop each)

%timeit array([[my_similarity_func(r1, r2) for r1 in x] for r2 in x])
1min 7s ± 468 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%timeit array([[my_similarity_func(r1, r2) for r1 in x] for r2 in x])
1min 7s ± 322 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%timeit array([[my_similarity_func(r1, r2) for r1 in x] for r2 in x])
1min 8s ± 1.31 s per loop (mean ± std. dev. of 7 runs, 1 loop each)

总之,有几种方法可以删除for循环,但在性能方面它们可能都是相似的。 如果性能很重要,最好以可以利用广播或其他优化的方式重新编写相似性函数。 这样做最坏情况下的相似性函数可以将运行时间减少到几百毫秒。

%timeit x.dot(x.T)**2 / sqrt(abs(sum(x, 1)[:,None] + sum(x.T, 0))) + 1
128 ms ± 3.14 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)