【问题标题】:Python: convert numpy array of signs to int and backPython:将numpy的符号数组转换为int并返回
【发布时间】:2017-01-03 17:12:59
【问题描述】:

我正在尝试从一个 numpy 符号数组(即,一个其条目为 1.-1. 的 numpy 数组)转换为整数,然后通过二进制表示形式返回。我有一些可行的方法,但它不是 Pythonic,我预计它会很慢。

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

然后

>>> 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 数组的东西——例如,

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

【问题讨论】:

  • 你在真实数据集上试过了吗?它有多大?
  • 我期望看到的最大数据集将包含约 1000 个元素的符号数组,但符号数组的数量可能达到数十亿——一个非常高的矩阵。 @二战
  • 既然你提到了它,我相信这只有在符号数组最多有 64 个元素时才有效。 @二战
  • 换句话说 - ~1000 位整数?
  • 对。 :) 将有许多数据集,每个符号向量的元素少于 100 个。但我可能会尝试使用多个 64 位整数。下面的答案已经有很大帮助。谢谢@wwii!

标签: python arrays numpy binary


【解决方案1】:

对于一维数组,您可以使用这种线性 Numpythonic 方法,使用 np.packbits:

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

对于倒车:

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

对于二维数组:

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

对于倒车:

>>> 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.]])

【讨论】:

  • 谢谢@Kasramvd!我不知道 packbits 和 unpackbits。看起来很有用。
  • 回复:your deleted answer on another question (with benchmarks for list swapping)。您应该取消删除它,并将其变成有关基准的答案。有趣的是,您的想法运行得如此之慢,而其中一个想法运行得如此之快。 IDK 很多关于 python 的知识,但似乎其他类似的列表操作问题可能有类似的解决方案选择具有类似的相对性能,所以指出这可能是一件有用的事情。
  • @PeterCordes 实际上,那时我已经很累了(大约 20 小时醒着)我只是想摆脱那个可怕的错误,但现在我认为仍然不需要我的解决方案,因为它是很明显,我的代码将比其他代码花费更长的时间,我不知道为什么我使用 2 for 循环(这意味着更多的解包、复杂性、调用、堆栈作业等),而我已经写了我的回答是其他人正在使用多次迭代、切片等,现在我认为唯一有趣的事情可能是这三个答案之间的基准。
  • 你能用那些 numpy 方法来处理超过八位的整数吗?
  • @wwii 在这种情况下,你的回答会很好用!
【解决方案2】:

我将从sig2int.. 开始。从符号表示转换为二进制

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

然后您可以简单地创建一个 2 的幂数组,将其乘以 二进制 并求和。

>>> 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
>>> 

然后通过添加轴信息使其对行进行操作,依靠广播。

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 x 100 位的数组上试了一下,它看起来很快

>>> 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])
>>> 

如果可以的话,我会添加相反的内容。 - 我能做的最好的事情依赖于一些没有任何 numpy 矢量化魔法的普通 Python,我还没有想出如何让它与一系列整数一起工作,除了迭代它们并一次转换它们 - 但时间仍然似乎可以接受。

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:

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

似乎适用于 1.2e305:

>>> 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
>>>

【讨论】:

  • sign2int 中需要注意的一件事是 0 ** 0 = 1。 (至少它在我的机器上。)
【解决方案3】:

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

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 函数:

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])]

【讨论】:

    【解决方案4】:

    经过一些测试,@wwii 的不使用字符串的 Numpythonic 方法似乎最适合我的需要。对于int2sign,我在指数上使用了一个for循环,并使用标准算法进行转换——对于64位整数,最多有64次迭代。 Numpy 的广播非常有效地发生在每个整数上。

    packbitsunpackbits 仅限于 8 位整数;否则,我怀疑那将是最好的(尽管我没有尝试)。

    这是我按照其他答案中的建议测试的具体实现(感谢大家!):

    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
    

    这是我的测试:

    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)
    

    这产生了以下结果:

    sign2int_np:    0.00165325 sec
    sign2int_str:   0.04121902 sec
    int2sign_np:    0.00318024 sec
    int2sign_str:   0.24846984 sec
    

    【讨论】:

      【解决方案5】:

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

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

      【讨论】:

      • 谢谢,@Jed!为了给出一些上下文,我正在通过采样估计超立方体角上的概率质量函数,其中每个样本都是一个角——每个角都可以表示为一个位串。我目前的方法是首先将每个样本转换为一个 int,然后在整数集上调用 numpy.unique 以获取计数。在这种情况下,packbitsuint8 的限制(据我所知)。因此,如果不仔细解析 packbits 输出,我就无法超过 8 维立方体。简单的除法算法让我得到 64 位整数,这对于现在来说已经足够了。
      • @user1416125 在每行的元组上使用numpy.unique,参见。 stackoverflow.com/questions/31097247/… 所以你对 64 维没有愚蠢的限制。也可以使用散列或布隆过滤器检查唯一性(概率)。
      猜你喜欢
      • 1970-01-01
      • 2011-04-10
      • 1970-01-01
      • 1970-01-01
      • 2011-11-16
      • 1970-01-01
      • 1970-01-01
      • 2016-06-22
      • 1970-01-01
      相关资源
      最近更新 更多