Python:将numpy符号数组转换为int和back

Python: convert numpy array of signs to int and back

我正试图从一个麻木的符号数组(也就是一个麻木的数组,它的条目要么是1.要么是-1.转换成一个整数,然后通过二进制表示返回。我有些东西可以用,但它不是Python,我希望它会慢一些。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def sign2int(s):
    s[s==-1.] = 0.
    bstr = ''
    for i in range(len(s)):
        bstr = bstr + str(int(s[i]))
    return int(bstr, 2)

def int2sign(i, m):
    bstr = bin(i)[2:].zfill(m)
    s = []
    for d in bstr:
        s.append(float(d))
    s = np.array(s)
    s[s==0.] = -1.
    return s

然后

1
2
3
4
5
6
7
8
>>> m = 4
>>> s0 = np.array([1., -1., 1., 1.])
>>> i = sign2int(s0)
>>> print i
11
>>> s = int2sign(i, m)
>>> print s
[ 1. -1.  1.  1.]

我关心的是(1)每个循环中的for循环,(2)必须构建一个作为字符串的中间表示。

归根结底,我也想要一些能与二维numpy数组一起工作的东西,例如,

1
2
3
>>> s = np.array([[1., -1., 1.], [1., 1., 1.]])
>>> print sign2int(s)
[5, 7]


我先从sig2int开始……从符号表示转换为二进制

1
2
3
4
5
>>> a
array([ 1., -1.,  1., -1.])
>>> (a + 1) / 2
array([ 1.,  0.,  1.,  0.])
>>>

然后你可以简单地创建一个两次幂的数组,乘以二进制和。

1
2
3
4
5
6
7
8
9
10
>>> powers = np.arange(a.shape[-1])[::-1]
>>> np.power(2, powers)
array([8, 4, 2, 1])
>>> a = (a + 1) / 2
>>> powers = np.power(2, powers)
>>> a * powers
array([ 8.,  0.,  2.,  0.])
>>> np.sum(a * powers)
10.0
>>>

然后通过添加轴信息和依靠广播使其在行上运行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def sign2int(a):
    # powers of two
    powers = np.arange(a.shape[-1])[::-1]
    np.power(2, powers, powers)
    # sign to"binary" - add one and divide by two
    np.add(a, 1, a)
    np.divide(a, 2, a)
    # scale by powers of two and sum
    np.multiply(a, powers, a)
    return np.sum(a, axis = -1)
>>> b = np.array([a, a, a, a, a])
>>> sign2int(b)
array([ 11.,  11.,  11.,  11.,  11.])
>>>

我在一个4×100位数组上尝试过,它看起来很快

1
2
3
4
5
6
7
8
9
10
11
12
>>> a = a.repeat(100)
>>> b = np.array([a, a, a, a, a])
>>> b
array([[ 1.,  1.,  1., ...,  1.,  1.,  1.],
       [ 1.,  1.,  1., ...,  1.,  1.,  1.],
       [ 1.,  1.,  1., ...,  1.,  1.,  1.],
       [ 1.,  1.,  1., ...,  1.,  1.,  1.],
       [ 1.,  1.,  1., ...,  1.,  1.,  1.]])
>>> sign2int(b)
array([  2.58224988e+120,   2.58224988e+120,   2.58224988e+120,
         2.58224988e+120,   2.58224988e+120])
>>>

如果我能找到的话,我会加上反面。-我所能做的最好的事情就是依靠一些普通的python,而不需要任何麻木的矢量化魔法,而且我还没有想到如何使它与一系列的int一起工作,而不是迭代它们并一次转换它们——但时间似乎仍然可以接受。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def foo(n):
    '''yields bits in increasing powers of two

    bit sequence from lsb --> msb
    '''

    while n > 0:
        n, r = divmod(n, 2)
        yield r

def int2sign(n):
    n = int(n)
    a = np.fromiter(foo(n), dtype = np.int8, count = n.bit_length())
    np.multiply(a, 2, a)
    np.subtract(a, 1, a)
    return a[::-1]

1324号工程:

1
2
3
4
5
>>> bin(1324)
'0b10100101100'
>>> a = int2sign(1324)
>>> a
array([ 1, -1,  1, -1, -1,  1, -1,  1,  1, -1, -1], dtype=int8)

似乎适用于1.2E305:

1
2
3
4
5
6
7
8
9
10
11
12
>>> n = int(1.2e305)
>>> n.bit_length()
1014
>>> a = int2sign(n)
>>> a.shape
(1014,)

>>> s = bin(n)
>>> s = s[2:]
>>> all(2 * int(x) -1 == y for x, y in zip(s, a))
True
>>>


对于一维阵列,您可以使用这种线性方法,使用np.packbits

1
2
>>> np.packbits(np.pad((s0+1).astype(bool).astype(int), (8-s0.size, 0), 'constant'))
array([11], dtype=uint8)

对于倒车:

1
2
3
4
>>> unpack = (np.unpackbits(np.array([11], dtype=np.uint8))[-4:]).astype(float)
>>> unpack[unpack==0] = -1
>>> unpack
array([ 1., -1.,  1.,  1.])

对于二维阵列:

1
2
3
>>> x, y = s.shape
>>> np.packbits(np.pad((s+1).astype(bool).astype(int), (8-y, 0), 'constant')[-2:])
array([5, 7], dtype=uint8)

对于倒车:

1
2
3
4
5
>>> unpack = (np.unpackbits(np.array([5, 7], dtype='uint8'))).astype(float).reshape(x, 8)[:,-y:]
>>> unpack[unpack==0] = -1
>>> unpack
array([[ 1., -1.,  1.],
       [ 1.,  1.,  1.]])


我认为江户十一〔六〕值得一看。给定实值符号数组a,可以使用numpy.packbits(a > 0)。减压由numpy.unpackbits完成。这会隐式地展平多维数组,因此如果您有多维数组,则需要在unpackbits之后使用reshape

请注意,可以将位打包与常规压缩(例如,zlib或lzma)结合起来。如果您的数据存在模式或偏差,您可能会得到一个有用的压缩因子,但是对于无偏差的随机数据,您通常会看到大小适度增加。


经过一点测试,@wwwiii的numpythonic方法(不使用字符串)似乎符合我最需要的。对于int2sign,我使用了一个标准的转换算法对指数进行for循环——对于64位整数,它最多有64次迭代。numpy的广播非常有效地跨每个整数进行。

packbitsunpackbits被限制为8位整数;否则,我怀疑这是最好的(尽管我没有尝试)。

以下是我测试的具体实现,遵循其他答案中的建议(感谢所有人!):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def _sign2int_str(s):
    return int(''.join(np.where(s == -1., 0, s).astype(int).astype(str)), 2)

def sign2int_str(s):
    return np.array(map(_sign2int_str, s))

def _int2sign_str(i, m):
    tmp = np.array(list(bin(i)[2:])).astype(int)
    return np.pad(np.where(tmp == 0, -1, tmp), (m - len(tmp), 0),"constant", constant_values = -1)

def int2sign_str(i,m):
    return np.array(map(lambda x: _int2sign_str(x, m), i.astype(int).tolist())).transpose()

def sign2int_np(s):
    p = np.arange(s.shape[-1])[::-1]
    s = s + 1
    return np.sum(np.power(s, p), axis = -1).astype(int)

def int2sign_np(i,m):
    N = i.shape[-1]
    S = np.zeros((m, N))
    for k in range(m):
        b = np.power(2, m - 1 - k).astype(int)
        S[k,:] = np.divide(i.astype(int), b).astype(float)
        i = np.mod(i, b)        
    S[S==0.] = -1.
    return S

这是我的测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
X = np.sign(np.random.normal(size=(5000, 20)))
N = 100

t = time.time()
for i in range(N):
    S = sign2int_np(X)
print 'sign2int_np: \t{:10.8f} sec'.format((time.time() - t)/N)

t = time.time()
for i in range(N):
    S = sign2int_str(X)
print 'sign2int_str: \t{:10.8f} sec'.format((time.time() - t)/N)

m = 20
S = np.random.randint(0, high=np.power(2,m), size=(5000,))

t = time.time()
for i in range(N):
    X = int2sign_np(S, m)
print 'int2sign_np: \t{:10.8f} sec'.format((time.time() - t)/N)

t = time.time()
for i in range(N):
    X = int2sign_str(S, m)
print 'int2sign_str: \t{:10.8f} sec'.format((time.time() - t)/N)

结果如下:

1
2
3
4
sign2int_np:    0.00165325 sec
sign2int_str:   0.04121902 sec
int2sign_np:    0.00318024 sec
int2sign_str:   0.24846984 sec


以下是您的函数的一些矢量化版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def sign2int(s):
    return int(''.join(np.where(s == -1., 0, s).astype(int).astype(str)), 2)

def int2sign(i, m):
    tmp = np.array(list(bin(i)[2:].zfill(m)))
    return np.where(tmp =="0","-1", tmp).astype(int)

s0 = np.array([1., -1., 1., 1.])

sign2int(s0)
# 11

int2sign(11, 5)
# array([-1,  1, -1,  1,  1])

要在二维数组上使用函数,可以使用map函数:

1
2
3
4
5
6
7
s = np.array([[1., -1., 1.], [1., 1., 1.]])

map(sign2int, s)
# [5, 7]

map(lambda x: int2sign(x, 4), [5, 7])
# [array([-1,  1, -1,  1]), array([-1,  1,  1,  1])]