将 2d 矩阵转换为 3d 单热矩阵 numpy
Posted
技术标签:
【中文标题】将 2d 矩阵转换为 3d 单热矩阵 numpy【英文标题】:Convert a 2d matrix to a 3d one hot matrix numpy 【发布时间】:2016-08-25 21:54:00 【问题描述】:我有 np 矩阵,我想将其转换为 3d 数组,其中元素的一个热编码作为第三维。有没有办法不用循环遍历每一行 例如
a=[[1,3],
[2,4]]
应该做成
b=[[1,0,0,0], [0,0,1,0],
[0,1,0,0], [0,0,0,1]]
【问题讨论】:
【参考方案1】:方法#1
这是一个滥用broadcasted
比较的厚颜无耻的单线 -
(np.arange(a.max()) == a[...,None]-1).astype(int)
示例运行 -
In [120]: a
Out[120]:
array([[1, 7, 5, 3],
[2, 4, 1, 4]])
In [121]: (np.arange(a.max()) == a[...,None]-1).astype(int)
Out[121]:
array([[[1, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 1],
[0, 0, 0, 0, 1, 0, 0],
[0, 0, 1, 0, 0, 0, 0]],
[[0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0]]])
对于0-based
索引,它将是 -
In [122]: (np.arange(a.max()+1) == a[...,None]).astype(int)
Out[122]:
array([[[0, 1, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 1],
[0, 0, 0, 0, 0, 1, 0, 0],
[0, 0, 0, 1, 0, 0, 0, 0]],
[[0, 0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0]]])
如果 one-hot 编码要覆盖从最小值到最大值的值范围,则偏移最小值,然后将其提供给 0-based
索引的建议方法。这也适用于本文后面讨论的其他方法。
这是在同一台上运行的示例 -
In [223]: a
Out[223]:
array([[ 6, 12, 10, 8],
[ 7, 9, 6, 9]])
In [224]: a_off = a - a.min() # feed a_off to proposed approaches
In [225]: (np.arange(a_off.max()+1) == a_off[...,None]).astype(int)
Out[225]:
array([[[1, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 1],
[0, 0, 0, 0, 1, 0, 0],
[0, 0, 1, 0, 0, 0, 0]],
[[0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0]]])
如果您可以接受一个布尔数组,其中True
代表1's
,而False 代表0's
,则可以跳过.astype(int)
转换。
方法#2
我们还可以初始化一个 zeros 数组并使用advanced-indexing
对输出进行索引。因此,对于0-based
索引,我们将拥有 -
def onehot_initialization(a):
ncols = a.max()+1
out = np.zeros(a.shape + (ncols,), dtype=int)
out[all_idx(a, axis=2)] = 1
return out
辅助函数 -
# https://***.com/a/46103129/ @Divakar
def all_idx(idx, axis):
grid = np.ogrid[tuple(map(slice, idx.shape))]
grid.insert(axis, idx)
return tuple(grid)
这在处理更大范围的值时应该会特别高效。
对于1-based
索引,只需输入a-1
作为输入。
方法 #3:稀疏矩阵解决方案
现在,如果您正在寻找稀疏数组作为输出和 AFAIK,因为 scipy 的内置稀疏矩阵仅支持 2D
格式,您可以获得一个稀疏输出,它是前面显示的输出的重新整形版本,前两个轴合并第三轴保持完整。 0-based
索引的实现看起来像这样 -
from scipy.sparse import coo_matrix
def onehot_sparse(a):
N = a.size
L = a.max()+1
data = np.ones(N,dtype=int)
return coo_matrix((data,(np.arange(N),a.ravel())), shape=(N,L))
同样,对于1-based
索引,只需输入a-1
作为输入。
示例运行 -
In [157]: a
Out[157]:
array([[1, 7, 5, 3],
[2, 4, 1, 4]])
In [158]: onehot_sparse(a).toarray()
Out[158]:
array([[0, 1, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 1],
[0, 0, 0, 0, 0, 1, 0, 0],
[0, 0, 0, 1, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0]])
In [159]: onehot_sparse(a-1).toarray()
Out[159]:
array([[1, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 1],
[0, 0, 0, 0, 1, 0, 0],
[0, 0, 1, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0]])
如果您可以接受稀疏输出,这将比前两种方法好得多。
基于 0 的索引的运行时比较
案例#1:
In [160]: a = np.random.randint(0,100,(100,100))
In [161]: %timeit (np.arange(a.max()+1) == a[...,None]).astype(int)
1000 loops, best of 3: 1.51 ms per loop
In [162]: %timeit onehot_initialization(a)
1000 loops, best of 3: 478 µs per loop
In [163]: %timeit onehot_sparse(a)
10000 loops, best of 3: 87.5 µs per loop
In [164]: %timeit onehot_sparse(a).toarray()
1000 loops, best of 3: 530 µs per loop
案例#2:
In [166]: a = np.random.randint(0,500,(100,100))
In [167]: %timeit (np.arange(a.max()+1) == a[...,None]).astype(int)
100 loops, best of 3: 8.51 ms per loop
In [168]: %timeit onehot_initialization(a)
100 loops, best of 3: 2.52 ms per loop
In [169]: %timeit onehot_sparse(a)
10000 loops, best of 3: 87.1 µs per loop
In [170]: %timeit onehot_sparse(a).toarray()
100 loops, best of 3: 2.67 ms per loop
挤出最佳性能
为了获得最佳性能,我们可以修改方法 #2 以在 2D
形状的输出数组上使用索引,并使用 uint8
dtype 来提高内存效率,从而加快分配速度,就像这样 -
def onehot_initialization_v2(a):
ncols = a.max()+1
out = np.zeros( (a.size,ncols), dtype=np.uint8)
out[np.arange(a.size),a.ravel()] = 1
out.shape = a.shape + (ncols,)
return out
时间安排 -
In [178]: a = np.random.randint(0,100,(100,100))
In [179]: %timeit onehot_initialization(a)
...: %timeit onehot_initialization_v2(a)
...:
1000 loops, best of 3: 474 µs per loop
10000 loops, best of 3: 128 µs per loop
In [180]: a = np.random.randint(0,500,(100,100))
In [181]: %timeit onehot_initialization(a)
...: %timeit onehot_initialization_v2(a)
...:
100 loops, best of 3: 2.38 ms per loop
1000 loops, best of 3: 213 µs per loop
【讨论】:
我称之为 Numpythonic! 你可能实际上并不想要.astype(int)
,因为这会导致不必要的复制
这更多是针对 OP 的评论,以及他们可能首先想要布尔数组的方式。以np.int8
的身份查看可能会忽略副本
这不仅仅是厚脸皮!
我认为这确实是一个很棒的技巧,我只是想分享一下,您甚至可以通过对魔法线的轻微修改使其与数组或任意数量的维度一起使用:(np.arange(a.max()) == a[..., None]-1).astype(int)
【参考方案2】:
如果您尝试为您的机器学习模型创建 one-hot 张量(您已安装 tensorflow
或 keras
),那么您可以使用来自 https://www.tensorflow.org/api_docs/python/tf/keras/backend/one_hot 或 https://www.tensorflow.org/api_docs/python/tf/one_hot 的 one_hot
函数
这是我正在使用的,并且在处理高维数据时效果很好。
以下是示例用法:
>>> import tensorflow as tf
>>> tf.one_hot([[0,2],[1,3]], 4).numpy()
array([[[1., 0., 0., 0.],
[0., 0., 1., 0.]],
[[0., 1., 0., 0.],
[0., 0., 0., 1.]]], dtype=float32)
【讨论】:
【参考方案3】:编辑: 我刚刚意识到我的答案已经包含在接受的答案中。不幸的是,作为一个未注册的用户,我不能再删除它了。
作为已接受答案的补充:如果您要编码的类数量非常少,并且如果您可以接受 np.bool
数组作为输出,我发现以下内容会稍微快一些:
def onehot_initialization_v3(a):
ncols = a.max() + 1
labels_one_hot = (a.ravel()[np.newaxis] == np.arange(ncols)[:, np.newaxis]).T
labels_one_hot.shape = a.shape + (ncols,)
return labels_one_hot
时间安排(10节课):
a = np.random.randint(0,10,(100,100))
assert np.all(onehot_initialization_v2(a) == onehot_initialization_v3(a))
%timeit onehot_initialization_v2(a)
%timeit onehot_initialization_v3(a)
# 102 µs ± 1.66 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
# 79.3 µs ± 815 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
但是,如果课程数量增加(现在是 100 个课程),情况会发生变化:
a = np.random.randint(0,100,(100,100))
assert np.all(onehot_initialization_v2(a) == one_hot_initialization_v3(a))
%timeit onehot_initialization_v2(a)
%timeit onehot_initialization_v3(a)
# 132 µs ± 1.4 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
# 639 µs ± 3.12 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
因此,根据您的问题,可能是更快的版本。
【讨论】:
【参考方案4】:这是使用 np.eye(恒等矩阵)和强大的 numpy 索引的最简单和最优雅的解决方案:
labels_3d = np.eye(N_CLASSES)[labels_2d]
【讨论】:
以上是关于将 2d 矩阵转换为 3d 单热矩阵 numpy的主要内容,如果未能解决你的问题,请参考以下文章