将整数转换为带适当填充的二进制数组
我有一些整数,范围是 0..2**m - 1
,我想把它们转换成长度为 m
的二进制 numpy 数组。举个例子,假设 m = 4
。那么 15
在二进制中是 1111
,所以输出应该是 (1,1,1,1)
。而 2
在二进制中是 10
,因此输出应该是 (0,0,1,0)
。如果 m
是 3
,那么 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 个回答
这里有一个稍微有点“黑科技”的解决办法。
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:]
看起来你可以直接修改得到的数组。我不太清楚具体的函数,但大多数实现,比如 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])
下面是一段代码,它可以把任何形状的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
(也就是相对于上面的第一行代码)不会对性能产生负面影响,甚至可能会有一点正面影响。
这里有一个一行代码的写法,利用了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)
你应该能够把这个变成向量化的形式,像这样:
>>> 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)
等等。我随便选择了一个方法。