洛伦谈MATLAB的艺术

将想法转化为MATLAB

随机数字的新方法,第一部分

今天我想介绍一位客座博主Peter Perkins,他是MathWorks的一名统计软件开发人员。在MathWorks之前,他是一名统计学家,为美国国家海洋渔业局从事海洋哺乳动物研究,他对MATLAB的兴趣之一是随机数生成和蒙特卡罗模拟激动。

内容

几乎所有使用过MATLAB的人都使用过兰德randn在某个时候起作用。然而,人们使用它们的方式千差万别。有时,您可能只是想用0或1以外的值填充一个向量或矩阵,以便尝试您所编写的一些新代码行。在光谱的另一端,您可能正在运行蒙特卡罗模拟,其中伪随机值的统计特性很重要。

但不管上下文如何,我经常听到的一个问题是,“我应该如何以及何时初始化或重新初始化或重置MATLAB的随机数生成器?”我通常给出的答案是另一个令人烦恼的问题:“为什么要这样做?”这是因为搞乱随机数生成器的状态可能会影响它生成的值的统计特性,而原始问题的正确答案取决于上下文——通常情况下,答案是“不要”。关于这个问题可以说很多什么时候为什么?,我将谈到这个问题。但最重要的是,我想展示一些怎样这在MATLAB R2008b中是新的。

如果你想运行我用来创建这个博客的代码,你需要R2008b或更高版本,你需要从一个新的MATLAB会话开始,或者执行这两个命令(别担心,我会解释它们的作用)。

mtstream = RandStream (‘mt19937ar’); RandStream.setDefaultStream(mtstream);

MATLAB中的随机流

新的MATLAB R2008b是随机数流的添加。随机数流就是它听起来的样子:一个随机值序列的源。通常,人们谈论的是来自标准均匀分布的值流,这也是MATLAB使用的感觉。实际上,MATLAB总是在抽象的随机数流下面兰德randn。但是在R2008b中,你可以直接与流进行交互,你甚至可以创建自己的流。这里有很多需要讨论的内容,但本文讨论的是如何初始化或重置流,因此下面只是一个简短的总结。有更多在MATLAB的文档中。

通常情况下,你根本不需要担心流;MATLAB在启动时为您创建一个。兰德,randn,以及兰迪函数从该流中提取值,您可以生成随机数,而无需知道或关心它们来自于哪个流。这并没有什么错——如果MATLAB中的随机数生成器正确地完成了它们的工作(并且在R2008b中,您可以在基于最先进的算法的三个生成器中进行选择),您应该能够使用兰德,randn兰迪,并将返回的所有内容作为独立的随机值处理。

但是你如何返回并重新生成你生成的随机值或者控制它们的生成方式呢?有一种新的方法可以做到这一点:你在MATLAB中与一种新的对象进行交互,它代表了一个随机流。让我们获取MATLAB在启动时创建的流。

defaultStream = RandStream.getDefaultStream ()
defaultStream=mt19937ar随机流(当前默认)种子:0 RandnAlg:Ziggurat

“mt19937ar”表示该流使用最常见的梅森素数捻线机算法,和下面的算法一样兰德有好几个版本。还有其他发电机选择;稍后再谈。MATLAB使用0的种子创建流;更多关于等一下。

是什么RandnAlg? R2008b的一个新特点是randn从相同的流中提取值兰德,当然,统一值必须转换为标准正态分布。这里显示的流使用金字形神塔算法,尽管你可以改变它(抱歉,不能再讲了;你得看书医生).

最后,该流的显示显示它是默认的流。这就意味着这是一条流兰德,randn,兰迪在下面,我将展示如何创建自己的流,并默认值。有几个原因可以解释为什么你想这么做。但首先,让我们看看如何使用默认流来重现结果。

重置随机流

有时,能够从MATLAB的随机数生成器中重现输出是很有用的。例如,您可能希望重复运行蒙特卡罗模拟,使用完全相同的随机值,因为您正在调试代码。实际上,MATLAB每次启动时都会将默认随机流初始化为相同的内部状态,因此调用相同的兰德,randn,兰迪将在每次启动MATLAB时返回相同的“随机”值(除非您采取步骤,稍后会有更多相关信息)。但是,每次您想要重新运行模拟时,都必须重新启动MATLAB,这将是一件乏味的事情。

重现随机数生成器输出的最简单方法是重置默认流,这会将该流恢复到最初创建时的状态。

