Numpy中的广播函数类似于矩阵乘法

时间:2016-03-12 18:58:39

标签: python numpy knn

所以我正在为一些更大的数据集构建一个KNN,我需要运行Leave-one-out交叉验证才能选择正确的K,因此速度很重要。

我正在尝试通过广播进行距离计算。

情况是:X是我的训练矩阵,是一个在行上有样本的二维矩阵。 Q是我的查询矩阵或测试数据,也是行上的样本。

我需要运行类似于矩阵乘法的东西,其中我将Q的每一行与X.T(x转置)的每一列匹配,并构建一个sample x sample矩阵,其中每个条目[i ,j]是查询样本i与训练样本j的距离。然后我将从前k个样本中排序并选择类的模式。

无论如何,在矩阵乘法中,numpy就是这样......但是它不是距离计算,而是分段乘法和求和(点积)。如果我可以将我的距离函数插入到该位置,我想我的KNN距离计算与numpy矩阵乘法一样快。

有没有办法使用广播或其他一些numpy技术来做到这一点?

甚至可能是一种并行化的方法吗?

示例代码:

import numpy as np

x1 = np.asarray([1.0,10.0,100.0])
x2 = np.asarray([40.0,60.0,80.0])
x3 = np.asarray([20.,30.,40.])
x = np.concatenate((x1.reshape(3,1),x2.reshape(3,1),x3.reshape(3,1)),axis=1)

y1 = np.asarray([4.0,88.0,35.0])
y2 = np.asarray([7.0,65.0,99.0])
y3 = np.asarray([40.0,13.0,27.0])
y = np.concatenate((y1.reshape(3,1),y2.reshape(3,1),y3.reshape(3,1)),axis=1)

def euclidean_distance(p1,p2): 
    return np.sqrt(np.sum((p1-p2)**2.0))

所以,我可能会写:

distances = np.zeros((y.shape[0],x.shape[0]))
for i in range(y.shape[0]):
   for j in range(x.shape[0]):
       distances[i,j] = euclidean_distance(y[i,:],x[j,:])

这就是我要排序的东西。在上面的当前for循环中,我只选择我的k个最近邻居并在该内循环中找到该类...但它比计算向量化计算中的所有距离要慢得多。

3 个答案:

答案 0 :(得分:2)

正如Divakar已经提到的,最简单的选择可能是scipy.spatial.distance.cdist

from scipy.spatial.distance import cdist

distances = cdist(y, x)                 # Euclidean
distances = cdist(y, x, 'mahalanobis')  # Mahalanobis

这是单线程但速度很快。您也可以使用np.linalg.norm

distances = np.linalg.norm(y[:, None, :] - x[None, :, :], axis=2)   # Euclidean

这会在xy中的行对上广播差异计算,以创建形状(3, 3, 3)的中间数组,然后计算最后一个轴上的欧几里德范数。这是多线程的,但是如果xy有很多行(它也没有利用距离矩阵的对称性),则涉及构建一个可能非常大的中间数组。

将第二种方法概括为计算马哈拉诺比斯距离而不是欧几里德距离是相当简单的(我将留下这部分让你弄清楚......)。

答案 1 :(得分:1)

我会:

  • 重复+将两个数组重塑为3D形式(3 x len(x)x len(y))
  • 沿轴线取差值,sqare和sum = 0
  • 现在你有一个2D数组的距离,可以沿适当的轴取最小值

这对你有帮助吗?或者我会尝试更明确地写出来......

对第2步的评论:你不必采取sqrt来找到最小值,你也可以最小化正方形

答案 2 :(得分:1)

尝试广播以实现交叉差异:

d = np.sqrt(np.sum((y[:,None,:]-x[None,:,:])**2,axis=-1))

我的测试脚本

import numpy as np

x1 = np.asarray([1.0,10.0,100.0])
x2 = np.asarray([40.0,60.0,80.0])
x3 = np.asarray([20.,30.,40.])
x = np.concatenate([i.reshape(-1,1) for i in [x1,x2,x3]], axis=1)
# see also column_stack

y1 = np.asarray([4.0,88.0,35.0])
y2 = np.asarray([7.0,65.0,99.0])
y3 = np.asarray([40.0,13.0,27.0])
"""
y1 = np.asarray([4.0,88.0])   # test 2d y
y2 = np.asarray([7.0,99.0])
y3 = np.asarray([13.0,27.0])
"""
y = np.concatenate([i.reshape(-1,1) for i in [y1,y2,y3]], axis=1)

def euclidean_distance(p1,p2):
    return np.sqrt(np.sum((p1-p2)**2.0))

distances = np.zeros((y.shape[0],x.shape[0]))
for i in range(y.shape[0]):
   for j in range(x.shape[0]):
       distances[i,j] = euclidean_distance(y[i,:],x[j,:])

print (distances)

d = np.sqrt(np.sum((y[:,None,:]-x[None,:,:])**2,axis=-1))
print(d)
制造

1230:~/mypy$ python2.7 stack35961972.py 
[[  38.70400496   54.2678542   120.60265337]
 [  90.79096871   79.98749902   33.13608305]
 [  68.45436436   46.42197755   68.95650803]]
[[  38.70400496   54.2678542   120.60265337]
 [  90.79096871   79.98749902   33.13608305]
 [  68.45436436   46.42197755   68.95650803]]