Генерация случайных чисел в 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, ценности действительно меняются. Итак, я не уверен в фактической случайности этого метода....Я не могу найти никакой документации, но, похоже, он использует какой-то детерминированный математический алгоритм вместо использования более переменных функций работающей машины. Даже если я иду туда и обратно, числа кажутся одинаковыми при возвращении к исходному значению...