【问题标题】:Numpy - how to convert an array of vector indices to a mask?Numpy - 如何将向量索引数组转换为掩码?
【发布时间】:2019-08-19 08:15:32
【问题描述】:

给定一个名为indicesnp.ndarray,每行有一个n 行和可变长度向量,我想创建一个n 行和m 行的布尔掩码,其中m 是前已知值等于indices 中可能的最大值。 请注意,indices 中指定的索引指的是每行索引,而不是全局矩阵索引。

例如,给定:

indices = np.array([
    [2, 0],
    [0],
    [4, 7, 1]
])

# Expected output
print(mask)
[[ True False  True False False False False False]
 [ True False False False False False False False]
 [False  True False False  True False False  True]]

m 是事先已知的(mask 中每一行的最大长度),不需要从 indices 推断

注意:这与将索引数组转换为掩码不同,其中索引引用生成的矩阵索引

【问题讨论】:

  • 你能把你的最后一句话解释清楚一点吗?对我来说,这看起来像是您的预期输出。
  • 如果你有两个向量,你可以创建一个最大尺寸的 zeros 向量,并使用索引来做类似mask[indices] = True的事情,但在这种情况下你不能这样做,因为索引是指到行内索引,而不是矩阵索引

标签: python numpy


【解决方案1】:

虽然没有直接的方法以完全矢量化的方式执行此操作,但对于较大的输入,单个应用 mask[full_row_indices, full_col_indices] 和预先计算的完整索引列表比多次应用 mask[partial_row_indices, partial_col_indices] 更快。 在内存方面,多个应用程序的要求也较低,因为不需要构建中间 full_row_indices/full_col_indices。 当然这通常取决于indices的长度。

为了了解不同可能的解决方案的速度有多快,我们测试了以下功能:

import numpy as np
import random


def gen_mask_direct(col_indices, cols=None):
    if cols is None:
        cols = np.max(np.concatenate(col_indices)) + 1
    rows = len(col_indices)
    mask = np.zeros((rows, cols), dtype=bool)
    for row_index, col_index in enumerate(col_indices):
        mask[row_index, col_index] = True
    return mask 


def gen_mask_loops(col_indices, cols=None):
    rows = len(col_indices)
    row_indices = tuple(i for i, j in enumerate(col_indices) for _ in j)
    col_indices = tuple(sum(col_indices, ()))
    if cols is None:
        cols = np.max(col_indices) + 1
    mask = np.zeros((rows, cols), dtype=bool)
    mask[row_indices, col_indices] = True
    return mask


def gen_mask_np_repeat(col_indices, cols=None):
    rows = len(col_indices)
    lengths = list(map(len, col_indices))
    row_indices = np.repeat(np.arange(rows), lengths)
    col_indices = np.concatenate(col_indices)
    if cols is None:
        cols = np.max(col_indices) + 1
    mask = np.zeros((rows, cols), dtype=bool)
    mask[row_indices, col_indices] = True
    return mask


def gen_mask_np_concatenate(col_indices, cols=None):
    rows = len(col_indices)
    row_indices = tuple(np.full(len(col_index), i) for i, col_index in enumerate(col_indices))
    row_indices = np.concatenate(row_indices)
    col_indices = np.concatenate(col_indices)
    if cols is None:
        cols = np.max(col_indices) + 1
    mask = np.zeros((rows, cols), dtype=bool)
    mask[row_indices, col_indices] = True
    return mask

gen_mask_direct()基本上就是@Derlin answer,实现了mask[partial_row_indices, partial_col_indices]的多种应用。 所有其他人都实现了mask[full_row_indices, full_col_indices] 的单个应用程序,并以不同的方式准备full_row_indicesfull_col_indices

  • gen_mask_loops() 使用直接循环
  • gen_mask_np_repeat() 使用 np.repeat()(它与 @Divakar answer 基本相同)
  • gen_mask_np_concatenate() 使用 np.full()np.concatenate() 的组合

快速健全性检查表明所有这些都是等效的:

funcs = gen_mask_direct, gen_mask_loops, gen_mask_np_repeat, gen_mask_np_concatenate

random.seed(0)
test_inputs = [
    (tuple(
        tuple(sorted(set([random.randint(0, n - 1) for _ in range(random.randint(1, n - 1))])))
                for _ in range(random.randint(1, n - 1))))
    for n in range(5, 6)
    ]
print(test_inputs)
# [((0, 2, 3, 4), (2, 3, 4), (1, 4), (0, 1, 4))]

for func in funcs:
    print('Func:', func.__name__)
    for test_input in test_inputs:    
        print(func(test_input).astype(int))
Func: gen_mask_direct
[[1 0 1 1 1]
 [0 0 1 1 1]
 [0 1 0 0 1]
 [1 1 0 0 1]]
Func: gen_mask_loops
[[1 0 1 1 1]
 [0 0 1 1 1]
 [0 1 0 0 1]
 [1 1 0 0 1]]
