以下代码迭代两个数组的每个元素以计算成对欧几里德距离。
def compute_distances_two_loops(X, Y):
num_test = X.shape[0]
num_train = Y.shape[0]
dists = np.zeros((num_test, num_train))
for i in range(num_test):
for j in range(num_train):
dists[i][j] = np.sqrt(np.sum((X[i] - Y[j])**2))
return dists
以下代码用于相同的目的,但使用单循环。
def compute_distances_one_loop(X, Y):
num_test = X.shape[0]
num_train = Y.shape[0]
dists = np.zeros((num_test, num_train))
for i in range(num_test):
dists[i, :] = np.sqrt(np.sum((Y - X[i])**2, axis=1))
return dists
以下是两者的时间比较。
two_loop_time = time_function(compute_distances_two_loops, X, Y)
print ('Two loop version took %f seconds' % two_loop_time)
>> Two loop version took 20.3 seconds
one_loop_time = time_function(compute_distances_one_loop, X, Y)
print ('One loop version took %f seconds' % one_loop_time)
>> One loop version took 80.9 seconds
X和Y都是带有形状的numpy.ndarray -
X:(500,3000) Y:(5000,3000)
出于直觉,结果不正确,单循环至少应以相同的速度运行。我在这里错过了什么?
PS:结果不是一次运行。我在不同的机器上运行了代码次数,结果相似。
答案 0 :(得分:2)
原因是循环体内的数组大小。 在两个循环变体中工作在两个3000个元素的数组上。这很容易适合至少cpu的二级缓存,它比主内存快得多,但它也足够大,计算距离比python循环迭代开销慢得多。
第二种情况是循环体在5000 * 3000元素上工作。这是如此多,以至于数据需要在每个计算步骤中转到主存储器(首先将Y-X [i]减法到一个临时数组中,将临时数转换为另一个临时数,然后将其读回以对其求和)。 对于所涉及的简单操作,主内存比cpu慢得多,因此尽管移除了循环,但它需要更长的时间。 你可以通过使用inplace操作写入预分配的临时数组来加快速度,但它仍然比这些数组大小的两个循环变量慢。
请注意scipy.spatial.distance.cdist(X, Y)
可能是最快的,因为它根本不需要任何临时值