Створення випадкових чисел після нормального розподілу в C / C ++


114

Як я можу легко генерувати випадкові числа після нормального розподілу в C або C ++?

Я не хочу використовувати Boost.

Я знаю, що Кнут розповідає про це досить довго, але зараз я не маю під рукою його книг.


Відповіді:


92

Існує багато методів генерування чисел, розподілених Гауссом, із звичайної RNG .

Перетворення Бокса-Мюллера зазвичай використовується. Він правильно виробляє значення при нормальному розподілі. Математика проста. Ви генеруєте два (рівномірні) випадкові числа, застосовуючи до них формулу, ви отримуєте два нормально розподілених випадкових числа. Поверніть одне, а інший збережіть для наступного запиту для випадкового числа.


10
Якщо вам потрібна швидкість, тоді полярний метод швидший. А алгоритм Зігурата ще більше (хоч і набагато складніше писати).
Джої

2
знайшов тут реалізацію Ziggurat people.sc.fsu.edu/~jburkardt/c_src/ziggurat/ziggurat.html Це досить повно.
dwbrito

24
Зауважте, C ++ 11 додає, std::normal_distributionщо робить саме те, що ви просите, не заглиблюючись у математичні деталі.

3
std :: normal_distribution не гарантовано є послідовним на всіх платформах. Я зараз роблю тести, і MSVC надає інший набір значень, наприклад, Clang. Двигуни C ++ 11, схоже, генерують однакові послідовності (даються однакові основи), але розподіли C ++ 11, схоже, реалізуються за допомогою різних алгоритмів на різних платформах.
Арно Дювенхаге

47

C ++ 11

C ++ 11 пропозицій std::normal_distribution, який я б пішов сьогодні.

C або старші C ++

Ось декілька рішень у порядку зростання складності:

  1. Додайте 12 рівномірних випадкових чисел від 0 до 1 і відніміть 6. Це буде відповідати середньому та стандартному відхиленню нормальної змінної. Очевидним недоліком є ​​те, що діапазон обмежений ± 6 - на відміну від справжнього нормального розподілу.

  2. Перетворення Box-Muller. Це перераховано вище і реалізується порівняно просто. Якщо вам потрібні дуже точні зразки, майте на увазі, що трансформація Box-Muller у поєднанні з деякими рівномірними генераторами страждає на аномалію під назвою Neave Effect 1 .

  3. Для кращої точності я пропоную намалювати обмундирування та застосувати зворотний кумулятивний нормальний розподіл для досягнення нормально розподілених змінних. Ось дуже хороший алгоритм зворотних кумулятивних нормальних розподілів.

1. HR Neave, “Про використання трансформації Box-Muller з мультиплікативними вродженими псевдовипадковими генераторами чисел”, Прикладна статистика, 22, 92-97, 1973


Ви випадково отримаєте ще одне посилання на pdf про ефект Neave? або оригінал посилання на статтю журналу? дякую
pyCthon

2
@stonybrooknick Оригінальна посилання додана. Класне зауваження: Хоча гуглінг "box muller neave", щоб знайти посилання, це дуже стаціонарне запитання виникло на першій сторінці результатів!
Петро Григорович

так, це не всі добре відомі поза певними невеликими спільнотами та групами інтересів
pyCthon

@Peter G. Чому хтось підтримає вашу відповідь? - можливо, той самий чоловік теж зробив свій коментар нижче, з чим я добре, але я вважав, що ваша відповідь була дуже хорошою. Було б добре, якби так, що зроблені низовики викликають реальний коментар.
Pete855217

"Додайте 12 рівномірних чисел від 0-1 і віднімайте 6." - розподіл цієї змінної матиме нормальний розподіл? Чи можете ви надати зв'язок із деривацією, тому що під час виведення центральної граничної теореми n -> + inf дуже потрібне припущення.
bruziuz

31

Швидкий і простий метод - це просто підбити кількість рівномірно розподілених випадкових чисел і взяти їх середнє значення. Щоб отримати повне пояснення, чому це працює, дивіться теорему про центральну межу .


+1 Дуже цікавий підхід. Чи перевірено, чи дійсно дають нормально розподілені підсистеми для менших груп?
Морлок

4
@Morlock Чим більша кількість зразків ви середньо оцінюєте, тим ближче до наближення до Гауссової дистрибуції. Якщо у вашій заяві суворі вимоги щодо точності розподілу, можливо, вам буде краще використовувати щось більш суворе, наприклад Box-Muller, але для багатьох застосувань, наприклад, генеруючи білий шум для аудіоприкладних програм, ви можете піти з досить невеликою кількістю усереднених зразків (наприклад, 16).
Пол Р

2
Плюс, як ви параметризуєте це, щоб отримати певну кількість дисперсії, скажімо, що ви хочете середнє значення 10 при стандартному відхиленні 1?
Морлок

