¿Cómo compartir el estado aleatorio numérico de un proceso padre con procesos hijos?

Establecí varias semillas al azar al principio de mi progtwig. Durante la ejecución del progtwig ejecuto una función varias veces usando multiprocessing.Process . multiprocessing.Process . La función utiliza numerosas funciones aleatorias para dibujar números aleatorios. El problema es que el 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 principal con el entorno de proceso secundario? Solo tenga en cuenta que quiero usar el 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 GumpHub de Numpy. Simplemente enviando el enlace del problema aquí para futuras referencias.

Incluso si logras hacer que esto funcione, no creo que haga lo que quieres. Tan pronto como tenga varios procesos que se extraen del mismo estado aleatorio en paralelo, ya no es determinista el orden en que llegan al estado, lo que significa que sus ejecuciones no serán repetibles. Probablemente hay maneras 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 no determinista:

Antes de generar un proceso hijo, pida al RNG un número aleatorio y páselo al niño. El niño puede 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 ejecuta la aplicación completa con una semilla fija.

Si su proceso principal realiza cualquier otro trabajo de RNG que pueda depender de forma no determinista de la ejecución de los niños, deberá generar las semillas para todos los procesos de su hijo, 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 corrida fija, ni siquiera necesita extraer una semilla de su RNG sembrado; solo use un contador que comience en 1 y auméntelo para cada nuevo proceso, y utilícelo como semilla. No sé si eso es aceptable, pero si lo es, 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 genere un nuevo proceso y pasar ese entero aleatorio al nuevo proceso para establecer la semilla aleatoria del número con ese entero. Este entero puede provenir de np.random.randint() .

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) 

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í mismo utiliza el algoritmo Mersenne Twister (consulte la parte de RandomState de la documentación ).

Esto significa que puede hacer lo que quiera, aunque una pregunta diferente es si será bueno y eficiente . 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 secuencia dentro de cada niño dependerá del orden en el que cada niño extraiga números aleatorios de la máquina de estado MT.

Tal vez sería mejor construir un grupo grande de números pseudoaleatorios para cada niño, guardando el estado de inicio de todo el generador una vez al inicio. Luego, cada hijo puede dibujar un valor de PRNG hasta que se agote su grupo particular, después de lo cual tiene la coordenada hijo con el padre para el siguiente grupo. El padre enumera qué hijos obtuvieron qué número de “pool’th”. El código tendría un aspecto similar a este (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 de “usados”). Al final del proceso, es posible ver qué hijos utilizaron qué valores de PRNG y volver a generar esos valores de PRNG si fuera necesario (¡recuerde registrar el estado de inicio interno de MT completo!).

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