Haskell中随机数的采样序列

Raf*_*ini 18 random haskell functional-programming referential-transparency

我需要用于模拟的小型高斯随机数列表,所以我尝试了以下方法:

import System.Random

seed = 10101
gen = mkStdGen seed

boxMuller mu sigma (r1,r2) =  mu + sigma * sqrt (-2 * log r1) * cos (2 * pi * r2) 
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

我明确假装的是,当我输入时:

x = normal 10 
y = normal 50
Run Code Online (Sandbox Code Playgroud)

我要将x作为前10个值,将map (boxMuller 0 1) $ pairs $ randoms gy作为此列表中的下50个值,依此类推.

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

jro*_*way 28

我认为你的计算需要在monad中抽象生成器的随机数是最干净的.这是代码的样子:

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

首先,加载模块:

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

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

然后我们将定义我们的"需要随机数的计算"monad; 在这种情况下,State StdGen被调用的别名R.(因为"随机"和"兰德"已经意味着别的东西.)

type R a = State StdGen a
Run Code Online (Sandbox Code Playgroud)

R的工作方式是:定义一个需要随机数流(monadic"action")的计算,然后用"运行它" runRandom:

runRandom :: R a -> Int -> a
runRandom action seed = evalState action $ mkStdGen seed
Run Code Online (Sandbox Code Playgroud)

这将采取操作和种子,并返回操作的结果.就像往常一样evalState,runReader等等.

现在我们只需要州立大学附近的糖.我们get用来获取StdGen,我们put用来安装一个新状态.这意味着,为了得到一个随机数,我们会写:

rand :: R Double
rand = do
  gen <- get
  let (r, gen') = random gen
  put gen'
  return r
Run Code Online (Sandbox Code Playgroud)

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

这是一个可以使用runRandom运行的"动作",所以让我们试试吧:

ghci> runRandom rand 42
0.11040701265689151                           
it :: Double     
Run Code Online (Sandbox Code Playgroud)

这是一个纯函数,因此如果使用相同的参数再次运行它,您将得到相同的结果.杂质留在你传递给runRandom的"动作"中.

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

randPair :: R (Double, Double)
randPair = do
  x <- rand
  y <- rand
  return (x,y)
Run Code Online (Sandbox Code Playgroud)

使用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)
Run Code Online (Sandbox Code Playgroud)

在实现了所有辅助函数/动作后,我们最终可以编写normals一个0参数的动作,它返回一个(懒惰生成的)无限正常分布的双精度列表:

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

如果你想要,你也可以不那么简洁地写这个:

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

normals :: R [Double]
normals = mapM (\_ -> oneNormal) $ repeat ()
Run Code Online (Sandbox Code Playgroud)

repeat ()给monadic动作一个无限的无任何流来调用函数(并且是法线结果无限长的原因).我最初写过[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
Run Code Online (Sandbox Code Playgroud)

编程monadic动作的常用技术适用; 保持尽可能少的应用程序R,事情不会太乱.

哦,还有其他的事情:懒惰可以干净地"泄漏"在monad边界之外.这意味着你可以写:

take 10 $ runRandom normals 42
Run Code Online (Sandbox Code Playgroud)

它会起作用.


Nor*_*sey 6

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

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

我已经完成了具有显式参数和monad的随机算法,并且没有一个真正令人满意.如果你是grok monads,我可能会建议使用一个包含尚未使用的无限随机数列表的状态monad.