Рубрики
Без рубрики

Псевдослучайные числа в Python: От арифметика до распределения вероятностей

Случайность – это то, что мы склонны принимать как должное в нашей повседневной жизни. “Это так случайно!” Мы … с меткой Python, алгоритмы.

Случайность – это то, что мы склонны принимать как должное в нашей повседневной жизни. «Это так случайно! «Мы скажем, когда кто-то делает что-то ненормальное или неожиданное, хотя есть доказательства того, что люди не могут сознательно достичь случайности . Для поистине стохастических процессов мы обращаемся к природе: рост бактерий, сроки радиоактивного распада и тепловой шум, создаваемый электрическими токами, все лежат претензию на истинную случайную последовательность.

Не всегда есть несколько плутоний-239, лежащих каждый раз, когда нам нужно использовать истинную случайность, поэтому компьютерные ученые разработали тесные приближения, называемые генераторами псевдослучайных номеров или PRNGS. Сегодня эти алгоритмы повсеместны в разработке программного обеспечения. Как отмечает Википедию , «Prngs являются центральными приложениями, такими как симуляция (например, для метода Монте-Карло), электронные игры (например, для процедурного поколения) и криптографии».

Сегодня я собираюсь пройти через относительно простой вид PRNG, называемый линейным конгруэнтским генератором (LCG). В современной реализации большинство случайных чисел полагаются на более сложные PRNG, но LCG дает нам прочную основу и будет достаточно случайным для наших скромных намерений.

В качестве предварительного требования вы должны быть знакомы с модульной арифметикой, что по сути просто представляют номера в качестве остатков, которые когда-то разделились на «модуль». Другими словами, цифры могут увеличиться до тех пор, пока они не достигнут установленного модуля и начать обертывание вокруг: 5 модуль 3 равен 2, 25 модуль 2 равен 1. Большинство часов находятся в системе модуло-12.

Для LCG нам нужен модуль, м исходное значение (или «семя»), х 0 , множитель, А и приращение, c . Оттуда, чтобы получить от одного числа x n к следующему х n + 1. Вы просто выполняете следующий алгоритм:

Давайте напишем это как функцию в Python, а затем назначить некоторые простые значения: модуль 10, множитель из 3, приращение 1 и значение семян 5.

def lcg(n, m, a, c, seed):
    sequence = []
    Xn = seed
    for i in range(n):
        Xn = (a*Xn + c) % m
        sequence.append(Xn)
    return(sequence)

lcg(10, 10, 3, 1, 5)
# => [6, 9, 8, 5, 6, 9, 8, 5, 6, 9]

Как видите, мы попросили десять случайных чисел и получили последовательность [6, 9, 8, 5, 6, 9, 8, 5, 6, 9] . Теперь, когда это проходит требования ЖГЧ, это действительно ужасно PRNG. Прежде всего, в модуле 10 существует только десять возможных значений, которые можно взять номер, и в нашем случае в нашем случае есть нерушимый 6-9-8-5 петлей, который дополнительно уменьшает место нашего состояния до четырех номеров.

То, что нам нужно, это гораздо больший модуль и уверенность в том, что каждое возможное число будет произведено один раз по любому вводу семян, или что известно как «полный период». ” Введите расширенный номер чисел. Теорема HULL и DOBELL, LCG будет иметь полный период, если:

  1. c и м относительно простые (то есть единственное положительное целое число, которое разделяет оба c и м 1).

  2. Если Q любое простое число, которое делит м Тогда Q также делится A – 1 Отказ

  3. Если 4 делится м Тогда 4 также делится A – 1 Отказ

Вы можете прочитать Оригинальная статья 1962 года, доказывая эту теорему здесь Отказ Вместо того, чтобы пытаться придумать некоторые числа, которые работают, мы будем украсть те, которые фактически используются для генерации случайных чисел на некоторых языках, включая C ++: м 32 , А , c .

Но перед тем, как попробовать эти новые входы, нам нужно решить детерминированную природу нашего LCG: подключите одно и то же семена, и вы получите тот же вывод, какой вид недействителен любые чувства случайности, которое мы требовали. Это фундаментальный недостаток в PRONGS в целом.

