【发布时间】:2020-09-25 00:31:48
【问题描述】:
给定一个 numpy 数组
import numpy as np
from numpy.random import random
N = 5
x = random(N)
如何将x 的(某些元素)的子集与-1 随机相乘,以改变数组中某些元素的符号?
【问题讨论】:
给定一个 numpy 数组
import numpy as np
from numpy.random import random
N = 5
x = random(N)
如何将x 的(某些元素)的子集与-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 占主导地位。比较时间时,请记住方法 index 和 xor__ 取决于索引数组。如果这是您的输入,请减去调用 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 倍。前一种方法允许您精确复制遮罩,而后一种方法允许您设置翻转元素的确切数量。
【讨论】:
np.negative 之外,所有这些都比 Julien 的慢。
或者这个:
x = np.where(random(N) > 0.5, -x, x)
(您可以将random(N) > 0.5 替换为适合您的任何其他随机规则...)
【讨论】:
你可以这样做:
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 没有给出负面元素
x = random(N) * 2 - 1? (不完全等效,但也许足够好?)