1
@Ben: ти міг би вказати мені на ефективне альго для цього? Я лише коли-небудь використовував техніку усереднення для генерування приблизно гауссового шуму для обробки аудіо та зображень із обмеженнями в режимі реального часу - якщо є спосіб досягти цього за менший цикл годин, то це може бути дуже корисним.
Пол Р

1
@Petter: ви, мабуть, праві в загальному випадку для значень з плаваючою комою. Ще є такі області застосувань, як аудіо, але там, де потрібно швидкий цілий (або фіксований) гауссовий шум, а точність не надто важлива, де простий метод усереднення є більш ефективним і корисним (особливо для вбудованих додатків, де можливо навіть немає бути апаратною підтримкою з плаваючою комою).
Пол Р

24

Я створив проект C ++ з відкритим кодом для нормально розподіленого показника генерації випадкових чисел .

Він порівнює кілька алгоритмів, в тому числі

  • Метод центральної граничної теореми
  • Перетворення коробки-Мюллера
  • Марсаглійський полярний метод
  • Алгоритм Зигурата
  • Метод зворотного відбору проб.
  • cpp11randomвикористовує C ++ 11 std::normal_distributionз std::minstd_rand(це фактично перетворення Box-Muller в кланге).

Результати одноточної ( float) версії на iMac Corei5-3330S@2.70GHz, кланг 6.1, 64-розрядні:

normaldistf

Для коректності програма перевіряє середнє, середнє відхилення, нахил та куртоз зразків. Було встановлено, що метод CLT шляхом підсумовування 4, 8 або 16 однакових чисел не має гарного куртозу, як інші методи.

Алгоритм Ziggurat має кращі показники, ніж інші. Однак він не підходить для паралелізму SIMD, оскільки йому потрібен пошук таблиці та гілок. Box-Muller з набором інструкцій SSE2 / AVX набагато швидше (x1.79, x2.99), ніж не-SIMD-версія алгоритму ziggurat.

Тому я запропоную використовувати Box-Muller для архітектури з наборами інструкцій SIMD, і, можливо, інакше зіггурат.


У PS еталоні використовується найпростіший LCG PRNG для генерації рівномірних розподілених випадкових чисел. Тому для деяких застосувань це може бути недостатньо. Але порівняння продуктивності повинно бути справедливим, оскільки всі реалізації використовують один і той же PRNG, тому тест в основному тестує ефективність трансформації.


2
"Але порівняння продуктивності повинно бути справедливим, тому що всі реалізації використовують один і той же PRNG". За винятком того, що BM використовує один вхідний RN на вихід, тоді як CLT використовує багато більше, тощо ... так що час для генерації рівномірних випадкових # питань.
greggo

14

Ось приклад C ++ на основі деяких посилань. Це швидко і брудно, вам краще не вигадувати та використовувати бібліотеку підвищення.

#include "math.h" // for RAND, and rand
double sampleNormal() {
    double u = ((double) rand() / (RAND_MAX)) * 2 - 1;
    double v = ((double) rand() / (RAND_MAX)) * 2 - 1;
    double r = u * u + v * v;
    if (r == 0 || r > 1) return sampleNormal();
    double c = sqrt(-2 * log(r) / r);
    return u * c;
}

Ви можете використовувати графік QQ, щоб вивчити результати та побачити, наскільки добре він наближає до реального нормального розподілу (класифікуйте ваші вибірки 1..x, перетворіть ранги на пропорції від загального підрахунку х, тобто скільки зразків, отримайте z-значення та побудувати їх. Вгору пряма лінія - бажаний результат).


1
Що таке зразокNormalManual ()?
розв’язання Пузлів

@solvingPuzzles - вибачте, виправив код. Це рекурсивний дзвінок.
Pete855217

1
Це неодмінно зазнає краху на якійсь рідкісній події (демонстрація додатку вашому начальникові дзвонить у дзвін?). Це має бути реалізовано за допомогою циклу, не використовуючи рекурсії. Метод виглядає незнайомим. Яке джерело / як воно називається?
свиня

Box-Muller транскрибував з реалізації Java. Як я вже сказав, це швидко і брудно, сміливо виправляйте.
Pete855217

1
За FWIW, багато компіляторів зможуть перетворити конкретний рекурсивний виклик у «стрибок до вершини функції». Питання в тому, чи хочете ви на це розраховувати :-) Також ймовірність того, що потрібно 10 ітерацій, становить 1 на 4,8 мільйона. p (> 20) - площа цього і т. д.
greggo

12

Використовуйте std::tr1::normal_distribution.

Простір імен std :: tr1 не є частиною прискорення. Це простір імен, що містить доповнення бібліотеки з технічного звіту C ++ 1 і доступний у сучасних компіляторах Microsoft та gcc, незалежно від прискорення.


