使用One-Hot编码numpy数组,该数组具有>2维

3
我有一个形状为 (192, 224, 192, 1) 的 numpy 数组。最后一个维度是整数类别,我想进行一次独热编码。例如,如果我有12个类,则希望结果数组的形状为 (192, 224, 192, 12),其中最后一个维度除了对应原始值的索引处有一个 1 外,所有元素都为零。

我可以使用许多 for 循环来简单地实现此操作,但是想知道是否有更好的方法。

3个回答

2
你可以创建一个新的零数组,然后使用高级索引来填充它。
# sample array with 12 classes
np.random.seed(123)
a = np.random.randint(0, 12, (192, 224, 192, 1))

b = np.zeros((a.size, a.max() + 1))

# use advanced indexing to get one-hot encoding
b[np.arange(a.size), a.ravel()] = 1

# reshape to original form
b = b.reshape(a.shape[:-1] + (a.max() + 1,))

print(b.shape)
print(a[0, 0, 0])
print(b[0, 0, 0])

输出

(192, 224, 192, 12)
[2]
[0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]

类似这个答案,但加上了数组重塑。


如果您不进行重塑,总索引数组会更短。 - Mad Physicist
1
@RitchieV。我已经发布了一个答案,并将其推广到任意维度。 - Mad Physicist
如果你有机会试用一下,请告诉我。我是从手机上发布的,所以不能保证代码没有错误。 - Mad Physicist
这个答案对我的问题很有效。我唯一需要做的更改是将 a.max() + 1 更改为我拥有的类别数。这个特定的机器学习问题是分割,所以整个数组都是我的标签,但并不是每个标签中都代表了每个类别,因此必须硬编码。 - PDPDPDPD
@PDPDPDPD 考虑点赞 Mad 的答案,它实际上更高效,包括一个通用函数,很高兴你修复了你的代码! - RichieV
谢谢您的建议。它最终加速了相当多! - PDPDPDPD

2

如果你知道最大值,你可以在一次索引操作中完成这个任务。假设有一个数组 am = a.max() + 1:

out = np.zeros(a.shape[:-1] + (m,), dtype=bool)
out[(*np.indices(a.shape[:-1], sparse=True), a[..., 0])] = True

如果您删除不必要的尾部维度,将会更加容易:

a = np.squeeze(a)
out = np.zeros(a.shape + (m,), bool)
out[(*np.indices(a.shape, sparse=True), a)] = True

在索引中明确元组对于进行星号扩展是必要的。

如果您想将其扩展到任意维度,也可以这样做。以下内容将在axis处向压缩数组中插入一个新维度。这里的axis是新轴在最终数组中的位置,与np.stack相一致,但与list.insert不一致:

def onehot(a, axis=-1, dtype=bool):
    pos = axis if axis >= 0 else a.ndim + axis + 1
    shape = list(a.shape)
    shape.insert(pos, a.max() + 1)
    out = np.zeros(shape, dtype)
    ind = list(np.indices(a.shape, sparse=True))
    ind.insert(pos, a)
    out[tuple(ind)] = True
    return out

如果您有一个需要扩展的单例维度,广义解法可以找到第一个可用的单例维度:
def onehot2(a, axis=None, dtype=bool):
    shape = np.array(a.shape)
    if axis is None:
        axis = (shape == 1).argmax()
    if shape[axis] != 1:
        raise ValueError(f'Dimension at {axis} is non-singleton')
    shape[axis] = a.max() + 1
    out = np.zeros(shape, dtype)
    ind = list(np.indices(a.shape, sparse=True))
    ind[axis] = a
    out[tuple(ind)] = True
    return out

为了使用最后一个可用的单例,请将 axis = (shape == 1).argmax() 替换为
axis = a.ndim - 1 - (shape[::-1] == 1).argmax()

以下是一些使用示例:
>>> np.random.seed(0x111)
>>> x = np.random.randint(5, size=(3, 2))
>>> x
array([[2, 3],
       [3, 1],
       [4, 0]])

