Генерация случайных чисел в PySpark
начнем с простой функции, которая всегда возвращает случайное целое число:
import numpy as np
def f(x):
return np.random.randint(1000)
и RDD, заполненный нулями и отображенный с помощью f
:
rdd = sc.parallelize([0] * 10).map(f)
поскольку выше RDD не сохраняется, я ожидаю, что каждый раз, когда я соберу, я получу другой вывод:
> rdd.collect()
[255, 512, 512, 512, 255, 512, 255, 512, 512, 255]
если мы игнорируем тот факт, что распределение значений на самом деле не выглядит случайным, это более или менее то, что происходит. Проблема начинается, когда мы берем только первый элемент:
assert len(set(rdd.first() for _ in xrange(100))) == 1
или
assert len(set(tuple(rdd.take(1)) for _ in xrange(100))) == 1
кажется, что каждый раз возвращается одно и то же число. Я смог воспроизвести это поведение на двух разных машинах с Spark 1.2, 1.3 и 1.4. Здесь я использую np.random.randint
но он ведет себя так же с random.randint
.
эта проблема, так же, как и не совсем случайные результаты с collect
, кажется, Python специфичен, и я не мог воспроизвести его с помощью Scala:
def f(x: Int) = scala.util.Random.nextInt(1000)
val rdd = sc.parallelize(List.fill(10)(0)).map(f)
(1 to 100).map(x => rdd.first).toSet.size
rdd.collect()
я пропустил что-то очевидное?
редактировать:
оказывается, источником проблемы является реализация Python RNG. Цитата официальная документация:
функции, поставляемые этим модулем, на самом деле являются связанными методами скрытого экземпляра random.Случайный класс. Вы можете создавать свои собственные экземпляры Random, чтобы получить генераторы, которые не разделяют состояние.
Я предполагаю, что NumPy работает одинаково и переписывает f
используя RandomState
экземпляра следующим образом
import os
import binascii
def f(x, seed=None):
seed = (
seed if seed is not None
else int(binascii.hexlify(os.urandom(4)), 16))
rs = np.random.RandomState(seed)
return rs.randint(1000)
делает это медленнее, но решает проблему.
хотя выше объясняется не случайные результаты от collect я до сих пор не понимаю, как это влияет first
/ take(1)
между несколькими действиями.
2 ответов
таким образом, фактическая проблема здесь относительно проста. Каждый подпроцесс в Python наследует свое состояние от своего родителя:
len(set(sc.parallelize(range(4), 4).map(lambda _: random.getstate()).collect()))
# 1
поскольку родительское состояние не имеет причин для изменения в этом конкретном сценарии, а продолжительность жизни работников ограничена, состояние каждого ребенка будет точно таким же при каждом запуске.
это, кажется, ошибка (или функция)randint
. Я вижу такое же поведение, но как только я меняю f
, ценности действительно меняются. Итак, я не уверен в фактической случайности этого метода....Я не могу найти никакой документации, но, похоже, он использует какой-то детерминированный математический алгоритм вместо использования более переменных функций работающей машины. Даже если я иду туда и обратно, числа кажутся одинаковыми при возвращении к исходному значению...