Numpy 数组:具有随机关系的行/列 argmax

Numpy arrays: row/column wise argmax with random ties

提问人:blipblop 提问时间:8/19/2018 最后编辑:user3483203blipblop 更新时间:8/20/2018 访问量:1869

问:

这是我在 Python 2.7 中尝试使用 Numpy 做的事情。假设我有一个由以下定义定义的数组:a

a = np.array([[1,3,3],[4,5,6],[7,8,1]])

我可以做或获取行/列明智的argmax:a.argmax(0)a.argmax(1)

a.argmax(0)
Out[329]: array([2, 2, 1], dtype=int64)
a.argmax(1)
Out[330]: array([1, 2, 1], dtype=int64)

但是,当出现像第一行这样的平局时,我想在平局之间随机决定 argmax(默认情况下,每当 argmax 或 argmin 中出现平局时,Numpy 都会返回第一个元素)。a

去年,有人提出了一个关于随机解决 Numpy argmax/argmin 关系的问题:通过列索引在 Numpy 数组的每一行中选择一个元素

然而,这个问题针对的是一维数组。在那里,得票最多的答案很有效。还有第二个答案也试图解决多维数组的问题,但不起作用 - 即它不会为每一行/列返回随机求解的最大值的索引。

由于我正在使用大型数组,因此最有效的方法是什么?

python 数组 numpy random argmax

评论

0赞 hpaulj 8/19/2018
链接的 SO 似乎无关紧要。如果你想要“最高性能”,你需要先给我们一个工作的例子。为了声称我的答案更好,我必须证明它得到了正确的值,并且运行得更快。为此,我宁愿不编造自己的示例和基本方法。

答:

5赞 John Zwinck 8/19/2018 #1

一个简单的方法是在开始时向所有值添加一个小的随机数,因此您的数据将如下所示:

a = np.array([[1.1827,3.1734,3.9187],[4.8172,5.7101,6.9182],[7.1834,8.5012,1.9818]])

这可以通过 来完成。a = a + np.random.random(a.shape)

如果以后需要取回原始值,可以删除小数部分。a.astype(int)

评论

2赞 user3483203 8/19/2018
您必须保证添加的数字小于最大数字和次大数字之间的最小差值(如果输入是整数,则可以正常工作)。不过,这是一个聪明的答案。
3赞 Divakar 8/19/2018 #2

通用案例解决方案,每组选择一个

为了解决从指定选择范围的数字列表/数组中选取随机数的一般情况,我们将使用创建统一兰德数组的技巧,添加由间隔长度指定的偏移量,然后执行 .实现将如下所示 -argsort

def random_num_per_grp(L):
    # For each element in L pick a random number within range specified by it
    r1 = np.random.rand(np.sum(L)) + np.repeat(np.arange(len(L)),L)
    offset = np.r_[0,np.cumsum(L[:-1])]
    return r1.argsort()[offset] - offset

示例案例 -

In [217]: L = [5,4,2]

In [218]: random_num_per_grp(L) # i.e. select one per [0-5,0-4,0-2]
Out[218]: array([2, 0, 1])

因此,输出将具有与输入中相同数量的元素,并且第一个输出元素将位于 in 、second in 等。L[0,5)[0,4)


在这里解决我们的问题

为了解决我们这里的情况,我们将使用一个修改后的版本(特别是删除函数末尾的偏移移除部分,如下所示 -

def random_num_per_grp_cumsumed(L):
    # For each element in L pick a random number within range specified by it
    # The final output would be a cumsumed one for use with indexing, etc.
    r1 = np.random.rand(np.sum(L)) + np.repeat(np.arange(len(L)),L)
    offset = np.r_[0,np.cumsum(L[:-1])]
    return r1.argsort()[offset] 

方法#1

一种解决方案可以这样使用它 -

def argmax_per_row_randtie(a):
    max_mask = a==a.max(1,keepdims=1)
    m,n = a.shape
    all_argmax_idx = np.flatnonzero(max_mask)
    offset = np.arange(m)*n
    return all_argmax_idx[random_num_per_grp_cumsumed(max_mask.sum(1))] - offset

验证

让我们在给定的样本上进行大量运行,并计算每行每个索引的出现次数

In [235]: a
Out[235]: 
array([[1, 3, 3],
       [4, 5, 6],
       [7, 8, 1]])

In [225]: all_out = np.array([argmax_per_row_randtie(a) for i in range(10000)])

# The first element (row=0) should have similar probabilities for 1 and 2
In [236]: (all_out[:,0]==1).mean()
Out[236]: 0.504

In [237]: (all_out[:,0]==2).mean()
Out[237]: 0.496

# The second element (row=1) should only have 2
In [238]: (all_out[:,1]==2).mean()
Out[238]: 1.0

# The third element (row=2) should only have 1
In [239]: (all_out[:,2]==1).mean()
Out[239]: 1.0

方法#2:使用掩码提高性能

我们可以利用并因此避免这种情况,以获得性能,就像使用布尔数组一样。此外,我们将泛化以涵盖行 (axis=1) 和列(axis=0),以给自己一个修改后的行,如下所示 -maskingflatnonzero

def argmax_randtie_masking_generic(a, axis=1): 
    max_mask = a==a.max(axis=axis,keepdims=True)
    m,n = a.shape
    L = max_mask.sum(axis=axis)
    set_mask = np.zeros(L.sum(), dtype=bool)
    select_idx = random_num_per_grp_cumsumed(L)
    set_mask[select_idx] = True
    if axis==0:
        max_mask.T[max_mask.T] = set_mask
    else:
        max_mask[max_mask] = set_mask
    return max_mask.argmax(axis=axis) 

样品运行在 和axis=0axis=1 -

In [423]: a
Out[423]: 
array([[1, 3, 3],
       [4, 5, 6],
       [7, 8, 1]])
In [424]: argmax_randtie_masking_generic(a, axis=1)
Out[424]: array([1, 2, 1])

In [425]: argmax_randtie_masking_generic(a, axis=1)
Out[425]: array([2, 2, 1])

In [426]: a[1,1] = 8

In [427]: a
Out[427]: 
array([[1, 3, 3],
       [4, 8, 6],
       [7, 8, 1]])

In [428]: argmax_randtie_masking_generic(a, axis=0)
Out[428]: array([2, 1, 1])

In [429]: argmax_randtie_masking_generic(a, axis=0)
Out[429]: array([2, 1, 1])

In [430]: argmax_randtie_masking_generic(a, axis=0)
Out[430]: array([2, 2, 1])
1赞 Bi Rico 8/20/2018 #3

您可以使用与输入形状相同的随机数数组,但屏蔽数组以仅留下候选数以供选择。

import numpy as np

def rndArgMax(a, axis):
    a_max = a.max(axis, keepdims=True)
    tmp = np.random.random(a.shape) * (a == a_max)
    return tmp.argmax(axis)

a = np.random.randint(0, 3, size=(2, 3, 4))
print(rndArgMax(a, 1))
# array([[1, 1, 2, 1],
#        [0, 1, 1, 1]])