在numpy中没有循环的类标签求和

时间:2014-12-26 02:36:18

标签: python numpy

我有一个矩阵,表示到一组点的k最近邻居的距离, 并且有一个最近邻居的类标签矩阵。 (均为N-by-k矩阵)

没有显式python循环的最佳方法是什么(实际上,我想在那些循环不起作用的theano中实现它)来构建一个(N-by-#类)矩阵,其(i,j)元素将是从第i个点到其k-NN点的距离之和与类标签'j'?

实施例:

# N = 2
# k = 5
# number of classes = 3

K_val  = np.array([[1,2,3,4,6],
                  [2,4,5,5,7]])

l_val  = np.array([[0,1,2,0,1],
                   [2,0,1,2,0]])

"""
result -> [[5,8,3],
           [11,5,7]]
"""

2 个答案:

答案 0 :(得分:2)

您可以使用此计算 numpy.bincount。它 有一个weights参数,可让您计算l_val中的项目 但是根据K_val对项目进行加权。

唯一的小障碍是K_vall_val的每一行似乎都是独立对待的。因此,向l_val添加一个班次,这样每行的值都与其他每一行都不同。


import numpy as np

num_classes = 3
K_val  = np.array([[1,2,3,4,6],
                  [2,4,5,5,7]])

l_val  = np.array([[0,1,2,0,1],
                   [2,0,1,2,0]])

def label_distance(l_val, K_val):
    nrows, ncols = l_val.shape
    shift = (np.arange(nrows)*num_classes)[:, np.newaxis]
    result = (np.bincount((l_val+shift).ravel(), weights=K_val.ravel(), 
                          minlength=num_classes*nrows)
              .reshape(nrows, num_classes))
    return result


print(label_distance(l_val, K_val))

产量

[[  5.   8.   3.]
 [ 11.   5.   7.]]

尽管senderle的方法非常优雅,但使用bincount更快:

def using_extradim(l_val, K_val): 
    return (K_val[:,:,None] * (l_val[:,:,None] == numpy.arange(3)[None,None,:])).sum(axis=1)

In [34]: K2 = np.tile(K_val, (1000,1))

In [35]: L2 = np.tile(l_val, (1000,1))

In [36]: %timeit using_extradim(L2, K2)
1000 loops, best of 3: 584 µs per loop

In [40]: %timeit label_distance(L2, K2)
10000 loops, best of 3: 67.7 µs per loop

答案 1 :(得分:2)

这是一种直接计算值的方法。正如unutbu的测试显示的那样,使用bincount对于大型数据集要快得多,但我认为值得知道如何使用香草广播来做到这一点:

>>> (K_val[:,:,None] * (l_val[:,:,None] == numpy.arange(3)[None,None,:])).sum(axis=1)
array([[ 5,  8,  3],
       [11,  5,  7]])

那有点毛茸茸,所以我会慢慢走过去。在您希望以后能够阅读的代码中,最好这样做!有四个步骤:

labels = numpy.arange(3)                                   
l_selector = l_val[:,:,None] == labels[None,None,:]
distances = (K_val[:,:,None] * l_selector)
result = distances.sum(axis=1)

首先,我们创建一个标签列表(上面为labels)。然后我们创建一个布尔索引数组:

>>> l_selector = l_val[:,:,None] == labels[None,None,:]

这会将l_vallabels扩展为可以一起广播的数组。 None值(相当于np.newaxis)会添加新的空尺寸:

>>> l_val[:,:,None].shape
(2, 5, 1)
>>> labels[None,None,:].shape
(1, 1, 3)

尺寸是对齐的,因此两个数组都可以沿着它们的空尺寸扩展(通过重复值):

>>> l_selector.shape
(2, 5, 3)

现在我们有一个(n_points, n_neighbors, n_labels)数组,其中每列对应一个标签。 (看看每行只有一个True值?)

>>> l_selector
array([[[ True, False, False],
        [False,  True, False],
        [False, False,  True],
        [ True, False, False],
        [False,  True, False]],

       [[False, False,  True],
        [ True, False, False],
        [False,  True, False],
        [False, False,  True],
        [ True, False, False]]], dtype=bool)

现在我们可以使用它来分离三个标签中每个标签的距离。但同样,我们必须确保我们的数组是可广播的,因此K_val[:,:,None]在这里:

>>> distances = (K_val[:,:,None] * l_selector)
>>> distances
array([[[1, 0, 0],
        [0, 2, 0],
        [0, 0, 3],
        [4, 0, 0],
        [0, 6, 0]],

       [[0, 0, 2],
        [4, 0, 0],
        [0, 5, 0],
        [0, 0, 5],
        [7, 0, 0]]])

现在我们所要做的就是对列进行总结。

>>> result = distances.sum(axis=1)
>>> result
array([[ 5,  8,  3],
       [11,  5,  7]])

您可能还会考虑转置方法,这需要更少的重塑:

>>> labels = numpy.arange(3)
>>> l_selector = l_val[None,:,:] == labels[:,None,None]
>>> distances = K_val * l_selector
>>> distances.sum(axis=-1)
array([[ 5, 11],
       [ 8,  5],
       [ 3,  7]])
>>> distances.sum(axis=-1).T
array([[ 5,  8,  3],
       [11,  5,  7]])