Як я можу легко генерувати випадкові числа після нормального розподілу в C або C ++?
Я не хочу використовувати Boost.
Я знаю, що Кнут розповідає про це досить довго, але зараз я не маю під рукою його книг.
Як я можу легко генерувати випадкові числа після нормального розподілу в C або C ++?
Я не хочу використовувати Boost.
Я знаю, що Кнут розповідає про це досить довго, але зараз я не маю під рукою його книг.
Відповіді:
Існує багато методів генерування чисел, розподілених Гауссом, із звичайної RNG .
Перетворення Бокса-Мюллера зазвичай використовується. Він правильно виробляє значення при нормальному розподілі. Математика проста. Ви генеруєте два (рівномірні) випадкові числа, застосовуючи до них формулу, ви отримуєте два нормально розподілених випадкових числа. Поверніть одне, а інший збережіть для наступного запиту для випадкового числа.
std::normal_distribution
що робить саме те, що ви просите, не заглиблюючись у математичні деталі.
C ++ 11 пропозицій std::normal_distribution
, який я б пішов сьогодні.
Ось декілька рішень у порядку зростання складності:
Додайте 12 рівномірних випадкових чисел від 0 до 1 і відніміть 6. Це буде відповідати середньому та стандартному відхиленню нормальної змінної. Очевидним недоліком є те, що діапазон обмежений ± 6 - на відміну від справжнього нормального розподілу.
Перетворення Box-Muller. Це перераховано вище і реалізується порівняно просто. Якщо вам потрібні дуже точні зразки, майте на увазі, що трансформація Box-Muller у поєднанні з деякими рівномірними генераторами страждає на аномалію під назвою Neave Effect 1 .
Для кращої точності я пропоную намалювати обмундирування та застосувати зворотний кумулятивний нормальний розподіл для досягнення нормально розподілених змінних. Ось дуже хороший алгоритм зворотних кумулятивних нормальних розподілів.
1. HR Neave, “Про використання трансформації Box-Muller з мультиплікативними вродженими псевдовипадковими генераторами чисел”, Прикладна статистика, 22, 92-97, 1973
Швидкий і простий метод - це просто підбити кількість рівномірно розподілених випадкових чисел і взяти їх середнє значення. Щоб отримати повне пояснення, чому це працює, дивіться теорему про центральну межу .
Я створив проект C ++ з відкритим кодом для нормально розподіленого показника генерації випадкових чисел .
Він порівнює кілька алгоритмів, в тому числі
cpp11random
використовує C ++ 11 std::normal_distribution
з std::minstd_rand
(це фактично перетворення Box-Muller в кланге).Результати одноточної ( float
) версії на iMac Corei5-3330S@2.70GHz, кланг 6.1, 64-розрядні:
Для коректності програма перевіряє середнє, середнє відхилення, нахил та куртоз зразків. Було встановлено, що метод CLT шляхом підсумовування 4, 8 або 16 однакових чисел не має гарного куртозу, як інші методи.
Алгоритм Ziggurat має кращі показники, ніж інші. Однак він не підходить для паралелізму SIMD, оскільки йому потрібен пошук таблиці та гілок. Box-Muller з набором інструкцій SSE2 / AVX набагато швидше (x1.79, x2.99), ніж не-SIMD-версія алгоритму ziggurat.
Тому я запропоную використовувати Box-Muller для архітектури з наборами інструкцій SIMD, і, можливо, інакше зіггурат.
У PS еталоні використовується найпростіший LCG PRNG для генерації рівномірних розподілених випадкових чисел. Тому для деяких застосувань це може бути недостатньо. Але порівняння продуктивності повинно бути справедливим, оскільки всі реалізації використовують один і той же PRNG, тому тест в основному тестує ефективність трансформації.
Ось приклад 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-значення та побудувати їх. Вгору пряма лінія - бажаний результат).
Використовуйте std::tr1::normal_distribution
.
Простір імен std :: tr1 не є частиною прискорення. Це простір імен, що містить доповнення бібліотеки з технічного звіту C ++ 1 і доступний у сучасних компіляторах Microsoft та gcc, незалежно від прискорення.
Так ви створюєте зразки на сучасному компіляторі 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
дійсно повинні бути посіяні.
Ви можете використовувати GSL . Наведено кілька повних прикладів, щоб продемонструвати, як його використовувати.
Погляньте на сторінку: http://www.cplusplus.com/reference/random/normal_distribution/ . Це найпростіший спосіб отримати нормальні розподіли.
Якщо ви використовуєте 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);
Існує безліч інших розподілів, які можна використовувати для перетворення результату двигуна випадкових чисел.
Я дотримувався визначення 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);
}
Це, можливо, не найкращий підхід, але це досить просто.
rand()
з RANDU
повертає нуль, так як Ln (0) не визначене.
cos(2*pi*rand/RAND_MAX)
, тоді як ви розмножуєтесь (rand()%2 ? -1.0 : 1.0)
.
Список поширених запитань comp.lang.c ділиться трьома різними способами легко генерувати випадкові числа з гауссовим розподілом.
Ви можете подивитися: http://c-faq.com/lib/gaussian.html
Реалізація 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;
}
Існують різні алгоритми зворотного кумулятивного нормального розподілу. Найбільш популярні в кількісних фінансах тестуються на 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-Мюллера до чисел з поганого лінійного конгруентного генератора, перетворені числа забезпечують надзвичайно слабке охоплення простору. Сюжети перетворених чисел із спіральними хвостами можна знайти в багатьох книгах, особливо в класичній книзі Ріплі, яка, мабуть, першою зробила це спостереження "
1) Графічно інтуїтивно зрозумілим способом генерування випадкових чисел Гаусса є використання подібного методу Монте-Карло. Ви б генерували випадкову точку у вікні навколо кривої Гаусса, використовуючи свій генератор псевдовипадкових чисел у С. Ви можете обчислити, чи знаходиться ця точка всередині або під гауссова розподілом, використовуючи рівняння розподілу. Якщо ця точка знаходиться всередині розподілу Гаусса, то ви отримали своє гауссове випадкове число як значення x точки.
Цей метод не є ідеальним, оскільки технічно крива Гаусса продовжується до нескінченності, і ви не змогли створити вікно, що наближається до нескінченності у вимірі x. Але крива Гаассія наближається до 0 у вимірі y досить швидко, тому я б не турбувався про це. Обмеження розміру змінних у C може бути більш обмежуючим фактором для вашої точності.
2) Іншим способом було б використання теореми центрального граничного значення, яка стверджує, що коли додаються незалежні випадкові величини, вони утворюють нормальний розподіл. Маючи на увазі цю теорему, ви можете наблизити випадкове число Гаусса, додавши велику кількість незалежних випадкових величин.
Ці методи не найпрактичніші, але цього можна очікувати, коли ви не хочете використовувати бібліотеку, що вже існує. Майте на увазі, що ця відповідь надходить від когось із мало чи обґрунтованим чи статистичним досвідом.
Метод Монте-Карло
Найінтуїтивнішим способом це було б використання методу Монте-Карло. Візьміть відповідний діапазон -X, + X. Більші значення X призведуть до більш точного нормального розподілу, але для сходження потрібно більше часу. а. Виберіть випадкове число z між -X до X. b. Тримайте з вірогідністю, N(z, mean, variance)
де N - гауссова розподіл. Відкиньте інакше і поверніться до кроку (a).
Погляньте, що я знайшов.
Ця бібліотека використовує алгоритм Ziggurat.
Комп'ютер - детермінований пристрій. Випадковості в розрахунку немає. Крім того, арифметичний пристрій в процесорі може оцінити суму за деяким кінцевим набором цілих чисел (виконуючи оцінку в кінцевому полі) і кінцевим набором реальних раціональних чисел. А також виконували побитові операції. Математика приймати справу з більш чудовими наборами, як [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
На жаль, я не знаю про існування перекладу цієї книги англійською мовою.