重置(默认流)z1=randn(1,5)
z1=0.53767 1.8339-2.2588 0.86217 0.31877

如果再做一次,得到的还是原来的值。

重置(默认流)z2=randn(1,5)
Z2 = 0.53767 1.8339 -2.2588 0.86217 0.31877

虽然简单,重置是一种钝的工具,只在非常粗略的规模上提供重现性——就好像你重新启动了MATLAB。这不是你应该做的事情,除非你真的想重复使用相同的随机序列。

在溪流中播种

所有伪随机数生成器都有种子的概念。可以将流看作是一个很长的值序列,封装在一个循环中,因为当耗尽时它会重复。可以将种子视为创建流时指定的圆上的起始位置。随机值的数量(通常)比种子的数量要多很多,所以不可能通过指定种子的方式从任何地方开始。通常没有简单的描述,比如种子0,相对于种子1或种子2的位置,除了知道它们代表不同的起始位置。但是如果您使用相同的种子创建一个流,那么每次都会得到相同的值序列。

当MATLAB启动时,它创建默认流,命令等价于

stream0=RandStream(‘mt19937ar’,“种子”, 0) RandStream.setDefaultStream (stream0);
stream0 = mt19937ar随机流种子:0 RandnAlg: Ziggurat

我们之前见过。如果我用不同的种子创建一个新流,例如

stream1=RandStream(‘mt19937ar’,“种子”,1)
stream1=mt19937ar随机流种子:1 RandnAlg:Ziggurat

并使默认流,

RandStream.setDefaultStream(stream1);

我将得到一个不同的随机值序列。

randn(1,5)
ans=-0.64901 1.1812-0.75845-1.1096-0.84555

但如果我总是用相同的种子,我总是会得到相同的随机数流。这很像重置当前的默认流——事实上,重置只不过是重新播种而已。我使用重置我已经创建了自己的流,所以我知道完全我得到什么。在此之前,我获得了可重复性,但具体的随机值取决于当前默认流下的生成器算法。

重新播种是一个大问题——你不会希望每次需要一组随机值时都创建新的流。如果你将其用作初始化步骤,也许在MATLAB启动时,也许在运行模拟之前,它最有用。

使用子流的再现性

我们已经看到了使用重置“种子”为了重现结果,你必须从头开始。R2008b中的一个新特性,子流,提供了一种新的方法来克服这个缺点。

还记得那个圆圈吗?子流只不过是随机数流中固定的“检查点”,由它们在圆周围的位置定义,以非常大的间隔均匀分布。子流本身的索引从1开始,您可以通过设置流的Substream财产。从不同的子流生成的值在统计上是独立的,就像流中的连续值一样。

R2008b中的两个新生成器算法支持子流:组合多重递归(万博1manbetx“mrg32k3a”)和乘法滞后斐波那契数列(“mlfg6331_64”)生成器(文档有参考资料)。我将创建前一个流,并将其设为默认流。

流= RandStream (“mrg32k3a”); RandStream.setDefaultStream(stream);

要将流重新定位到特定子流的开始位置,我只需设置它的Substream财产。

流。Substream = 2;

很像种子,子流只不过是沿着随机值序列的特定点的别名。那么它们有什么不同呢?有一些不同之处,我将在下次讨论,但现在,最明显的不同是,您可以在子流之间跳转,而无需创建新的流。这是一个相当轻量级的操作。

让我们看看如果我在循环的每次迭代之前将一个随机数流定位到子流的开头会发生什么。

对于i=1:5流。子流=i;z=rand(1,i)结束
Z = 0.5582 0.85273 Z = 0.16662 0.29236 0.77278 Z = 0.34773 0.38864 0.80161 0.95025 Z = 0.56709 0.68377 0.29879 0.59318 0.69247

现在我可以简单地通过将流重新定位到相应的子流来复制第三次迭代的结果。我不需要重新运行整个循环,我甚至不需要事先知道我想看这个迭代。

i=3;流.子流=i;z=rand(1,i)
z=0.16662 0.29236 0.77278

如果这个循环是一个耗时的蒙特卡罗模拟,那么能够在不重新运行整个过程的情况下重新创建和研究任何特定的迭代将是一个真正的福音。

