Haskell 中的随机数采样序列

发布于 2024-08-18 07:19:46 字数 774 浏览 11 评论 0原文

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

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 个值在此列表中,等等。

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

I need small lists of gaussian random numbers for a simulation and so I tried the following:

import System.Random

seed = 10101
gen = mkStdGen seed

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

This is just the Box-Muller algorithm - given r1, r2 uniform random numbers in the [0,1] interval it returns a gaussian random number.

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

So I used this normals function everytime I needed my list of random numbers.

The problem with that must be apparent: it generates always the same sequence cause I'm using always the same seed! I'm not getting new sequences, I'm only getting the first n values of the sequence all the time.

What I was pretending clearly was that, when I type:

x = normal 10 
y = normal 50

I'd have x to be the first 10 values of map (boxMuller 0 1) $ pairs $ randoms g and y to be the next 50 values on this list, and so on.

Of course this is impossible, cause a function must always return the same values given the same input. How do I escape this trap?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

熊抱啵儿 2024-08-25 07:19:46

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

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

首先,加载模块:(

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

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

然后我们将定义“需要随机数的计算”monad;在本例中,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 的“动作”内。

无论如何,您的函数需要成对的随机数,所以让我们编写一个操作来生成一对随机数:

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

使用 runRandom 运行此操作,您将看到该对中的两个不同的数字,就像您一样d 期望。但请注意,您不必为“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)

实现了所有辅助函数/操作后,我们终于可以编写 < code>normals,一个 0 个参数的操作,返回一个(延迟生成的)正态分布双精度数的无限列表:

normals :: R [Double]
normals = mapM (\_ -> boxMuller 0 1 <
gt; 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 (<) <
gt; xys

runRandom myAlgorithm 42

应用编程单子​​动作的常用技术;在 R 中保留尽可能少的应用程序,这样事情就不会太混乱。

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

take 10 $ runRandom normals 42

并且它会起作用。

I think that doing your computations that require random numbers inside a monad that abstracts away the generator is the cleanest thing. Here is what that code would look like:

We are going to put the StdGen instance in a state monad, then provide some sugar over the state monad's get and set method to give us random numbers.

First, load the modules:

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

(Normally I would probably not specify the imports, but this makes it easy to understand where each function is coming from.)

Then we will define our "computations requiring random numbers" monad; in this case, an alias for State StdGen called R. (Because "Random" and "Rand" already mean something else.)

type R a = State StdGen a

The way R works is: you define a computation that requires a stream of random numbers (the monadic "action"), and then you "run it" with runRandom:

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

This takes an action and a seed, and returns the results of the action. Just like the usual evalState, runReader, etc.

Now we just need sugar around the State monad. We use get to get the StdGen, and we use put to install a new state. This means, to get one random number, we would write:

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

We get the current state of the random number generator, use it to get a new random number and a new generator, save the random number, install the new generator state, and return the random number.

This is an "action" that can be run with runRandom, so let's try it:

ghci> runRandom rand 42
0.11040701265689151                           
it :: Double     

This is a pure function, so if you run it again with the same arguments, you will get the same result. The impurity stays inside the "action" that you pass to runRandom.

Anyway, your function wants pairs of random numbers, so let's write an action to produce a pair of random numbers:

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

Run this with runRandom, and you'll see two different numbers in the pair, as you'd expect. But notice that you didn't have to supply "rand" with an argument; that's because functions are pure, but "rand" is an action, which need not be pure.

Now we can implement your normals function. boxMuller is as you defined it above, I just added a type signature so I can understand what's going on without having to read the whole function:

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

With all the helper functions/actions implemented, we can finally write normals, an action of 0 arguments that returns a (lazily-generated) infinite list of normally-distributed Doubles:

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

You could also write this less concisely if you want:

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

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

repeat () gives the monadic action an infinite stream of nothing to invoke the function with (and is what makes the result of normals infinitely long). I had initially written [1..], but I rewrote it to remove the meaningless 1 from the program text. We are not operating on integers, we just want an infinite list.

Anyway, that's it. To use this in a real program, you just do your work requiring random normals inside an R action:

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 (<) <
gt; xys

runRandom myAlgorithm 42

The usual techniques for programming monadic actions apply; keep as little of your application in R as possible, and things won't be too messy.

Oh, and on other thing: laziness can "leak" outside of the monad boundary cleanly. This means you can write:

take 10 $ runRandom normals 42

and it will work.

决绝 2024-08-25 07:19:46

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

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

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

The list you get from randoms is infinite, and when you use the finite prefix, you need not to throw away the infinite tail. You can pass the random numbers in as an additional parameter and return the unused random numbers as an additional result, or you can park an infinite sequence of random numbers in a state monad.

A similar problem occurs for compilers and other codes that want a supply of unique symbols. This is just a real pain in Haskell, because you are threading state (of the random-number generator or of the symbol generator) throughout the code.

I've done randomized algorithms both with explicit parameters and with a monad, and neither one is really satisfying. If you grok monads I probably have a slight recommendation to use a state monad containing the infinite list of random numbers that have not yet been used.

肤浅与狂妄 2024-08-25 07:19:46

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

You could also sidestep the problem by using newStdGen and then you'll get a different seed (virtually) every time.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文