случайно в диапазоне, присутствует ли смещение числа для новой версии rand ()?

Читая различные другие вопросы SO, при использовании rand ()% N может случиться, что вы измените смещение для получаемого вами псевдонима, поэтому вам обычно приходится вводить некоторую обработку диапазона.

Однако во всех случаях всегда упоминалась rand (), а не новые функции random () или arcrandom4 () или нативные методы C ++ 11. Что происходит, когда вы запускаете эти процедуры через набор? Вы получаете предвзятость, как RAND ()?

Благодарю.

2

Решение

Что происходит, когда вы запускаете эти процедуры через набор? Вы получаете уклон
как rand ()?

Ответ таков: это зависит от соотношения между размером диапазона, возвращаемого генератором, и делителем при работе по модулю. Если делитель неравномерно делит диапазон, распределение будет искажено. Отношение смещения находится в диапазоне [1, 2], где 1 означает отсутствие смещения (как для равномерного распределения), и смещение увеличивается с делителем. относительно arcrandom4() это приводит к искаженному распределению, полученному во всех случаях, когда делитель по модулю не является четным делителем 2 ^ 32. Обоснование этого объясняется ниже.


Вступление. Уклон

Представьте, что мы пытаемся смоделировать равномерное распределение int на интервале [0, 99] с

int x = rand() % 100;

Оператор% делает распределение вероятности X перекошенным, потому что RAND_MAX, который является максимальным значением для rand (), может быть не равен k * 100 + 99. Это приводит к тому, что, если вы представите все части длиной 100 в диапазоне 0-RAND_MAX, то вы можете видим, что последняя часть, вероятно, не даст полный диапазон 0-99. Поэтому у вас есть больше чисел, которые генерируют 0, 1, 2 …, p, но не обязательно p + 1, …, 98, 99 (еще 1 вхождение для каждого числа в 0, 1, 2, …, p ). Неточность этого подхода увеличивается с увеличением делитель, который не делит диапазон равномерно и максимальный уклон по сравнению с равномерным распределением равен 2.

В следующих разделах ниже мы показываем, что смещение, измеренное как отношение вероятности получения числа из [0, p] к вероятности числа из [p + 1, n], равно (к + 1) / к и мы подтверждаем это 2 примерами.


формула

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

x = rand() % ( n + 1)

где rand() какой-то генератор и ( n + 1) делитель в операции по модулю. Картинка ниже показывает нашу точку зрения:

введите описание изображения здесь

Мы можем видеть, как числа в диапазоне [ 0, n] делятся на те, которые повторяют k + 1 раз (числа [ 0, p]) и это повторяется k раз (числа [ p + 1, n]) в одном испытании, которое «взять число из распределения, полученного x = rand() % (n+1)«. п определяется как остаток при делении максимального числа (то есть Rand_MAX), заданного генератором, на (n + 1), которое является размером желаемого диапазона:

p = (N — 1)% (n + 1)

N — 1 = k * (n + 1) + p

и К это частное

k = (N — 1 — p) / (n + 1)

В одном испытании есть

(p + 1) * (k + 1) + (n — p) * k =

= p + 1 + k (n + 1) = N

возможные результаты. Таким образом, вероятность получения элемента, который повторяется k раз, равна k / N. Обозначим

f_0 = (k + 1) / N, вероятность для каждого элемента из [0, p]

f_1 = k / N, вероятность для каждого элемента из [p + 1, n]

Допустим, что мы будем выражать смещение выборки из этого, преобразованного распределения по равномерному распределению как отношение вероятности элемента, который принадлежит [ 0, p] к вероятности элемента из диапазона [ p + 1, n]:

смещение = f_0 / f_1 = (k + 1) / k

Итак, числа вдвое чаще?

Нет. Тот факт, что когда мы смотрим на повторяемость чисел на снимках, не подразумевает отношение 2. Это отношение является особым случаем, если диапазон генератора делится ровно на 2 поддиапазона. В общем случае отношение смещения составляет (k + 1) / k и асимптотически уменьшается, когда делитель n + 1 стремится к 1 (а k стремится к N).


Примеры

Теперь рассмотрим два простых примера (как предложено @dyp). Сначала мы сгенерируем 1000 * 1000 образцов из распределения, заданного

x = rand ()% m

с генератором std::uniform_int_distribution<> dist(0, 19) и делитель m = n + 1 равен 15, а следующий равен 6.

Пример 1

int x = rand() % 15; // n + 1 = 15, rand is uniform distribution over [0,19]

Тестовая программа:

#include <iostream>
#include <random>
#include <vector>

