如果你觉得内容对你有帮助,请在 GitHub 上点个 star 吧!

1.4. 随机数生成#

numpy.random 模块用于生成随机数。

Hide code cell content
import numpy as np

随机数生成器:Random Generator#

最新版本的 NumPy 使用 Random Generator 生成随机数,它可以生成各种分布(Distribution)。在 Random Generator API 之前,NumPy 还有一个较老版本随机数生成器 API,又被成为 Random State API。我们先介绍基于 Random Generator 的 API。

生成随机数之前需要先要创建一个随机数生成器,default_rng(seed=None) 是 Random Generator API 所推荐的构造函数。

rng = np.random.default_rng() 

我们基于 default_rng() 生成浮点型随机数:

rng = np.random.default_rng(12345)

rfloat = rng.random()
rfloat
0.22733602246716966

查看 rfloat 的数据类型。

type(rfloat)
float

接下来,我们用 default_rng 函数生成 3 个范围在 [0,10) 内的随机数。

import numpy as np
rng = np.random.default_rng(12345)
rints = rng.integers(low=0, high=10, size=3)
rints
array([6, 2, 7])

查看 rints 的数据类型。

type(rints[0])
numpy.int64

随机数种子#

在计算机中,生成随机数通常是通过一个算法来实现,具体而言是实现一个伪随机数生成器,该生成器根据一个名为种子(Seed)的数值来生成一个随机数序列。随机数种子相当于是一个起始的引子,使用同样的种子会生成同样的随机数序列。因此种子值在生成随机数时起到了确定性的作用。随机数种子通常是一个整数,可以是任意值。在使用随机数生成器时,可以通过指定种子值来控制生成的随机数序列。如果不指定种子值,则通常会使用系统时间作为默认的种子值,以确保每次生成的随机数序列都是不同的。随机数种子在很多应用中都很重要,尤其是在需要重现随机结果的情况下。通过使用相同的种子值,可以确保在不同的计算机上生成的随机数序列是一致的。

需要注意的是,由于计算机生成的随机数是基于算法的伪随机数,因此种子值的选择也会影响到生成的随机数的质量。较好的做法是选择一个高熵的种子值,以提高随机数的质量和安全性。

在 Random Generator API 中,随机数种子用 np.random.default_rng(seed=s) 方法来设置,s 是用户传入的种子值。

下面的例子中,我们设置随机种子为 42 ,使用 random() 方法在 [0,1) 范围内随机生成结构为 \(2 \times 3 \times 4\) 的整数数组。因为我们设置了随机种子,每次运行下方随机数生成代码生成的随机数均相同。

rng = np.random.default_rng(seed=42)
arr1 = rng.random((2, 3, 4))
arr1
array([[[0.77395605, 0.43887844, 0.85859792, 0.69736803],
        [0.09417735, 0.97562235, 0.7611397 , 0.78606431],
        [0.12811363, 0.45038594, 0.37079802, 0.92676499]],

       [[0.64386512, 0.82276161, 0.4434142 , 0.22723872],
        [0.55458479, 0.06381726, 0.82763117, 0.6316644 ],
        [0.75808774, 0.35452597, 0.97069802, 0.89312112]]])

当我们退出 Python 编辑器后并重新启动,我们会发现在设置随机种子为 42 时,我们会得到同样的随机数。

rng = np.random.default_rng(seed=42)
arr2 = rng.random((2, 3, 4))
arr2
array([[[0.77395605, 0.43887844, 0.85859792, 0.69736803],
        [0.09417735, 0.97562235, 0.7611397 , 0.78606431],
        [0.12811363, 0.45038594, 0.37079802, 0.92676499]],

       [[0.64386512, 0.82276161, 0.4434142 , 0.22723872],
        [0.55458479, 0.06381726, 0.82763117, 0.6316644 ],
        [0.75808774, 0.35452597, 0.97069802, 0.89312112]]])

Note

很多网络上的示例代码经常使用 42 作为随机种子值,这其实是一个约定俗成的习惯,但并不是出于任何特殊原因或技术要求。实际上,选择随机种子值是主观的,可以是任何整数。使用 42 作为随机种子值主要由于以下原因:

  1. 42 是一个著名的数字:在计算机科学和科幻文化中,数字 42 具有一定的象征意义。在道格拉斯 · 亚当斯的小说《银河系漫游指南》中,超级电脑 “深思照片” 认为生命、宇宙以及一切的答案是 42。因此,一些程序员选择 42 作为随机种子值,是一种幽默或致敬。

  2. 方便记忆:42 是一个简单的数字,使用它作为随机种子值可以更容易地记住和重现特定的随机数序列。

