ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

NumPy 随机数生成实战指南:基于 numpy-rng 掌握 Generator API 的采样、洗牌与分布

NumPy 随机数生成实战指南:基于 numpy-rng 掌握 Generator API 的采样、洗牌与分布
  • 示例工程
  • 教程

【免费下载链接】materials

Bonus materials, exercises, and example projects for our Python tutorials

项目地址:https://gitcode.com/gh_mirrors/ma/materials
点击查看免费下载

导读

本文以 numpy-rng 目录中的配套代码为线索,系统讲解 NumPy 伪随机数生成器(Pseudo-Random Number Generator,PRNG)的新一代GeneratorAPI 用法。从环境搭建、基础采样到多维数组生成、洗牌与排列、位生成器(BitGenerator)与种子管理,再到泊松分布建模,读者可以对照仓库中的 19 个源码文件,一步步掌握np.random.default_rng()的完整工作流,并理解其与旧版np.random.seed()体系的差异。读完本文,你将能够用 NumPy 生成可复现的随机整数、随机浮点数、随机抽样、打乱数组以及符合特定概率分布的样本,并借助 matplotlib 与 SciPy 验证统计结果。

环境准备:创建虚拟环境并安装依赖

numpy-rng/README.md 给出了标准的复现步骤:先创建并激活虚拟环境,再安装依赖。

$ python -m venv venv $ source venv/bin/activate (venv) $ python -m pip install -r requirements.txt -c constraints.txt

其中两份依赖文件分工明确:

  • numpy-rng/requirements.txt 声明了最小依赖集合:

    numpy>=1.17 scipy matplotlib

    numpy>=1.17是使用新版GeneratorAPI 的最低版本门槛,scipy与matplotlib则用于本文最后一节泊松分布的解析计算与绘图。

  • numpy-rng/constraints.txt 通过-c参数以约束(constraints)方式固定了经过测试的精确版本(如numpy==1.24.3、scipy==1.10.1、matplotlib==3.7.1)。约束文件只锁定版本而非声明直接依赖,因而在保持环境可复现的同时,不会覆盖其他包的传递依赖解析。

装好依赖后,每个示例文件都可以独立运行:

(venv) $ python filename.py

例如直接运行入门示例:

(venv) $ python default_rng.py

新版随机数入口:np.random.default_rng()

NumPy 1.17 起引入了基于Generator的新一代随机数体系,其推荐入口就是np.random.default_rng()。仓库的 numpy-rng/default_rng.py 是最简示例:

import numpy as np default_rng = np.random.default_rng() print(default_rng) print(default_rng.random())

运行后会先打印生成器对象信息(其中包含默认位生成器PCG64及随机种子),再输出一个[0, 1)区间的随机浮点数。

与旧版np.random全局函数式接口相比,default_rng()有两点核心差异:

  1. 对象式设计:每次调用default_rng()都会得到独立的Generator实例,其内部状态与全局np.random互不干扰,天然适合并发与多模块场景;
  2. 默认位生成器升级:默认使用PCG64(而不是旧版的MT19937梅森旋转),随机性统计质量更好,且支持流式并行。

Generator上的常用方法在旧接口中基本都能找到对应物,例如:

Generator方法对应旧接口用途
rng.integers()np.random.randint()随机整数
rng.random()np.random.random_sample()[0, 1)均匀浮点数
rng.uniform()np.random.uniform()指定区间的均匀分布
rng.choice()np.random.choice()从数组中随机抽取
rng.shuffle()np.random.shuffle()原地打乱数组
rng.permutation()np.random.permutation()返回打乱后的副本
rng.poisson()np.random.poisson()泊松分布采样

生成随机整数:integers()的边界语义

numpy-rng/integers.py 集中演示了integers()在不同参数下的行为:

