【问题标题】:Sampling sequences of random numbers in Haskell在 Haskell 中对随机数序列进行采样
【发布时间】:2011-01-07 19:22:16
【问题描述】:

我需要一个小的高斯随机数列表来进行模拟,所以我尝试了以下方法:

import System.Random

seed = 10101
gen = mkStdGen seed

boxMuller mu sigma (r1,r2) =  mu + sigma * sqrt (-2 * log r1) * cos (2 * pi * r2) 

这只是 Box-Muller 算法 - 给定 [0,1] 区间中的 r1、r2 均匀随机数,它返回一个高斯随机数。

normals 0 g = [] 
normals n g = take n $ map (boxMuller 0 1) $ pairs $ randoms g
    where pairs (x:y:zs) = (x,y):(pairs zs)

所以我每次需要我的随机数列表时都使用这个normals 函数。

问题一定很明显:它总是生成相同的序列,因为我总是使用相同的种子!我没有得到新的序列,我一直只得到序列的前 n 个值。

当我打字时,我清楚地假装是这样的:

x = normal 10 
y = normal 50

我会让 x 成为 map (boxMuller 0 1) $ pairs $ randoms g 的前 10 个值,y 成为这个列表中的下 50 个值,依此类推。

当然这是不可能的,因为在给定相同输入的情况下,函数必须始终返回相同的值。我该如何逃脱这个陷阱?

【问题讨论】:

  • 为什么这是一个维基?似乎是一个简单易懂的问题。
  • 糟糕。我错误地检查了 wiki 框。

标签: haskell functional-programming random referential-transparency


【解决方案1】:

我认为在抽象生成器的 monad 中进行需要随​​机数的计算是最干净的事情。下面是该代码的样子:

我们将把 StdGen 实例放在一个状态单子中,然后在状态单子的 get 和 set 方法上提供一些糖来给我们随机数。

首先,加载模块:

import Control.Monad.State (State, evalState, get, put)
import System.Random (StdGen, mkStdGen, random)
import Control.Applicative ((<$>))

(通常我可能不会指定导入,但这很容易理解每​​个函数的来源。)

然后我们将定义我们的“需要随机数的计算”单子;在这种情况下,State StdGen 的别名称为R。 (因为“Random”和“Rand”已经有别的意思了。)

type R a = State StdGen a

R 的工作方式是:定义一个需要随机数流的计算(一元“动作”),然后使用runRandom“运行”它:

runRandom :: R a -> Int -> a
runRandom action seed = evalState action $ mkStdGen seed

这需要一个动作和一个种子,并返回动作的结果。就像平常的evalStaterunReader等一样

现在我们只需要 State monad 周围的糖。我们使用get 来获取StdGen,我们使用put 来安装新状态。这意味着,要获得一个随机数,我们会这样写:

rand :: R Double
rand = do
  gen <- get
  let (r, gen') = random gen
  put gen'
  return r

我们得到随机数生成器的当前状态,用它得到一个新的随机数和一个新的生成器,保存随机数,安装新的生成器状态,返回随机数。

这是一个可以用runRandom运行的“动作”,我们来试试吧:

ghci> runRandom rand 42
0.11040701265689151                           
it :: Double     

这是一个纯函数,所以如果你用同样的参数再次运行它,你会得到同样的结果。杂质留在您传递给 runRandom 的“动作”中。

无论如何,你的函数需要成对的随机数,所以让我们编写一个动作来产生一个随机数pair

randPair :: R (Double, Double)
randPair = do
  x <- rand
  y <- rand
  return (x,y)

使用 runRandom 运行它,您会看到这对中的两个不同的数字,正如您所期望的那样。但请注意,您不必提供带有参数的“rand”;那是因为函数是纯粹的,但“rand”是一个动作,它不必是纯粹的。

现在我们可以实现您的normals 函数。 boxMuller 就像你在上面定义的那样,我只是添加了一个类型签名,这样我就可以理解发生了什么而无需阅读整个函数:

boxMuller :: Double -> Double -> (Double, Double) -> Double
boxMuller mu sigma (r1,r2) =  mu + sigma * sqrt (-2 * log r1) * cos (2 * pi * r2)

在实现了所有辅助函数/动作之后,我们终于可以编写normals,这是一个包含 0 个参数的动作,它返回一个(延迟生成的)正态分布双精度数的无限列表:

normals :: R [Double]
normals = mapM (\_ -> boxMuller 0 1 <$> randPair) $ repeat ()

如果你愿意,你也可以写得不那么简洁:

oneNormal :: R Double
oneNormal = do
    pair <- randPair
    return $ boxMuller 0 1 pair

normals :: R [Double]
normals = mapM (\_ -> oneNormal) $ repeat ()

repeat () 为单子动作提供了一个无限的无源流来调用函数(这就是使法线的结果无限长的原因)。我最初写了[1..],但我重写了它以从程序文本中删除无意义的1。我们不是对整数进行操作,我们只需要一个无限列表。

不管怎样,就是这样。要在实际程序中使用它,您只需在 R 动作中完成需要随机法线的工作:

someNormals :: Int -> R [Double]
someNormals x = liftM (take x) normals

myAlgorithm :: R [Bool]
myAlgorithm = do
   xs <- someNormals 10
   ys <- someNormals 10
   let xys = zip xs ys
   return $ uncurry (<) <$> xys

runRandom myAlgorithm 42

适用于编程一元动作的常用技术;在R 中尽可能少地保留您的应用程序,这样事情就不会太乱了。

哦,还有另一件事:懒惰可以干净地“泄漏”到 monad 边界之外。这意味着你可以写:

take 10 $ runRandom normals 42

它会起作用的。

【讨论】:

  • 我一直在努力寻找一个关于如何在 Haskell 中创建随机数的非常简单的描述——这个答案终于让我明白了!!非常感谢!!!!
【解决方案2】:

你从randoms得到的列表是无限的,当你使用有限前缀时,你不需要扔掉无限尾。您可以将随机数作为附加参数传入,并将未使用的随机数作为附加结果返回,或者您可以将无限的随机数序列存放在 state monad 中。

编译器和其他需要提供唯一符号的代码也会出现类似的问题。这只是 Haskell 中的一个真正痛苦,因为您在整个代码中线程化状态(随机数生成器或符号生成器)。

我已经使用显式参数和单子完成了随机算法,但没有一个是真正令人满意的。如果您了解 monad,我可能会稍微建议使用包含尚未使用的无限随机数列表的状态 monad。

【讨论】:

    【解决方案3】:

    您也可以通过使用newStdGen 来回避这个问题,然后您每次都会(实际上)获得不同的种子。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-09-22
      • 1970-01-01
      • 1970-01-01
      • 2012-01-06
      • 1970-01-01
      相关资源
      最近更新 更多