18

シミュレーションにはガウス乱数の小さなリストが必要なので、次のことを試しました。

import System.Random

seed = 10101
gen = mkStdGen seed

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

これは単なるボックスミュラーアルゴリズムです。[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をこのリストの最初の10個の値にmap (boxMuller 0 1) $ pairs $ randoms gし、yを次の50個の値にします。

もちろん、これは不可能です。関数は、同じ入力が与えられた場合、常に同じ値を返す必要があるためです。この罠から逃れるにはどうすればよいですか?

4

3 に答える 3

28

ジェネレーターを抽象化するモナド内で乱数を必要とする計算を行うのが最もクリーンなことだと思います。そのコードは次のようになります。

StdGenインスタンスを状態モナドに配置し、状態モナドのgetおよびsetメソッドに砂糖を加えて、乱数を取得します。

まず、モジュールをロードします。

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

(通常、インポートを指定しない可能性がありますが、これにより、各関数がどこから来ているのかを簡単に理解できます。)

次に、「乱数を必要とする計算」モナドを定義します。この場合、State StdGen呼び出されたのエイリアスR。(「ランダム」と「ランド」はすでに別の意味を持っているためです。)

type R a = State StdGen a

Rの仕組みは次のとおりです。乱数のストリーム(モナディック「アクション」)を必要とする計算を定義してから、次のように「実行」しrunRandomます。

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

これはアクションとシードを取り、アクションの結果を返します。いつものようevalStaterunReader、など

今、私たちは州のモナドの周りに砂糖が必要です。getStdGenを取得するために使用しput、新しい状態をインストールするために使用します。つまり、1つの乱数を取得するには、次のように記述します。

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を使用してこれを実行すると、予想どおり、ペアに2つの異なる数値が表示されます。ただし、「rand」に引数を指定する必要はないことに注意してください。これは、関数が純粋であるためですが、「ランド」はアクションであり、純粋である必要はありません。

これで、関数を実装できますnormalsboxMuller上で定義したとおり、型アノテーションを追加しただけなので、関数全体を読まなくても何が起こっているのかを理解できます。

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

すべてのヘルパー関数/アクションが実装されたら、最終的にnormals、正規分布のDoubleの(遅延生成された)無限リストを返す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。そうすれば、物事が煩雑になることはありません。

ああ、そして他のことについて:怠惰はモナド境界の外にきれいに「漏れる」可能性があります。これは、次のように書くことができることを意味します。

take 10 $ runRandom normals 42

そしてそれは動作します。

于 2010-01-24T00:16:55.367 に答える
6

取得するリストrandomsは無限であり、有限プレフィックスを使用する場合は、無限のテールを破棄する必要はありません。乱数を追加のパラメーターとして渡し、未使用の乱数を追加の結果として返すか、状態モナドに乱数の無限のシーケンスをパークすることができます。

同様の問題は、一意のシンボルの供給を必要とするコンパイラやその他のコードでも発生します。これはHaskellの本当の苦痛です。なぜなら、コード全体に(乱数ジェネレーターまたはシンボルジェネレーターの)状態をスレッド化しているからです。

明示的なパラメーターとモナドの両方を使用してランダム化アルゴリズムを実行しましたが、どちらも実際には満足のいくものではありません。モナドを使用する場合は、まだ使用されていない乱数の無限のリストを含む状態モナドを使用することをお勧めします。

于 2010-01-21T16:10:28.643 に答える
1

を使用して問題を回避することもできnewStdGenます。そうすれば、毎回異なるシードを(事実上)取得できます。

于 2010-01-21T19:00:06.100 に答える