给定一个numpy数组
import numpy as np
from numpy.random import random
N = 5
x = random(N)
如何随机选择(x中的某些元素)的子集,并将它们乘以-1,以改变数组中一些元素的符号?
x = np.where(random(N) > 0.5, -x, x)
(您可以将random(N) > 0.5
更换为适合您的其他随机规则...)
假设你有一个布尔掩码,指示哪些元素需要翻转。那么你可以这样操作:
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
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 |
+-----+------+-------+-------+-------+-------+-------+
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倍。前一种方法允许您精确复制掩码,而后者则允许您设置确切数量的翻转元素。
你可以做:
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
不能给出负元素。 - develaristx = random(N) * 2 - 1
?(不是完全等价,但也许足够好了?) - Kelly Bundy