Давайте начнем с простой функции, которая всегда возвращает случайное целое число:
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()
Я пропустил что-то очевидное здесь?
Edit
Оказывается, источником проблемы является реализация RNG Python. Чтобы процитировать официальную документацию:
Функции, предоставляемые этим модулем, являются фактически связанными методами скрытого экземпляра класса random.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)
делает это медленнее, но решает проблему.
В то время как выше объясняется не случайные результаты сбора, я до сих пор не понимаю, как это влияет на first
/take(1)
между несколькими действиями.