【问题标题】:How to randomly change the signs of some of the elements in a numpy array?如何随机更改numpy数组中某些元素的符号?
【发布时间】:2020-09-25 00:31:48
【问题描述】:

给定一个 numpy 数组

import numpy as np
from numpy.random import random
N = 5
x = random(N)

如何将x 的(某些元素)的子集与-1 随机相乘,以改变数组中某些元素的符号?

【问题讨论】:

    标签: python numpy random


    【解决方案1】:

    假设您有一个布尔掩码,指示要翻转哪些元素。然后你可以这样做:

    x[mask] *= -1
    

    此方法也适用于花哨的索引:

    x[index] *= -1 
    

    您也可以非常有效地使用np.negative

    np.negative(x, where=mask, out=x)
    

    这种方法可能是最有效的。

    生成掩码很简单。您可以编码一个简单的条件,如

    mask = np.random.random(N) >= 0.66
    

    或者您可以使用np.random.choice 来选择一个随机的花式索引:

    index = np.random.choice(N, size=N // 2, replace=False)
    

    最后,您可以通过 XOR 使用一些真正的技巧来做到这一点。这个想法是 IEEE 754 将符号位编码为数字的最高位。您可以通过使用浮点值的整数表示来翻转该一位来翻转符号。当然,这只适用于浮点数。

    您可以根据浮点数的大小调整整数的大小,或者简单地将np.uint8 与位0x80 一起使用。索引将按浮点数的大小进行缩放。

    x.view(np.uint8)[index * x.itemsize] ^= 0x80
    

    这假定了 little-endian 字节顺序。对于大端,使用偏移量:

    x.view(np.uint8)[(index + 1) * x.itemsize - 1] ^= 0x80
    

    时间

    以下是我在中等功率笔记本电脑上运行的一些基准测试:

    import numpy as np
    from timeit import repeat
    
    def where(x, mask):
        x = np.where(mask, -x, x)
    
    def mask_(x, mask):
        x[mask] *= -1
    
    def index(x, mask):
        x[np.flatnonzero(mask)] *= -1 
    
    def negat(x, mask):
        np.negative(x, where=mask, out=x)
    
    def xor__(x, mask):
        x.view(np.uint8)[np.flatnonzero(mask) * x.itemsize] ^= 0x80
    
    for E in range(2, 7):
        N = 10**E
        x = np.random.random(N)
    
        for P in (0.1, 0.5, 0.9):
            mask = np.random.random(N) < P
    
            print(f'E = {E}, P = {P}:')
    
            for func in where, mask_, index, negat, xor__:
                B = 10**(7 - E)
                t = min(repeat(lambda: func(x, mask), number=B)) / B
                print(f'{func.__name__}: {t:.3g}')
    

    结果,以P分隔:

    P = 0.1
    +-----+------+---------------------------------------+
    |     |      |                  Func                 |
    | Exp | Unit +-------+-------+-------+-------+-------+
    |     |      | where | mask_ | index | negat | xor__ |
    +-----+------+-------+-------+-------+-------+-------+
    |  2  |  μs  |  4.11 |  6.20 |  12.5 | *3.06 |  14.9 |
    |  3  |  μs  |  7.47 |  12.4 |  15.9 | *5.55 |  18.6 |
    |  4  |  μs  | *32.3 |  94.0 |  41.6 |  38.9 |  49.8 |
    |  5  |  ms  | *.258 |  1.06 |  .582 |  .575 |  .602 |
    |  6  |  ms  |  15.7 |  10.5 |  6.44 | *5.87 |  6.57 |
    +-----+------+-------+-------+-------+-------+-------+
    
    P = 0.5
    +-----+------+---------------------------------------+
    |     |      |                  Func                 |
    | Exp | Unit +-------+-------+-------+-------+-------+
    |     |      | where | mask_ | index | negat | xor__ |
    +-----+------+-------+-------+-------+-------+-------+
    |  2  |  μs  |  4.11 |  6.53 |  13.0 | *3.48 |  15.4 |
    |  3  |  μs  | *7.42 |  17.1 |  20.1 |  9.71 |  26.0 |
    |  4  |  μs  | *32.0 |  234. |  140. |  130. |  150. |
    |  5  |  ms  | *.268 |  2.41 |  1.27 |  1.43 |  1.36 |
    |  6  |  ms  |  15.5 |  27.7 |  20.5 | *14.2 |  21.1 |
    +-----+------+-------+-------+-------+-------+-------+
    
    P = 0.9
    +-----+------+---------------------------------------+
    |     |      |                  Func                 |
    | Exp | Unit +-------+-------+-------+-------+-------+
    |     |      | where | mask_ | index | negat | xor__ |
    +-----+------+-------+-------+-------+-------+-------+
    |  2  |  μs  |  4.11 |  6.23 |  13.2 | *3.13 |  15.7 |
    |  3  |  μs  |  7.81 |  15.0 |  23.6 | *6.40 |  28.4 |
    |  4  |  μs  | *31.5 |  116. |  104. |  54.8 |  130. |
    |  5  |  ms  | *.263 |  1.24 |  .882 |  .612 |  1.02 |
    |  6  |  ms  |  16.6 |  18.4 |  21.0 | *6.24 |  22.9 |
    +-----+------+-------+-------+-------+-------+-------+
    

    结论:对于小数组(10^6 个元素),np.negative 通常是最快的方法。对于 10^3-10^4 元素左右的最佳位置,np.where 占主导地位。比较时间时,请记住方法 indexxor__ 取决于索引数组。如果这是您的输入,请减去调用 np.flatnonzero 所需的时间。

    在所有情况下,P 确定的翻转元素的比例对结果影响不大。

    作为参考,我还计算了使用 np.random.choice 创建索引与使用掩码之间的差异。这些时间有点近似,因为这两个操作的结果并不完全相同:

    def thresh(n, p):
        return np.flatnonzero(np.random.random(n) < p)
    
    def choice(n, p):
        return np.random.choice(n, size=round(n * p), replace=False)
    
    for E in range(2, 7):
        N = 10**E
        for P in (0.1, 0.5, 0.9):
            print(f'E = {E}, P = {P}:')
            for func in thresh, choice:
                B = 10**(7 - E)
                t = min(repeat(lambda: func(N, P), number=B)) / B
                print(f'{func.__name__}: {t:.3g}')
    

    时间安排(汇总到表格中):

    +-----+------+-----------------------------------------------------+
    |     |      |                          P                          |
    |     |      +-----------------+-----------------+-----------------+
    | Exp | Unit |       0.1       |       0.5       |        0.9      |
    |     |      +--------+--------+--------+--------+--------+--------+
    |     |      | thresh | choice | thresh | choice | thresh | choice |
    +-----+------+--------+--------+--------+--------+--------+--------+
    |  2  |  μs  |  14.8  |  35.2  |  15.3  |  34.9  |  14.7  |  34.9  |
    |  3  |  μs  |  34.2  |  75.8  |  40.6  |  75.5  |  34.8  |  76.0  |
    |  4  |  μs  |  214.  |  494.  |  267.  |  494.  |  206.  |  494.  |
    |  5  |  ms  |  1.96  |  4.60  |  2.48  |  4.60  |  1.89  |  4.60  | 
    |  6  |  ms  |  26.1  |  50.1  |  34.5  |  50.3  |  30.7  |  50.2  |
    +-----+------+--------+--------+--------+--------+--------+--------+
    

    对随机数组设置阈值并调用 np.flatnonzero 总是比使用 np.random.choice 快约 2 倍。前一种方法允许您精确复制遮罩,而后一种方法允许您设置翻转元素的确切数量。

    【讨论】:

    • @HeapOverflow。我已经稍微修正了我的措辞,以从 ufunc 路由中排除花哨的索引。还添加了一种新的顶级方法
    • 我测试了一下,除了 np.negative 之外,所有这些都比 Julien 的慢
    • @HeapOverflow。数组大小有多大?从根本上说,它们都具有相同的复杂性,所以我想处理一个布尔索引比仅仅通过一个数组并否定整个事情有更多的开销。我更新了我的措辞。感谢您运行基准测试
    • 一百万个元素,见results and code
    • @HeapOverflow。我更新了时间、计算,并为不同的索引生成方法添加了基准。享受吧!
    【解决方案2】:

    或者这个:

    x = np.where(random(N) > 0.5, -x, x)
    

    (您可以将random(N) &gt; 0.5 替换为适合您的任何其他随机规则...)

    【讨论】:

      【解决方案3】:

      你可以这样做:

      import random
      x = [each*random.choice([-1,1]) for each in x]
      

      一口气:

      x = [each*random.choice([-1,1]) for each in random(N)]
      

      其中random(N) 是生成N 随机数的随机数生成器,即它可以是numpy.random.random,如问题示例中所示。

      【讨论】:

      • 我喜欢这个。怎么改成独立的生成器,而不是x的二次改造?太糟糕了random.random 没有给出负面元素
      • @develarist x = random(N) * 2 - 1? (不完全等效,但也许足够好?)
      猜你喜欢
      • 1970-01-01
      • 2016-07-12
      • 1970-01-01
      • 2020-02-13
      • 1970-01-01
      • 1970-01-01
      • 2015-10-02
      • 1970-01-01
      • 2016-10-07
      相关资源
      最近更新 更多