Тем не менее, есть истечение к их предсказуемости – а именно, что постановка и разделяя семя, можно создать воспроизводимые результаты, которые имеют решающее значение в науках – а также несколько способов вокруг него. Привязывая семя к чему-то невидимым и постоянно меняющимся, как текущее количество микросекунтов прошло второго, мы можем реинзнуть некоторую искусственную случайность. Так наряду с нашими новыми входами мы будем использовать встроенный Python datetime Библиотека для нашего семени:

from datetime import datetime
lcg(10, 2**32, 11695477, 1, datetime.now().microsecond)
# => [1090430687, 498347756, 2363706845, 1780651778, 1345777131,
#        826090344, 275756681, 2092763550, 396794679,3763540772]

Это выглядит намного лучше. Обычно, однако, мы не столько заинтересованы в случайном количестве от 0 до 2 32 , а скорее случайное число между 0 и 1 – стандартное равномерное распределение U (0,1) Отказ Поскольку это распределение ограничено в 0 и 1, он может быть умножен на более крупные числа для создания более крупных равномерных распределений, а также плавно отображается к вероятности пробелов, что пригодится позже.

Разделив наш вывод модулем (или, быть точным, модулем минус 1), мы можем перенести наши случайные числа на строку от 0 до 1. Технически, только конечная группа чисел может быть выведена, поэтому мы не достигли истинного равномерного распределения, где возможно каждое мыслимое реальное число между 0 и 1, и нет точечной массы в любом месте. Но это различие незначительно с большим количеством, как 2 32 Отказ Давайте переписаним нашего LCG для выпрыснуть номера от 0 до 1, а затем график последовательности тысячи случайных чисел по сравнению с тысячами случайных чисел, создаваемых встроенным Python Случайные () Функция:

from random import random
import matplotlib.pyplot as plt

def uni(n, m, a, c, seed):
    sequence = []
    Xn = seed
    for i in range(n):
        Xn = ((a*Xn + c) % m)
        sequence.append(Xn/float(m-1))
    return(sequence)

x = range(1000)
y_1 = uni(1000, 2**32, 11695477, 1, datetime.now().microsecond)
y_2 = [random() for i in range(1000)]

plt.plot(x, y_1, "o", color="blue")
plt.show()

plt.plot(x, y_2, "o", color="red")
plt.show()

Слева – тысяча случайных чисел, графируемых в последовательности, мы производим их, а справа выделяются встроенным Python встроенным Случайные () Функция, которая, для записи, опирается на Mersenne Twister , относительно современный алгоритм, который сегодня является золотым стандартом для PRNGS. В то время как любой компьютерный ученый скажет вам, что второй алгоритм гораздо более надежный, невооруженным глазом, который они кажутся сопоставимыми.

Более того, стандартное равномерное распределение действует как шлюз для всех видов других, более сложных распределений, поскольку он работает на вероятности расстояния от 0 до 1 (все имеет где-то между 0% и 100% вероятностью происхождения). Так что до тех пор, пока мы можем найти способ сопоставить наши события в равномерное распространение, мы можем случайным образом образец от них.

В качестве простого примера мы можем имитировать переворачивать монету из равномерного распределения, вызвав любую случайное число, превышающее 0,5 голов и что-то меньше хвоста. Вот функция, которая принимает несколько листов и возвращает, сколько головок появилось (формально, биномиальное распределение с N Испытания и P 0,5). Мы будем запускать тысячу испытаний сотен листьев каждая и построить гистограмму с результатами:

def coin_flips(n):
    flips = uni(n, 2**32, 11695477, 1, datetime.now().microsecond)
    heads = sum([i<0.5 for i in flips])
    return heads

trials = [coin_flips(100) for i in range(1000)]

plt.hist(trials, bins=range(min(trials), max(trials)))
plt.show()

Как видите, наши пробные данные являются однородными и почти симметричными, которые мы ожидали от биномальной случайной переменной.

Теперь, как мы уже упоминали, эти PRNG неоспоримо недоступны. Они детерминированы и не действительно униформа. Знаменитый математик и компьютерный ученый Джон Фон Нейман однажды сказал, что «любой, кто считает арифметические методы получения случайных цифр, конечно, в состоянии греха».

Тем не менее, я нахожу это довольно замечательным, что с добавлением и умножением, самые основные математические строительные блоки, мы можем приблизить образец из случайной вариабели, такой как биномиальное распределение.

Оригинал: “https://dev.to/walker/pseudo-random-numbers-in-python-from-arithmetic-to-probability-distributions”