import numpy as np rng = np.random.default_rng() for i in range(5): print(rng.integers(3)) # 等价于 rng.integers(low=0, high=3) for count in range(5): print(rng.integers(low=1, high=4)) for count in range(5): print(rng.integers(low=7)) for count in range(5): print(rng.integers(low=1, high=4, endpoint=True)) for count in range(5): print(rng.integers(low=7, endpoint=True))

关键参数语义如下:

  • low与high:采样区间为[low, high),包含low但不包含high(与旧版randint一致)。所以rng.integers(3)只可能返回0、1、2,rng.integers(low=1, high=4)只可能返回1、2、3;
  • 只传一个参数时,它被视为high,low默认取0,因此rng.integers(low=7)返回[0, 7)内的整数;
  • endpoint=True会把上界改为包含,即rng.integers(low=1, high=4, endpoint=True)的取值范围扩展为1、2、3、4,rng.integers(low=7, endpoint=True)则返回0~7共 8 个可能值。

此外integers()还支持size参数批量生成数组(参见 numpy-rng/array_constraints.py 中的rng.integers(size=(2, 2), low=1, high=5)),返回的是[1, 5)内均匀分布的2×2整数矩阵。

生成随机数组:random()与多维size

numpy-rng/arrays.py 展示了如何一次性生成任意形状的[0, 1)均匀浮点数数组:

import numpy as np rng = np.random.default_rng() print(rng.random(size=(5,))) print(rng.random(size=(5, 3))) print(rng.random(size=(3, 4, 2)))
  • size=(5,)返回长度为 5 的一维数组;
  • size=(5, 3)返回5×3的二维数组;
  • size=(3, 4, 2)返回三维数组,共3×4×2 = 24个样本。

size接受任意正整数形状元组,是构造随机数据、初始化权重矩阵(如神经网络参数)的常用手段。

指定区间的浮点数:uniform()

numpy-rng/uniform_for_floats.py 演示了如何把均匀分布限定到指定区间:

import numpy as np rng = np.random.default_rng() print(rng.uniform()) print(rng.uniform(low=3.4, high=5.6))
  • 不带参数时rng.uniform()等价于rng.random(),返回[0, 1)的浮点数;
  • rng.uniform(low=3.4, high=5.6)返回[3.4, 5.6)区间内均匀分布的浮点数。

与整数接口一致,uniform()也支持size参数,例如rng.uniform(size=(2, 2), low=1, high=5)(见 numpy-rng/array_constraints.py)会生成一个2×2的均匀浮点矩阵。

从数组中随机抽取:choice()与shuffle参数

一维数组的无放回抽样

numpy-rng/choice.py 演示了从一维数组中随机选择:

import numpy as np rng = np.random.default_rng() input_array_1d = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]) print(rng.choice(input_array_1d, size=3, replace=False)) print(rng.choice(input_array_1d, size=(2, 3), replace=False))
  • size=3, replace=False:无放回抽取 3 个互不重复的元素;
  • size=(2, 3), replace=False:抽取结果排列成2×3的形状(每个样本仍来自原数组的一次无放回选择,整体构成一个含 6 个不重复元素的抽样结果)。

replace=False要求size不超过数组长度;若想允许重复,则使用replace=True(默认行为)。

二维数组与axis、shuffle参数

numpy-rng/choice_shuffle.py 把案例扩展到二维数组,并展示了 NumPy 1.24 新增的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() print(rng.choice(input_array, size=2)) # 沿 axis=0 抽取 2 行 print(rng.choice(input_array, size=2, axis=1)) # 沿 axis=1 抽取 2 列 print(rng.choice(input_array, size=3, replace=False)) # 无放回抽取 3 行 rng = np.random.default_rng(seed=100) print(rng.choice(input_array, size=3, replace=False, shuffle=False)) rng = np.random.default_rng(seed=100) print(rng.choice(input_array, size=3, replace=False, shuffle=True)) rng = np.random.default_rng(seed=100) print(rng.choice(input_array, size=3, replace=True, shuffle=False)) rng = np.random.default_rng(seed=100) print(rng.choice(input_array, size=3, replace=True, shuffle=True))

