1.4. 随机数生成#
numpy.random
模块用于生成随机数。
Show 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 作为随机种子值主要由于以下原因:
42 是一个著名的数字:在计算机科学和科幻文化中,数字 42 具有一定的象征意义。在道格拉斯 · 亚当斯的小说《银河系漫游指南》中,超级电脑 “深思照片” 认为生命、宇宙以及一切的答案是 42。因此,一些程序员选择 42 作为随机种子值,是一种幽默或致敬。
方便记忆: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]]])