25
Він не просив стандарту, він просив "не підвищувати".
JoeG

12

Так ви створюєте зразки на сучасному компіляторі C ++.

#include <random>
...
std::mt19937 generator;
double mean = 0.0;
double stddev  = 1.0;
std::normal_distribution<double> normal(mean, stddev);
cerr << "Normal: " << normal(generator) << endl;

generatorдійсно повинні бути посіяні.
Вальтер

Він завжди насіннєвий. Є насіння за замовчуванням.
Петтер



4

Якщо ви використовуєте C ++ 11, ви можете використовувати std::normal_distribution:

#include <random>

std::default_random_engine generator;
std::normal_distribution<double> distribution(/*mean=*/0.0, /*stddev=*/1.0);

double randomNumber = distribution(generator);

Існує безліч інших розподілів, які можна використовувати для перетворення результату двигуна випадкових чисел.


Про це вже згадував Бен ( stackoverflow.com/a/11977979/635608 )
матч

3

Я дотримувався визначення PDF-файлу, наведеного в http://www.mathworks.com/help/stats/normal-distribution.html, і придумав це:

const double DBL_EPS_COMP = 1 - DBL_EPSILON; // DBL_EPSILON is defined in <limits.h>.
inline double RandU() {
    return DBL_EPSILON + ((double) rand()/RAND_MAX);
}
inline double RandN2(double mu, double sigma) {
    return mu + (rand()%2 ? -1.0 : 1.0)*sigma*pow(-log(DBL_EPS_COMP*RandU()), 0.5);
}
inline double RandN() {
    return RandN2(0, 1.0);
}

Це, можливо, не найкращий підхід, але це досить просто.


-1 Не працює, наприклад, RANDN2 (0,0, d + 1,0). Макроси для цього горезвісні.
Петтер

Макрос буде помилкою , якщо rand()з RANDUповертає нуль, так як Ln (0) не визначене.
interDist

Ви справді спробували цей код? Схоже, ви створили функцію, яка генерує числа, розподілені Релеєм . Порівняйте з перетворенням Box-Muller , де вони розмножуються cos(2*pi*rand/RAND_MAX), тоді як ви розмножуєтесь (rand()%2 ? -1.0 : 1.0).
HelloGoodbye

1

Список поширених запитань comp.lang.c ділиться трьома різними способами легко генерувати випадкові числа з гауссовим розподілом.

Ви можете подивитися: http://c-faq.com/lib/gaussian.html


1

Реалізація Box-Muller:

#include <cstdlib>
#include <cmath>
#include <ctime>
#include <iostream>
using namespace std;
 // return a uniformly distributed random number
double RandomGenerator()
{
  return ( (double)(rand()) + 1. )/( (double)(RAND_MAX) + 1. );
}
 // return a normally distributed random number
double normalRandom()
{
  double y1=RandomGenerator();
  double y2=RandomGenerator();
  return cos(2*3.14*y2)*sqrt(-2.*log(y1));
}

int main(){
double sigma = 82.;
double Mi = 40.;
  for(int i=0;i<100;i++){
double x = normalRandom()*sigma+Mi;
    cout << " x = " << x << endl;
  }
  return 0;
}

1

Існують різні алгоритми зворотного кумулятивного нормального розподілу. Найбільш популярні в кількісних фінансах тестуються на http://chasethedevil.github.io/post/monte-carlo--inverse-cumulative-normal-distribution/

На мій погляд, існує не так багато стимулів для використання що - то інше , ніж алгоритм AS241 від Wichura : це машина точність, надійність і швидко. Вузькі плями рідко бувають у генерації випадкових чисел Гаусса.

Крім того, це показує недолік Зігурату як подібних підходів.

Найголовніша відповідь прихильників Box-Müller, ви повинні знати, що він знає недоліки. Цитую https://www.sciencedirect.com/science/article/pii/S0895717710005935 :

в літературі Бокс-Мюллер іноді розглядають як трохи неповноцінний, в основному з двох причин. По-перше, якщо застосовувати метод Box-Мюллера до чисел з поганого лінійного конгруентного генератора, перетворені числа забезпечують надзвичайно слабке охоплення простору. Сюжети перетворених чисел із спіральними хвостами можна знайти в багатьох книгах, особливо в класичній книзі Ріплі, яка, мабуть, першою зробила це спостереження "


0

1) Графічно інтуїтивно зрозумілим способом генерування випадкових чисел Гаусса є використання подібного методу Монте-Карло. Ви б генерували випадкову точку у вікні навколо кривої Гаусса, використовуючи свій генератор псевдовипадкових чисел у С. Ви можете обчислити, чи знаходиться ця точка всередині або під гауссова розподілом, використовуючи рівняння розподілу. Якщо ця точка знаходиться всередині розподілу Гаусса, то ви отримали своє гауссове випадкове число як значення x точки.