要点说明:

  • 默认沿axis=0抽取(对二维数组即抽取整行),axis=1则沿列方向抽取;
  • shuffle参数控制抽样结果的排列方式:shuffle=True时抽样结果在内部被打乱后再返回,shuffle=False时则保持抽取顺序。两种模式下样本集合相同,只是返回顺序不同;
  • 从源码可以看出,相同seed=100下分别用shuffle=False与shuffle=True调用,打印结果会呈现"集合相同、顺序不同"的差异,这正是该参数存在的意义——当后续步骤依赖抽样结果的原始顺序时,应使用shuffle=False。

打乱与排列:shuffle()、permutation()与permuted()

用扑克牌案例理解三种方法

仓库用扑克牌数据构造了直观的演示场景。numpy-rng/decks.py 提供两个辅助函数:

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]) def create_high_cards(): HIGH_CARDS = "10 J Q K A".split() SUITS = "♣ ♢ ♡ ♠".split() return np.array([r + s for s in SUITS for r in HIGH_CARDS])

create_deck()生成一副 52 张牌的数组(按花色排列),create_high_cards()则生成只含10 J Q K A的 20 张"高牌"数组。

shuffle():原地打乱

numpy-rng/shuffle.py 演示原地洗牌:

import numpy as np from decks import create_deck rng = np.random.default_rng() deck_of_cards = create_deck() rng.shuffle(deck_of_cards) print(deck_of_cards[0:3]) rng.shuffle(deck_of_cards) print(deck_of_cards[0:3])

rng.shuffle()会直接修改传入数组(原地操作,返回None),连续调用两次洗牌,每次牌面顺序都不同。注意:旧版np.random.shuffle()只支持一维数组,而新版Generator.shuffle()通过axis参数支持多维数组,例如 numpy-rng/permutation_vs_shuffle.py 中的rng.shuffle(high_deck, axis=0)与rng.shuffle(high_deck, axis=1)分别沿行、列方向原地打乱。

permutation():返回打乱后的副本

numpy-rng/permutation_vs_shuffle.py 对照演示了permutation()与shuffle()的关键差异:

import numpy as np from decks import create_high_cards NUMBER_OF_SUITES = 4 NUMBER_OF_RANKS = 5 rng = np.random.default_rng() high_deck = create_high_cards().reshape((NUMBER_OF_SUITES, NUMBER_OF_RANKS)) print(rng.permutation(high_deck, axis=0)) # 打乱行,不修改原数组 print(rng.permutation(high_deck, axis=0)) print(rng.permutation(high_deck, axis=1)) # 打乱列,不修改原数组 print(rng.permutation(high_deck, axis=1)) print(high_deck) # 原数组保持不变 rng.shuffle(high_deck, axis=0) # 原地打乱行 print(high_deck) rng.shuffle(high_deck, axis=1) # 原地打乱列 print(high_deck)

(其中high_deck是一个4×5的"高牌"矩阵,每行一个花色、每列一个点数,构造过程见 numpy-rng/high_deck_example.py。)

对比结论:

  • permutation()返回打乱后的新数组副本,原数组保持不变(源码中连续调用多次后打印high_deck,其内容始终未变);
  • shuffle()是原地操作,直接改写原数组;
  • 两者都支持axis指定打乱方向:axis=0打乱行顺序,axis=1打乱列顺序。

permuted():独立洗刷每一轴

numpy-rng/permuted.py 介绍 NumPy 1.20 引入的permuted():

import numpy as np from decks import create_high_cards NUMBER_OF_SUITES = 4 NUMBER_OF_RANKS = 5 high_deck = create_high_cards().reshape((NUMBER_OF_SUITES, NUMBER_OF_RANKS)) rng = np.random.default_rng() print(rng.permuted(high_deck, axis=0)) # 逐列独立打乱行序 print(rng.permuted(high_deck, axis=1)) # 逐行独立打乱列序 print(rng.permuted(rng.permuted(high_deck, axis=1), axis=0)) # 先列后行

