ฉันจะสร้างตัวเลขสุ่มตามการแจกแจงปกติใน C หรือ C ++ ได้อย่างไร
ฉันไม่ต้องการใช้ Boost ใด ๆ
ฉันรู้ว่า Knuth พูดถึงเรื่องนี้เป็นเวลานาน แต่ตอนนี้ฉันไม่มีหนังสือของเขาอยู่ในมือ
ฉันจะสร้างตัวเลขสุ่มตามการแจกแจงปกติใน C หรือ C ++ ได้อย่างไร
ฉันไม่ต้องการใช้ Boost ใด ๆ
ฉันรู้ว่า Knuth พูดถึงเรื่องนี้เป็นเวลานาน แต่ตอนนี้ฉันไม่มีหนังสือของเขาอยู่ในมือ
คำตอบ:
มีหลายวิธีที่จะมีสร้างตัวเลขแบบเกาส์กระจายจาก RNG
Box-Muller แปลงเป็นที่นิยมใช้ มันสร้างค่าได้อย่างถูกต้องด้วยการแจกแจงปกติ คณิตศาสตร์เป็นเรื่องง่าย คุณสร้างตัวเลขสุ่มสอง (เหมือนกัน) และด้วยการใช้สูตรกับพวกเขาคุณจะได้รับตัวเลขสุ่มแบบกระจายตามปกติสองตัว ส่งคืนหนึ่งและบันทึกอีกอันสำหรับคำขอหมายเลขสุ่มครั้งต่อไป
std::normal_distribution
สิ่งที่ทำตามที่คุณขอโดยไม่ต้องเจาะลึกรายละเอียดทางคณิตศาสตร์
ข้อเสนอ C ++ 11 std::normal_distribution
ซึ่งเป็นวิธีที่ฉันจะทำในวันนี้
วิธีแก้ปัญหาตามลำดับความซับซ้อนจากน้อยไปมาก:
เพิ่มตัวเลขสุ่ม 12 ตัวจาก 0 เป็น 1 และลบ 6 ซึ่งจะจับคู่ค่าเฉลี่ยและส่วนเบี่ยงเบนมาตรฐานของตัวแปรปกติ ข้อเสียเปรียบที่ชัดเจนคือช่วงถูก จำกัด ไว้ที่± 6 ซึ่งแตกต่างจากการแจกแจงปกติจริง
การแปลงร่างของ Box-Muller นี่คือรายการข้างต้นและค่อนข้างง่ายในการนำไปใช้ หากคุณต้องการตัวอย่างแม่นยำมาก แต่ทราบว่ากล่อง-Muller แปลงรวมกับบางเครื่องปั่นไฟทุกข์เครื่องแบบจากความผิดปกติที่เรียกว่า Neave ผล1
เพื่อความแม่นยำสูงสุดฉันขอแนะนำให้วาดเครื่องแบบและใช้การแจกแจงปกติสะสมผกผันเพื่อให้ได้ตัวแปรที่กระจายตามปกติ นี่คืออัลกอริทึมที่ดีมากสำหรับการแจกแจงปกติแบบสะสมผกผัน
1. HR Neave,“ เกี่ยวกับการใช้การแปลง Box-Muller ด้วยเครื่องกำเนิดตัวเลขหลอกแบบคูณที่สอดคล้องกัน,” สถิติประยุกต์, 22, 92-97, 1973
วิธีที่ง่ายและรวดเร็วคือการรวมตัวเลขสุ่มที่กระจายเท่า ๆ กันจำนวนหนึ่งแล้วนำมาหาค่าเฉลี่ย ดูCentral Limit Theoremสำหรับคำอธิบายทั้งหมดว่าเหตุใดจึงได้ผล
ฉันสร้างโครงการที่มา C ++ เปิดให้บริการสำหรับการกระจายตามปกติจำนวนสุ่มมาตรฐานรุ่น
มันเปรียบเทียบอัลกอริทึมต่างๆรวมถึง
cpp11random
ใช้ C ++ 11 std::normal_distribution
กับstd::minstd_rand
(จริงๆแล้วคือ Box-Muller transform ในเสียงดัง)ผลลัพธ์ของfloat
เวอร์ชันsingle-precision ( ) บน iMac Corei5-3330S@2.70GHz, clang 6.1, 64-bit:
เพื่อความถูกต้องโปรแกรมจะตรวจสอบค่าเฉลี่ยค่าเบี่ยงเบนมาตรฐานความเบ้และเคอร์โทซิสของตัวอย่าง พบว่าวิธี CLT โดยการสรุปเลขที่สม่ำเสมอ 4, 8 หรือ 16 ไม่มีความเคอร์โทซิสที่ดีเหมือนวิธีอื่น ๆ
อัลกอริทึม Ziggurat มีประสิทธิภาพที่ดีกว่าขั้นตอนอื่น ๆ อย่างไรก็ตามมันไม่เหมาะสำหรับการขนาน SIMD เนื่องจากต้องการการค้นหาตารางและสาขา Box-Muller พร้อมชุดคำสั่ง SSE2 / AVX นั้นเร็วกว่ามาก (x1.79, x2.99) มากกว่าอัลกอริทึม ziggurat เวอร์ชันที่ไม่ใช่ SIMD
ดังนั้นฉันจะแนะนำให้ใช้ Box-Muller สำหรับสถาปัตยกรรมที่มีชุดคำสั่ง SIMD และอาจเป็น ziggurat เป็นอย่างอื่น
ป.ล. เกณฑ์มาตรฐานใช้ 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 เปลี่ยนอันดับเป็นสัดส่วนของจำนวนทั้งหมด x เช่นจำนวนตัวอย่างรับค่า z และลงจุดเส้นตรงขึ้นไปคือผลลัพธ์ที่ต้องการ)
ใช้std::tr1::normal_distribution
.
std :: tr1 namespace ไม่ได้เป็นส่วนหนึ่งของการเพิ่ม เป็นเนมสเปซที่มีการเพิ่มไลบรารีจาก C ++ Technical Report 1 และพร้อมใช้งานในคอมไพเลอร์และ gcc ของ Microsoft ที่เป็นปัจจุบันโดยไม่ขึ้นกับการเพิ่ม
นี่คือวิธีสร้างตัวอย่างบนคอมไพเลอร์ 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 บางตัวอย่างที่สมบูรณ์จะได้รับการแสดงให้เห็นถึงวิธีการใช้งาน
หากคุณใช้ 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นั่นคือความแม่นยำของเครื่องจักรเชื่อถือได้และรวดเร็ว ปัญหาคอขวดมักไม่ค่อยเกิดขึ้นในการสร้างตัวเลขสุ่มแบบเกาส์
นอกจากนี้ยังแสดงให้เห็นถึงข้อเสียเปรียบของ Ziggurat เช่นแนวทาง
คำตอบยอดนิยมสำหรับผู้สนับสนุน Box-Müllerคุณควรทราบว่ามีข้อบกพร่องที่ทราบแล้ว ฉันอ้างhttps://www.sciencedirect.com/science/article/pii/S0895717710005935 :
ในวรรณกรรม Box - Muller บางครั้งถูกมองว่าด้อยกว่าเล็กน้อยสาเหตุหลักมาจากสองประการ ขั้นแรกถ้ามีใครใช้วิธี Box – Muller กับตัวเลขจากเครื่องกำเนิดไฟฟ้าเชิงเส้นที่ไม่ดีตัวเลขที่แปลงแล้วจะให้พื้นที่ครอบคลุมที่แย่มาก พล็อตตัวเลขแปลงร่างที่มีหางเป็นเกลียวมีอยู่ในหนังสือหลายเล่มโดยเฉพาะอย่างยิ่งในหนังสือคลาสสิกของ Ripley ซึ่งน่าจะเป็นคนแรกที่สังเกตเห็น "
1) วิธีที่ใช้งานง่ายแบบกราฟิกที่คุณสามารถสร้างตัวเลขสุ่มแบบเกาส์เซียนคือการใช้สิ่งที่คล้ายกับวิธีมอนติคาร์โล คุณจะสร้างจุดสุ่มในกล่องรอบ ๆ เส้นโค้งเกาส์เซียนโดยใช้ตัวสร้างตัวเลขสุ่มหลอกใน C คุณสามารถคำนวณได้ว่าจุดนั้นอยู่ภายในหรือใต้การแจกแจงแบบเสียนโดยใช้สมการของการแจกแจง หากจุดนั้นอยู่ภายในการแจกแจงแบบเกาส์แสดงว่าคุณจะได้ตัวเลขสุ่มแบบเกาส์เซียนเป็นค่า x ของจุด
วิธีนี้ไม่สมบูรณ์แบบเนื่องจากในทางเทคนิคแล้วเส้นโค้งแบบเกาส์เซียนจะไปสู่อินฟินิตี้และคุณไม่สามารถสร้างกล่องที่เข้าใกล้อินฟินิตี้ในมิติ x ได้ แต่เส้นโค้ง Guassian เข้าใกล้ 0 ในมิติ y เร็วมากดังนั้นฉันจึงไม่ต้องกังวลเรื่องนั้น ข้อ จำกัด ของขนาดของตัวแปรใน C อาจเป็นปัจจัยจำกัดความแม่นยำของคุณมากกว่า
2) อีกวิธีหนึ่งคือการใช้ Central Limit Theorem ซึ่งระบุว่าเมื่อมีการเพิ่มตัวแปรสุ่มอิสระจะเป็นการแจกแจงแบบปกติ เมื่อคำนึงถึงทฤษฎีบทนี้คุณสามารถประมาณจำนวนสุ่มแบบเสียนได้โดยการเพิ่มตัวแปรสุ่มอิสระจำนวนมาก
วิธีการเหล่านี้ไม่ใช่วิธีที่ใช้ได้จริงที่สุด แต่เป็นสิ่งที่คาดหวังได้เมื่อคุณไม่ต้องการใช้ไลบรารีที่มีมาก่อน โปรดทราบว่าคำตอบนี้มาจากผู้ที่มีประสบการณ์ด้านแคลคูลัสหรือสถิติเพียงเล็กน้อยหรือไม่มีเลย
วิธีมอนติคาร์โลวิธี
ที่ง่ายที่สุดในการทำเช่นนี้คือการใช้วิธีมอนติคาร์โล ใช้ช่วงที่เหมาะสม -X, + X ค่า X ที่มากขึ้นจะส่งผลให้การแจกแจงแบบปกติแม่นยำขึ้น แต่ใช้เวลาในการบรรจบกันนานขึ้น ก. เลือกตัวเลขสุ่มzระหว่าง -X ถึง X ข. คงไว้ด้วยความน่าจะเป็นN(z, mean, variance)
ที่ N คือการแจกแจงแบบเกาส์เซียน ปล่อยอย่างอื่นแล้วกลับไปที่ขั้นตอน (a)
ลองดูสิ่งที่ฉันพบ
ไลบรารีนี้ใช้อัลกอริทึม Ziggurat
คอมพิวเตอร์เป็นอุปกรณ์กำหนด ไม่มีการสุ่มในการคำนวณ ยิ่งไปกว่านั้นอุปกรณ์เลขคณิตใน CPU สามารถประเมินผลรวมเหนือชุดตัวเลขจำนวนเต็ม จำกัด (ทำการประเมินผลในเขตข้อมูล จำกัด ) และชุดจำนวนจริงที่มีเหตุผล และยังดำเนินการในระดับบิต คณิตศาสตร์จัดการกับเซตที่ยอดเยี่ยมอื่น ๆ เช่น [0.0, 1.0] โดยมีจำนวนคะแนนไม่สิ้นสุด
คุณสามารถฟังสายในคอมพิวเตอร์ด้วยคอนโทรลเลอร์บางตัวได้ แต่จะมีการแจกแจงแบบสม่ำเสมอหรือไม่? ฉันไม่รู้ แต่ถ้าสันนิษฐานว่าสัญญาณเป็นผลมาจากการสะสมค่าตัวแปรสุ่มอิสระจำนวนมากคุณจะได้รับตัวแปรสุ่มแบบกระจายปกติโดยประมาณ (พิสูจน์แล้วในทฤษฎีความน่าจะเป็น)
มีอยู่อัลกอริทึมที่เรียกว่า - ตัวสร้างสุ่มหลอก ในขณะที่ฉันเข้าใจจุดประสงค์ของตัวสร้างสุ่มหลอกคือการเลียนแบบการสุ่ม และเกณฑ์ของความดีคือ: - การกระจายเชิงประจักษ์จะถูกรวมเข้าด้วยกัน (ในบางแง่ - ชี้, สม่ำเสมอ, L2) เป็นทางทฤษฎี - ค่าที่คุณได้รับจากเครื่องกำเนิดไฟฟ้าแบบสุ่มดูเหมือนจะเป็นค่าที่เหมาะสม แน่นอนว่ามันไม่เป็นความจริงจาก 'มุมมองที่แท้จริง' แต่เราคิดว่ามันเป็นความจริง
วิธีหนึ่งที่ได้รับความนิยม - คุณสามารถ summ 12 irv ด้วยการแจกแจงแบบสม่ำเสมอ .... แต่พูดตามตรงในระหว่างการหาค่า Central Limit Theorem ด้วยการช่วย Fourier Transform, Taylor Series จำเป็นต้องมี n -> + inf สมมติฐานสองสามครั้ง ตัวอย่างเช่นทฤษฎี - โดยส่วนตัวแล้วฉันไม่ได้เน้นย้ำว่าผู้คนทำผลรวมของ 12 irv ด้วยการแจกแจงแบบสม่ำเสมออย่างไร
ฉันมีทฤษฎีความสามารถในการเรียนในมหาวิทยาลัย และอนุภาคสำหรับฉันมันเป็นเพียงคำถามทางคณิตศาสตร์ ในมหาวิทยาลัยฉันเห็นโมเดลต่อไปนี้:
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;
}
วิธีการทำเช่นนี้เป็นเพียงตัวอย่างฉันเดาว่ามีวิธีอื่นในการนำไปใช้
การพิสูจน์ว่าถูกต้องสามารถพบได้ในหนังสือเล่มนี้ "Moscow, BMSTU, 2004: XVI Probability Theory, Example 6.12, p.246-247" ของKrishchenko Alexander Petrovich ISBN 5-7038-2485-0
น่าเสียดายที่ฉันไม่รู้เกี่ยวกับการแปลหนังสือเล่มนี้เป็นภาษาอังกฤษ