NumPy 随机数生成
随机数生成是数值计算、模拟仿真和机器学习中的重要组成部分。NumPy 提供了功能强大的随机数生成模块 numpy.random,支持多种概率分布和采样方法。本章将详细介绍 NumPy 随机数生成的使用方法和最佳实践。
Generator:新一代随机数生成器
从 NumPy 1.17 开始,推荐使用新的 Generator API。相比旧的 RandomState,Generator 具有更好的统计特性和更高的性能。
创建 Generator
使用 default_rng 函数创建 Generator 实例:
import numpy as np
# 创建默认的随机数生成器
rng = np.random.default_rng()
print(f"Generator 类型: {type(rng)}")
# 使用种子创建可复现的生成器
rng = np.random.default_rng(seed=42)
print(f"带种子的 Generator: {rng}")
为什么使用种子?
种子决定了随机数序列的起点。使用相同的种子可以保证结果可复现,这在调试、测试和科学研究中非常重要。
import numpy as np
# 相同种子产生相同的随机数序列
rng1 = np.random.default_rng(seed=42)
rng2 = np.random.default_rng(seed=42)
print(f"rng1 第一个随机数: {rng1.random()}")
print(f"rng2 第一个随机数: {rng2.random()}")
# 两者输出相同
# 不使用种子,每次运行结果不同
rng3 = np.random.default_rng()
rng4 = np.random.default_rng()
print(f"\n不同种子:")
print(f"rng3: {rng3.random()}")
print(f"rng4: {rng4.random()}")
推荐的种子生成方式
对于需要高质量随机性的场景,可以使用 secrets 模块生成种子:
import numpy as np
import secrets
# 生成一个 128 位的随机种子
seed = secrets.randbits(128)
print(f"随机种子: {seed}")
rng = np.random.default_rng(seed)
print(f"随机数: {rng.random()}")
基本随机数生成
浮点数随机数
random 方法生成 [0, 1) 区间内均匀分布的浮点数:
import numpy as np
rng = np.random.default_rng(seed=42)
# 生成单个随机浮点数
single = rng.random()
print(f"单个随机数: {single}")
# 生成指定数量的随机数
arr = rng.random(5)
print(f"5 个随机数: {arr}")
# 生成指定形状的随机数组
matrix = rng.random((3, 4))
print(f"3x4 随机矩阵:\n{matrix}")
整数随机数
integers 方法生成指定范围内的随机整数:
import numpy as np
rng = np.random.default_rng(seed=42)
# 生成 [0, 10) 区间的随机整数(不包含 10)
single_int = rng.integers(0, 10)
print(f"单个整数: {single_int}")
# 生成多个随机整数
arr_int = rng.integers(0, 10, size=5)
print(f"5 个整数: {arr_int}")
# 只指定上限,默认下限为 0
arr_int2 = rng.integers(10, size=5)
print(f"[0, 10) 的 5 个整数: {arr_int2}")
# 包含上限(endpoint=True)
arr_int3 = rng.integers(0, 5, size=10, endpoint=True)
print(f"[0, 5] 包含端点: {arr_int3}")
# 多维数组
matrix_int = rng.integers(0, 100, size=(3, 4))
print(f"3x4 整数矩阵:\n{matrix_int}")
从数组中随机选择
choice 方法从给定的一维数组中随机采样:
import numpy as np
rng = np.random.default_rng(seed=42)
# 从列表中随机选择
arr = np.array([10, 20, 30, 40, 50])
single_choice = rng.choice(arr)
print(f"单个选择: {single_choice}")
# 选择多个元素(可重复)
multi_choice = rng.choice(arr, size=5)
print(f"多次选择(可重复): {multi_choice}")
# 不重复选择
unique_choice = rng.choice(arr, size=3, replace=False)
print(f"不重复选择: {unique_choice}")
# 带概率权重的选择
probabilities = [0.1, 0.1, 0.1, 0.1, 0.6] # 50 被选中的概率最大
weighted_choice = rng.choice(arr, size=10, p=probabilities)
print(f"加权选择: {weighted_choice}")
随机字节
bytes 方法生成随机字节:
import numpy as np
rng = np.random.default_rng(seed=42)
# 生成随机字节
random_bytes = rng.bytes(10)
print(f"10 个随机字节: {random_bytes}")
均匀分布
均匀分布是最基本的概率分布,所有值在指定区间内出现的概率相等。
连续均匀分布
uniform 方法生成指定区间内的均匀分布随机数:
import numpy as np
rng = np.random.default_rng(seed=42)
# [low, high) 区间的均匀分布
uniform_samples = rng.uniform(0, 10, size=10)
print(f"[0, 10) 均匀分布: {uniform_samples}")
# 默认是 [0, 1),与 random() 类似
uniform_default = rng.uniform(size=5)
print(f"[0, 1) 均匀分布: {uniform_default}")
# 验证分布特性
large_samples = rng.uniform(0, 1, size=100000)
print(f"\n100000 个样本统计:")
print(f"均值(理论值 0.5): {np.mean(large_samples):.4f}")
print(f"标准差(理论值 {np.sqrt(1/12):.4f}): {np.std(large_samples):.4f}")
离散均匀分布
使用 integers 方法生成离散均匀分布:
import numpy as np
rng = np.random.default_rng(seed=42)
# 模拟掷骰子(1-6)
dice_rolls = rng.integers(1, 7, size=100)
print(f"100 次骰子投掷: {dice_rolls[:20]}...")
# 统计各点数出现次数
unique, counts = np.unique(dice_rolls, return_counts=True)
print(f"\n点数分布:")
for u, c in zip(unique, counts):
print(f" {u}: {c} 次 ({c/100:.1%})")
正态分布
正态分布(高斯分布)是最重要的概率分布,在自然科学和社会科学中广泛应用。
标准正态分布
standard_normal 方法生成标准正态分布(均值=0,标准差=1):
import numpy as np
rng = np.random.default_rng(seed=42)
# 标准正态分布
standard_normal = rng.standard_normal(10)
print(f"标准正态分布: {standard_normal}")
# 验证统计特性
large_samples = rng.standard_normal(100000)
print(f"\n100000 个样本统计:")
print(f"均值(理论值 0): {np.mean(large_samples):.4f}")
print(f"标准差(理论值 1): {np.std(large_samples):.4f}")
一般正态分布
normal 方法可以指定均值和标准差:
import numpy as np
rng = np.random.default_rng(seed=42)
# 指定均值和标准差
# normal(loc=均值, scale=标准差, size=形状)
normal_samples = rng.normal(loc=50, scale=10, size=10)
print(f"均值50,标准差10的正态分布: {normal_samples}")
# 模拟身高分布(假设平均身高 170cm,标准差 8cm)
heights = rng.normal(loc=170, scale=8, size=1000)
print(f"\n1000 人身高统计:")
print(f"平均身高: {np.mean(heights):.1f} cm")
print(f"最高: {np.max(heights):.1f} cm")
print(f"最矮: {np.min(heights):.1f} cm")
print(f"95% 置信区间: [{np.percentile(heights, 2.5):.1f}, {np.percentile(heights, 97.5):.1f}] cm")
对数正态分布
当变量的对数服从正态分布时,使用对数正态分布:
import numpy as np
rng = np.random.default_rng(seed=42)
# 对数正态分布
# lognormal(mean=对数均值, sigma=对数标准差)
lognormal_samples = rng.lognormal(mean=0, sigma=1, size=10)
print(f"对数正态分布样本: {lognormal_samples}")
# 对数正态分布常用于模拟收入、价格等正偏态数据
incomes = rng.lognormal(mean=10.5, sigma=0.5, size=1000)
print(f"\n模拟收入分布:")
print(f"中位数收入: {np.median(incomes):.0f}")
print(f"平均收入: {np.mean(incomes):.0f}")
其他常用分布
指数分布
指数分布常用于模拟等待时间:
import numpy as np
rng = np.random.default_rng(seed=42)
# 指数分布(scale = 1/λ,即平均等待时间)
# 模拟顾客到达的平均等待时间为 5 分钟
wait_times = rng.exponential(scale=5, size=1000)
print(f"平均等待时间: {np.mean(wait_times):.2f} 分钟")
print(f"标准差: {np.std(wait_times):.2f} 分钟")
print(f"最大等待时间: {np.max(wait_times):.2f} 分钟")
泊松分布
泊松分布用于模拟单位时间内事件发生的次数:
import numpy as np
rng = np.random.default_rng(seed=42)
# 泊松分布(lam = 平均发生次数)
# 模拟每小时平均接到 5 个电话
calls_per_hour = rng.poisson(lam=5, size=24) # 24 小时
print(f"24 小时每小时电话数: {calls_per_hour}")
print(f"总电话数: {np.sum(calls_per_hour)}")
# 二项分布:n 次试验中成功的次数
# 模拟抛 10 次硬币,正面朝上的次数(重复 1000 次)
coin_flips = rng.binomial(n=10, p=0.5, size=1000)
print(f"\n10 次抛硬币中正面次数分布:")
unique, counts = np.unique(coin_flips, return_counts=True)
for u, c in zip(unique, counts):
print(f" {u} 次: {c} 次 ({c/1000:.1%})")
Beta 分布
Beta 分布常用于贝叶斯统计,表示概率的概率分布:
import numpy as np
rng = np.random.default_rng(seed=42)
# Beta 分布在 [0, 1] 区间
# 参数 a, b 控制分布形状
# a=1, b=1: 均匀分布
# a > b: 左偏
# a < b: 右偏
# a = b: 对称
# 模拟转化率的先验分布
# 假设之前观察到 80 次成功,20 次失败
conversion_rates = rng.beta(a=81, b=21, size=10)
print(f"转化率样本: {conversion_rates}")
print(f"平均转化率: {np.mean(conversion_rates):.4f}")
Gamma 分布
Gamma 分布是指数分布的推广:
import numpy as np
rng = np.random.default_rng(seed=42)
# Gamma 分布
# shape (k): 形状参数
# scale (θ): 尺度参数
# 均值 = k * θ
gamma_samples = rng.gamma(shape=2, scale=2, size=10)
print(f"Gamma 分布样本: {gamma_samples}")
print(f"理论均值: {2 * 2}")
print(f"实际均值: {np.mean(gamma_samples):.2f}")
卡方分布
卡方分布广泛用于假设检验和置信区间估计:
import numpy as np
rng = np.random.default_rng(seed=42)
# 卡方分布(df = 自由度)
# 常用于假设检验
chi2_samples = rng.chisquare(df=5, size=10)
print(f"卡方分布样本 (df=5): {chi2_samples}")
# 验证:卡方分布的均值 = df
large_samples = rng.chisquare(df=10, size=100000)
print(f"\ndf=10 的卡方分布:")
print(f"均值(理论值 10): {np.mean(large_samples):.4f}")
print(f"方差(理论值 20): {np.var(large_samples):.4f}")
F 分布
F 分布用于比较两个方差的比值,在方差分析(ANOVA)中常用:
import numpy as np
rng = np.random.default_rng(seed=42)
# F 分布(dfnum = 分子自由度, dfden = 分母自由度)
f_samples = rng.f(dfnum=5, dfden=10, size=10)
print(f"F 分布样本 (df1=5, df2=10): {f_samples}")
# 应用:ANOVA 中的 F 检验模拟
# 模拟 F 统计量的分布
f_stats = rng.f(dfnum=3, dfden=27, size=10000)
critical_value = np.percentile(f_stats, 95)
print(f"\nF(3, 27) 分布 95% 分位点: {critical_value:.4f}")
威布尔分布
威布尔分布常用于可靠性分析和寿命测试:
import numpy as np
rng = np.random.default_rng(seed=42)
# 威布尔分布(a = 形状参数)
# a < 1: 失效率递减(早期失效)
# a = 1: 失效率恒定(随机失效,即指数分布)
# a > 1: 失效率递增(磨损失效)
# 形状参数 a = 1.5(失效率递增,模拟设备老化)
weibull_samples = rng.weibull(a=1.5, size=10)
print(f"威布尔分布样本 (a=1.5): {weibull_samples}")
# 应用:设备寿命模拟
device_lifetimes = rng.weibull(a=2.0, size=1000) * 1000 # 单位:小时
print(f"\n设备寿命统计:")
print(f"平均寿命: {np.mean(device_lifetimes):.0f} 小时")
print(f"中位数寿命: {np.median(device_lifetimes):.0f} 小时")
print(f"可靠度 90% 对应时间: {np.percentile(device_lifetimes, 10):.0f} 小时")
负二项分布
负二项分布用于计算达到指定成功次数所需的试验次数:
import numpy as np
rng = np.random.default_rng(seed=42)
# 负二项分布(n = 成功次数, p = 成功概率)
# 返回达到 n 次成功所需的失败次数
# 模拟销售电话:平均需要打多少个失败电话才能获得 5 个客户
# 假设成功率 p = 0.2
failed_calls = rng.negative_binomial(n=5, p=0.2, size=10)
total_calls = failed_calls + 5 # 加上成功的 5 次
print(f"获得 5 个客户所需的总电话数: {total_calls}")
# 大样本统计
all_calls = rng.negative_binomial(n=5, p=0.2, size=10000) + 5
print(f"\n获得 5 个客户所需电话数统计:")
print(f"平均: {np.mean(all_calls):.1f} 次")
print(f"理论均值: {5/0.2:.1f} 次")
多项分布
多项分布是二项分布的推广,用于多种可能结果的试验:
import numpy as np
rng = np.random.default_rng(seed=42)
# 多项分布(n = 试验次数, pvals = 各结果的概率)
# 模拟掷骰子 10 次
dice_probs = [1/6] * 6 # 每个面概率相等
results = rng.multinomial(n=10, pvals=dice_probs)
print(f"掷骰子 10 次各点数出现次数: {results}")
print(f"验证总和: {sum(results)}")
# 应用:市场调研模拟
# 4 个品牌的市场份额调查
market_share = [0.35, 0.30, 0.20, 0.15] # 品牌 A, B, C, D 的市场份额
survey = rng.multinomial(n=1000, pvals=market_share)
print(f"\n1000 人调查结果:")
brands = ['品牌A', '品牌B', '品牌C', '品牌D']
for brand, count in zip(brands, survey):
print(f" {brand}: {count} 人 ({count/10:.1f}%)")
三角分布
三角分布适用于只知道最小值、最大值和最可能值的情况:
import numpy as np
rng = np.random.default_rng(seed=42)
# 三角分布(left = 最小值, mode = 众数, right = 最大值)
# 项目工期估计:最少 5 天,最可能 8 天,最多 15 天
durations = rng.triangular(left=5, mode=8, right=15, size=10)
print(f"项目工期估计(天): {durations}")
# 大样本分析
all_durations = rng.triangular(left=5, mode=8, right=15, size=10000)
print(f"\n工期估计统计:")
print(f"均值: {np.mean(all_durations):.1f} 天")
print(f"中位数: {np.median(all_durations):.1f} 天")
print(f"80% 完工时间: {np.percentile(all_durations, 80):.1f} 天")
几何分布
几何分布计算首次成功所需的试验次数:
import numpy as np
rng = np.random.default_rng(seed=42)
# 几何分布(p = 成功概率)
# 返回首次成功前的失败次数
# 模拟销售电话:需要打多少次才能成交(成功率 20%)
failures = rng.geometric(p=0.2, size=10)
print(f"首次成交所需的电话次数: {failures}")
# 验证:几何分布的均值 = 1/p
large_samples = rng.geometric(p=0.2, size=10000)
print(f"\n统计:")
print(f"平均电话次数: {np.mean(large_samples):.1f}")
print(f"理论均值: {1/0.2:.1f}")
拉普拉斯分布
拉普拉斯分布比正态分布有更尖锐的峰值和更重的尾部:
import numpy as np
rng = np.random.default_rng(seed=42)
# 拉普拉斯分布(loc = 位置参数, scale = 尺度参数)
laplace_samples = rng.laplace(loc=0, scale=1, size=10)
print(f"拉普拉斯分布样本: {laplace_samples}")
# 对比拉普拉斯分布和正态分布
laplace_data = rng.laplace(loc=0, scale=1, size=10000)
normal_data = rng.normal(loc=0, scale=1, size=10000)
print(f"\n分布对比(10000 样本):")
print(f"拉普拉斯 - 峰度: {np.mean((laplace_data - np.mean(laplace_data))**4) / np.var(laplace_data)**2:.2f}")
print(f"正态 - 峰度: {np.mean((normal_data - np.mean(normal_data))**4) / np.var(normal_data)**2:.2f}")
# 拉普拉斯分布的峰度大于正态分布(3),表示更尖锐的峰值
多元正态分布
生成相关的多维随机变量:
import numpy as np
rng = np.random.default_rng(seed=42)
# 多元正态分布
mean = [0, 0] # 均值向量
cov = [[1, 0.8], # 协方差矩阵
[0.8, 1]]
# 生成二维正态分布样本
samples = rng.multivariate_normal(mean, cov, size=1000)
print(f"样本形状: {samples.shape}")
# 计算相关系数
correlation = np.corrcoef(samples[:, 0], samples[:, 1])[0, 1]
print(f"生成数据的相关系数: {correlation:.4f}")
print(f"理论相关系数: 0.8")
随机排列与洗牌
shuffle:原地洗牌
shuffle 方法直接修改原数组:
import numpy as np
rng = np.random.default_rng(seed=42)
# 一维数组洗牌
arr = np.arange(10)
print(f"原数组: {arr}")
rng.shuffle(arr)
print(f"洗牌后: {arr}")
# 多维数组:默认按第一个轴洗牌
arr_2d = np.arange(12).reshape(3, 4)
print(f"\n原二维数组:\n{arr_2d}")
rng.shuffle(arr_2d)
print(f"洗牌后(按行):\n{arr_2d}")
# 洗牌 Python 列表
my_list = ['A', 'B', 'C', 'D', 'E']
rng.shuffle(my_list)
print(f"\n列表洗牌后: {my_list}")
permutation:返回排列副本
permutation 方法不修改原数组,返回新的排列:
import numpy as np
rng = np.random.default_rng(seed=42)
# 一维数组
arr = np.arange(10)
permuted = rng.permutation(arr)
print(f"原数组: {arr}")
print(f"排列后: {permuted}")
print(f"原数组未变: {arr}")
# 多维数组按轴排列
arr_2d = np.arange(12).reshape(3, 4)
permuted_2d = rng.permutation(arr_2d, axis=0)
print(f"\n按行排列:\n{permuted_2d}")
permuted_2d_col = rng.permutation(arr_2d, axis=1)
print(f"\n按列排列:\n{permuted_2d_col}")
permuted:独立排列各切片
permuted 方法可以独立地排列每个切片:
import numpy as np
rng = np.random.default_rng(seed=42)
arr = np.arange(12).reshape(3, 4)
print(f"原数组:\n{arr}")
# 每行独立洗牌
permuted = rng.permuted(arr, axis=1)
print(f"\n每行独立洗牌:\n{permuted}")
# 原地操作
rng.permuted(arr, axis=1, out=arr)
print(f"\n原地洗牌后:\n{arr}")
三种排列方法的区别
import numpy as np
rng = np.random.default_rng(seed=42)
arr = np.arange(12).reshape(3, 4)
print("=== 三种排列方法对比 ===")
print(f"原数组:\n{arr}")
# shuffle: 原地修改,按第一轴整体洗牌
arr1 = arr.copy()
rng.shuffle(arr1)
print(f"\nshuffle: 原地,整体洗牌\n{arr1}")
# permutation: 返回副本,按指定轴整体洗牌
arr2 = rng.permutation(arr, axis=1)
print(f"\npermutation: 副本,整体洗牌(按列)\n{arr2}")
# permuted: 可以原地或副本,每切片独立洗牌
arr3 = rng.permuted(arr, axis=1)
print(f"\npermuted: 每行独立洗牌\n{arr3}")
并行随机数生成
在并行计算中,需要确保各个进程的随机数序列相互独立:
import numpy as np
# 使用 SeedSequence 创建独立的随机数生成器
seed = 42
ss = np.random.SeedSequence(seed)
# 创建多个独立的子种子
child_seeds = ss.spawn(4) # 生成 4 个独立的种子
# 为每个子种子创建独立的 Generator
generators = [np.random.default_rng(s) for s in child_seeds]
# 每个生成器产生独立的随机数序列
for i, rng in enumerate(generators):
print(f"Generator {i}: {rng.random(3)}")
实战示例
示例1:模拟股票价格走势
使用几何布朗运动模拟股票价格:
import numpy as np
def simulate_stock_price(S0, mu, sigma, T, dt, n_simulations):
"""
模拟股票价格走势
参数:
S0: 初始价格
mu: 预期收益率
sigma: 波动率
T: 总时间(年)
dt: 时间步长
n_simulations: 模拟次数
"""
rng = np.random.default_rng(seed=42)
n_steps = int(T / dt)
# 生成随机增量
Z = rng.standard_normal((n_simulations, n_steps))
# 计算价格路径
drift = (mu - 0.5 * sigma ** 2) * dt
diffusion = sigma * np.sqrt(dt) * Z
# 累积对数收益
log_returns = drift + diffusion
log_prices = np.log(S0) + np.cumsum(log_returns, axis=1)
prices = np.exp(log_prices)
# 添加初始价格
prices = np.column_stack([np.full(n_simulations, S0), prices])
return prices
# 模拟参数
S0 = 100 # 初始价格
mu = 0.1 # 年化收益率 10%
sigma = 0.2 # 年化波动率 20%
T = 1.0 # 1 年
dt = 1/252 # 日频
prices = simulate_stock_price(S0, mu, sigma, T, dt, 5)
print("股票价格模拟(5 条路径):")
print(f"初始价格: {prices[0, 0]:.2f}")
print(f"最终价格范围: [{prices[:, -1].min():.2f}, {prices[:, -1].max():.2f}]")
示例2:蒙特卡洛积分
使用随机采样估计积分值:
import numpy as np
def monte_carlo_integral(func, a, b, n_samples):
"""
使用蒙特卡洛方法估计积分
∫[a,b] f(x) dx ≈ (b-a) * mean(f(X))
"""
rng = np.random.default_rng(seed=42)
# 在 [a, b] 区间均匀采样
samples = rng.uniform(a, b, n_samples)
# 计算函数值
values = func(samples)
# 积分估计
integral = (b - a) * np.mean(values)
# 标准误差
std_error = (b - a) * np.std(values) / np.sqrt(n_samples)
return integral, std_error
# 估计 ∫[0, π] sin(x) dx = 2
integral, error = monte_carlo_integral(np.sin, 0, np.pi, 100000)
print(f"估计值: {integral:.6f}")
print(f"真实值: {2.0:.6f}")
print(f"误差: {abs(integral - 2):.6f}")
print(f"标准误差: {error:.6f}")
示例3:数据增强
在机器学习中使用随机采样进行数据增强:
import numpy as np
def augment_data(X, noise_level=0.1, n_augment=5):
"""
对数据进行随机增强
参数:
X: 原始数据 (n_samples, n_features)
noise_level: 噪声强度
n_augment: 每个样本增强的次数
"""
rng = np.random.default_rng(seed=42)
n_samples, n_features = X.shape
augmented = []
for _ in range(n_augment):
# 添加高斯噪声
noise = rng.normal(0, noise_level, X.shape)
augmented.append(X + noise)
# 合并原始数据和增强数据
return np.vstack([X] + augmented)
# 模拟数据
X = np.array([[1, 2], [3, 4], [5, 6]])
print(f"原始数据:\n{X}")
X_augmented = augment_data(X, noise_level=0.1, n_augment=2)
print(f"\n增强后数据形状: {X_augmented.shape}")
print(f"增强后数据:\n{X_augmented}")
示例4:随机采样模拟
模拟抽样调查:
import numpy as np
rng = np.random.default_rng(seed=42)
# 总体(假设 10000 人)
population_size = 10000
support_rate = 0.52 # 真实支持率
population = rng.binomial(1, support_rate, population_size)
print(f"总体支持率: {population.mean():.4f}")
# 不同样本量的抽样
for sample_size in [100, 500, 1000, 5000]:
sample = rng.choice(population, size=sample_size, replace=False)
sample_rate = sample.mean()
margin_error = 1.96 * np.sqrt(sample_rate * (1 - sample_rate) / sample_size)
print(f"样本量 {sample_size:4d}: 支持率 {sample_rate:.4f} ± {margin_error:.4f}")
性能优化
使用 Generator 而非全局函数
import numpy as np
import time
# 旧方式:使用全局 RandomState
start = time.time()
for _ in range(1000):
np.random.rand(100)
time_old = time.time() - start
# 新方式:使用 Generator
rng = np.random.default_rng()
start = time.time()
for _ in range(1000):
rng.random(100)
time_new = time.time() - start
print(f"旧方式: {time_old:.4f}s")
print(f"新方式: {time_new:.4f}s")
预生成随机数
如果需要大量随机数,一次性生成比分批生成更高效:
import numpy as np
import time
rng = np.random.default_rng(seed=42)
# 方式1:分批生成
start = time.time()
batch_results = []
for _ in range(1000):
batch_results.append(rng.random(100))
time_batch = time.time() - start
# 方式2:一次性生成
rng = np.random.default_rng(seed=42)
start = time.time()
all_at_once = rng.random((1000, 100))
time_once = time.time() - start
print(f"分批生成: {time_batch:.4f}s")
print(f"一次性生成: {time_once:.4f}s")
Generator vs RandomState
NumPy 仍保留旧的 RandomState API,但推荐使用新的 Generator:
import numpy as np
# 旧方式(不推荐)
np.random.seed(42)
old_random = np.random.rand(5)
print(f"旧方式: {old_random}")
# 新方式(推荐)
rng = np.random.default_rng(42)
new_random = rng.random(5)
print(f"新方式: {new_random}")
# 主要区别:
# 1. Generator 使用更好的算法(PCG64)
# 2. Generator 方法更一致
# 3. Generator 更快
# 4. Generator 支持更多分布
小结
本章介绍了 NumPy 随机数生成的核心功能:
| 功能 | 方法 | 说明 |
|---|---|---|
| 创建生成器 | default_rng(seed) | 推荐的创建方式 |
| 浮点数 | random(size) | [0, 1) 均匀分布 |
| 整数 | integers(low, high, size) | 指定范围整数 |
| 选择 | choice(arr, size) | 从数组采样 |
| 正态分布 | normal(loc, scale, size) | 高斯分布 |
| 均匀分布 | uniform(low, high, size) | 均匀分布 |
| 洗牌 | shuffle(arr) | 原地洗牌 |
| 排列 | permutation(arr) | 返回排列副本 |
最佳实践:
- 使用
Generator而非RandomState - 使用种子确保可复现性
- 大量随机数时一次性生成
- 并行计算时使用
SeedSequence.spawn
练习
- 模拟掷两个骰子 10000 次,统计点数之和的分布
- 使用蒙特卡洛方法估计圆周率
- 生成符合指定均值和方差的正态分布数据
- 实现一个简单的随机游走模拟
- 使用 Bootstrap 方法估计数据的置信区间