与shuffle()、permutation()的关键区别在于:

  • permuted()是沿指定轴逐切片独立打乱。对axis=0,它会取出每一列,把这一列的 4 行各自独立地重新排列,各列之间互不影响;
  • 这与shuffle(axis=0)(整行作为一个整体参与打乱,行内元素保持相对顺序)形成鲜明对比——后者打乱的是"行的排列",前者打乱的是"每一列内部的行序"。

位生成器(BitGenerator)与种子管理

显式构造:Generator+ 位生成器

新架构把"随机数引擎"(位生成器)与"分布采样"(Generator)分离。默认情况下default_rng()使用PCG64位生成器,而 numpy-rng/generator_swap_and_seeds.py 演示了显式组合方式:

from numpy.random import PCG64DXSM, Generator pcg64dxsm_rng = Generator(PCG64DXSM()) print(pcg64dxsm_rng.random()) rng1 = Generator(PCG64DXSM(seed=100)) print(rng1.random()) print(rng1.random()) rng2 = Generator(PCG64DXSM(seed=100)) print(rng2.random()) print(rng2.random())

要点:

  • PCG64DXSM是PCG64的改进版本(使用 DXSM 乘法),提供更高质量的随机位输出;
  • 两个seed=100的Generator实例会产出完全相同的随机数序列,这正是可复现实验的关键:只要种子一致,结果就可复现;
  • 位生成器决定底层随机位,Generator决定如何把随机位映射为各分布样本,因此更换位生成器(如换成MT19937、Philox或SFC64)无需改动上层采样代码。

通过array_constraints.py认识多维约束

numpy-rng/array_constraints.py 同时使用了显式Generator(PCG64DXSM())与size参数:

from numpy.random import PCG64DXSM, Generator rng = Generator(PCG64DXSM()) print(rng.integers(size=(2, 2), low=1, high=5)) print(rng.uniform(size=(2, 2), low=1, high=5))

这展示了位生成器 +Generator组合与旧式np.random全局接口在 API 上完全一致的使用体验,且支持批量形状参数。

泊松分布:从理论公式到随机采样

手工计算概率:cars_per_minute.py

numpy-rng/cars_per_minute.py 先用泊松分布的概率质量函数手工验证:

import math lam = 4 cars_per_minute = [0, 4, 8] for cars in cars_per_minute: probability = lam**cars * math.exp(-lam) / math.factorial(cars) print(f"P({cars}) = {probability:.1%}")

场景设定为"平均每分钟有 4 辆车通过"(即lam = 4),公式P(k) = λᵏ·e⁻λ / k!给出每分钟恰好出现k辆车的概率:P(0) ≈ 1.8%,P(4) ≈ 19.5%,P(8) ≈ 2.9%。这个手算结果正是后续随机采样的理论参照。

随机采样:poisson.py

numpy-rng/poisson.py 用Generator.poisson()生成泊松样本:

import numpy as np rng = np.random.default_rng() scalar = rng.poisson(lam=5) print(scalar) sample_1d_array = rng.poisson(lam=5, size=4) print(sample_1d_array) sample_2d_array = rng.poisson(lam=5, size=(2, 3)) print(sample_2d_array)
  • lam是泊松分布的均值参数(λ),也等于其方差;
  • 不传size返回标量,size=4返回一维样本数组,size=(2, 3)返回二维样本矩阵;
  • 每个样本都是非负整数,围绕lam波动,且分布越接近理论 PMF。

可视化验证:随机样本 vs 理论分布

仓库提供了两个绘图脚本,从两个角度交叉验证泊松分布:

numpy-rng/random_poisson_plot.py 对rng.poisson(lam=5, size=10_000)抽出的 10 000 个样本做频数统计并绘图:

