我正在尝试使用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))
*我知道有一些库可以计算sklearn
或scipy
之类的相似之处。还存在一种奇特的线性代数方式。但在这里,我只是想知道是否有可能替换这个for循环。
答案 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)