10

私は独自のモナドProducerを使用して、ランダム性に依存する値を作成するを持っています:Random

policies :: Producer (Policy s a) Random x

Randomまたはmwc-randomから実行できるラッパーです。STIO

newtype Random a =
  Random (forall m. PrimMonad m => Gen (PrimState m) -> m a)

runIO :: Random a -> IO a
runIO (Random r) = MWC.withSystemRandom (r @ IO)

プロデューサーはpolicies、単純な強化学習アルゴリズムから、ますます優れたポリシーを生成します。

にインデックスを付けることで、たとえば 5,000,000 回の反復後にポリシーを効率的にプロットできますpolicies

Just convergedPolicy <- Random.runIO $ Pipes.index 5000000 policies
plotPolicy convergedPolicy "policy.svg"

ここで、500,000 ステップごとに中間ポリシーをプロットして、それらがどのように収束するかを確認したいと考えています。プロデューサーを使用して、たとえば 10 個のポリシー (500,000 回の反復ごとに 1 つ) のpoliciesリスト ( ) を抽出し、それらすべてをプロットする関数をいくつか作成しました。[Policy s a]

ただし、これらの関数は、学習反復の合計回数が同じ (つまり 5,000,000) であっても、上記のように最終ポリシーをプロットするよりもはるかに時間がかかり (10 倍)、多くのメモリ (4 倍) を使用します。これは、ガベージ コレクターを禁止するリストを抽出したためではないかと考えています。

慣用的なパイプ スタイルでは、すべての要素をメモリにロードするのではなく、要素が生成されるとすぐに要素を消費します。

Producerがランダムなモナド (つまりRandom) の上にあり、生成したい効果が にある場合、このようなパイプを消費する正しいアプローチは何IOですか?

別の言い方をすれば、 aProducer (Policy s a) Random xを aに接続したいのですConsumer (Policy s a) IO x

4

1 に答える 1

2

Randomジェネレーターを読み取るリーダーです

import Control.Monad.Primitive
import System.Random.MWC

newtype Random a = Random {
    runRandom :: forall m. PrimMonad m => Gen (PrimState m) -> m a
}

Random aaを a に自明に変換できますReaderT (Gen (PrimState m)) m a。この簡単な操作は、 aをhoistに変えたいものです。Producer ... Random aProducer ... IO a

import Control.Monad.Trans.Reader

toReader :: PrimMonad m => Random a -> ReaderT (Gen (PrimState m)) m a
toReader = ReaderT . runRandom

は些細なことなので、 ingtoReaderによるランダム生成のオーバーヘッドはありません。hoistこの関数は、その型シグネチャを示すためだけに記述されています。

import Pipes

hoistToReader :: PrimMonad m => Proxy a a' b b' Random                          r ->
                                Proxy a a' b b' (ReaderT (Gen (PrimState m)) m) r
hoistToReader = hoist toReader

ここで取るべきアプローチは 2 つあります。簡単なアプローチはhoistConsumer同じモナドに入り、パイプを一緒に構成して実行することです。

type ReadGenIO = ReaderT GenIO IO

toReadGenIO :: MFunctor t => t Random a -> t ReadGenIO a
toReadGenIO = hoist toReader

int :: Random Int
int = Random uniform

ints :: Producer Int Random x
ints = forever $ do
    i <- lift int
    yield i

sample :: Show a => Int -> Consumer a IO ()
sample 0 = return ()
sample n = do
    x <- await
    lift $ print x
    sample (n-1)

sampleSomeInts :: Effect ReadGenIO ()
sampleSomeInts = hoist toReader ints >-> hoist lift (sample 1000)

runReadGenE :: Effect ReadGenIO a -> IO a
runReadGenE = withSystemRandom . runReaderT . runEffect

example :: IO ()
example = runReadGenE sampleSomeInts

Pipes.Liftパイプのユーザーが知っておくべき別のツールのセットがあります。これらは、Randomモナドのようなトランスフォーマーを に分散して実行するためのツールですProxy。ここには、よく知られたトランスフォーマーをトランスフォーマー ライブラリーから実行するためのビルド済みツールがあります。それらはすべて から構築されていdistributeます。Proxy ... (t m) aは、を実行するために使用するツールで一度だけt (Proxy ... m) a実行できるに変わります。t

import Pipes.Lift

runRandomP :: PrimMonad m => Proxy a a' b b' Random r ->
                             Gen (PrimState m) -> Proxy a a' b b' m r
runRandomP = runReaderT . distribute . hoist toReader

パイプを結合して srunEffectを取り除くために使用することはできますが、sProxyを結合するときにジェネレーター引数を自分でジャグリングすることになりますProxy ... IO r

于 2016-09-10T07:53:03.620 に答える