У меня есть Producer
, который создает значения, зависящие от случайности, используя мою собственную Random
monad:
policies :: Producer (Policy s a) Random x
Random
является оболочкой над mwc-random
, которая может быть запущена из ST
или IO
:
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 шагов, чтобы увидеть, как они сходятся. Я написал несколько функций, которые принимают создателя policies
и извлекают список ([Policy s a]
), скажем, из 10 политик - каждый раз каждые 500 000 итераций - и затем закладывают все их.
Однако эти функции занимают гораздо больше времени (10x) и используют больше памяти (4x), чем просто вывод окончательной политики, как указано выше, хотя общее количество итераций обучения должно быть одинаковым (т.е. 5 000 000). Я подозреваю, что это связано с извлечением списка, запрещающего сборщик мусора, и это похоже на унииоматическое использование Pipes:
Стиль идиоматических труб потребляет элементы немедленно, поскольку они генерируются вместо того, чтобы загружать все элементы в память.
Какой правильный подход к потреблению такой трубы, когда Producer
находится над некоторой случайной монадой (т.е. Random
), и эффект, который я хочу создать, находится в IO
?
Другими словами, я хочу подключить Producer (Policy s a) Random x
к Consumer (Policy s a) IO x
.