Как обмениваться случайным состоянием nump родительского процесса с дочерними процессами?

Я установил случайное семя в начале моей программы. Во время выполнения программы я запускаю функцию несколько раз, используя multiprocessing.Process. Функция использует случайные функции numpy для рисования случайных чисел. Проблема в том, что Process получает копию текущей среды. Поэтому каждый процесс выполняется независимо, и все они начинаются с того же случайного семени, что и родительская среда.

Итак, мой вопрос заключается в том, как я могу поделиться случайным состоянием numpy в родительской среде с дочерней технологической средой? Просто отметьте, что я хочу использовать Process для своей работы и вам нужно использовать отдельный класс и сделать import numpy в этом классе отдельно. Я попытался использовать multiprocessing.Manager, чтобы разделить случайное состояние, но кажется, что все работает не так, как ожидалось, и я всегда получаю одинаковые результаты. Кроме того, не имеет значения, переместите ли цикл for внутри drawNumpySamples или оставьте его в main.py; Я все еще не могу получить разные числа, и случайное состояние всегда одно и то же. Вот упрощенная версия моего кода:

# 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()

И в основном файле:

# 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

Примечание. Я использую Python 3.5. Я также разместил вопрос на странице Numpy GitHub. Просто отправьте ссылку проблемы здесь для дальнейшего использования.

3 ответа

Даже если вам удастся это сделать, я не думаю, что он будет делать то, что вы хотите. Как только у вас есть несколько процессов, вытягивающих из одного и того же случайного состояния параллельно, его уже не детерминирован, какой порядок они получают в состоянии, то есть ваши прогоны на самом деле не повторяются. Вероятно, есть способы обойти это, но это кажется нетривиальной проблемой.

Между тем есть решение, которое должно решить как нужную проблему, так и проблему недетерминизма:

Перед нерестом дочернего процесса спросите RNG о случайном числе и передайте его ребенку. Затем ребенок может посеять это число. Каждый ребенок будет иметь другую случайную последовательность от других детей, но такую ​​же случайную последовательность, которую получил тот же ребенок, если вы запустили все приложение с фиксированным семенем.

Если ваш основной процесс выполняет любую другую работу RNG, которая может зависеть от недетерминированности при выполнении детей, вам нужно предварительно создать семена для всех ваших дочерних процессов, чтобы, прежде чем вытащить любую другую случайную числа.

Как указал senderle в комментарии: если вам не нужны несколько отдельных прогонов, но только один фиксированный прогон, вам даже не нужно вытаскивать семя из вашего сеянного RNG; просто используйте счетчик, начинающийся с 1, и увеличивайте его для каждого нового процесса и используйте его как семя. Я не знаю, приемлемо ли это, но если это так, трудно упроститься.

Как заметил Амир в комментарии: лучший способ - нарисовать случайное целое число каждый раз, когда вы создаете новый процесс и передаете это случайное целое в новый процесс, чтобы установить случайное число numpy с этим целым числом. Это целое число действительно имеет значение np.random.randint().


К счастью, согласно документации, вы можете получить доступ к полное состояние генератора случайных чисел numpy с помощью get_state и снова установите его, используя set_state. Сам генератор использует алгоритм Mersenne Twister (см. RandomState часть документации).

Это означает, что вы можете делать все, что хотите, хотя будет ли это хорошо и эффективно, это совсем другой вопрос. Как abarnert указывает, независимо от того, как вы разделяете родительское состояние - это может использовать метод Alex Hall, который выглядит правильно - ваша последовательность в каждом дочернем будет зависеть от порядка, в котором каждый ребенок рисует случайные числа из конечного автомата MT.

Возможно, было бы лучше построить большой пул псевдослучайных чисел для каждого ребенка, сохранив начальное состояние всего генератора один раз в начале. Затем каждый ребенок может нарисовать значение PRNG до тех пор, пока не закончится его конкретный пул, после чего у вас есть дочерняя координата с родителем для следующего пула. Родитель перечисляет, какие дети получили номер "пул". Код будет выглядеть примерно так (обратите внимание, что было бы целесообразно превратить это в бесконечный генератор с помощью метода next):

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 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.

Таким образом, через элементы менеджера (состояние MT и наш идентификатор и индекс добавляются в список "used" ) не так много сообщений. В конце процесса можно увидеть, какие дети использовали значения PRNG, и, если необходимо, повторно генерировать эти значения PRNG (не забудьте записать полное состояние начала MT)!

Изменить для добавления: способ думать об этом так: МТ на самом деле не случайный. Это периодический период с очень длительным периодом. Когда вы используете какой-либо такой RNG, ваше семя просто отправной точкой в ​​течение периода. Чтобы получить повторяемость, вы должны использовать неслучайные числа, например набор из книги. Существует (виртуальная) книга с каждым числом, которое выходит из генератора MT. Мы собираемся записать страницы (страницы) этой книги, которые мы использовали для каждой группы вычислений, чтобы мы могли повторно открыть книгу на эти страницы позже и повторить те же вычисления.


Вам нужно обновить состояние Manager каждый раз, когда вы получаете случайное число:

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)

licensed under cc by-sa 3.0 with attribution.