这个问题涉及当从连续种子生成连续的随机数时({1}}观察到的时间相关性的起源(其中一个种子为每个种子丢弃相同数量的生成器)。
在Using mkStdGen from System.Random to generate random booleans Answer 1和Using mkStdGen from System.Random to generate random booleans Answer 2中,有人建议(基于引用其中的reddit文章)应该丢弃前几个生成器以获得合理的结果。然而,我发现不管有多少发生器丢弃,当观察分布的时间方面时,如果用连续种子生成连续的随机数(一个丢弃每个种子的相同数量的生成器),则获得不希望的结果。
我的问题是 System.Random
中使用的算法是什么导致不同种子之间以所描述的方式存在时间相关性?
如果我们生成一个无限的随机布尔序列,那么获得具有相同值的System.Random
个连续布尔值的概率P(n)
(例如n
中的[True,True,True]
)是[False,True,True,True,False]
。作为一个
快速检查我们是否有正常化条件:
(1/2)^n
以下代码:
P(1)+P(2)+....P(infty) = (1/2) + (1/2)^2 + ... = 1
使用来自连续种子的生成器生成每个连续的布尔值,在使用得到的随机结果之前丢弃10个生成器。生成10000个随机数的序列,因此我们期望大约5000个布尔值跟随相反的布尔值(例如module Main where
import Data.List
import System.Random
generateNthGenerator startGen 0 = startGen
generateNthGenerator startGen n = generateNthGenerator newGen (n-1)
where newGen = snd $ ((random startGen) :: (Bool,StdGen))
better_mkStdGen generation seed =
generateNthGenerator (mkStdGen seed) generation
randomNums generation =
map (fst . random . (better_mkStdGen generation)) [0 .. maxBound] :: [Bool]
-- e.g. [True,True,False,False,False,True,True,True,False,False]
sortedLengthOfConsecutives num randList =
sort $ map length $ take num $ group randList
frequencyOfConsecutives sortedLengthOfCons =
map (\x -> (head x, length x)) $ group sortedLengthOfCons
results = frequencyOfConsecutives
$ sortedLengthOfConsecutives 10000
$ randomNums 10
main = do
print results -- [(8,1493),(9,8507)]
中的[True]
),因为有2500个布尔值,后面跟着相同的布尔值布尔值,然后是反对的布尔值(例如[False,True,False,False]
中的[True,True]
),大约1250个布尔值,分为3个等等。
所以从上面的代码我们得到1493个8组和8507个9组。这不是预期的结果,无论丢弃多少个发生器,我们都得到类似的结果(在上面的例子中,每个种子丢弃的发生器数量是10)。 [注意当我们对[False,True,True,False]
进行相同的实验时,我们没有得到相同的行为,请参阅后面的内容。
如果我们使用以前生成的生成器生成连续的布尔值(我猜这是最初设计使用它的方式,因为tf-random
本身会返回一个新的生成器) ,我们似乎得到了更合理的结果:
random
所以我们得到4935个单身(大约等于0.5 * 10000),2513个双重(大约等于0.5 ^ 2 * 10000),1273个三元组(大约等于0.5 ^ 3 * 10000)等,这正是我们所期望的。
所以看来,如果我们生成(通过module Main where
import Data.List
import System.Random
generateRandomInner gen =
let (randBool, newGen) = (random gen)::(Bool,StdGen)
in randBool:(generateRandomInner newGen)
generateRandoms seed =
let (randBool, newGen) = (random $ mkStdGen seed)::(Bool,StdGen)
in randBool:(generateRandomInner newGen)
seed = 0
randomNums = generateRandoms seed
sortedLengthOfConsecutives num randList =
sort $ map length $ take num $ group randList
frequencyOfConsecutives sortedLengthOfCons =
map (\x -> (head x, length x)) $ group sortedLengthOfCons
results = frequencyOfConsecutives
$ sortedLengthOfConsecutives 10000
$ randomNums
main = do
print results
--[(1,4935),(2,2513),(3,1273),(4,663),(5,308),
-- (6,141),(7,86),(8,45),(9,16),(10,12),(11,6),
-- (12,1),(13,1)]
)一个随机序列,其中每个连续随机生成连续种子,我们为每个种子丢弃相同数量的生成器,不希望的相关性持续存在废弃的发电机。
随机数生成的算法属性是什么?
导致此问题的System.Random
?
请注意,如果我们使用上面的失败方法System.Random
(redditt文章),那么我们会得到预期的结果:
tf-random
即。 50%是单身人士,25%是双人(2人组)等......
答案 0 :(得分:1)
让我们首先看一下代码说的内容,然后我们就可以实现目标。
首先,适用于random
的{{1}}相当于:
Bool
事实上,如果我将randomB :: StdGen -> (Bool, StdGen)
randomB g = let (i, g') = next g in (i `mod` 2 == 1, g')
替换为适合您程序的random
,我会得到相同的结果。关键在于,为了确定布尔值,我们关心的是下一个randomB
值是偶数还是奇数。
接下来,让我们看一下Int
的定义:
StdGen
所以两个data StdGen
= StdGen Int32 Int32
是州。让我们看看它们是如何使用Int32
进行初始化的,以及如何使用mkStdGen
进行调整:
next
...
mkStdGen :: Int -> StdGen -- why not Integer ?
mkStdGen s = mkStdGen32 $ fromIntegral s
mkStdGen32 :: Int32 -> StdGen
mkStdGen32 s
| s < 0 = mkStdGen32 (-s)
| otherwise = StdGen (s1+1) (s2+1)
where
(q, s1) = s `divMod` 2147483562
s2 = q `mod` 2147483398
注意两件有趣的事情:
初始化stdNext :: StdGen -> (Int, StdGen)
-- Returns values in the range stdRange
stdNext (StdGen s1 s2) = (fromIntegral z', StdGen s1'' s2'')
where z' = if z < 1 then z + 2147483562 else z
z = s1'' - s2''
k = s1 `quot` 53668
s1' = 40014 * (s1 - k * 53668) - k * 12211
s1'' = if s1' < 0 then s1' + 2147483563 else s1'
k' = s2 `quot` 52774
s2' = 40692 * (s2 - k' * 52774) - k' * 3791
s2'' = if s2' < 0 then s2' + 2147483399 else s2'
的方式保证它将为1,除非您向s2
发送一个非常高的数字,在这种情况下它将为2(少于200个值)将mkStdGen
初始化为2的Int32
范围。
状态的两半保持不同 - 更新的s2
仅取决于之前的s2
,而不取决于之前的s2
,反之亦然。
因此,如果你检查从s1
传递给某一定数代的生成器,那么它们状态的后半部分将始终相同。
尝试将其添加到您的程序中:
better_mkStdGen
那么问题是,为什么print $ map (dropWhile (/= ' ') . show . better_mkStdGen 10) [0 .. 20]
中的混合函数无法正确混合最后一位。请注意,它的编写方式s1
和s1'
将具有与k
相同的奇偶校验,因此s1
状态只有与前一个s1
不同的奇偶校验如果s1
最终小于零,则说明状态。
此时我需要手动一点,并说s1'
的计算方式意味着如果s1'
的两个初始值彼此接近,s1
的两个值也将是最接近的,并且通常只会是最初的40014倍,在我们允许s1'
的范围内使得相邻值很可能最终在零的同一侧。