如何对 numpy 数组的列进行二值化?

How to binarize the columns of a numpy array?

我想对 numpy 数组的列进行二进制编码(不是单热编码):

a = np.array([[2, 3, 5], [4, 6, 8], [3, 7, 9]], dtype=np.uint8)

输出:

>>>print("np.unpackbits(a,axis=1):\n{}".format(np.unpackbits(a, axis=1)))
>>>np.unpackbits(a,axis=1):
[[0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 1 0 0 0 0 0 1 0 1]
 [0 0 0 0 0 1 0 0 0 0 0 0 0 1 1 0 0 0 0 0 1 0 0 0]
 [0 0 0 0 0 0 1 1 0 0 0 0 0 1 1 1 0 0 0 0 1 0 0 1]]

非常接近我的要求,但是输出数组中有一些多余的零,这不是我想要的。

例如,对于第一列:[2,4,3],它在输出中被编码为 [0 0 0 0 0 0 1 0, 0 0 0 0 0 1 0 0, 0 0 0 0 0 0 1 1]。但是,将其编码为 [0 1 0,1 0 0,0 1 1].

就足够了

我想知道是否存在一些现成的代码或 sklearn/numpy 模块能够将上述数组转换为没有冗余零的二进制表示形式。

提前致谢!

现成的你没有这么好的东西。如果您愿意接受少量的额外位(我们希望因子为 8),您可以通过最少的额外努力获得您想要的结果。

# Extract the even-numbered columns.
# Take each uint8 of the form 0000abcd and transform it into abcd0000.
x = a[:,::2]<<4

# The if-statement handles a shape mismatch for odd column counts.
# Take each of the abcd0000 uint8s we just created and add an adjacent
# 0000efgh value to it to get abcdefgh.
if a.shape[1]&1:
    x[:,:-1] += a[:,1::2]
else:
    x += a[:,1::2]

# As long as every element was small enough that there wasn't overflow,
# we just shrank the array by half. Pack it as before.
np.unpackbits(x, axis=1)

由于以下几个原因,使用最少的位数稍微有点困难:

  1. 您需要计算最小位宽。不过这只是 int(a.max()).bit_length(),所以不要太担心这一步。
  2. 您需要将每个 8 位整数打包到新的 B 位通道中。如果 B 不是 8 的因数,那将是相当痛苦的,因为单个输入整数将跨越多个输出整数。

就其他解决方案而言,您总是会招致大量 运行 时间开销并自行创建解压缩数组。下面的代码绝对不是最优的(并且可能不是 运行 -- 有人应该测试它),但它应该足够简单来说明这个想法。

def bits(x, n):
    def _foo(x):
        for _ in range(n):
            yield x&1
            x >>= 1
    return reversed(_foo(x))

def unpack(L, n):
    for x in L:
        yield from bits(x, n)

width = int(a.max()).bit_length()
result = np.array([list(unpack(L, width)) for L in a], dtype=bool)

如果你想保留numpy数组结构,你必须将所有解包后的数字截断到相同的位数。您可以使用以下公式确定所需的位数:

>>> a = np.array([[2, 3, 5], [4, 6, 8], [3, 7, 9]], dtype=np.uint8)
>>> nbits = int(np.floor(np.log2(np.max(a)))+1)
>>> nbits
4

您可以创建从 8nbits 的 np.unpackedbits 的截断版本,方法是首先在新轴上解包,截断,然后重塑您想要的形状:

>>> np.unpackbits(a[...,np.newaxis], axis=2)[:,:,8-nbits:].reshape(a.shape[0],-1)
array([[0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1],
       [0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0],
       [0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1]], dtype=uint8)

获得此结果的另一种方法是过滤 np.unpackedbits 列的结果:

>>> u = np.unpackbits(a, axis=1)
>>> u[:,[i for i in range(u.shape[1]) if i%8>=8-nbits]]
array([[0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1],
       [0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0],
       [0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1]], dtype=uint8)

最后但同样重要的是,如果你想删除所有只包含零的列,你可以使用:

>>> u[:,u.any(axis=0)]
array([[0, 1, 0, 0, 1, 1, 0, 1, 1],
       [1, 0, 0, 1, 1, 0, 1, 0, 0],
       [0, 1, 1, 1, 1, 1, 1, 0, 1]], dtype=uint8)


编辑

如果您希望确定每列的最大位数:

>>> nbits = (np.floor(np.log2(np.max(a,axis=0)))+1).astype('int')
>>> nbits
array([3, 3, 4])

然后您可以过滤相对于此向量的列:

>>> u = np.unpackbits(a, axis=1)
>>> u[:,[i for i in range(u.shape[1]) if i%8>=8-nbits[i//8]]]
array([[0, 1, 0, 0, 1, 1, 0, 1, 0, 1],
       [1, 0, 0, 1, 1, 0, 1, 0, 0, 0],
       [0, 1, 1, 1, 1, 1, 1, 0, 0, 1]], dtype=uint8)

这应该可以完成工作!