Генераторы Дискретно Распределенных Случайных Величин

Эта статья является продолжением поста Генераторы непрерывно распределенных случайных величин .

В этой главе учтено, что все теоремы из предыдущей статьи уже доказаны и указанные в ней генераторы уже реализованы.

Как и раньше, у нас есть базовый генератор натуральных чисел от 0 до RAND_MAX:

  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
   

unsigned long long BasicRandGenerator() { unsigned long long randomVariable; // some magic here .

return randomVariable; }

С дискретными величинами все более интуитивно понятно.

Функция распределения дискретной случайной величины:

Генераторы дискретно распределенных случайных величин

Несмотря на простоту распределений дискретных случайных величин, генерировать их иногда сложнее, чем непрерывные.

Начнем, как и в прошлый раз, с тривиального примера.



Распределение Бернулли



Генераторы дискретно распределенных случайных величин



Генераторы дискретно распределенных случайных величин

Возможно, самый быстрый и очевидный способ сгенерировать случайную величину с распределением Бернулли — сделать это следующим образом (все генераторы возвращают двойное значение только для согласованности интерфейса):

void setup(double p) { unsigned long long boundary = (1 - p) * RAND_MAX; // we storage this value for large sampling } double Bernoulli(double) { return BasicRandGenerator() > boundary; }

Иногда можно сделать это быстрее.

«Иногда» означает «в случае, когда p представляет собой степень 1/2».

Дело в том, что если целое число, возвращаемое функцией BasicRandGenerator(), является равномерно распределенной случайной величиной, то каждый ее бит распределен равномерно.

Это означает, что в двоичном представлении число состоит из битов, распределенных по Бернулли.

Поскольку в этих статьях функция базового генератора возвращает unsigned long long, у нас есть 64 бита.

Вот трюк, который можно проделать для p = 1/2:

double Bernoulli(double) { static const char maxDecimals = 64; static char decimals = 1; static unsigned long long X = 0; if (decimals == 1) { /// refresh decimals = maxDecimals; X = BasicRandGenerator(); } else { --decimals; X >>= 1; } return X & 1; }

Если время работы функции BasicRandGenerator() недостаточно короткое, а криптоустойчивостью и размером периода генератора можно пренебречь, то для таких случаев существует алгоритм, использующий только одну равномерно распределенную случайную величину для любого объема выборки из распределения Бернулли:

void setup(double p) { q = 1.0 - p; U = Uniform(0, 1); } double Bernoulli(double p) { if (U > q) { U -= q; U /= p; return 1.0; } U /= q; return 0.0; }

Почему этот алгоритм работает?

Генераторы дискретно распределенных случайных величин



Генераторы дискретно распределенных случайных величин



Равномерное распределение



Генераторы дискретно распределенных случайных величин



Генераторы дискретно распределенных случайных величин

Я уверен, что любой из вас помнит, что его первый генератор случайных чисел от a до b выглядел так:

double UniformDiscrete(int a, int b) { return a + rand() % (b - a + 1); }

Что ж, это вполне правильное решение.

С единственным и не всегда верным предположением — у вас есть хороший базовый генератор.

Если он неисправен, как старый C rand(), то вы каждый раз будете получать четное число после нечетного.

Если вы не доверяете своему генератору, то лучше написать так:

double UniformDiscrete(int a, int b) { return a + round(BasicRandGenerator() * (b - a) / RAND_MAX); }

Также следует отметить, что распределение не будет полностью равномерным, если RAND_MAX не будет делиться на длину интервала b - a + 1. Однако разница не будет столь существенной, если эта длина достаточно мала.



Геометрическое распределение



Генераторы дискретно распределенных случайных величин



Генераторы дискретно распределенных случайных величин

Геометрически распределенная случайная величина с параметром p — это экспоненциально распределенная случайная величина с параметром -ln(1 — p), округленная до ближайшего целого числа.

Доказательство Пусть W — случайная величина, распределенная экспоненциально с параметром -ln(1 - p).

Затем:

Генераторы дискретно распределенных случайных величин



void setupRate(double p) { rate = -ln(1 - p); } double Geometric(double) { return floor(Exponential(rate)); }

Можно ли это сделать быстрее? Иногда.

Давайте посмотрим на функцию распределения:

Генераторы дискретно распределенных случайных величин

и воспользуемся обычным методом инверсии: генерируем стандартную равномерно распределенную случайную величину U и возвращаем минимальное значение k, при котором эта сумма больше U:

double GeometricExponential(double p) { int k = 0; double sum = p, prod = p, q = 1 - p; double U = Uniform(0, 1); while (U < sum) { prod *= q; sum += prod; ++k; } return k; }

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

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

Есть секрет. Значения p, (1-p) * p, (1-p)^2 * p,… можно вычислить заранее и запомнить.

Вопрос в том, где остановиться.

И здесь вступает в действие свойство геометрического распределения, которое оно унаследовало от экспоненциального – недостаток памяти:

Генераторы дискретно распределенных случайных величин

Благодаря этому свойству вы можете запомнить только несколько первых значений (1-p)^i * p, а затем использовать рекурсию:

// works nice for p > 0.2 and tableSize = 16 void setupTable(double p) { table[0] = p; double prod = p, q = 1 - p; for (int i = 1; i < tableSize; ++i) { prod *= q; table[i] = table[i - 1] + prod; } } double GeometricTable(double p) { double U = Uniform(0, 1); /// handle tail by recursion if (U > table[tableSize - 1]) return tableSize + GeometricTable(p); /// handle the main body int x = 0; while (U > table[x]) ++x; return x; }



Биномиальное распределение



Генераторы дискретно распределенных случайных величин



Генераторы дискретно распределенных случайных величин

По определению случайная величина с биномиальным распределением — это сумма n случайных величин с распределением Бернулли:

double BinomialBernoulli(double p, int n) { double sum = 0; for (int i = 0; i != n; ++i) sum += Bernoulli(p); return sum; }

Однако существует линейная зависимость от n, которую необходимо обойти.

Вы можете использовать теорему: если Y_1, Y_2,.

— случайные величины с геометрическим распределением с параметром p, а X — наименьшее целое число, такое, что:

Генераторы дискретно распределенных случайных величин

тогда X имеет биномиальное распределение.

Доказательство По определению, случайная величина с геометрическим распределением Y — это количество экспериментов Бернулли до первого успеха.

Существует альтернативное определение: Z = Y + 1 — количество экспериментов Бернулли до первого успеха включительно.

Это означает, что сумма независимых Z_i представляет собой количество экспериментов Бернулли до X + 1 успеха включительно.

И эта сумма больше n тогда и только тогда, когда среди первых n тестов не более X успешных.

Соответственно:

Генераторы дискретно распределенных случайных величин

q.e.d. Время выполнения следующего кода увеличивается только с увеличением значения n * p.

double BinomialGeometric(double p, int n) { double X = -1, sum = 0; do { sum += Geometric(p) + 1.0; ++X; } while (sum <= n); return X; }

И все же, временная сложность увеличивается.

Биномиальное распределение имеет одну особенность.

По мере роста n оно стремится к нормальному распределению или, если p ~ 1/n, то к распределению Пуассона.

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

Но что, если этого недостаточно? В книге Люка Девроя «Неравномерная генерация случайных переменных» есть пример алгоритма, который работает одинаково быстро для любых больших значений n*p. Идея алгоритма заключается в выборке с дисперсией с использованием нормального и экспоненциального распределений.

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



Распределение Пуассона



Генераторы дискретно распределенных случайных величин



Генераторы дискретно распределенных случайных величин

Если W_i — случайная величина со стандартным экспоненциальным распределением с лямбда-плотностью, то:

Генераторы дискретно распределенных случайных величин

Доказательство Пусть f_k(y) будет плотностью стандартного распределения Эрланга (сумма k стандартных экспоненциально распределенных случайных величин):

Генераторы дискретно распределенных случайных величин

Затем

Генераторы дискретно распределенных случайных величин

и вероятность получения k:

Генераторы дискретно распределенных случайных величин

совпадает с распределением Пуассона.

q.e.d. Используя это свойство, можно написать генератор в виде суммы экспоненциально распределенных величин со степенью плотности:

double PoissonExponential(double rate) { int k = -1; double s = 0; do { s += Exponential(1); ++k; } while (s < rate); return k; }

Популярный алгоритм Кнута основан на том же свойстве.

Вместо суммы показательных переменных, каждая из которых может быть получена обращением через -ln(U), используется произведение равномерных случайных величин:

Генераторы дискретно распределенных случайных величин

а потом:

Генераторы дискретно распределенных случайных величин

Заранее запомнив значение exp(-rate), мы последовательно умножаем U_i до тех пор, пока произведение не превысит его.



void setup(double rate) expRateInv = exp(-rate); } double PoissonKnuth(double) { double k = 0, prod = Uniform(0, 1); while (prod > expRateInv) { prod *= Uniform(0, 1); ++k; } return k; }

Можно использовать генерацию только одной случайной величины и метод инверсии:

Генераторы дискретно распределенных случайных величин

Поиск k, удовлетворяющего этому условию, лучше начинать с наиболее вероятного значения, то есть с Floor(rate).

Сравниваем U с вероятностью того, что X < floor(rate) and go up if U is greater, or down otherwise:

void setup(double rate) floorLambda = floor(rate); FFloorLambda = F(floorLamda); // P(X < floorLambda) PFloorLambda = P(floorLambda); // P(X = floorLambda) } double PoissonInversion(double) { double U = Uniform(0,1); int k = floorLambda; double s = FFloorLambda, p = PFloorLambda; if (s < U) { do { ++k; p *= lambda / k; s += p; } while (s < U); } else { s -= p; while (s > U) { p /= lambda / k; --k; s -= p; } } return k; }

Проблема со всеми тремя генераторами одна — их сложность растёт вместе с параметром плотности.

Но есть спасение — при больших значениях плотности распределение Пуассона стремится к нормальному распределению.

Также можно использовать сложный алгоритм из упомянутой выше книги «Неравномерная генерация случайных величин», или просто приблизительный, пренебрегая точностью во имя скорости (в зависимости от того, какая задача).



Отрицательное биномиальное распределение



Генераторы дискретно распределенных случайных величин



Генераторы дискретно распределенных случайных величин

Отрицательное биномиальное распределение также называют распределением Паскаля (если r является целым числом) и распределением Филда (если r может быть действительным).

Используя характеристическую функцию, легко доказать, что распределение Паскаля представляет собой сумму r геометрически распределенных значений с параметром 1 – p:

double Pascal(double p, int r) { double x = 0; for (int i = 0; i != r; ++i) x += Geometric(1 - p); return x; }

Проблема такого алгоритма очевидна — линейная зависимость от r. Нам нужно что-то, что будет одинаково хорошо работать в любых условиях.

И отличная недвижимость нам в этом поможет. Если случайная величина X имеет распределение Пуассона:

Генераторы дискретно распределенных случайных величин

где плотность случайна и имеет гамма-распределение:

Генераторы дискретно распределенных случайных величин

тогда X имеет отрицательное биномиальное распределение.

Поэтому его иногда называют гамма-распределением Пуассона.

Доказательство

Генераторы дискретно распределенных случайных величин

Теперь мы можем быстро написать генератор случайных величин с распределением Паскаля для больших значений параметра r.

double Pascal(double p, int r) { return Poisson(Gamma(r, p / (1 - p))); }



Гипергеометрическое распределение



Генераторы дискретно распределенных случайных величин



Генераторы дискретно распределенных случайных величин

Представьте себе, что в урне N шаров и K из них белые.

Вы рисуете n шаров.

Число белых среди них будет иметь гипергеометрическое распределение.

В общем, лучше использовать алгоритм, использующий это определение:

double HyperGeometric(int N, int n, int K) { double sum = 0, p = static_cast<double>(K) / N; for (int i = 1; i <= n; ++i) { if (Bernoulli(p) && ++sum == K) return sum; p = (K - sum) / (N - i); } return sum; }

Или вы можете использовать выборку с отклонением через биномиальное распределение с параметрами:

Генераторы дискретно распределенных случайных величин

и постоянная М:

Генераторы дискретно распределенных случайных величин

Алгоритм биномиального распределения хорошо работает в крайних случаях больших n и даже больших N (таких, что n << N).

For the rest, it is better to use the previous one, despite its growing complexity with increasing input parameters.

Логарифмическое распределение



Генераторы дискретно распределенных случайных величин



Генераторы дискретно распределенных случайных величин

Для этого распределения существует полезная теорема.

Если U и V — равномерно распределенные случайные величины от 0 до 1, то величина

Генераторы дискретно распределенных случайных величин

будет иметь логарифмическое распределение.

Доказательство Вспомнив генераторы экспоненциального и геометрического распределений, легко увидеть, что X, заданный формулой выше, удовлетворяет следующему распределению:

Генераторы дискретно распределенных случайных величин

Докажем, что X имеет логарифмическое распределение:

Генераторы дискретно распределенных случайных величин

q.e.d. Чтобы ускорить алгоритм, можно использовать два приема.

Первое: если V > p, то X = 1, поскольку p > = 1-(1-p)^U. Второе: пусть q = 1-(1-p)^U, тогда если V > q, то X = 1, если V > q^2, то X = 2 и т. д. Таким образом можно вернуть наиболее вероятные значения 1 и 2 без необходимости часто считать логарифмы.



void setup(double p) { logQInv = 1.0 / log(1.0 - p); } double Logarithmic(double p) { double V = Uniform(0, 1); if (V >= p) return 1.0; double U = Uniform(0, 1); double y = 1.0 - exp(U / logQInv); if (V > y) return 1.0; if (V <= y * y) return floor(1.0 + log(V) / log(y)); return 2.0; }



Зета-распределение



Генераторы дискретно распределенных случайных величин

Знаменатель – дзета-функция Римана:

Генераторы дискретно распределенных случайных величин



Генераторы дискретно распределенных случайных величин

Для дзета-распределения существует алгоритм, позволяющий избежать вычисления дзета-функции Римана.

Вам просто нужно уметь генерировать распределение Парето.

По просьбе читателей могу приложить доказательства.



void setup(double s) { double sm1 = s - 1.0; b = 1 - pow(2.0, -sm1); } double Zeta(double) { /// rejection sampling from rounded down Pareto distribution int iter = 0; do { double X = floor(Pareto(sm1, 1.0)); double T = pow(1.0 + 1.0 / X, sm1); double V = Uniform(0, 1); if (V * X * (T - 1) <= T * b) return X; } while (++iter < 1e9); return NAN; /// doesn't work }

Наконец, небольшие алгоритмы для других сложных распределений:

double Skellam(double m1, double m2) { return Poisson(m1) - Poisson(m2); } double Planck(double a, double b) { double G = Gamma(a + 1, 1); double Z = Zeta(a + 1) return G / (b * Z); } double Yule(double ro) { double prob = 1.0 / Pareto(ro, 1.0); return Geometric(prob) + 1; }

Реализация этих и других генераторов на C++17. Теги: #случайная величина #генераторы случайных чисел #теория вероятностей #дискретное распределение #случайность не случайна #программирование #Алгоритмы #математика

Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.