int main()
{
std::random_device rd;
std::mt19937 mt(rd());
std::uniform_int_distribution<> dist(0, 19);
std::vector<int> v(15);
const int runs = 1000 * 1000;
for (int i = 0; i < runs; ++i)
{
++v[dist(mt) % v.size()];
}

for (int i = 0; i < v.size(); ++i)
{
std::cout << i << ": " << v[i] << "\n";
}
}

код

результат:

0: 100500
1: 100016
2: 99724
3: 99871
4: 99936
5: 50008
6: 49762
7: 50023
8: 50123
9: 49963
10: 50117
11: 50049
12: 49885
13: 49760
14: 50263

Мы видим, что в этом случае числа в диапазоне [0, p] = [0, 4] появляются примерно в два раза чаще, чем остальные. Это соответствует нашей формуле смещения

смещение = f_0 / f_1 = (k + 1) / k = 2/1

Пример 2

int x = rand() % 6; // n + 1 = 6, rand is uniform distribution over [0,19]

Тестовая программа:

#include <iostream>
#include <random>
#include <vector>

int main()
{
std::random_device rd;
std::mt19937 mt(rd());
std::uniform_int_distribution<> dist(0, 19);
std::vector<int> v(6);
const int runs = 1000 * 1000;
for (int i = 0; i < runs; ++i)
{
++v[dist(mt) % v.size()];
}

for (int i = 0; i < v.size(); ++i)
{
std::cout << i << ": " << v[i] << "\n";
}
}

код

результат:

0: 199875
1: 199642
2: 149852
3: 149789
4: 150237
5: 150605

В этом случае мы видим, что числа в диапазоне [0, p] = [0, 1] появляются не вдвое чаще, чем остальные, но в соотношении примерно 20/15. На самом деле это 4/3, так как наша формула смещения в этом случае

смещение = f_0 / f_1 = (k + 1) / k = 4/3

Картинка ниже помогает понять этот результат.

введите описание изображения здесь

полный код

3

Другие решения

Следующий ответ не будет вдаваться в подробности Блог Эрика Липперта на ту же тему. Также, этот вопрос и его ответы иметь дело с той же темой.

Большая часть предвзятости, которая исходит от выполнения rand() % N не из rand() часть — это из % N часть.

Давайте рассмотрим «хорошую» реализацию rand (), которая генерирует все числа от 0 до 100 (выбранные для простоты) с равной вероятностью — равномерное распределение. Далее, скажем, что мы хотим использовать эту реализацию rand () для генерации случайных чисел от 0 до 80, поэтому мы делаем rand() % 80, Давайте разберем возможности того, что может произойти дальше:

  1. rand () генерирует число от 0 до 79. Любое число от 0 до 79% 80 остается неизменным
  2. rand () генерирует число от 80 до 100. Любое число от 80 до 100% 80 преобразуется в 0 до 20

Это означает, что есть два пути в конечном итоге с числом от 0 и 20, но только в одну сторону в конечном итоге с номером от 21 до 79. Получение числа от 0 до 20 более вероятно чем получить число от 21 до 79. Обычно это нежелательное свойство.

Любое значение N, которое делится равномерно на максимальное значение rand (), не будет иметь этой проблемы, потому что будет одинаковое количество способов генерировать любое значение. Кроме того, смещение намного меньше для небольших значений N, чем для значений N, близких к максимальному значению rand ().

Итак, что насчет функций, отличных от rand ()? Если они возвращают значения из некоторого фиксированного диапазона, и вы выполняете операцию мод, они будут страдать от того же смещения. Если вы вызываете случайную функцию, которая принимает диапазон в качестве аргументов, вам не нужно выполнять операцию мода. Функция, вероятно, будет обрабатывать любые смещения внутри страны.

6

C ++ 11 решил эту проблему, добавив альтернативные генераторы случайных чисел.

Причина, по которой использование% (по модулю) для ограничения вашего случайного числа диапазоном является плохой, связана не столько со смещением, сколько с типичной реализацией rand (), линейного конгруэнтного генератора (LCG). Большинство языковых сред выполнения используют LCG для своей случайной функции; только очень недавно разработанные языки имеют тенденцию отличаться.

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

Понимая различные генераторы случайных чисел (linear_congruential_engine, mersenne_twister_engine, subtract_with_carry_engine), вы сможете найти лучший вариант для вашего приложения.

есть очень хорошая ссылка на новые реализации C ++ в Случайные Двигатели в C ++ 11

Как сказал @dpy, std ::iform_int_distribution — это опция, предоставляемая c ++ для случайных распределений. Он рассматривает проблему смещения, даже если двигатель случайного генератора имеет. Но если вы установите диапазон от 1 до 19 и сохраните его в массиве размером 15 с помощью операции%, проблема смещения снова появится, как обсуждалось во многих постах здесь.

3
По вопросам рекламы ammmcru@yandex.ru
Adblock
detector