python python-3.x numpy scipy multiprocessing

python - ¿Cómo compartir un estado aleatorio numpy de un proceso primario con procesos secundarios?



python-3.x scipy (3)

Establezco semilla aleatoria numpy al comienzo de mi programa. Durante la ejecución del programa ejecuto una función varias veces usando multiprocessing.Process . multiprocessing.Process . La función utiliza funciones aleatorias numpy para dibujar números aleatorios. El problema es que Process obtiene una copia del entorno actual. Por lo tanto, cada proceso se ejecuta de forma independiente y todos comienzan con la misma semilla aleatoria que el entorno principal.

Entonces, mi pregunta es ¿cómo puedo compartir el estado aleatorio de numpy en el entorno primario con el entorno del proceso secundario? Solo tenga en cuenta que quiero usar Process para mi trabajo y necesito usar una clase separada e import numpy en esa clase por separado. Intenté usar multiprocessing.Manager para compartir el estado aleatorio, pero parece que las cosas no funcionan como se esperaba y siempre obtengo los mismos resultados. Además, no importa si muevo el bucle for dentro de drawNumpySamples o lo dejo en main.py ; Todavía no puedo obtener números diferentes y el estado aleatorio es siempre el mismo. Aquí hay una versión simplificada de mi código:

# randomClass.py import numpy as np class myClass(self): def __init__(self, randomSt): print (''setup the object'') np.random.set_state(randomSt) def drawNumpySamples(self, idx) np.random.uniform()

Y en el archivo principal:

# main.py import numpy as np from multiprocessing import Process, Manager from randomClass import myClass np.random.seed(1) # set random seed mng = Manager() randomState = mng.list(np.random.get_state()) myC = myClass(randomSt = randomState) for i in range(10): myC.drawNumpySamples() # this will always return the same results

Nota : yo uso Python 3.5. También publiqué un problema en la página de GitHub de Numpy. Simplemente enviando el enlace del problema here para referencia futura.


Afortunadamente, de acuerdo con la documentación , puede acceder al estado completo del generador de números aleatorios get_state usando get_state y configurarlo nuevamente usando set_state . El generador en sí utiliza el algoritmo Mersenne Twister (consulte la parte RandomState de la documentación ).

Esto significa que puede hacer lo que quiera, aunque si será bueno y eficiente es una cuestión completamente diferente. Como señala abarnert , no importa cómo comparta el estado de los padres, esto podría usar el método de Alex Hall , que parece correcto, su secuenciación dentro de cada niño dependerá del orden en que cada niño extraiga números aleatorios de la máquina de estados MT.

Quizás sería mejor construir un gran grupo de números pseudoaleatorios para cada niño, guardando el estado de inicio de todo el generador una vez al comienzo. Luego, cada elemento secundario puede dibujar un valor PRNG hasta que se agote su grupo particular, después de lo cual se hace que el elemento secundario se coordine con el elemento primario para el grupo siguiente. El padre enumera qué hijos obtuvieron qué número de "grupo". El código se vería así (tenga en cuenta que tendría sentido convertir esto en un generador infinito con el next método):

class PrngPool(object): def __init__(self, child_id, shared_state): self._child_id = child_id self._shared_state = shared_state self._numbers = [] def next_number(self): if not self.numbers: self._refill() return self.numbers.pop(0) # XXX inefficient def _refill(self): # ... something like Alex Hall''s lock/gen/unlock, # but fill up self._numbers with the next 1000 (or # however many) numbers after adding our ID and # the index "n" of which n-through-n+999 numbers # we took here. Any other child also doing a # _refill will wait for the lock and get an updated # index n -- eg, if we got numbers 3000 to 3999, # the next child will get numbers 4000 to 4999.

De esta manera, no hay tanta comunicación a través de los elementos del Administrador (el estado de MT y nuestro ID e índice se agregan a la lista "usada"). Al final del proceso, es posible ver qué niños usaron qué valores de PRNG y volver a generar esos valores de PRNG si es necesario (¡recuerde registrar el estado de inicio interno completo de MT!).

Editar para agregar: la forma de pensar sobre esto es así: el MT no es realmente aleatorio. Es periódico con un período muy largo. Cuando usa cualquiera de estos RNG, su semilla es simplemente un punto de partida dentro del período. Para obtener la repetibilidad debe usar números no aleatorios, como un conjunto de un libro. Hay un libro (virtual) con cada número que sale del generador de MT. Vamos a escribir qué página (s) de este libro usamos para cada grupo de cálculos, para que podamos volver a abrir el libro en esas páginas más tarde y volver a hacer los mismos cálculos.


Debe actualizar el estado del Manager cada vez que obtenga un número aleatorio:

import numpy as np from multiprocessing import Manager, Pool, Lock lock = Lock() mng = Manager() state = mng.list(np.random.get_state()) def get_random(_): with lock: np.random.set_state(state) result = np.random.uniform() state[:] = np.random.get_state() return result np.random.seed(1) result1 = Pool(10).map(get_random, range(10)) # Compare with non-parallel version np.random.seed(1) result2 = [np.random.uniform() for _ in range(10)] # result of Pool.map may be in different order assert sorted(result1) == sorted(result2)


Incluso si logras que esto funcione, no creo que haga lo que quieres. Tan pronto como tenga varios procesos tirando del mismo estado aleatorio en paralelo, ya no es determinista el orden en que cada uno llega al estado, lo que significa que sus ejecuciones en realidad no serán repetibles. Probablemente hay formas de evitar eso, pero parece un problema no trivial.

Mientras tanto, hay una solución que debería resolver tanto el problema que desea como el problema del no determinismo:

Antes de generar un proceso hijo, solicite al RNG un número aleatorio y páselo al niño. El niño puede entonces sembrar con ese número. Cada niño tendrá una secuencia aleatoria diferente a la de otros niños, pero la misma secuencia aleatoria que obtuvo el mismo niño si vuelve a ejecutar la aplicación completa con una semilla fija.

Si su proceso principal realiza cualquier otro trabajo de RNG que pueda depender de manera no determinista de la ejecución de los elementos secundarios, deberá generar previamente las semillas de todos sus procesos secundarios, en orden, antes de extraer cualquier otro número aleatorio.

Como señaló Senderle en un comentario: si no necesita varias ejecuciones distintas, pero solo una ejecución fija, ni siquiera necesita extraer una semilla de su RNG sembrado; solo use un contador que comience en 1 e increméntelo para cada nuevo proceso, y úselo como semilla. No sé si eso es aceptable, pero si es así, es difícil ser más simple que eso.

Como Amir señaló en un comentario: una mejor manera es dibujar un entero aleatorio cada vez que genera un nuevo proceso y pasar ese entero aleatorio al nuevo proceso para establecer la semilla aleatoria del numpy con ese entero. Este entero de hecho puede provenir de np.random.randint() .