17

I ont besoin de petites listes de nombres aléatoires gaussiennes pour une simulation et donc j'ai essayé les suivantes:séquences d'échantillonnage de nombres aléatoires dans Haskell

import System.Random 

seed = 10101 
gen = mkStdGen seed 

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

Ceci est juste l'algorithme Box-Muller - r1 données, uniforme r2 aléatoire nombres dans l'intervalle [0,1] renvoie un nombre aléatoire gaussien.

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

J'utilise cette fonction normals chaque fois que je devais ma liste de nombres aléatoires.

Le problème avec cela doit être apparent: il génère toujours la même séquence car j'utilise toujours la même graine! Je ne reçois pas de nouvelles séquences, je ne reçois que les n premières valeurs de la séquence tout le temps.

Ce que je prétendais clairement est que, quand je tape:

x = normal 10 
y = normal 50 

Je dois x être les 10 premières valeurs de map (boxMuller 0 1) $ pairs $ randoms g et y être les 50 prochaines valeurs sur cette liste, et ainsi sur.

Bien sûr, cela est impossible, car une fonction doit toujours renvoyer les mêmes valeurs pour la même entrée. Comment puis-je échapper à ce piège?

+2

Pourquoi est-ce un wiki? Cela ressemble à une question directe. – Dan

+0

Oups. J'ai vérifié la boîte wiki par erreur. –

Répondre

27

Je pense que faire vos calculs qui nécessitent des nombres aléatoires à l'intérieur d'une monade qui fait abstraction du générateur est la chose la plus propre. Voici à quoi ressemblera ce code:

Nous allons mettre l'instance StdGen dans un monad state, puis fournir un peu de sucre sur la méthode get et set de la monad de l'état pour nous donner des nombres aléatoires.

En premier lieu, charger les modules:

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

(Normalement, je ne serais probablement pas préciser les importations, mais cela le rend facile à comprendre où chaque fonction vient.)

Ensuite, nous allons définir nos "calculs nécessitant des nombres aléatoires" monad; dans ce cas, un alias pour State StdGen appelé R. (Parce que "Random" et "Rand" signifient déjà quelque chose d'autre.)

type R a = State StdGen a 

La façon dont R fonctionne est: vous définissez un calcul qui nécessite un flux de nombres aléatoires (la monade « action »), puis vous « exécutez » avec runRandom:

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

Cela prend une action et une graine, et renvoie les résultats de l'action. Tout comme les habituels evalState, runReader, etc.

Maintenant, nous avons juste besoin de sucre autour de la monade de l'Etat. Nous utilisons get pour obtenir le StdGen, et nous utilisons put pour installer un nouvel état. Cela signifie, pour obtenir un nombre aléatoire, nous écrivions:

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

On a l'état actuel du générateur de nombres aléatoires, l'utiliser pour obtenir un nouveau numéro aléatoire et un nouveau générateur, enregistrer le numéro aléatoire, installez le nouvel état du générateur et renvoie le nombre aléatoire.

Ceci est une « action » qui peut être exécuté avec runRandom, donc nous allons essayer:

ghci> runRandom rand 42 
0.11040701265689151       
it :: Double  

Ceci est une pure fonction, donc si vous l'exécutez à nouveau avec les mêmes arguments, vous obtiendrez le même résultat. L'impureté reste à l'intérieur de "l'action" que vous passez à runRandom.

Quoi qu'il en soit, votre fonction veut paires de nombres aléatoires, donc nous allons écrire une action pour produire une paire de nombres aléatoires:

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

Exécuter ce avec runRandom, et vous verrez deux numéros différents la paire, comme vous l'attendez. Mais remarquez que vous n'aviez pas à fournir "rand" avec un argument; C'est parce que les fonctions sont pures, mais "rand" est une action qui n'a pas besoin d'être pure.

Maintenant, nous pouvons implémenter votre normals fonction. boxMuller est comme vous l'avez défini ci-dessus, je viens d'ajouter une signature de type pour que je puisse comprendre ce qui se passe sans avoir à lire toute la fonction:

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

Avec toutes les fonctions d'aide/actions mises en œuvre, nous pouvons enfin écrire normals , une action de 0 arguments qui renvoie un (paresseusement généré) liste infinie de Doubles normalement distribués:

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

vous pouvez également écrire ce moins de façon concise si vous voulez:

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

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

repeat() donne l'action monadique un flux infini de rien pour invoquer la fonction avec (et est ce qui rend le résultat des normales infiniment long). J'avais initialement écrit [1..], mais je l'ai réécrit pour enlever le 1 sans signification du texte du programme. Nous ne fonctionnons pas sur des entiers, nous voulons juste une liste infinie.

Quoi qu'il en soit, c'est tout.Pour l'utiliser dans un programme réel, vous venez de faire votre travail nécessitant Normales au hasard dans une action 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 

Les techniques habituelles pour les actions monades de programmation applicables; Gardez aussi peu de votre application dans R que possible, et les choses ne seront pas trop en désordre.

Oh, et d'autre part: la paresse peut "fuir" proprement à l'extérieur de la frontière de la monade. Cela signifie que vous pouvez écrire:

take 10 $ runRandom normals 42 

et cela fonctionnera.

6

La liste que vous obtenez de randoms est infinie, et lorsque vous utilisez le préfixe fini, vous n'avez pas besoin de jeter la queue infinie. Vous pouvez passer les nombres aléatoires en tant que paramètre supplémentaire et renvoyer les nombres aléatoires non utilisés comme résultat supplémentaire, ou vous pouvez parquer une séquence infinie de nombres aléatoires dans une monade d'état.

Un problème similaire se produit pour les compilateurs et les autres codes qui souhaitent fournir des symboles uniques. C'est juste une vraie douleur dans Haskell, parce que vous êtes threading état (du générateur de nombres aléatoires ou du générateur de symboles) dans tout le code.

J'ai fait des algorithmes randomisés à la fois avec des paramètres explicites et avec une monade, et aucun n'est vraiment satisfaisant. Si vous grok monads j'ai probablement une légère recommandation d'utiliser une monade d'état contenant la liste infinie de nombres aléatoires qui n'ont pas encore été utilisés.

1

Vous pouvez également contourner le problème en utilisant newStdGen, puis vous obtiendrez une graine différente (virtuellement) à chaque fois.

Questions connexes