1. 官方文档
random --- 生成伪随机数 — Python 3.12.2 文档
random — Generate pseudo-random numbers — Python 3.12.2 documentation
Random Generator — NumPy v1.26 Manual
2. 准备知识
2.1 伪随机数生成器与真随机数生成器
(PRNG, pseudorandom number generator; TRNG, true random number generator)
TRNG,真随机数生成器,是基于物理过程的随机数生成器,如环境噪声、热噪声等。TRNG生成的随机数是真正的随机数,因为它们是基于物理过程的,不可预测且不可重现(例如,扔骰子)。
PRNG,伪随机数生成器,是基于算法的随机数生成器。它使用一个初始种子 (seed) 来生成随机数序列,但是这个序列是有规律的,可重复的,因此称为 “伪随机数”。PRNG 用一个种子编号来初始化数字的生成,使用相同种子的 PRNG 将产生相同的数字。PRNG 通常使用计算机时钟时间作为默认种子(时间以纳秒为单位进行测量,因此连续运行数字生成器会产生不同的种子值,进而产生不同的随机数序列)。
PRNG 有一个周期属性,表示在开始重复之前经历的迭代次数,即同样的数字可能会再次出现。不过,在实践中这不算是一个问题,因为周期长度很长,通常能达到 2^64 等级。
在使用 Python random 模块时,代码如 random.seed(999)、random.seed(1234) 的作用是:设置 random 模块底层的随机数生成器的种子,从而使后续生成的随机数具有确定性:输入 A 的结果总是输出 B。下面通过一个简化的例子,理解种子的作用原理。
class NotSoRandom(object):
def seed(self, a=3):
"""Seed the world's most mysterious random number generator."""
self.seedval = a
def random(self):
"""Look, random numbers!"""
self.seedval = (self.seedval * 3) % 19
return self.seedval
_inst = NotSoRandom()
seed = _inst.seed
random = _inst.random
seed(1234)
[random() for _ in range(10)]
# [16, 10, 11, 14, 4, 12, 17, 13, 1, 3]
seed(1234)
[random() for _ in range(10)]
# [16, 10, 11, 14, 4, 12, 17, 13, 1, 3]
PRNG 通常用于计算机程序中的随机数生成,但是在安全性要求较高的场合,如密码学中,不能使用 PRNG。
2.2 密码学安全的伪随机数生成器
(CSPRNG, Cryptographically Secure Pseudo-Random Number Generator)
CSPRNG 是一种生成随机数的算法,可以生成高质量的随机数序列,这些随机数序列可以用于密码学、安全协议、密钥生成等安全领域。CSPRNG 的生成过程是基于种子或熵 (entropy),通过一系列的复杂运算生成随机数序列,这些随机数序列满足统计学上的随机性,同时也满足密码学上的安全性要求,不容易被预测和攻击。
CSPRNG 的一个关键点是它们仍然是伪随机的。它以某种内部确定性的方式被设计,但它们添加了一些其他变量或具有一些属性,使它们“足够随机”,从而不会返回确定的结果。
3. Python 标准库:random
Python random 使用 Mersenne Twister 伪随机数生成器 (PRNG)。
3.1 生成随机数
3.1.1 Bookkeeping 函数 (用于重现随机数的函数)
函数 | 描述 |
random.seed(a=None, version=2) | 如果不指定种子,则使用系统时间作为种子。 |
random.getstate() | 返回当前随机数生成器的状态信息。可将返回结果保存下来,后续通过 setstate() 函数恢复随机数生成器的状态。 |
random.setstate(state) | 将随机数生成器的状态设置为 state 参数指定的状态信息。 state 是通过之前调用 getstate() 获得的。 |
3.1.2 整数 (离散值)
random.randint(a, b) | 返回 [a, b] 范围内的一个随机整数。 左闭右闭 (include both endpoints)。 |
random.randrange(stop) random.randrange(start, stop[, step]) | 返回从 range(start, stop[, step]) 随机选择的一个整数,start 默认为0,step 默认为1 (同 range 函数)。 大致等价于:choice(range(start, stop, step)) 当未设置 step 或 step 设置为1时,对应范围为:[a,b) |
random.binomialvariate(n=1, p=0.5) | 二项分布。 |
3.1.3 浮点数 (连续值)
函数 | 描述 |
random.random() | 返回 [0.0,1.0) 范围内的一个随机浮点数。 左闭右开 (The result will always be less than the right-hand endpoint (1.0))。 |
random.uniform(a, b) | 返回 [a, b) (或[b, a)) 范围内的一个随机浮点数。 当 a <= b 时 a <= N < b ,当 b < a 时 b <= N < a 。 |
random.gauss(mu=0.0, sigma=1.0) | 正态分布,也称高斯分布。 mu 是平均值,sigma 是标准差。 此函数要稍快于 normalvariate。 多线程注意事项:当两个线程同时调用此方法时,它们有可能将获得相同的返回值。 这可以通过三种办法来避免。 1) 让每个线程使用不同的随机数生成器实例。 2) 在所有调用外面加锁。 3) 改用速度较慢但是线程安全的 normalvariate() 函数。 |
random.normalvariate(mu=0.0, sigma=1.0) | 正态分布。 mu 是平均值,sigma 是标准差。 速度较慢但线程安全。 |
random.lognormvariate(mu, sigma) | 对数正态分布。 |
random.expovariate(lambd=1.0) | 指数分布。 参数 lambd 本应命名为 “lambda” ,但这是 Python 保留字。 如果 lambd 为正,则返回值的范围为 0 到正无穷大; 如果 lambd 为负,则返回值从负无穷大到 0。 |
random.betavariate(alpha, beta) | Beta 分布。alpha > 0 ,beta > 0。 |
random.gammavariate(alpha, beta) | Gamma 分布。alpha > 0 ,beta > 0。 |
random.vonmisesvariate(mu, kappa) | 冯·米塞斯分布。 |
random.paretovariate(alpha) | 帕累托分布。 alpha 是形状参数。 |
random.weibullvariate(alpha, beta) | 威布尔分布。 alpha 是比例参数,beta 是形状参数。 |
3.1.4 代码:random、uniform 等函数以及 random.seed 的用法
import random
# random.seed(100)
print(list(range(3, 18, 2)))
res1 = random.random()
res2 = random.random()
res3 = random.uniform(3, 8)
res4 = random.randint(3, 8)
res5 = random.randrange(3, 18, 2)
print(res1, res2, res3, res4, res5)
# random.seed(100)
res1 = random.random()
res2 = random.random()
res3 = random.uniform(3, 8)
res4 = random.randint(3, 8)
res5 = random.randrange(3, 18, 2)
print(res1, res2, res3, res4, res5)
未调用 random.seed() 时(注释掉第3行和第13行),两次运行结果不同,打印结果为:
[3, 5, 7, 9, 11, 13, 15, 17] 0.6384560624069965 0.1806474179450056 6.0138107909081935 4 7 0.19264065598707336 0.8411747144619733 7.568884211246141 4 17
调用 random.seed() 时 (第3行和第13行去注释)。两次运行结果相同,打印结果为:
[3, 5, 7, 9, 11, 13, 15, 17] 0.1456692551041303 0.45492700451402135 6.853919028295111 8 15 0.1456692551041303 0.45492700451402135 6.853919028295111 8 15
3.1.5 代码:getstate 与 setstate 的用法
import random
random_numbers = [random.randint(1, 100) for i in range(10)]
state = random.getstate()
random_numbers2 = [random.randint(1, 100) for i in range(10)]
random.setstate(state)
random_numbers3 = [random.randint(1, 100) for i in range(10)]
print(random_numbers)
print(random_numbers2)
print(random_numbers3)
# [43, 30, 40, 99, 27, 23, 19, 25, 45, 48]
# [81, 53, 27, 52, 60, 72, 36, 49, 21, 84]
# [81, 53, 27, 52, 60, 72, 36, 49, 21, 84]
3.2 序列随机化操作 (序列用函数 Functions for sequences)
函数 | 描述 |
random.choice(seq) | 从非空序列 seq 返回一个随机元素。 如果 seq 为空,则引发 IndexError。 |
random.choices(population, weights=None, *, cum_weights=None, k=1) | 返回从 population 抽取的允许重复选择的长度为 k 的列表(有放回)。 如果指定了 weight 序列,则根据相对权重进行选择;如果给出 cum_weights 序列,则根据累积权重(可能使用 itertools.accumulate() 计算)进行选择。 相对权重 [10, 5, 30, 5] 相当于累积权重 [10, 15, 45, 50]。 相对权重在进行选择之前会转换为累积权重,因此提供累积权重可以节省工作量。权重值应当非负且为有限的数值。 如果既未指定 weight 也未指定 cum_weights ,则以相等的概率进行选择。 1) 如果 population 为空,引发 IndexError。 2) 如果提供了权重序列,则它必须与 population 序列的长度相同。 3) 同时提供 weights 和 cum_weights,引发 IndexError。 |
random.shuffle(x) | 就地将序列 x 随机打乱位置 (直接改变原序列)。 如果不想改变原序列,而是返回一个新的打乱的序列,请使用 sample(x, k=len(x))。 |
random.sample(population, k, *, counts=None) | 返回从 population 中抽取的元素无重复的长度为 k 的列表(无放回)。 结果列表是元素来自原序列的新列表,按选择顺序排列,原序列保持不变。总体成员不必是 hashable 或 unique (总体可包含重复元素) 。 形参 counts 表示重复的元素的个数,例如,sample(['red', 'blue'], counts=[4, 2], k=5) 等价于 sample(['red', 'red', 'red', 'red', 'blue', 'blue'], k=5)。 要从一系列整数中选择样本,请使用 range() 对象作为参数。 例如,从大量人群中采样可使用:sample(range(10000000), k=60) ,快速且节省空间。 1) 如果样本大小大于总体大小,则引发 ValueError。 2) counts 的元素个数必须与 population 相同 。 |
3.2.1 .random.choice() -- 有放回抽样
import random
items = ['one', 'two', 'three', 'four', 'five']
random.seed(100)
random.choices(items)
random.choices(items, k=4)
for i in range(10):
if 1 % 2 == 1:
random.seed(100)
print(random.choices(items, weights=(10, 10, 1, 1, 1), k=4))
else:
random.choices(items, cum_weights=(10, 20, 21, 22, 23), k=4)
# 注:不能同时提供 weights 和 cum_weights 参数
# random.choices(items, weights=(10, 10, 1, 1, 1), cum_weights=(10, 20, 21, 22, 23), k=4)
# 报错:TypeError: Cannot specify both weights and cumulative weights
# 返回结果
# ['one']
# ['three', 'four', 'four', 'four']
# ['one', 'two', 'two', 'two']
# ['one', 'two', 'two', 'two']
# ['one', 'two', 'two', 'two']
# ['one', 'two', 'two', 'two']
# ['one', 'two', 'two', 'two']
# ['one', 'two', 'two', 'two']
# ['one', 'two', 'two', 'two']
# ['one', 'two', 'two', 'two']
# ['one', 'two', 'two', 'two']
# ['one', 'two', 'two', 'two']
3.2.2 .random.sample() -- 无放回抽样 (抽样数等于序列数时,等同于不改变原序列,将原序列重排序)
import random
items = list(range(100))
# 注1:k 的值不能大于 population 元素个数
# random.sample(items, k=120)
# 报错:ValueError: Sample larger than population or is negative
# 注2:counts 元素个数必须与 population 一致
# random.sample(items, counts=[2, 3], k=10)
# 报错:ValueError: The number of counts does not match the population
for i in range(10):
random.seed(100)
random.sample(items, k=10)
# 返回结果
# [18, 58, 98, 22, 90, 50, 93, 44, 55, 64]
# [18, 58, 98, 22, 90, 50, 93, 44, 55, 64]
# [18, 58, 98, 22, 90, 50, 93, 44, 55, 64]
# [18, 58, 98, 22, 90, 50, 93, 44, 55, 64]
# [18, 58, 98, 22, 90, 50, 93, 44, 55, 64]
# [18, 58, 98, 22, 90, 50, 93, 44, 55, 64]
# [18, 58, 98, 22, 90, 50, 93, 44, 55, 64]
# [18, 58, 98, 22, 90, 50, 93, 44, 55, 64]
# [18, 58, 98, 22, 90, 50, 93, 44, 55, 64]
# [18, 58, 98, 22, 90, 50, 93, 44, 55, 64]
# 参数counts的用法
items2 = ["one", "two", "three"]
random.sample(items2, counts=[2,2,2], k=4)
# ['one', 'three', 'three', 'two']
import random
# 注 population 不必是hashable
# 不可哈希列表抽样
items = [{"A": 1}, {"B": 2}, {"C": 3}, {"D": 4}] # TypeError: unhashable type: 'dict'
random.sample(items, k=2)
# [{'A': 1}, {'C': 3}]
3.2.3 .random.shuffle -- 序列重排序 (改变原序列)
import random
items = ['one', 'two', 'three', 'four', 'five']
new_items = random.sample(items,k=len(items))
print(new_items)
print(items)
random.shuffle(items)
print(items)
# 返回结果
# ['two', 'five', 'one', 'four', 'three']
# ['one', 'two', 'three', 'four', 'five']
# ['one', 'three', 'five', 'two', 'four']
一个例子:生成n个长度为k的密码(规定可用字符)
import string
def unique_strings(k: int, ntokens: int,
pool: str=string.ascii_letters) -> set:
"""Generate a set of unique string tokens.
k: Length of each token
ntokens: Number of tokens
pool: Iterable of characters to choose from
For a highly optimized version:
https://stackoverflow.com/a/48421303/7954504
"""
seen = set()
# An optimization for tightly-bound loops:
# Bind these methods outside of a loop
join = ''.join
add = seen.add
while len(seen) < ntokens:
token = join(random.choices(pool, k=k))
add(token)
return seen
unique_strings(k=8, ntokens=3)
# {'LNAzEhJS', 'iNKgcElq', 'zNQUHnSV'}
unique_strings(k=10, ntokens=3)
# {'GHMDrafkBL', 'iPTckmehHg', 'kuWuLGIyCK'}
4. numpy:random
numpy 用于构建和操作大型多维数组。如果想生成一个随机数或者比较小的随机数序列,首选使用 random,因为 numpy 有一些开销,random 可能更快;如果需要生成数量较多的随机数序列,建议使用 numpy.random。
numpy.random 使用自己的伪随机数生成器 (PRNG),与 Python random 模块内部使用的生成器不是同一个,因此使用相同的种子,调用Python random 得到的结果与调用 numpy.random 产生的结果不相同。
Python random 模块使用 Mersenne twister 算法,尽管该算法广泛应用于 Python 代码中,但是它生成的数字能被预测,并且需要一定的计算能力。numpy 从1.17版本开始使用更有效的 permuted congruential generator-64 (PCG64) 算法,这种算法生成的数字更难预测,而且更快,占用的资源更少。
注意:numpy PRNG不适合用于加密目的,只适用于数据分析任务。如果需要用于加密目的的随机数,应使用加密安全的伪随机数生成器 (CSPRNG)。
4.1 生成随机数
4.1.1 .random() 方法 -- 随机数生成器的使用
numpy 数字生成的核心是 BitGenerator 类,这个类允许您指定算法(Generator 对象)和种子。Generator 具有一系列获取随机数或执行随机化操作的方法。
默认情况下,BitGenerator 使用 PCG64 和来自计算机时钟的种子。numpy 提供了一个非常方便的 default_rng() 函数,可直接返回默认 Generator,能够方便地使用它的一系列功能强大的方法,比如:Generator.random() 返回在半闭半开区间 [0.0,1.0] 内64位浮点数(random() 方法还包括一个很少使用的 dtype 参数。缺省情况下,该值设置为 np.float64,生成64位浮点数;如果设置为np.float32,可以生成32位浮点数)。
import numpy as np
default_rng = np.random.default_rng()
default_rng
# Generator(PCG64) at 0x1D5E9213580
default_rng.random()
# 0.8077391779976406 -- 随机生成的浮点数
PCG64 是对Mersenne twister 算法的优化,不过从统计学上看,它仍然有一些缺点,新版本的PCG64DXSM 解决了这些问题,且将成为未来 numpy 版本的默认设置。如果真的需要更强大的 PCG64DXSM 算法,可以先显式地创建一个 Generator 对象,然后将 PCG64DXSM 传递给 BitGenerator。具体应用方法如下:
from numpy.random import Generator, PCG64DXSM
pcg64dxsm_rng = Generator(PCG64DXSM())
pcg64dxsm_rng.random()
最后,看一下随机种子的应用:
rng1 = np.random.default_rng(seed=100)
rng1.random()
rng1.random()
rng2 = np.random.default_rng(seed=100)
rng2.random()
rng2.random()
# 返回结果:
# 0.8349816305020089
# 0.5965540269678873
# 0.8349816305020089
# 0.5965540269678873
4.1.2 .uniform 方法 -- 浮点数 Random Floating-Point Numbers
.random() 生成 [0.0,1.0) 范围内的随机浮点数,如果想输出指定范围内的随机数,可以使用.uniform() 方法。
.uniform() 方法用于输出指定范围内的随机浮点数,可以指定 low 和 high 参数,它还包含一个size参数,用于生成随机 numpy 数组。
import numpy as np
rng = np.random.default_rng()
rng.uniform() # 生成 [0, 1) 范围内的随机浮点数
rng.uniform(low=3.4, high=5.6) # 生成 [3.4, 5.6) 范围内的随机浮点数
rng.uniform(high=10) # 生成 [0, 10) 范围内的随机浮点数
# 返回结果
# 0.7342951994115056
# 3.5048330515448076
# 2.8684277204898754
4.1.3 .integers() 方法 -- 整数 Random Integer Numbers
可以使用 Generator 对象的 .integers() 方法生成随机整数。.integers()方法默认生成 64 位整数。这是因为它的 dtype 参数被设置为 np.int64。如果将 dtype 设置为 np.int32,将获得 32 位整数。
.integers() 方法包含三个参数,用于定义生成整数随机数的范围:low,high 和 endpoint。
- low 参数与 high 参数:low 是必选参数,换句话说,.integers () 方法至少要有一个参数。
- 如果使用单个参数调用.integers() ,如 rng.integers(3) ,参数代表范围的上界,范围下界默认为 0,所以 rng.integers(3) 对应范围 [0,3) ,即可能取到的值有0,1,2。这也是最常见的使用 .integers() 的方式。
- 如果提供了 low 参数,同时也提供了 high 参数时,low 参数表示 .integers() 函数可以选择的最小整数,如 rng.integers(low=1, high=4) 对应 [1,4) 。
- 如果没有提供 high 参数,只提供了 low 参数,会造成困扰,程序将会把 low 参数视为范围的上界,范围下界默认为 0,即 rng.integers(low=3) 等同于 rng.integers(3) 或更显式的 rng.integers(low=0, high=3)。
- endpoint:表示是否包含上界。如前所述,随机数范围默认是半闭半开区间 [low,high),即 endpoint 默认为 False。如果将 endpoint 设置为True,则两端都包含,区间为 [low, high]。例如:prng.integers(low=1, high=4, endpoint=True) 表示生成 [1,4] 范围内的一个随机数。
import numpy as np
rng = np.random.default_rng()
[rng.integers(3) for i in range(5)]
[rng.integers(low=5) for i in range(5)]
[rng.integers(low=6, high=8) for i in range(5)]
[rng.integers(low=6, high=8, endpoint=True) for i in range(5)]
# 返回结果
# [2, 0, 1, 2, 1]
# [0, 2, 0, 3, 0]
# [6, 7, 7, 6, 7]
# [7, 8, 6, 7, 8]
4.1.4 size 参数 -- 随机数组 Random NumPy Arrays
可以使用 Generator 对象的.random()、.uniform() 或 .integers() 方法的 size 参数 生成一个包含随机数的 NumPy Array。上述三种方法中,size 的默认值都是 None,对应生成单个数字,如果您将元组赋值给size,那么将得到一个数组。
- 如果要创建一维数组,size参数可设置为一个整数或具有单个元素的元组,如: size=x 或 size=(x,);
- size=(x, y) 表示创建一个包含 x 行和 y 列的二维数组;size=(x, y, z) 表示生成一个包含 x 组的 y 行和 z 列的三维数组。
import numpy as np
rng = np.random.default_rng()
rng.random(size=3)
rng.random(size=(3,))
rng.random(size=(2, 3))
rng.random(size=(3, 2, 3))
# 返回结果
# array([0.97780467, 0.1254622 , 0.19502728])
# array([0.46059264, 0.43210196, 0.86817838])
# array([[0.63261436, 0.21680677, 0.31006415],
# [0.9840874 , 0.80143655, 0.58305313]])
# array([[[0.19366441, 0.09999187, 0.49170082],
# [0.10643325, 0.52211856, 0.89387167]],
# [[0.54778564, 0.82977201, 0.1803082 ],
# [0.42749766, 0.47355084, 0.43805102]],
# [[0.07852607, 0.21988269, 0.8827316 ],
# [0.46184435, 0.79262899, 0.5575091 ]]])
4.2 数组随机化操作 (Randomizing Existing NumPy Arrays)
4.2.1 .choice() 方法 -- 随机选择原始数据的元素创建数组
Generator 对象的 .choice() 方法用于以各种不同的方式从给定数组中选择随机样本。
a: 原始数据,array_like。
size 参数:表示调用 .choice() 创建的随机数组的 Shape。size=3 表示创建一个一维数据,包含从原始数组中随机选择的3个元素。size=(2,3) 表示创建一个2乘3的,包含6个随机元素的数组。
replace 参数:默认为 True,表示同一元素可能被多次选中(有放回);replace 为 False 表示不能多次选择同一的元素(无放回)。需要注意的是,.choice() 方法根据元素在原始数组中的位置选择元素,如果某个值在原始数据中多次出现,无论 replace 是 True 还是 False,最终都有可能取到重复的值。
a 为一维数组时,很好理解:
import numpy as np
rng = np.random.default_rng()
input_array_1d = np.array([1, 2, 3, 4, 5])
rng.choice(input_array_1d, size=3, replace=False)
rng.choice(input_array_1d, size=(2, 2), replace=False)
rng.choice(input_array_1d, size=(2, 2), replace=True)
# 返回结果
# array([5, 2, 4])
# array([[5, 2],
# [3, 1]])
# array([[1, 5],
# [3, 3]])
当 a 未多维数据数,会沿指定维度,将其他几个维度的数据视作整体,实际返回的数据 shape 比 size 的维度多,具体来说,如果 a 为二维数组时,会将完整的行 (axis=0) 或列 (axis=1) 视为单个元素,实际返回的数据比 size 参数多1个维度,详细内容参考 4.2.2。
import numpy as np
rng = np.random.default_rng(seed=10)
a = rng.integers(low=1, high=100, size=(3, 4, 5))
rng = np.random.default_rng(seed=100)
b = rng.choice(a, size=(2, 3), axis=0)
print(b.shape)
b = rng.choice(a, size=(2, 3), axis=1)
print(b.shape)
b = rng.choice(a, size=(2, 3), axis=2)
print(b.shape)
# (2, 3, 4, 5)
# (3, 2, 3, 5)
# (3, 4, 2, 3)
4.2.2 .choice() 方法 -- 随机选择 (完整的) 行或列
choice() 方法的 axis 参数:从数组中随机选择一个或多个完整的行或列。
当 a 为多维数组,axis 默认为 0,size 参数表示取完整的行或列。对于二维数组,axis=0 (默认值) 表示取行,axis=1 表示取列。
import numpy as np
rng = np.random.default_rng(seed=10)
a = rng.integers(low=1, high=100, size=(3, 4))
a
# array([[77, 95, 27, 21],
# [79, 83, 51, 15],
# [83, 51, 16, 14]], dtype=int64)
rng = np.random.default_rng(seed=100)
rng.choice(a, size=(2, 3))
# array([[[83, 51, 16, 14],
# [83, 51, 16, 14],
# [77, 95, 27, 21]],
# [[79, 83, 51, 15],
# [77, 95, 27, 21],
# [77, 95, 27, 21]]], dtype=int64)
rng = np.random.default_rng(seed=100)
rng.choice(a, size=(2, 3), axis=0)
# array([[[83, 51, 16, 14],
# [83, 51, 16, 14],
# [77, 95, 27, 21]],
# [[79, 83, 51, 15],
# [77, 95, 27, 21],
# [77, 95, 27, 21]]], dtype=int64)
import numpy as np
rng = np.random.default_rng()
input_array = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
[10, 11, 12]]
)
print(input_array.shape)
# (4, 3) 原始数据为4行3列
rng.choice(input_array, size=2)
# 默认 axis=0,表示取2行,最终的数据规格是(2, 3)
# array([[7, 8, 9],
# [4, 5, 6]])
rng.choice(input_array, size=2, axis=0)
# 单行的规格是(1,3),取2行,最终的数据规格是(2, 3)
# array([[ 4, 5, 6],
# [10, 11, 12]])
rng.choice(input_array, size=2, axis=1)
# 单列的规格是(4,1),取2列,最终的数据规格是(4, 2)
# array([[ 1, 3],
# [ 4, 6],
# [ 7, 9],
# [10, 12]])
size 为多维的时候,返回结果如下:
b = rng.choice(input_array, size=(2,3), axis=0)
print(b.shape) # (2, 3, 3)、
b
# array([[[ 4, 5, 6],
# [ 7, 8, 9],
# [ 7, 8, 9]],
# [[ 1, 2, 3],
# [ 7, 8, 9],
# [10, 11, 12]]])
a = rng.choice(input_array, size=(2,3), axis=1)
print(a.shape) # (4, 2, 3)
a
# array([[[ 2, 2, 1],
# [ 3, 2, 2]],
# [[ 5, 5, 4],
# [ 6, 5, 5]],
# [[ 8, 8, 7],
# [ 9, 8, 8]],
# [[11, 11, 10],
# [12, 11, 11]]])
choice() 方法的 shuffle 参数:额外地增加了一层随机性,在完成初始随机选择之后,以行 (axis=1时为列) 为单位重新排序,行 (axis=1时为列) 内部,单个元素的顺序保持不变。shuffle 参数默认为True,但需在 replace 参数为 False 时才会生效 (即,如果将 shuffle 为 False 或 replace 为True,不会发生额外的shuffle操作) 。
import numpy as np
input_array = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]])
rng = np.random.default_rng(seed=100)
rng.choice(input_array, size=3, replace=False, shuffle=False)
rng = np.random.default_rng(seed=100)
rng.choice(input_array, size=3, replace=False, shuffle=True)
rng = np.random.default_rng(seed=100)
rng.choice(input_array, size=3, replace=False)
rng = np.random.default_rng(seed=100)
rng.choice(input_array, size=3, replace=True, shuffle=False)
rng = np.random.default_rng(seed=100)
rng.choice(input_array, size=3, replace=True, shuffle=True)
rng = np.random.default_rng(seed=100)
rng.choice(input_array, size=3, replace=True)
4.2.3 .shuffle() 方法 -- 数组元素随机化 Shuffling Arrays Randomly
模拟卡牌游戏:下述代码中,先将所有卡牌 (由数值和花色组成) 转换为 numpy 数组,然后进行洗牌,显示洗牌后的前三个元素,即前三张牌,之后重新洗牌,再次抽出前三张牌。
相比于choice() 方法,使用 shuffle() 方法实现数组元素随机化是更优的选择,能够节省内存。
import numpy as np
def create_deck():
RANKS = "2 3 4 5 6 7 8 9 10 J Q K A".split()
SUITS = "♣ ♢ ♡ ♠".split()
return np.array([r + s for s in SUITS for r in RANKS])
rng = np.random.default_rng()
deck_of_cards = create_deck()
deck_of_cards
# array(['2♣', '3♣', '4♣', '5♣', '6♣', '7♣', '8♣', '9♣', '10♣', 'J♣', 'Q♣', 'K♣', 'A♣',
# '2♢', '3♢', '4♢', '5♢', '6♢', '7♢', '8♢', '9♢', '10♢', 'J♢', 'Q♢', 'K♢', 'A♢',
# '2♡', '3♡', '4♡', '5♡', '6♡', '7♡', '8♡', '9♡', '10♡', 'J♡', 'Q♡', 'K♡', 'A♡',
# '2♠', '3♠', '4♠', '5♠', '6♠', '7♠', '8♠', '9♠', '10♠', 'J♠', 'Q♠', 'K♠', 'A♠'], dtype='<U3')
rng.shuffle(deck_of_cards)
deck_of_cards[0:3]
# array(['9♢', '3♡', 'K♡'], dtype='<U3')
rng.shuffle(deck_of_cards)
deck_of_cards[0:3]
# array(['A♢', '9♣', 'A♠'], dtype='<U3')
多维数组的场景下,shuffle 作用于某一维度 (默认axis=0)。
import numpy as np
def create_deck():
RANKS = "2 3 4 5 6 7 8 9 10 J Q K A".split()
SUITS = "♣ ♢ ♡ ♠".split()
return np.array([[r + s for r in RANKS] for s in SUITS])
rng = np.random.default_rng()
deck_of_cards = create_deck()
deck_of_cards
# array([['2♣', '3♣', '4♣', '5♣', '6♣', '7♣', '8♣', '9♣', '10♣', 'J♣', 'Q♣', 'K♣', 'A♣'],
# ['2♢', '3♢', '4♢', '5♢', '6♢', '7♢', '8♢', '9♢', '10♢', 'J♢', 'Q♢', 'K♢', 'A♢'],
# ['2♡', '3♡', '4♡', '5♡', '6♡', '7♡', '8♡', '9♡', '10♡', 'J♡', 'Q♡', 'K♡', 'A♡'],
# ['2♠', '3♠', '4♠', '5♠', '6♠', '7♠', '8♠', '9♠', '10♠', 'J♠', 'Q♠', 'K♠', 'A♠']], dtype='<U3')
rng.shuffle(deck_of_cards)
deck_of_cards
# 默认axis=0,打乱行的顺序,行内容没有打乱
# array([['2♠', '3♠', '4♠', '5♠', '6♠', '7♠', '8♠', '9♠', '10♠', 'J♠', 'Q♠', 'K♠', 'A♠'],
# ['2♡', '3♡', '4♡', '5♡', '6♡', '7♡', '8♡', '9♡', '10♡', 'J♡', 'Q♡', 'K♡', 'A♡'],
# ['2♣', '3♣', '4♣', '5♣', '6♣', '7♣', '8♣', '9♣', '10♣', 'J♣', 'Q♣', 'K♣', 'A♣'],
# ['2♢', '3♢', '4♢', '5♢', '6♢', '7♢', '8♢', '9♢', '10♢', 'J♢', 'Q♢', 'K♢', 'A♢']], dtype='<U3')
import numpy as np
def create_deck():
RANKS = "2 3 4 5 6 7 8 9 10 J Q K".split()
SUITS = "♣ ♢ ♡ ♠".split()
return np.array([[[r + s for r in RANKS[3*(i-1):i*3]] for i in range(1, 5)] for s in SUITS])
deck_of_cards = create_deck()
print(deck_of_cards.shape)
# (4, 4, 3)
rng = np.random.default_rng(seed=100)
rng.shuffle(deck_of_cards, axis=0)
deck_of_cards
deck_of_cards = create_deck()
rng = np.random.default_rng(seed=100)
rng.shuffle(deck_of_cards, axis=1)
deck_of_cards
deck_of_cards = create_deck()
rng = np.random.default_rng(seed=100)
rng.shuffle(deck_of_cards, axis=2)
deck_of_cards
返回结果:
# 原始数据
array([[['2♣', '3♣', '4♣'],
['5♣', '6♣', '7♣'],
['8♣', '9♣', '10♣'],
['J♣', 'Q♣', 'K♣']],
[['2♢', '3♢', '4♢'],
['5♢', '6♢', '7♢'],
['8♢', '9♢', '10♢'],
['J♢', 'Q♢', 'K♢']],
[['2♡', '3♡', '4♡'],
['5♡', '6♡', '7♡'],
['8♡', '9♡', '10♡'],
['J♡', 'Q♡', 'K♡']],
[['2♠', '3♠', '4♠'],
['5♠', '6♠', '7♠'],
['8♠', '9♠', '10♠'],
['J♠', 'Q♠', 'K♠']]], dtype='<U3')
# axis=0
array([[['2♢', '3♢', '4♢'],
['5♢', '6♢', '7♢'],
['8♢', '9♢', '10♢'],
['J♢', 'Q♢', 'K♢']],
[['2♡', '3♡', '4♡'],
['5♡', '6♡', '7♡'],
['8♡', '9♡', '10♡'],
['J♡', 'Q♡', 'K♡']],
[['2♠', '3♠', '4♠'],
['5♠', '6♠', '7♠'],
['8♠', '9♠', '10♠'],
['J♠', 'Q♠', 'K♠']],
[['2♣', '3♣', '4♣'],
['5♣', '6♣', '7♣'],
['8♣', '9♣', '10♣'],
['J♣', 'Q♣', 'K♣']]], dtype='<U3')
# axis=1
array([[['5♣', '6♣', '7♣'],
['8♣', '9♣', '10♣'],
['J♣', 'Q♣', 'K♣'],
['2♣', '3♣', '4♣']],
[['5♢', '6♢', '7♢'],
['8♢', '9♢', '10♢'],
['J♢', 'Q♢', 'K♢'],
['2♢', '3♢', '4♢']],
[['5♡', '6♡', '7♡'],
['8♡', '9♡', '10♡'],
['J♡', 'Q♡', 'K♡'],
['2♡', '3♡', '4♡']],
[['5♠', '6♠', '7♠'],
['8♠', '9♠', '10♠'],
['J♠', 'Q♠', 'K♠'],
['2♠', '3♠', '4♠']]], dtype='<U3')
# axis=2
array([[['4♣', '3♣', '2♣'],
['7♣', '6♣', '5♣'],
['10♣', '9♣', '8♣'],
['K♣', 'Q♣', 'J♣']],
[['4♢', '3♢', '2♢'],
['7♢', '6♢', '5♢'],
['10♢', '9♢', '8♢'],
['K♢', 'Q♢', 'J♢']],
[['4♡', '3♡', '2♡'],
['7♡', '6♡', '5♡'],
['10♡', '9♡', '8♡'],
['K♡', 'Q♡', 'J♡']],
[['4♠', '3♠', '2♠'],
['7♠', '6♠', '5♠'],
['10♠', '9♠', '8♠'],
['K♠', 'Q♠', 'J♠']]], dtype='<U3')
4.2.4 .permutation() 方法 和 .permuted() 方法 -- 不改变原始数据的重排 Reordering Arrays Randomly
(1) .permutation()
除了 .shuffle() 方法,.permutation() 方法也可以表示对数组的重排,差别在于:前者是一个原地操作,直接修改原始数组,后者则是返回一个随机排列的副本,不会改变原始数组。
import numpy as np
def create_deck():
RANKS = "2 3 4 5 6 7 8 9 10 J Q K".split()
SUITS = "♣ ♢ ♡ ♠".split()
return np.array([[[r + s for r in RANKS[3*(i-1):i*3]] for i in range(1, 5)] for s in SUITS])
deck_of_cards = create_deck() # (4, 4, 3)
rng = np.random.default_rng(seed=100)
rng.permutation(deck_of_cards, axis=0)
rng = np.random.default_rng(seed=100)
rng.permutation(deck_of_cards, axis=1)
rng = np.random.default_rng(seed=100)
rng.permutation(deck_of_cards, axis=2)
返回结果与 4.2.3 .suffle() 方法的结果完全相同,差别在于原始数据 deck_of_cards 没有发生改变,permutaion 操作返回一个新数组。
(2) .permuted()
.permuted() 方法同样用于沿一个指定轴对数组进行重排,不过,不同于 .suffle() 和 .permutation(),.permuted() 的打乱操作在非重排的维度上是独立的;此外,同 .permutation() 方法一样,它不修改原始数组,而是返回一个新数组。
通过一个例子来看:
import numpy as np
def create_deck():
RANKS = "2 3 4 5 6 7 8 9 10 J Q K".split()
SUITS = "♣ ♢ ♡ ♠".split()
return np.array([[[r + s for r in RANKS[3*(i-1):i*3]] for i in range(1, 5)] for s in SUITS])
deck_of_cards = create_deck()
deck_of_cards
rng = np.random.default_rng(seed=100)
rng.permuted(deck_of_cards, axis=0)
rng = np.random.default_rng(seed=100)
rng.permuted(deck_of_cards, axis=1)
rng = np.random.default_rng(seed=100)
rng.permuted(deck_of_cards, axis=2)
deck_of_cards
返回结果:
# 原始数据
# 使用 permuted() 方法不改变原数组
array([[['2♣', '3♣', '4♣'],
['5♣', '6♣', '7♣'],
['8♣', '9♣', '10♣'],
['J♣', 'Q♣', 'K♣']],
[['2♢', '3♢', '4♢'],
['5♢', '6♢', '7♢'],
['8♢', '9♢', '10♢'],
['J♢', 'Q♢', 'K♢']],
[['2♡', '3♡', '4♡'],
['5♡', '6♡', '7♡'],
['8♡', '9♡', '10♡'],
['J♡', 'Q♡', 'K♡']],
[['2♠', '3♠', '4♠'],
['5♠', '6♠', '7♠'],
['8♠', '9♠', '10♠'],
['J♠', 'Q♠', 'K♠']]], dtype='<U3')
# axis= 0
# 打乱对应位置的数据在四组的顺序
# 比如原始数据中,各组第一行第一列的元素分别是:['2♣', '2♢', '2♡', '2♠'],打乱后为:['2♢', '2♡', '2♠','2♣']
# 同理对各组第一行第二列的元素进行打乱,此操作与对第一行第一列的操作是独立的,即Row1Col1和Row1Col2的打乱规则可以不同。
array([[['2♢', '3♡', '4♡'],
['5♣', '6♢', '7♢'],
['8♡', '9♠', '10♠'],
['J♢', 'Q♠', 'K♢']],
[['2♡', '3♢', '4♣'],
['5♠', '6♣', '7♣'],
['8♣', '9♡', '10♢'],
['J♠', 'Q♣', 'K♠']],
[['2♠', '3♣', '4♢'],
['5♢', '6♠', '7♡'],
['8♠', '9♣', '10♣'],
['J♡', 'Q♡', 'K♣']],
[['2♣', '3♠', '4♠'],
['5♡', '6♡', '7♠'],
['8♢', '9♢', '10♡'],
['J♣', 'Q♢', 'K♡']]], dtype='<U3')
# axis = 1
# 打乱每组中,四行的顺序,行内部的内容保持不变,只改变行顺序
# 不同组的打乱原则都是不一样的
array([[['5♣', '9♣', '10♣'],
['8♣', '6♣', '4♣'],
['J♣', '3♣', '7♣'],
['2♣', 'Q♣', 'K♣']],
[['2♢', '6♢', '7♢'],
['J♢', '3♢', '4♢'],
['5♢', 'Q♢', '10♢'],
['8♢', '9♢', 'K♢']],
[['8♡', 'Q♡', 'K♡'],
['2♡', '9♡', '7♡'],
['J♡', '3♡', '4♡'],
['5♡', '6♡', '10♡']],
[['5♠', 'Q♠', '7♠'],
['J♠', '3♠', 'K♠'],
['8♠', '9♠', '4♠'],
['2♠', '6♠', '10♠']]], dtype='<U3')
# axis = 2
# 打乱每行内部的顺序
# 不同组,同一组不同行的打乱原则都独立,每行三个数保持不变
array([[['4♣', '3♣', '2♣'],
['6♣', '7♣', '5♣'],
['10♣', '9♣', '8♣'],
['K♣', 'J♣', 'Q♣']],
[['2♢', '3♢', '4♢'],
['6♢', '5♢', '7♢'],
['8♢', '9♢', '10♢'],
['Q♢', 'J♢', 'K♢']],
[['2♡', '4♡', '3♡'],
['7♡', '5♡', '6♡'],
['10♡', '8♡', '9♡'],
['Q♡', 'K♡', 'J♡']],
[['4♠', '3♠', '2♠'],
['7♠', '6♠', '5♠'],
['9♠', '8♠', '10♠'],
['K♠', 'J♠', 'Q♠']]], dtype='<U3')
彻底重排(重排数组所有元素)
import numpy as np
def create_deck():
RANKS = "2 3 4 5 6 7 8 9 10 J Q K".split()
SUITS = "♣ ♢ ♡ ♠".split()
return np.array([[[r + s for r in RANKS[3*(i-1):i*3]] for i in range(1, 5)] for s in SUITS])
deck_of_cards = create_deck()
rng = np.random.default_rng(seed=100)
rng.permuted(rng.permuted(rng.permuted(deck_of_cards, axis=0), axis=1), axis=2)
# 返回结果
# array([[['9♠', '8♡', '4♡'],
# ['3♡', '7♢', 'J♢'],
# ['10♠', '2♢', '6♢'],
# ['K♢', 'Q♠', '5♣']],
# [['Q♣', '4♣', '5♠'],
# ['3♢', '10♢', '8♣'],
# ['2♡', 'K♠', '6♣'],
# ['J♠', '7♣', '9♡']],
# [['Q♡', '10♣', '2♠'],
# ['4♢', 'J♡', '9♣'],
# ['3♣', '8♠', 'K♣'],
# ['6♠', '7♡', '5♢']],
# [['5♡', '4♠', 'Q♢'],
# ['2♣', '6♡', '7♠'],
# ['J♣', '10♡', '3♠'],
# ['K♡', '9♢', '8♢']]], dtype='<U3')
4.4 生成来自指定分布的样本
4.4.1 poisson
import numpy as np
import matplotlib.pyplot as plt
def draw_distribution_fig(sample_array):
values, frequency = np.unique(sample_array, return_counts=True)
plt.title("Random Poisson Distribution.")
plt.xlabel("Values")
plt.ylabel("Frequency")
plt.plot(values, frequency, "ro")
plt.show()
rng = np.random.default_rng(seed=100)
poisson_samples = rng.poisson(lam=5, size=10000)
draw_distribution_fig(poisson_samples)
4.4.2 normal
rng = np.random.default_rng(seed=100)
mu, sigma = 5.0, 2.0
normal_samples = rng.normal(loc=mu, scale=sigma, size=10000)
count, bins, ignored = plt.hist(normal_samples, 30, density=True)
plt.plot(bins, 1/(sigma * np.sqrt(2 * np.pi)) * np.exp( - (bins - mu)**2 / (2 * sigma**2) ),
linewidth=2, color='r')
plt.show()
4.3 Python random V.S. NumPy random
Python random | NumPy random | Desc |
random() | random() | Random float in [0.0, 1.0) |
uniform(a, b) | uniform(low=a,high=b) | Random float in [a, b) |
randint(a, b) | integers(low=a,high=b,endpoint=True) | Random integer in [a, b] |
randrange(a, b[, step]) | integers(low=a,high=b) | Random integer in [a, b) |
choice(seq) choices(seq, k=1) | choice() | Random element from seq Random k elements from seq with replacement |
sample(population, k) | choice() with replace=False | Random k elements from seq without replacement |
shuffle(x) | shuffle() | Shuffle the sequence x in place |
normalvariate(mu, sigma) or gauss(mu, sigma) | normal(mu, sigma) | Sample from a normal distribution with mean mu and standard deviation sigma |