import matplotlib.pyplot as plt import numpy as np rng = np.random.default_rng() samples = rng.poisson(lam=5, size=10_000) values, frequency = np.unique(samples, return_counts=True) plt.title("Random Poisson Distribution.") plt.xlabel("Values") plt.ylabel("Frequency") plt.plot(values, frequency, "ro") plt.show()

numpy-rng/sample_poisson_plot.py 则用 SciPy 按理论公式计算λ = 4时k = 0…29的概率并绘图:

import matplotlib.pyplot as plt import numpy as np from scipy.special import factorial lam = 4 k_values = np.arange(0, 30) probabilities = np.power(lam, k_values) * np.exp(-lam) / factorial(k_values) plt.plot(k_values, probabilities, "ro") plt.title("Sample Poisson Distribution.") plt.xlabel("k") plt.ylabel("P(k)") plt.show()

运行这两个脚本可以看到:随机采样的直方图形状与理论概率曲线高度吻合——这正是"随机数生成器忠实服从目标分布"的可视化证据,也解释了 README 中"在教程最后部分才会用到 matplotlib 和 scipy"的原因。

文件清单与运行路线

本节梳理 numpy-rng 目录的全部源码,便于按学习路径逐个运行:

文件主题运行方式
default_rng.py新版随机数入口python default_rng.py
integers.py随机整数与边界语义python integers.py
arrays.py多维随机数组python arrays.py
uniform_for_floats.py指定区间均匀浮点数python uniform_for_floats.py
choice.py一维数组抽样python choice.py
choice_shuffle.pychoice的axis与shuffle参数python choice_shuffle.py
shuffle.py原地洗牌python shuffle.py
permutation_vs_shuffle.py副本排列 vs 原地打乱python permutation_vs_shuffle.py
permuted.py逐轴独立重排python permuted.py
decks.py扑克牌数据辅助函数python decks.py
high_deck_example.py高牌矩阵构造python high_deck_example.py
generator_swap_and_seeds.py位生成器与种子复现python generator_swap_and_seeds.py
array_constraints.py显式Generator与多维采样python array_constraints.py
cars_per_minute.py泊松 PMF 手算python cars_per_minute.py
poisson.py泊松随机采样python poisson.py
random_poisson_plot.py随机样本频数图python random_poisson_plot.py
sample_poisson_plot.py理论 PMF 曲线图python sample_poisson_plot.py

建议按"入门 → 整数/浮点 → 抽样 → 洗牌/排列 → 种子与位生成器 → 泊松分布"的顺序依次运行,与教程正文对照理解。

小结

通过 numpy-rng 目录的配套代码,本文完整覆盖了 NumPy 新一代随机数生成的核心知识面:

  1. 入口统一:始终从np.random.default_rng()创建Generator,获得与全局状态隔离、可复现的随机源;
  2. 边界与形状:integers()遵循[low, high)(endpoint=True可含上界),random()、uniform()、poisson()均支持size生成任意形状的样本数组;
  3. 三种打乱语义:shuffle()原地修改、permutation()返回副本、permuted()沿轴逐切片独立重排,三者在多维场景下行为差异明显,需按数据形态选择;
  4. 种子与位生成器:相同种子产出完全一致的序列;Generator与PCG64DXSM等位生成器可灵活组合,实现"换引擎不改采样代码";
  5. 分布验证闭环:以泊松分布为例,通过手算 PMF(cars_per_minute.py)、随机采样(poisson.py)与双绘图脚本交叉验证,形成从理论到实证的完整方法论。

掌握这套GeneratorAPI,你便能在模拟、抽样、数据增强、蒙特卡洛实验等场景中写出既高效又可复现的随机逻辑。

  • 示例工程
  • 教程

【免费下载链接】materials

Bonus materials, exercises, and example projects for our Python tutorials

项目地址:https://gitcode.com/gh_mirrors/ma/materials
点击查看免费下载
上一篇:TypeORM迁移避坑指南:解决90%开发者会遇到的问题
下一篇:Bluebird `.mapSeries` 与 `Promise.mapSeries`:串行映射的源码级深度解析

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进