请记住,您不需要过度使用子流。它们是用来使用的,你不必担心在移动到下一个子流之前“用完”所有的值,但采取另一个极端,每次生成新值时跳到不同的子流是毫无意义的。子流并没有添加随机性,它们只是让复制价值变得更容易。

保存和恢复状态

兰德randn伪随机数发生器,所有伪随机生成器算法都有一个内部状态,用于确定下一个值。这是一个确定性算法。还记得那个圆圈吗?圆上的每个点对应于不同的状态。但对于几乎所有现代算法来说,状态比生成的最后一个数字或流中当前位置的简单索引更复杂。

让我们获取默认生成器的状态。要做到这一点,我将阅读陈述财产。

defaultStream=RandStream.getDefaultStream()savedState=defaultStream.State;谁状态数据
defaultStream = mrg32k3a随机流(当前默认)Seed: 0 RandnAlg: Ziggurat Name Size Bytes Class Attributes savedState 12x1 48 uint32

如果我打电话给兰德要生成一些随机值,

u1=兰特(1,5)
u1=0.83911 0.51069 0.84469 0.68938 0.23777

然后我恢复我保存的状态,

defaultStream。陈述= savedState;

兰德会在下次调用时生成完全相同的值。

u2=兰特(1,5)
u2=0.83911 0.51069 0.84469 0.68938 0.23777

同样的道理也适用于randn以及兰迪。因为这三个函数都从相同的默认流中提取值,所以只需要担心一种状态——默认流的状态。我可以使用前面保存的状态来复制结果randn例如。

defaultStream。陈述= savedState; z1 = randn(1,5)
Z1 = 0.048564 0.87031 0.52365 0.095609 -0.79233
defaultStream.State=savedState;z2=randn(1,5)
z2=0.048564 0.87031 0.52365 0.095609-0.79233

记住这一点很重要,因为只有一个默认流,调用一个函数会影响其他函数返回的特定值。

savedState=defaultStream.State;u1=rand(1,5)u2=rand(1,5)
u1=0.045212 0.48758 0.75138 0.36639 0.5676 u2=0.10969 0.27046 0.10359 0.87516 0.28295
defaultStream。陈述= savedState;%这将返回与前面相同的向量u1=兰特(1,5)%在两次通话之间给rand打电话z=randn(1,5);%这将返回与以前不同的值u2=兰特(1,5)
U1 = 0.045212 0.48758 0.75138 0.36639 0.5676 u2 = 0.20425 0.043083 0.54273 0.10648 0.20139

这些函数并不独立地处理它们返回的值,但是,这三个函数生成的值仍然可以被视为统计上与生成顺序无关。

保存和恢复状态的一个实际应用可能是通过在运行之前保存状态来复制整个蒙特卡罗模拟运行。另一种可能是在蒙特卡罗模拟中重新创建一个特别有趣的迭代,在该迭代之前保存状态。另一种可能是只重新创建一个对兰德这发生在代码的深处,因此您可以在其他地方重新创建该调用,可能是为了调试。只要您在适当的位置保存了状态,您就可以“跳转到”在任何地方在随机值序列中。这是相对于重置或播种流的优势,但这也是一个陷阱:你必须事先保存正确的状态。

另外,MATLAB中各种生成器的内部状态具有不同的大小和格式,您可以通过查看陈述不同流的属性。但我建议不要太熟悉或修改这些状态向量的内部工作。读它们,写它们,是的,也许甚至捆起来,打上烙印,但不要试着去理解它们。

不要在哪里做这些事

有时,谨慎是valor更好的部分。请小心理解为什么以及何时执行这些操作。请记住,重置或替换默认流,或写入其状态,或跳转到子流会影响所有后续调用兰德,randn,兰迪,这可能不是你想在代码深处做的事情——太容易忘记它就在那里。特别是,替换默认流可能不需要做很多工作。

下一次

我们已经看到了从MATLAB的随机数生成器中复制结果的四种方法。

  • 重置一个流
  • 使用固定种子创建新流
  • Substreams
  • 显式保存和恢复生成器状态

但有时需要的是“再现性的反面”——您希望确保模拟的不同运行使用不同的随机输入,因为您希望能够假设不同的运行在统计上是独立的。直到下一次。。。

其他情况吗?

你能想到在任何情况下,你需要复制我所展示的例子无法处理的随机数吗?请让我知道在这里




与MATLAB®7.7一起发布

|

评论

要留下评论,请点击在这里登录到您的MathWorks帐户或创建一个新帐户。