将整数转换为带适当填充的二进制数组

12 投票
6 回答
27379 浏览
提问于 2025-04-17 21:07

我有一些整数,范围是 0..2**m - 1,我想把它们转换成长度为 m 的二进制 numpy 数组。举个例子,假设 m = 4。那么 15 在二进制中是 1111,所以输出应该是 (1,1,1,1)。而 2 在二进制中是 10,因此输出应该是 (0,0,1,0)。如果 m3,那么 2 应该转换成 (0,1,0)

我试过用 np.unpackbits(np.uint8(num)),但是这样得到的数组长度不对。比如,

np.unpackbits(np.uint8(15))
Out[5]: array([0, 0, 0, 0, 1, 1, 1, 1], dtype=uint8)

我希望有一种方法可以适用于我代码中任何的 m

6 个回答

0

这里有一个稍微有点“黑科技”的解决办法。

def bin_array(num, m):
    """Returns an array representing the binary representation of num in m bits."""
    bytes = int(math.ceil(m / 8.0))
    num_arr = np.arange(num, num+1, dtype='>i%d' %(bytes))
    return np.unpackbits(num_arr.view(np.uint8))[-1*m:]     
0

看起来你可以直接修改得到的数组。我不太清楚具体的函数,但大多数实现,比如 np.unpackbits,并不知道数字的大小——毕竟,Python 的整数可以非常大,没有固定的大小。

不过,如果你知道 m 的值,你可以很容易地“修复”这个数组。基本上,解包函数会给你一些位数(这些位数是8的倍数),这是根据数字中最高的1来决定的。你只需要去掉多余的0,或者在前面加上0,就能得到正确的位数:

m = 4
mval = np.unpackbits(np.uint8(15))

if len(mval) > m:
   mval = mval[m-len(mval):]
elif m > len(mval):
   # Create an extra array, and extend it
   mval = numpy.concatenate([numpy.array([0]*(m-len(mval)), dtype=uint8), mval])
1

下面是一段代码,它可以把任何形状的numpy整数数组(比如整数、向量、矩阵等)转换成一个numpy布尔数组,形状和原数组一样,但多出一个维度,这个维度包含了整数在每个位置的位元素。而且你可以指定转换的位深度。

我觉得这个方法很不错,因为它效率高,而且只用一行代码就能完成。假设我们有一个要转换成深度为Nbits的整数输入int_input

binary_output = (np.bitwise_and(int_input.ravel()[:, None], np.array([2**i for i in np.arange(Nbits)[::-1]])) > 0).reshape(np.concatenate([np.asarray(int_input.shape), [Nbits]]))

例子 1:向量[0, 1, 3, 7, 115]通过上面的代码转换成位深度为8的结果是:

[[False False False False False False False False]
 [False False False False False False False  True]
 [False False False False False False  True  True]
 [False False False False False  True  True  True]
 [False  True  True  True False False  True  True]]

例子 2:更复杂一些,我们需要准备数据:一个包含2,000行和10,000列随机整数元素的矩阵,转换成位深度为16:

Nbits = 16
rng = np.random.default_rng()
int_input = np.floor(2**(Nbits-1)*np.asarray(rng.random([10**3, 10**4]))).astype("uint"+Nbits.__str__())

使用上面的代码后,结果会变成一个形状为(2000, 10000, 16)的numpy逻辑数组。

在速度方面,使用timeit对上述2,000万元素的转换进行10次测试,总共大约需要7.5秒。

最后,例子 3:在处理非常大的数据集(相对于计算机的内存)时,转换会占用很多临时内存。如果把转换分成更小的块来进行,这个问题可以缓解。通过定义块大小BlkSz(例如BlkSz = 1000),沿着第一个轴进行处理,代码就变成了:

binary_output = np.concatenate([(np.bitwise_and(int_input[b*BlkSz:np.min([(b+1)*BlkSz, int_input.shape[0]]), :].ravel()[:, None], np.array([2**i for i in np.arange(Nbits)[::-1]])) > 0).reshape(np.concatenate([np.asarray(int_input[b*BlkSz:np.min([(b+1)*BlkSz, int_input.shape[0]]), :].shape), [Nbits]])) for b in range(int_input.shape[0]//BlkSz+1)], axis=0)

这里int_input需要有超过1个轴(这在大数据集中是正常的),但BlkSz可以是任何值——int_input的第一个轴的长度不需要能被BlkSz整除,甚至可以小于这个轴的长度。添加BlkSz(也就是相对于上面的第一行代码)不会对性能产生负面影响,甚至可能会有一点正面影响。

10

这里有一个一行代码的写法,利用了numpy.binary_repr的快速处理方式:

def bin_array(num, m):
    """Convert a positive integer num into an m-bit bit vector"""
    return np.array(list(np.binary_repr(num).zfill(m))).astype(np.int8)

举个例子:

In [1]: bin_array(15, 6)
Out[1]: array([0, 0, 1, 1, 1, 1], dtype=int8)

这是一个可以同时处理整个numpy整数数组的向量化版本:

def vec_bin_array(arr, m):
    """
    Arguments: 
    arr: Numpy array of positive integers
    m: Number of bits of each integer to retain

    Returns a copy of arr with every element replaced with a bit vector.
    Bits encoded as int8's.
    """
    to_str_func = np.vectorize(lambda x: np.binary_repr(x).zfill(m))
    strs = to_str_func(arr)
    ret = np.zeros(list(arr.shape) + [m], dtype=np.int8)
    for bit_ix in range(0, m):
        fetch_bit_func = np.vectorize(lambda x: x[bit_ix] == '1')
        ret[...,bit_ix] = fetch_bit_func(strs).astype("int8")

    return ret 

举个例子:

In [1]: vec_bin_array(np.array([[100, 42], [2, 5]]), 8)

Out[1]: array([[[0, 1, 1, 0, 0, 1, 0, 0],
                [0, 0, 1, 0, 1, 0, 1, 0]],

               [[0, 0, 0, 0, 0, 0, 1, 0],
                [0, 0, 0, 0, 0, 1, 0, 1]]], dtype=int8)
17

你应该能够把这个变成向量化的形式,像这样:

>>> d = np.array([1,2,3,4,5])
>>> m = 8
>>> (((d[:,None] & (1 << np.arange(m)))) > 0).astype(int)
array([[1, 0, 0, 0, 0, 0, 0, 0],
       [0, 1, 0, 0, 0, 0, 0, 0],
       [1, 1, 0, 0, 0, 0, 0, 0],
       [0, 0, 1, 0, 0, 0, 0, 0],
       [1, 0, 1, 0, 0, 0, 0, 0]])

这个方法会获取合适的位权重,然后进行按位与运算:

>>> (1 << np.arange(m))
array([  1,   2,   4,   8,  16,  32,  64, 128])
>>> d[:,None] & (1 << np.arange(m))
array([[1, 0, 0, 0, 0, 0, 0, 0],
       [0, 2, 0, 0, 0, 0, 0, 0],
       [1, 2, 0, 0, 0, 0, 0, 0],
       [0, 0, 4, 0, 0, 0, 0, 0],
       [1, 0, 4, 0, 0, 0, 0, 0]])

有很多方法可以把非零的地方转换成1,比如用 (> 0)*1,或者 .astype(bool).astype(int) 等等。我随便选择了一个方法。

撰写回答