>>> a = onehot(x, axis=-1, dtype=int)
>>> a.shape
(3, 2, 5)
>>> a
array([[[0, 0, 1, 0, 0],    # 2
        [0, 0, 0, 1, 0]],   # 3

       [[0, 0, 0, 1, 0],    # 3
        [0, 1, 0, 0, 0]],   # 1

       [[0, 0, 0, 0, 1],    # 4
        [1, 0, 0, 0, 0]]]   # 0

>>> b = onehot(x, axis=-2, dtype=int)
>>> b.shape
(3, 5, 2)
>>> b
array([[[0, 0],
        [0, 0],
        [1, 0],
        [0, 1],
        [0, 0]],

       [[0, 0],
        [0, 1],
        [0, 0],
        [1, 0],
        [0, 0]],

       [[0, 1],
        [0, 0],
        [0, 0],
        [0, 0],
        [1, 0]]])

onehot2要求您将要添加的维度标记为单例:

>>> np.random.seed(0x111)
>>> y = np.random.randint(5, size=(3, 1, 2, 1))
>>> y
array([[[[2],
         [3]]],
       [[[3],
         [1]]],
       [[[4],
         [0]]]])

>>> c = onehot2(y, axis=-1, dtype=int)
>>> c.shape
(3, 1, 2, 5)
>>> c
array([[[[0, 0, 1, 0, 0],
         [0, 0, 0, 1, 0]]],

       [[[0, 0, 0, 1, 0],
         [0, 1, 0, 0, 0]]],

       [[[0, 0, 0, 0, 1],
         [1, 0, 0, 0, 0]]]])

>>> d = onehot2(y, axis=-2, dtype=int)
ValueError: Dimension at -2 is non-singleton

>>> e = onehot2(y, dtype=int)
>>> e.shape
(3, 5, 2, 1)
>>> e.squeeze()
array([[[0, 0],
        [0, 0],
        [1, 0],
        [0, 1],
        [0, 0]],

       [[0, 0],
        [0, 1],
        [0, 0],
        [1, 0],
        [0, 0]],

       [[0, 1],
        [0, 0],
        [0, 0],
        [0, 0],
        [1, 0]]])

很有趣看到np.indices被使用,我需要更多的经验来使用高级索引。 - RichieV
1
@RichieV。我已经撤销了你的编辑。onehot中的索引是有意这样做的。它旨在对问题中的a.squeeze进行操作,而不是a。但你关于错误的观点是正确的 :) - Mad Physicist
1
@RichieV。我已经添加了一些示例,以展示这两个函数的使用方式,符合您测试的精神。 - Mad Physicist
感谢您的代码。这个非常棒,与其他答案相比速度超快。 - PDPDPDPD
@PDPDPDPD。RichieV的答案非常相似。如果速度很重要,我会将其与我的基准测试。解开和打开非常便宜,因为它们不会复制内存。 - Mad Physicist
你的代码表现更好,我相信。虽然我没有进行全面测试,但它让我能够消除之前需要的一个扩展维度调用。 - PDPDPDPD

0

SciKit-learn有一个编码器:

from sklearn.preprocessing import OneHotEncoder

# Data
values = np.array([1, 3, 2, 4, 1, 2, 1, 3, 5])
val_reshape = values.reshape(len(values), 1)

# One-hot encoding
oh = OneHotEncoder(sparse = False) 
oh_arr = oh.fit_transform(val_reshape)

print(oh_arr)

output: 
[[1. 0. 0. 0. 0.]
 [0. 0. 1. 0. 0.]
 [0. 1. 0. 0. 0.]
 [0. 0. 0. 1. 0.]
 [1. 0. 0. 0. 0.]
 [0. 1. 0. 0. 0.]
 [1. 0. 0. 0. 0.]
 [0. 0. 1. 0. 0.]
 [0. 0. 0. 0. 1.]]


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接