值得注意的是,使用 42 作为随机种子值并不会使随机数序列更加随机或具有更高的质量。在实际应用中,选择随机种子值时应根据具体需求和安全性要求进行评估和选择。

随机数生成#

整数#

如果想要生成随机整数,可以使用函数 integers(low, high=None, size=None, dtype=np.int64, endpoint=False) ,数值范围为 \([low,high)\)。 若 endpoint=True ,则数值范围改变为 \([low,high]\)。 若 high=None (默认值),则数值范围为 \([0,low]\)

integers() 函数可以从指定数据类型的 “离散均匀” 分布中返回随机整数。

下面的例子,我们在 [0,2) 随机生成 10 个随机整数。

rng = np.random.default_rng()
rng.integers(low=2, high=None, size=10)
array([1, 1, 0, 0, 1, 0, 0, 0, 0, 0])

接下来,我们在 [0,5) 随机生成结构为 \(2 \times 3 \times 4\) 的整数数组。

rng.integers(low=5, high=None, size=(2, 3, 4))
array([[[4, 4, 2, 3],
        [0, 3, 3, 3],
        [1, 3, 2, 0]],

       [[4, 1, 4, 3],
        [0, 1, 4, 2],
        [0, 4, 2, 2]]])

使用 integers() 函数,我们可以同时生成几个具有不同上限(或下限)的随机整数。 例如,在下面的例子里,我们生成三组整数:

在三个不同上限, [1,3)[1,5)[1,1) ,随机生成结构为 \(1 \times 3\) 的整数数组 arr1

arr1 = rng.integers(1, [3, 5, 10])
arr1
array([1, 1, 5])

在三个不同下限, [1,10)[3,10)[5,10) ,随机生成结构为 \(1 \times 3\) 的整数数组 arr2

arr2 = rng.integers([1, 3, 5], 10)
arr2
array([6, 6, 6])

随机生成结构为 \(2 \times 4\) 的整数数组 arr3

arr3 = rng.integers([1, 3, 5, 7], [[10],[20]])
arr3
array([[ 7,  7,  6,  7],
       [10,  4, 19,  8]])

浮点数#

如果想要生成浮点型随机数,可以使用函数 random(size=None, dtype=np.float64, out=None) ,数值范围为 \([0.0, 1.0)\)

下面的例子,我们生成数值范围为 \([0.0, 1.0)\) 的浮点型随机数。

rng = np.random.default_rng()
rfloat = rng.random()

查看 rfloat 的数值类型。

type(rfloat)
float

随机生成维度为 \(2 \times 2 \times 3\) 的数组,服从 [0,1) 均匀分布。

rng.random((2, 2, 3))
array([[[0.69043733, 0.37606242, 0.65940416],
        [0.5136057 , 0.49864869, 0.19782748]],

       [[0.730943  , 0.92909854, 0.77622959],
        [0.56590965, 0.78527898, 0.75840335]]])

对于 [a,b)的样本,若想要随机生成来自指定间隔内的“连续均匀”分布,可以使用 uniform() 函数或将 random() 的输出乘以 (b - a) 并添加 a(b - a) * random() + a

下面的例子,我们在 [-5,0) 随机生成结构为 \(3 \times 2\) 的整数数组。

5 * rng.random((3, 2)) - 5
array([[-3.63168381, -3.66462336],
       [-3.794256  , -1.53588542],
       [-2.49770707, -1.80248065]])

排列#

一个典型的随机数场景是生成排列组合。

shuffle(x[, axis]) 方法就像扑克牌中的洗牌,可以在原地打乱对象 x 。如果 x 是多维数组,则按照第一维“洗牌”。现在,我们在 \([100,400)\) 范围内生成随机整数数组,并将生成的矩阵按照行“洗牌”。

x  = rng.integers(100, 400, size=(3, 2))
print("===before shuffle===")
print(x)
rng.shuffle(x)
print("===after shuffle===")
print(x)
===before shuffle===
[[377 219]
 [337 180]
 [169 315]]
===after shuffle===
[[337 180]
 [169 315]
 [377 219]]

permutation(x[, axis]) 方法生成了某种可能的排列。如果 x 是多维数组,则按照第一维生成一种新的排列。

