Почему случайная функция NumPy, по-видимому, отображает шаблон в своих сгенерированных значениях?
Я играл с NumPy и Pillow и наткнулся на интересный результат, который, по-видимому, демонстрирует шаблон в результатах NumPy random.random()
.
Здесь образец полного кода для создания и сохранения 100 из этих изображений (с семенем 0), выше - первые четыре изображения, сгенерированные этим кодом.
import numpy as np
from PIL import Image
np.random.seed(0)
img_arrays = np.random.random((100, 256, 256, 3)) * 255
for i, img_array in enumerate(img_arrays):
img = Image.fromarray(img_array, "RGB")
img.save("{}.png".format(i))
Выше представлены четыре разных изображения, созданных с использованием PIL.Image.fromarray()
на четырех различных массивах NumPy, созданных с использованием numpy.random.random((256, 256, 3)) * 255
для генерации 256-х мерной сетки RGB-значений в четырех разные экземпляры Python (то же самое происходит и в одном экземпляре).
Я заметил, что это происходит только в моем ограниченном тестировании, когда ширина и высота изображения равны двум, я не уверен, как это интерпретировать.
Хотя это может быть трудно увидеть из-за сглаживания браузера (вы можете загружать изображения и просматривать их в средствах просмотра изображений без сглаживания), есть четкие фиолетово-коричневые столбцы пикселей, каждая восьмая колонка, начиная с третьего столбца каждый изображение. Чтобы убедиться, я проверил это на 100 различных изображениях, и все они следовали этому шаблону.
Что здесь происходит? Я предполагаю, что такие шаблоны являются причиной того, что люди всегда говорят, что используют криптографически безопасные генераторы случайных чисел, когда требуется истинная случайность, но есть ли конкретное объяснение, почему это происходит в частности?
Ответы
Ответ 1
Не обвиняйте Numpy, обвините PIL/Pillow. ;) Вы генерируете float, но PIL ожидает целые числа, а его преобразование float в int не делает то, что мы хотим. Дальнейшие исследования необходимы, чтобы точно определить, что делает PIL...
В то же время вы можете избавиться от этих строк, явно преобразовывая ваши значения в неподписанные 8-битные целые числа:
img_arrays = (np.random.random((100, 256, 256, 3)) * 255).astype(np.uint8)
Как отмечает FHTMitchell в комментариях, более эффективная форма
img_arrays = np.random.randint(0, 256, (100, 256, 256, 3), dtype=np.uint8)
Здесь типичный выход из этого модифицированного кода:
Функция PIL Image.fromarray имеет известную ошибку, как описано здесь. Поведение, которое вы видите, вероятно, связано с этой ошибкой, но я думаю, что это может быть независимое. ;)
FWIW, вот несколько тестов и обходных решений, которые я сделал по ошибке, упомянутой в связанном вопросе.
Ответ 2
Я почти уверен, что проблема связана с dtype, но не по причинам, которые вы думаете. Вот один из них: np.random.randint(0, 256, (1, 256, 256, 3), dtype=np.uint32)
Обратите внимание, что dtype не np.uint8
:
Вы видите рисунок;)? PIL интерпретирует 32-битные (4 байта) значения (возможно, 4 пикселя RGBK) по-разному от 8-битных значений (RGB для одного пикселя). (См. Ответ PM 2Ring).
Первоначально вы проходили 64-битные значения float, они также будут интерпретироваться по-разному (и, вероятно, неправильно из того, как вы намеревались).
Ответ 3
Документы Python для случайных() говорят это:
Python использует Mersenne Twister в качестве основного генератора. Он производит 53-битные прецизионные поплавки и имеет период 2 ** 19937-1. Основная реализация в C является быстрой и поточной. Mersenne Twister является одним из наиболее широко протестированных генераторов случайных чисел. Однако, будучи полностью детерминированным, он не подходит для всех целей и совершенно непригоден для криптографических целей.
Лучшие генераторы случайных чисел проходят тесты случайности, часто используются генераторы случайных чисел меньшего качества, потому что они быстры и считаются "достаточно хорошими".
В " Простых тестах случайности " в январе 2002 года Марсалья и Цанг они установили, что подмножество " Диярдской батареи тестов " можно использовать для оценки случайности серии чисел, в частности, gcd, тесты гориллы и дня рождения. См. " Описания тестов Dieharder " для обсуждения энтропии и комментариев к этим тестам.
В ходе наших программных головоломок и кода для гольфа некоторые люди предприняли попытку разработать код для прохождения тестов Diehard в этом вопросе: " Создайте генератор случайных чисел, который проходит тесты Diehard ".
Вы должны ожидать увидеть шаблоны во всех, кроме лучших (и, вероятно, медленных) RNG.
Современный стандарт статистического тестирования RNG, " NIST SP 800-22 - Рекомендация для генерации случайных чисел с использованием детерминированных генераторов случайных битов " (Обзор) представляет собой серию тестов, среди прочего, оценивается близость доли единиц до ½, то есть число единиц и нулей в последовательности должно быть примерно одинаковым.
Статья, опубликованная на веб-сайте ACM " Алгоритм 970: оптимизация статистического тестового набора NIST и алгоритма Berlekamp-Massey " Январь 2017 года, Sýs, Říha и Matyáš, обещает огромное ускорение алгоритмов NIST с их реимплантацией.