Python 基础 (标准库 & NumPy):random (生成伪随机数)

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 () 方法至少要有一个参数。
  1. 如果使用单个参数调用.integers() ,如 rng.integers(3) ,参数代表范围的上界,范围下界默认为 0,所以 rng.integers(3) 对应范围 [0,3) ,即可能取到的值有0,1,2。这也是最常见的使用 .integers() 的方式。
  2. 如果提供了 low 参数,同时也提供了 high 参数时,low 参数表示 .integers() 函数可以选择的最小整数,如 rng.integers(low=1, high=4) 对应 [1,4) 。
  3. 如果没有提供 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,那么将得到一个数组。

  1. 如果要创建一维数组,size参数可设置为一个整数或具有单个元素的元组,如: size=x 或 size=(x,);
  2. 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

  • 15
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值