
1. 从“伪随机”说起为什么MATLAB的随机数值得深究很多朋友刚接触MATLAB时觉得随机数生成无非就是rand、randn、randi这几个函数用起来似乎很简单。但当你真正需要用它来做蒙特卡洛模拟、算法初始化、或者生成可重复的实验数据时就会发现这里面门道不少。比如为什么我两次运行程序得到的“随机”序列不一样为什么我同事用同样的代码在他的机器上跑出来的结果和我不同更深入一点在并行计算或者某些优化算法中如何确保随机数的“质量”和“可控性”这些问题都指向了随机数函数背后那些容易被忽略的细节。我自己在早期做信号处理算法的性能评估时就曾踩过一个坑用randn生成高斯噪声来测试滤波器每次测试结果波动很大导致算法性能不稳定无法得出可靠结论。后来才发现问题出在没有固定随机数种子每次实验的噪声基底都不同相当于每次都在不同的“随机宇宙”里测试结果自然没有可比性。这个经历让我意识到用好随机数远不止是调用一个函数那么简单它关乎到计算结果的可靠性、实验的可复现性以及程序行为的确定性。今天我们就抛开简单的函数说明深入到MATLAB随机数生成的内核结合我这些年踩过的坑和总结的经验把rand、randn、randi、randperm等常用函数以及更关键的随机数生成器RNG、种子设置、流控制等高级用法系统地梳理一遍。目标是让你不仅能“生成”随机数更能“驾驭”随机数让它成为你科研和工程中可靠的工具而不是一个飘忽不定的变量。2. 基础函数三剑客rand,randn,randi的核心差异与选用逻辑虽然它们都姓“rand”但生成的数据分布和适用场景天差地别。选错了函数你的模拟结果可能从一开始就偏离了方向。2.1rand: 均匀分布的“万能钥匙”及其精度陷阱rand函数生成的是开区间(0,1)内均匀分布的伪随机数。这是最基础、最常用的随机数来源。它的调用格式非常灵活rand(): 生成一个标量。rand(n): 生成一个 n×n 的矩阵。rand(m, n): 生成一个 m×n 的矩阵。rand([m, n, p, ...]): 生成一个多维数组。为什么是(0,1)均匀分布因为这个区间内的均匀分布是构建其他任何复杂分布的基石。理论上通过逆变换采样等方法你可以从均匀分布U(0,1)得到任意你想要的分布指数分布、韦伯分布等。MATLAB的统计工具箱和机器学习工具箱里的许多分布随机数生成函数底层都依赖于rand。一个关键的精度陷阱rand默认生成双精度double浮点数。这意味着它的值域虽然是(0,1)但其数值精度是有限的。当你需要生成大量随机数或者进行极其精密的数值计算时需要意识到浮点误差的累积。例如理论上概率为0.5的事件在大量rand生成的样本中其频率会无限接近0.5但永远不会精确等于0.5这是由伪随机数的离散性和浮点表示决定的。注意千万不要用rand() 0.5这样的条件来判断随机数是否等于某个特定值因为概率几乎为零。正确的做法是使用区间判断如rand() 0.5。实战场景举例模拟伯努利试验如果你要模拟一次硬币抛掷正面1概率为p反面0概率为1-p。最直接的做法是p 0.7; % 正面概率 result (rand() p); % 若 rand() 生成的值小于0.7则结果为逻辑值 true (1)否则为 false (0)这里rand()生成了一个均匀随机数通过与阈值p比较将其转化为一个伯努利随机变量。这是蒙特卡洛模拟中最基本的操作之一。2.2randn: 高斯分布正态分布的“标准工兵”randn生成的是标准正态分布均值为0方差为1的伪随机数。在信号处理、金融建模、误差分析等领域高斯噪声或高斯假设无处不在randn因此成为不可或缺的工具。调用格式与rand完全一致。如果你想得到均值为mu标准差为sigma的正态分布随机数需要进行一个简单的线性变换mu 5; sigma 2; data mu sigma * randn(1000, 1); % 生成1000个服从 N(5, 4) 的样本与rand的本质区别randn的算法比rand更复杂。早期MATLAB版本使用经典的Marsaglia’s ziggurat算法来高效生成正态分布随机数。它并不是通过对rand生成的均匀分布做变换得来的虽然数学上等价而是有自己独立的底层算法。这意味着改变rand的随机数生成器状态不会影响randn的输出反之亦然。这一点在设置随机种子时需要特别注意我们会在第4节详细讨论。一个常见误解有人认为randn生成的数大部分在0附近所以“不够随机”。恰恰相反这正是正态分布的特性约68%的值落在[-1,1]区间约95%落在[-2,2]区间。如果你需要生成“看起来”更均匀分散的随机数那应该用rand而不是质疑randn的随机性。实战场景为信号添加高斯白噪声t 0:0.01:10; clean_signal sin(2*pi*0.5*t); % 干净的0.5Hz正弦信号 SNR_dB 20; % 信噪比20dB signal_power mean(clean_signal.^2); noise_power signal_power / (10^(SNR_dB/10)); % 生成功率为noise_power的高斯噪声 noise sqrt(noise_power) * randn(size(clean_signal)); noisy_signal clean_signal noise;这里的关键是randn生成的是单位功率方差为1的噪声。要得到指定功率的噪声需要乘以目标标准差即方差的平方根。2.3randi: 整数随机数的“范围控制器”randi用于生成均匀分布的随机整数。这在需要随机索引、随机抽样、模拟离散事件如掷骰子时非常有用。基本语法randi(imax): 生成一个1到imax之间的随机整数。randi(imax, n): 生成一个 n×n 的矩阵。randi(imax, m, n): 生成一个 m×n 的矩阵。randi([imin, imax], ...): 生成imin到imax包含两端之间的随机整数。底层原理randi本质上是对rand函数生成的(0,1)均匀随机数进行缩放、平移和取整操作。例如randi([a, b])在内部近似等价于a floor((b-a1)*rand())。但randi的实现经过了优化能更好地处理整数范围的边界情况确保每个整数出现的概率严格相等。一个重要细节区间包含性randi([imin, imax])生成的随机整数包含imin和imax。这与Python中numpy.random.randint的默认行为不包含上限不同混用时容易出错。例如要模拟一个六面骰子直接用randi([1, 6])即可。实战场景从数据集中随机抽取样本data rand(1000, 10); % 假设有1000个样本每个样本10个特征 sample_size 100; % 方法1使用 randi 生成随机索引可能有重复 random_indices randi([1, size(data, 1)], sample_size, 1); sampled_data_with_replacement data(random_indices, :); % 方法2无放回抽样使用 randperm见2.4节 random_indices_unique randperm(size(data, 1), sample_size); sampled_data_without_replacement data(random_indices_unique, :);这里清晰地展示了randi有放回和randperm无放回在抽样问题上的不同用途。2.4 特殊成员randperm与randsamplerandperm用于生成一个随机排列。randperm(n)返回1到n的一个随机排列即洗牌。randperm(n, k)返回从1到n中随机抽取的k个不重复的整数。这在进行无放回随机抽样、交叉验证数据分割时极其方便。randsample函数功能更强大但属于Statistics and Machine Learning Toolbox。它可以从指定总体向量中抽样。支持有放回和无放回。可以指定每个元素被抽中的权重非均匀抽样。 如果你有这个工具箱randsample是更全面的抽样工具如果没有用randperm和索引操作也能实现大部分无放回抽样的需求。3. 引擎盖之下理解随机数生成器RNG与种子这才是控制MATLAB随机数行为的“总开关”。很多人调用了rng(default)或rng(0)但并不清楚这行代码到底做了什么导致在复杂程序中依然无法控制随机性。3.1 什么是伪随机数生成器计算机无法生成真正的随机数只能通过确定的算法从一个初始值种子开始计算出一个看起来随机的数列。这个算法就是伪随机数生成器Pseudo-Random Number Generator, PRNG。只要种子相同生成的序列就完全相同。这既是缺点非真随机也是巨大的优点可重复性。MATLAB目前默认的也是自R2014b以来推荐的生成器是twister即Mersenne Twister算法MT19937。它的周期极长2^19937-1能通过大多数统计随机性测试在科学计算中广泛应用。但在rng函数中它被标记为twister而更早的mt19937ar是与之兼容的旧式语法。3.2rng函数你的随机数控制台rng函数用于控制全局随机数生成器的状态。它的几种关键用法1. 查询当前设置s rng; % 将当前RNG的状态保存到结构体s中s包含了生成器类型、种子、以及内部状态向量。这个状态向量才是决定下一个随机数是什么的关键。2. 设置种子以获得可重复结果rng(42); % 将种子设置为42并重置生成器 a rand(1,5); rng(42); % 再次重置到相同状态 b rand(1,5); % b 将和 a 完全相同这是确保实验可复现的最简单有效的方法。在程序开头尤其是在脚本中设置一个固定种子那么每次运行都会得到一模一样的随机数序列。3. 重置为默认启动状态rng(default);这会将RNG重置为MATLAB启动时的默认状态。不同版本的MATLAB其“默认”种子可能不同。所以依赖default来保证跨版本的可复现性并不可靠更好的做法是记录下你使用的具体种子值如42。4. 指定生成器算法rng(0, philox); % 使用Philox 4x32生成器种子为0 rng(0, threefry); % 使用Threefry 4x64生成器种子为0 rng(0, combRecursive); % 使用组合多重递归生成器philox和threefry是支持并行随机数生成的现代算法在并行计算中能保证子流之间的独立性。combRecursive在统计性质上可能更优适合对随机数质量要求极高的蒙特卡洛模拟。选择哪个取决于你的具体需求速度、并行性、统计质量。3.3 一个隐蔽的巨坑rand、randn、randi的状态是独立的这是我早年踩过的最大的坑。在MATLAB的旧版本大约R2014a之前中rand和randn共享同一个全局流global stream设置rand的状态会影响randn。但在现代MATLAB中使用twister,philox等生成器时rand、randn、randi有各自独立的内部状态。这意味着rng(100); a1 rand(1,3); b1 randn(1,3); rng(100); % 只重置了基础生成器状态 a2 rand(1,3); % a2 等于 a1 b2 randn(1,3); % b2 不一定等于 b1因为randn的内部状态没有被完全重置要完全重置所有随机数函数到同一个起点正确的方法是保存和恢复完整的RNG状态savedState rng; % 保存完整状态 % ... 执行一些随机操作 ... rng(savedState); % 恢复到之前保存的精确状态 % 现在无论是rand, randn还是randi都会从之前中断的地方继续4. 进阶掌控随机数流、并行计算与性能当你从单次模拟进阶到大规模蒙特卡洛实验或者开始使用并行计算工具箱Parallel Computing Toolbox时对随机数的管理就需要更精细的策略。4.1 随机数流为不同的任务分配独立的随机源在复杂的程序中你可能希望模块A和模块B使用完全独立、互不干扰的随机数序列以确保修改模块B的随机逻辑不会影响模块A的结果。这时可以使用RandStream对象来创建独立的随机数流。% 创建两个独立的随机数流使用相同的种子但不同的子流编号 stream1 RandStream(mlfg6331_64, Seed, 0, NormalTransform, Ziggurat); stream2 RandStream(mlfg6331_64, Seed, 0, NormalTransform, Ziggurat); % 将全局流临时设置为stream1 defaultStream RandStream.getGlobalStream(); RandStream.setGlobalStream(stream1); data_from_moduleA rand(100,1); % 使用stream1 % 将全局流切换为stream2 RandStream.setGlobalStream(stream2); data_from_moduleB randn(100,1); % 使用stream2 % 恢复默认全局流 RandStream.setGlobalStream(defaultStream);RandStream给了你更底层的控制权包括选择不同的生成器算法如mlfg6331_64是一种适合并行的生成器、指定正态分布变换算法Ziggurat,Polar等。但对于大多数日常应用管理好rng的全局状态已经足够。4.2 并行计算中的随机数如何避免“重复”的随机在parfor循环或spmd块中如果每个工作进程worker都简单地调用rand()它们很可能会生成高度相关甚至完全相同的随机数序列因为这取决于每个worker上RNG的初始状态。这会导致并行模拟的结果出现系统性偏差。MATLAB的并行计算工具箱提供了优雅的解决方案1. 让每个Worker拥有独立且可复现的流parpool(local, 4); % 启动一个包含4个worker的并行池 spmd % 每个worker独立设置RNG基于labindexworker的编号生成不同的种子 rng(0 labindex); % 为每个worker设置一个唯一且确定的种子 localRand rand(1e6, 1); % 每个worker生成不同的随机序列 end % 最后将各worker的结果合并 combinedResult cat(1, localRand{:});这种方法简单但需要确保种子间隔足够大避免不同流的序列重叠。2. 使用支持并行的生成器及其子流更推荐的方法是使用如Philox或Threefry这类生成器它们天生支持创建多个统计独立的子流Substream。parpool(local, 4); spmd % 创建一个基于worker索引的子流 stream RandStream(Threefry, Seed, 0, NormalTransform, Polar); stream.Substream labindex; RandStream.setGlobalStream(stream); localRand rand(1e6, 1); % 每个worker的随机序列都是独立且可复现的 end子流机制保证了不同worker上的随机数序列既是独立的又来自于同一个主种子管理和复现起来非常方便。这是进行大规模并行蒙特卡洛模拟的首选方法。4.3 性能考量批量生成 vs. 循环内生成随机数生成也有性能开销。一个常见的反模式是在循环内部反复调用rand()生成单个随机数。% 低效做法 n 1e6; slowResult zeros(n, 1); for i 1:n slowResult(i) rand(); end % 高效做法 fastResult rand(n, 1);MATLAB是向量化语言一次性生成整个数组比在循环中调用一百万次rand()要快几个数量级。这是因为向量化调用减少了函数解析和调用的开销并允许底层库进行更高效的优化。对于randn和randi也是如此务必遵循**“预分配向量化”**的原则。5. 从理论到实践常见应用模式与避坑指南掌握了基本函数和底层控制我们来看看如何把它们安全、高效地应用到实际项目中。5.1 模式一可重复实验的标准化流程为了保证你的研究或项目结果能被他人或未来的自己精确复现建议在代码开头建立如下标准流程function myExperiment(experimentSeed) % MYEXPERIMENT 一个可重复的随机实验框架 % 输入 experimentSeed: 实验种子用于控制所有随机性。 % 1. 设置全局随机种子核心步骤 if nargin 1 experimentSeed 20240517; % 默认种子 end rng(experimentSeed, twister); % 明确指定生成器 % 2. 记录实验配置包括随机种子 config.experimentSeed experimentSeed; config.rngSettings rng; % 保存完整的RNG设置 save(experiment_config.mat, config); % 3. 进行你的实验操作... data randn(1000, 1); % ... 其他依赖于随机数的计算 % 4. 可选在需要严格隔离的模块使用独立流 streamForShuffling RandStream(Threefry, Seed, experimentSeed); streamForShuffling.Substream 2; idx randperm(streamForShuffling, 100); % 使用特定流进行排列 fprintf(实验完成。种子%d用于复现。\n, experimentSeed); end这个流程的好处是只要提供相同的experimentSeed整个实验从数据生成到后续所有随机操作包括使用randperm的随机排列都会完全一致。5.2 模式二蒙特卡洛模拟的参数化与批处理做蒙特卡洛模拟时我们通常需要多次运行同一随机过程。这里的关键是每次运行应该是独立的但模拟本身应该是可重复的。function results monteCarloSimulation(numSimulations, samplesPerSim) % MONTECARLOSIMULATION 蒙特卡洛模拟示例 % numSimulations: 模拟次数 % samplesPerSim: 每次模拟的样本数 results zeros(numSimulations, 1); for simIdx 1:numSimulations % 为每一次独立的模拟设置一个唯一的、确定的种子。 % 这里使用基础种子加上模拟索引确保每次模拟的随机源不同 % 但整个模拟过程是可重复的。 rng(1000 simIdx); % 生成本次模拟的随机输入 randomInput randn(samplesPerSim, 1); % 执行你的模拟逻辑例如计算路径评估模型 % 假设我们计算随机游走的最终位置 randomWalk cumsum(randomInput); results(simIdx) randomWalk(end); end % 分析结果 estimatedMean mean(results); estimatedStd std(results); fprintf(基于 %d 次模拟估计均值 %.4f标准差 %.4f\n, ... numSimulations, estimatedMean, estimatedStd); end这种模式确保了1每次模拟是统计独立的2重新运行整个函数会得到完全相同的一组模拟结果3你可以单独复现第k次模拟只需将种子设置为1000 k。5.3 高频踩坑点与解决方案坑在并行parfor中误用rng问题在parfor循环内部直接调用rng来设置种子由于worker之间的执行顺序不确定可能导致不可预知的结果。解决使用spmd块或为每个worker分配唯一的子流如4.2节所述。或者在parfor循环外生成所有需要的随机数然后通过切片分配到各worker。% 安全做法在循环外生成所有随机数 totalNumbers 1e7; allRandomNumbers rand(totalNumbers, 1); parfor i 1:10 startIdx (i-1)*1e6 1; endIdx i*1e6; myChunk allRandomNumbers(startIdx:endIdx); % 处理 myChunk end坑误以为randi生成的整数范围是左闭右开问题受其他编程语言如Python影响错误地使用randi([1, N])以为不包含N。解决牢记MATLAB的randi区间是两端包含的。randi([a, b])生成的是a, a1, ..., b。坑用随机数做初始化导致算法结果不稳定问题在机器学习中用randn初始化神经网络权重每次训练得到的最终模型性能有波动难以比较不同超参的效果。解决在训练脚本的最开始固定随机种子。这确保了权重初始化的随机性、数据打乱的随机性如果用了randperm、甚至任何dropout层的随机性都是一致的使超参数比较变得公平。% 深度学习训练脚本开头 rng(default); % 或一个固定的数字种子 % 然后初始化权重、分割数据...坑需要生成特定非标准分布随机数时自己写转换函数出错问题需要生成服从指数分布、卡方分布等随机数时自己根据概率论公式进行逆变换可能因忽略分布函数的定义域或变换的单调性而出错。解决优先使用Statistics and Machine Learning Toolbox中的专业函数如exprnd,chi2rnd,gamrnd等。它们经过严格测试更可靠。如果必须自己实现务必仔细验证生成的随机数的统计特性如直方图、均值、方差。% 使用工具箱推荐 % lambda 2; % exponentialData exprnd(1/lambda, [10000,1]); % 自己实现指数分布逆变换法 lambda 2; u rand(10000, 1); % 均匀分布 exponentialDataManual -log(1-u) / lambda; % 注意是 -log(1-U)/lambda % 验证mean(exponentialDataManual) 应接近 1/lambda (0.5)6. 性能优化与替代方案当生成海量随机数例如数十亿时默认的Mersenne Twister可能不是最快的选择。你可以根据场景选择不同的生成器生成器类型 (rng参数)特点适用场景twister(默认)周期长统计性质好通用性强。大多数科学计算通用蒙特卡洛模拟。simdTwister基于MT的SIMD优化版本生成速度快。需要快速生成大量随机数的场景。combRecursive组合多重递归生成器统计性质可能更优。对随机数质量要求极高的金融模拟。philox/threefry支持并行子流速度快。并行计算需要多个独立随机流。mlfg6331_64/mrg32k3a旧版并行生成器。遗留代码或需要与旧版本兼容的并行计算。设置方法rng(seed, generatorType)例如rng(0, philox)。此外对于超大规模随机数生成可以考虑使用rand/randn的分布式数组版本如果拥有Parallel Computing Toolbox% 在并行池已开启的情况下 D distributed.rand(1e9, 1); % 在集群内存中生成一个包含10亿个随机数的分布式数组这允许你生成超出单机内存容量的随机数据集计算会自动分布到各个worker上。最后关于随机数的“质量”对于绝大多数工程和科研应用MATLAB默认的生成器已经足够好。除非你在进行密码学相关的工作需要密码学安全的随机数应使用randc或外部硬件设备或者你的蒙特卡洛模拟维度极高对随机数的均匀性、相关性有极端要求否则不必过度纠结于生成器算法的选择。把重点放在正确设置种子保证复现性和合理使用向量化保证性能上更能提升你的工作效率和代码可靠性。