rng.permutation(10)
array([6, 2, 1, 4, 9, 7, 3, 8, 0, 5])
rng.permutation([1, 4, 9, 12, 15])
array([ 9,  4, 12,  1, 15])
x  = rng.integers(100, 400, size=(3, 2))
rng.permutation(x)
array([[269, 334],
       [321, 146],
       [316, 248]])

permuted(x[, axis, out] 方法可以随机生成沿着轴 axis 随机排列 x 。 与 shuffle(x) 不同,给定轴上的每个切片都独立于其他切片进行 “洗牌”。

axis 是需要进行洗牌的轴。每个切片独立于其他切片进行洗牌。

下面的例子,我们首先生成一个测试数组 x

x  = rng.integers(15, size=(3, 5))
x
array([[ 2, 11,  0, 10, 12],
       [ 9,  0,  0, 14,  4],
       [14, 11, 13,  2, 10]])

沿行打乱 x

y = rng.permuted(x, axis=1)
y
array([[ 2, 11,  0, 12, 10],
       [ 0,  0,  9,  4, 14],
       [ 2, 10, 11, 13, 14]])

沿列打乱 x

z = rng.permuted(x, axis=0)
z
array([[14,  0,  0, 10,  4],
       [ 9, 11,  0,  2, 12],
       [ 2, 11, 13, 14, 10]])

查看 x ,发现 x 没有被更改:

x
array([[ 2, 11,  0, 10, 12],
       [ 9,  0,  0, 14,  4],
       [14, 11, 13,  2, 10]])

若要“就地”移动x的行,即改变 x 本身,我们需要将 x 作为 out 参数设置。注意,当给定 out 参数时,返回值是 out

y = rng.permuted(x, axis=1, out=x)
x
array([[ 2, 11,  0, 10, 12],
       [ 0,  0, 14,  9,  4],
       [ 2, 14, 13, 11, 10]])

现在验证原测试数组 x ,与返回值 y 是否一致,即 x 是否“就地”按行打乱。

y is x
True

分布#

numpy.random 模块中提供了生成概率分布的函数。

离散型随机变量#

二项分布#

binomial(n, p, size=None) 从二项分布中抽取样本。参数包括: n (试验次数,数值范围为 \([0,+∞)\)); p (成功概率,数值范围为 \([0,1]\)); size (输出的形状)。

从分布中抽取样本:

rng = np.random.default_rng()
n, p = 10, .5  
s = rng.binomial(n, p, 1000)

下面我们来进行一个真实的例子,一家公司钻了 9 口石油勘探井,每口井的成功概率估计为 0.1。 九口井全部失败。发生这种情况的可能性有多大? 让我们对该模型进行 20,000 次试验,并计算发生上述情况的概率。

sum(rng.binomial(9, 0.1, 20000) == 0)/20000
0.38945

泊松分布#

poisson(lam=1.0, size=None) 从泊松分布中抽取样本。 泊松分布是二项分布对于大N的极限。

从分布中抽取样本:

rng = np.random.default_rng()
s = rng.poisson(5, 10000)
s
array([6, 4, 4, ..., 4, 1, 5])

超几何分布#

hypergeometric(ngood, nbad, nsample, size=None) 函数从超几何分布中抽取样本。 它的参数包括: ngood (做出好的选择的方法数量), nbad (做出坏的选择的方法数量)和 nsample (采样的项目数,小于或等于ngood + nbad之和),size (输出的形状)。以上参数均非负。

rng = np.random.default_rng()
ngood, nbad, nsamp = 100, 2, 10
s = rng.hypergeometric(ngood, nbad, nsamp, 1000)
s
array([10, 10,  9, 10, 10, 10, 10, 10, 10, 10, 10, 10,  9, 10, 10, 10, 10,
       10, 10, 10, 10, 10, 10, 10, 10, 10,  9, 10, 10, 10,  9, 10,  9, 10,
       10, 10, 10, 10, 10,  9, 10, 10, 10, 10, 10, 10,  9, 10, 10, 10, 10,
       10, 10, 10, 10, 10, 10,  9, 10, 10, 10, 10, 10,  9,  9, 10,  9, 10,
       10,  9, 10, 10, 10, 10,  9, 10, 10, 10,  9,  9, 10,  9, 10, 10, 10,
       10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  9, 10,  9,  9,
       10, 10, 10,  9,  9, 10, 10, 10, 10, 10, 10, 10, 10, 10,  9, 10, 10,
       10, 10, 10, 10, 10, 10,  9, 10, 10, 10, 10, 10, 10, 10, 10,  9, 10,
        9, 10, 10, 10, 10, 10,  9,  9, 10, 10, 10,  9, 10, 10, 10,  9, 10,
        9, 10,  9, 10, 10,  9, 10, 10, 10, 10, 10,  9, 10, 10, 10, 10, 10,
       10, 10,  9, 10, 10, 10, 10, 10,  9, 10, 10, 10, 10, 10, 10, 10,  9,
       10, 10,  9, 10, 10,  9, 10,  9,  9, 10, 10, 10, 10, 10, 10,  9, 10,
       10,  9, 10, 10,  9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  9, 10,
        9,  9, 10, 10,  9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
        9, 10, 10, 10, 10,  9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  9,
       10, 10, 10, 10,  9,  9, 10, 10, 10,  9, 10, 10,  9, 10, 10, 10, 10,
        9, 10,  9, 10, 10, 10, 10,  9,  9, 10, 10, 10, 10, 10, 10, 10, 10,
        9,  9, 10,  9, 10, 10, 10, 10, 10, 10,  9, 10, 10,  9, 10,  9, 10,
       10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  8, 10, 10,  9,  9, 10,
       10, 10, 10, 10, 10, 10,  9, 10, 10, 10, 10, 10, 10, 10,  9, 10,  8,
        9, 10, 10, 10, 10, 10, 10, 10,  9,  9, 10, 10, 10, 10, 10, 10,  9,
       10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  9, 10, 10, 10,
        9, 10, 10, 10, 10, 10, 10, 10,  9, 10,  9, 10, 10, 10, 10, 10, 10,
       10, 10,  9, 10, 10, 10, 10, 10, 10,  9, 10,  9, 10, 10,  9, 10, 10,
        8, 10, 10, 10, 10, 10,  9, 10, 10,  9, 10,  9, 10,  9, 10, 10, 10,
       10, 10, 10, 10, 10, 10, 10,  9, 10, 10, 10, 10,  9, 10, 10, 10, 10,
       10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  9, 10, 10,
       10,  9, 10, 10,  9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
       10, 10, 10,  9, 10, 10, 10, 10,  9,  9,  9,  9, 10,  9, 10,  9, 10,
       10, 10, 10, 10, 10, 10, 10, 10, 10,  9, 10, 10, 10, 10,  9,  9,  9,
       10,  9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  9, 10,  9, 10, 10,
       10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  9, 10, 10,
       10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
       10, 10,  8, 10, 10, 10, 10, 10, 10, 10, 10,  9, 10, 10,  9,  9, 10,
       10, 10, 10, 10, 10,  9, 10,  9, 10, 10, 10, 10, 10, 10, 10, 10, 10,
       10, 10,  9, 10,  9,  9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
       10, 10, 10, 10, 10, 10, 10, 10, 10,  9, 10,  8, 10, 10, 10, 10, 10,
        9, 10, 10, 10,  9, 10,  9, 10, 10,  9, 10, 10, 10, 10, 10, 10, 10,
       10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  9, 10, 10, 10, 10,
        9, 10, 10,  9, 10, 10, 10,  9, 10,  9,  9, 10, 10, 10,  9, 10,  9,
       10, 10, 10,  9,  8,  9, 10,  8, 10, 10, 10,  9,  8, 10, 10, 10,  9,
       10,  9, 10, 10, 10, 10,  9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
       10, 10,  9, 10, 10, 10,  8, 10, 10, 10, 10,  9, 10,  9, 10,  9, 10,
       10, 10,  9, 10, 10, 10, 10,  9,  9, 10, 10, 10, 10, 10,  9, 10,  9,
       10, 10, 10, 10, 10,  9, 10, 10, 10, 10,  9, 10, 10, 10, 10, 10, 10,
        9, 10, 10, 10,  9, 10, 10, 10, 10, 10, 10,  9, 10,  9, 10,  9, 10,
       10,  9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  9, 10,  9,  9, 10,
       10, 10,  9,  8, 10, 10,  9, 10, 10, 10, 10, 10, 10, 10,  9,  8, 10,
        9, 10, 10, 10, 10, 10, 10, 10,  9, 10, 10, 10, 10, 10, 10, 10, 10,
       10, 10,  9, 10, 10,  9, 10, 10,  9,  9, 10,  9, 10, 10,  9, 10, 10,
       10,  9, 10, 10,  8, 10,  9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
        9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  9, 10,
       10, 10, 10,  9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
       10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  9, 10, 10, 10, 10, 10,
       10, 10, 10, 10, 10, 10, 10,  9, 10, 10, 10, 10, 10, 10, 10, 10, 10,
       10,  9, 10, 10, 10,  9, 10, 10, 10, 10, 10,  9, 10, 10, 10, 10, 10,
       10,  9, 10, 10, 10, 10, 10, 10, 10, 10, 10,  9,  9,  9, 10, 10, 10,
       10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  9, 10, 10, 10, 10, 10,
       10, 10, 10, 10, 10, 10, 10, 10, 10,  9,  9,  9, 10, 10])

例如,假设你有一个装有15颗白色弹珠和15颗黑色小球的弹珠。如果你随机抽取15颗弹珠,其中12颗或更多是一种颜色的可能性有多大?

s = rng.hypergeometric(15, 15, 15, 100000)
sum(s>=12)/100000. + sum(s<=3)/100000 
0.00281

连续性随机变量#

均匀分布#

uniform(low, high, size) 从均匀分布中抽取样本。它的参数包括: low (输出间隔的下限,默认值为 0。);high (输出间隔的上限,默认值为 1。);size (输出的形状)。

正态分布#

normal(loc, scale, size) 函数从正态(高斯)分布中抽取随机样本。 它的参数包括: loc (分布的平均值,默认值为 0。);scale (分布的标准差,必须非负,默认值为 1。);size (输出的形状)。

下面我们从分布中抽取样本:

np.random.default_rng()
s = rng.uniform(-1, 0, 1000)

接下来,我们随机生成一个正态分布的样本的 \(2 \times 4\) 序列,平均值为 3 ,标准差为 2.5 :

rng.normal(3, 2.5, size=(2, 4))
array([[ 0.19398645, -0.37285486,  2.20269266,  1.45086765],
       [-1.61830281,  3.02871741, -2.48018529,  4.03935919]])

指数分布#

exponential(scale, size) 函数从指数分布中抽取样本。 参数包括: scale (比例参数,必须非负,默认值为 1。);size (输出的形状)。

下面我们从分布中抽取样本:

s = rng.exponential(3, 1000)

接下来我们来解决一道数学应用题目: 假设一家公司有10000个客户支持代理,客户呼叫之间的平均时间是4分钟,客户在接下来的4到5分钟内打电话的概率有多大?

n = 10000
time_between_calls = rng.exponential(scale=4, size=n) #题目告知参数设置
x = ((time_between_calls < 5).sum())/n  #客户在5分钟内打电话的概率
y = ((time_between_calls < 4).sum())/n  #客户在4分钟内打电话的概率
x - y  #得到客户在4到5分钟内打电话的概率
0.08100000000000007

老版本随机数 API#

上面介绍了 NumPy 最新的随机数 API,较老的 API 没有随机数生成器和 default_rng() 方法。较老版本的随机数 API 又被成为 RandomState API。

均匀分布#

在 RandomState API 中,函数 rand(d0, d1, ..., dn) 是使用最多的随机数生成方法,其中 \(d_0 \sim d_n\) 用来设置数组的维度,最终生成一个 [0,1) 之间的 N 维浮点数组。

例如,随机生成维度为 \(2 \times 2 \times 3\) 的数组,服从 [0,1) 均匀分布。

np.random.rand(2, 2, 3)
array([[[0.7889164 , 0.15195504, 0.18090323],
        [0.9160419 , 0.66120841, 0.71033731]],

       [[0.62115291, 0.38081007, 0.04188282],
        [0.30355158, 0.11264447, 0.8451043 ]]])

正态分布#

randn(d0, d1, ..., dn) 方法生成的浮点数服从标准正态分布。

例如,随机生成维度为 \(2 \times 2 \times 3\) 的数组,服从标准正态分布。

np.random.randn(2, 2, 3)
array([[[-0.69116487, -0.71304809,  1.17545016],
        [ 2.94471983,  0.27195741, -0.40955671]],

       [[-0.46384838,  0.14182464, -1.18426511],
        [ 0.54771799,  0.30882418,  1.34024231]]])

数值范围#

如果想设置随机数生成的数值范围,可以使用 randint(low, high, size),数值范围为 \([low,high)\)

np.random.randint(low=100, high=400, size=(2,3,4))
array([[[336, 329, 345, 381],
        [204, 170, 303, 253],
        [381, 350, 237, 207]],

       [[377, 376, 128, 185],
        [336, 101, 219, 373],
        [278, 362, 336, 166]]])