Func: gen_mask_np_repeat
[[1 0 1 1 1]
 [0 0 1 1 1]
 [0 1 0 0 1]
 [1 1 0 0 1]]
Func: gen_mask_np_concatenate
[[1 0 1 1 1]
 [0 0 1 1 1]
 [0 1 0 0 1]
 [1 1 0 0 1]]

以下是一些基准测试(使用来自here 的代码):

并以最快的速度缩放:

支持整体声明,通常情况下,对完整索引单个应用 mask[...] 比对部分索引多个应用 mask[...] 更快。


为了完整起见,以下代码用于生成输入、比较输出、运行基准测试并准备绘图:

def gen_input(n):
    random.seed(0)
    return tuple(
        tuple(sorted(set([random.randint(0, n - 1) for _ in range(random.randint(n // 2, n - 1))])))
        for _ in range(random.randint(n // 2, n - 1)))


def equal_output(a, b):
    return np.all(a == b)


input_sizes = tuple(int(2 ** (2 + (3 * i) / 4)) for i in range(13))
print('Input Sizes:\n', input_sizes, '\n')


runtimes, input_sizes, labels, results = benchmark(
    funcs, gen_input=gen_input, equal_output=equal_output,
    input_sizes=input_sizes)


plot_benchmarks(runtimes, input_sizes, labels, units='ms')
plot_benchmarks(runtimes, input_sizes, labels, units='ms', zoom_fastest=2)

【讨论】:

  • 我检查了这个虚拟数据indices = np.array([list(np.random.randint(per_row, size=i)) for i in np.random.binomial(n=per_row, p=0.5, size=rows)]),根据rowsper_row 的比率,一个比另一个快,但差异总是100%,所以没有数量级
  • 当然够了。也许我表达得不够清楚。在这两种方法中,都有 较慢 循环路径(不一定是数量级),并且对于特定的大小组合(您的 rowsper_row),您将获得不同的结果。如果rows << per_row 部分屏蔽更快,如果rows >> per_row 部分屏蔽更慢。如果 rows ~ per_row 正如 OP 所表明的那样,那么人们可能通常期望部分掩蔽会慢约 2 倍,这不是一个数量级,但可能很重要。在内存方面,部分应用总是更好。
【解决方案2】:

这是一个变种:

def create_mask(indices, m):
    mask = np.zeros((len(indices), m), dtype=bool)
    for i, idx in enumerate(indices):
        mask[i, idx] = True
    return mask

用法:

>>> create_mask(indices, 8)
array([[ True, False,  True, False, False, False, False, False],
       [ True, False, False, False, False, False, False, False],
       [False,  True, False, False,  True, False, False,  True]])

【讨论】:

  • 为了清晰起见,我喜欢这段代码,但在速度方面,一旦len(indices) 增加,这可能会比完全矢量化的方法慢得多。
  • @norok2 因为indices 是一个dtype 数组object,我看不出你怎么能轻松地对其进行矢量化。至少它只取决于行数,而不是元素数。
  • @MaartenFabré 基本上是这样的:stackoverflow.com/a/57552904/5218354
  • lens = np.array(list(map(len,indices))) 也迭代了indices,所以我怀疑这会产生很大的不同。从原始indices 制作数组是如此复杂,我怀疑会为你带来很多速度,我更喜欢这个解决方案的清晰度而不是@Divakar 的
  • @MaartenFabré 我喜欢怀疑,但我更喜欢measuring :-)
【解决方案3】:

这是一种方法-

def mask_from_indices(indices, ncols=None):
    # Extract column indices
    col_idx = np.concatenate(indices)

    # If number of cols is not given, infer it based on max column index
    if ncols is None:
        ncols = col_idx.max()+1

    # Length of indices, to be used as no. of rows in o/p
    n = len(indices)

    # Initialize o/p array
    out = np.zeros((n,ncols), dtype=bool)

    # Lengths of each index element that represents each group of col indices
    lens = np.array(list(map(len,indices)))

    # Use np.repeat to generate all row indices
    row_idx = np.repeat(np.arange(len(lens)),lens)

    # Finally use row, col indices to set True values
    out[row_idx,col_idx] = 1
    return out    

示例运行 -

In [89]: mask_from_indices(indices)
Out[89]: 
array([[ True, False,  True, False, False, False, False, False],
       [ True, False, False, False, False, False, False, False],
       [False,  True, False, False,  True, False, False,  True]])

【讨论】:

  • 您能否详细说明mask_from_indices 中的每个步骤的作用?
  • @bluesummers 添加了 cmets。
猜你喜欢
  • 2014-10-28
  • 2022-10-18
  • 2015-03-24
  • 2014-12-22
  • 2021-03-01
  • 1970-01-01
  • 2017-11-02
  • 1970-01-01
  • 2017-04-16
相关资源
最近更新 更多