Цей метод не є ідеальним, оскільки технічно крива Гаусса продовжується до нескінченності, і ви не змогли створити вікно, що наближається до нескінченності у вимірі x. Але крива Гаассія наближається до 0 у вимірі y досить швидко, тому я б не турбувався про це. Обмеження розміру змінних у C може бути більш обмежуючим фактором для вашої точності.

2) Іншим способом було б використання теореми центрального граничного значення, яка стверджує, що коли додаються незалежні випадкові величини, вони утворюють нормальний розподіл. Маючи на увазі цю теорему, ви можете наблизити випадкове число Гаусса, додавши велику кількість незалежних випадкових величин.

Ці методи не найпрактичніші, але цього можна очікувати, коли ви не хочете використовувати бібліотеку, що вже існує. Майте на увазі, що ця відповідь надходить від когось із мало чи обґрунтованим чи статистичним досвідом.


0

Метод Монте-Карло Найінтуїтивнішим способом це було б використання методу Монте-Карло. Візьміть відповідний діапазон -X, + X. Більші значення X призведуть до більш точного нормального розподілу, але для сходження потрібно більше часу. а. Виберіть випадкове число z між -X до X. b. Тримайте з вірогідністю, N(z, mean, variance)де N - гауссова розподіл. Відкиньте інакше і поверніться до кроку (a).



-3

Комп'ютер - детермінований пристрій. Випадковості в розрахунку немає. Крім того, арифметичний пристрій в процесорі може оцінити суму за деяким кінцевим набором цілих чисел (виконуючи оцінку в кінцевому полі) і кінцевим набором реальних раціональних чисел. А також виконували побитові операції. Математика приймати справу з більш чудовими наборами, як [0.0, 1.0] з нескінченною кількістю очок.

Ви можете прослухати якийсь провід всередині комп'ютера за допомогою якогось контролера, але чи буде він рівномірним розподілом? Не знаю. Але якщо припустити, що його сигнал є результатом накопичення значень величезної кількості незалежних випадкових величин, то ви отримаєте приблизно нормальну розподілену випадкову змінну (це було доведено в теорії ймовірностей)

Існують алгоритми з назвою - псевдо випадковий генератор. Як я задумав, метою псевдовипадкового генератора є емуляція випадковості. А критеріями Гуднеса є: - емпіричний розподіл зведений (в деякому сенсі - точковий, рівномірний, L2) до теоретичних - величини, які ви отримуєте від випадкового генератора, здаються ідеальними. Звичайно, це неправда з "реальної точки зору", але ми припускаємо, що це правда.

Один з популярних методів - ви можете підбити 12 irv з рівномірним розподілом .... Але якщо чесно під час виведення теореми про центральний ліміт за допомогою програми перетворення Фур'є, серії Тейлора, потрібно мати кілька припущень n -> + inf. Так, наприклад, теоретично - Особисто я не підкреслюю, як люди виконують суму 12 ірв при рівномірному розподілі.

У мене в університеті була теорія схильності. І особливість для мене - це лише математичне питання. В університеті я побачив таку модель:


double generateUniform(double a, double b)
{
  return uniformGen.generateReal(a, b);
}

double generateRelei(double sigma)
{
  return sigma * sqrt(-2 * log(1.0 - uniformGen.generateReal(0.0, 1.0 -kEps)));
}
double generateNorm(double m, double sigma)
{
  double y2 = generateUniform(0.0, 2 * kPi);
  double y1 = generateRelei(1.0);
  double x1 = y1 * cos(y2);
  return sigma*x1 + m;
}

Таким чином, наскільки це був лише приклад, я думаю, що існують інші способи його реалізації.

Про те, що це правильно, можна знайти в цій книзі "Москва, БМСТУ, 2004: XVI Теорія ймовірностей, Приклад 6.12, с.246-247" Крищенка Олександра Петровича ISBN 5-7038-2485-0

На жаль, я не знаю про існування перекладу цієї книги англійською мовою.


У мене є кілька голосів. Дайте мені знати, що тут поганого?
bruziuz

Питання в тому, як генерувати псевдо випадкові числа на комп’ютері (я знаю, мова тут вільна), це не питання математичного існування.
користувач2820579

Так, ви праві. І відповідь - як генерувати псевдо випадкове число з нормальним розподілом на основі генератора, який має рівномірний розподіл. Вихідний код надано, його можна переписати будь-якою мовою.
bruziuz

Звичайно, я думаю, що хлопець шукає, наприклад, "Числові рецепти на C / C ++". До речі, лише для доповнення нашої дискусії, автори цієї останньої книги дають цікаві посилання на пару псевдовипадкових генераторів, які відповідають стандартам на те, що вони є "пристойними" генераторами.
користувач2820579

1
Я зробив резервну копію тут: sites.google.com/site/burlachenkok/download
bruziuz
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.