Почему случайная функция NumPy, по-видимому, отображает шаблон в своих сгенерированных значениях?

Я играл с NumPy и Pillow и наткнулся на интересный результат, который, по-видимому, демонстрирует шаблон в результатах NumPy random.random().

Image One Image Two Image Three Image Four

Здесь образец полного кода для создания и сохранения 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) 

Здесь типичный выход из этого модифицированного кода:

random image made using Numpy


Функция PIL Image.fromarray имеет известную ошибку, как описано здесь. Поведение, которое вы видите, вероятно, связано с этой ошибкой, но я думаю, что это может быть независимое. ;)

FWIW, вот несколько тестов и обходных решений, которые я сделал по ошибке, упомянутой в связанном вопросе.

Ответ 2

Я почти уверен, что проблема связана с dtype, но не по причинам, которые вы думаете. Вот один из них: np.random.randint(0, 256, (1, 256, 256, 3), dtype=np.uint32) Обратите внимание, что dtype не np.uint8:

enter image description here

Вы видите рисунок;)? 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 с их реимплантацией.