การแปลงการแจกแจงแบบสม่ำเสมอเป็นการแจกแจงแบบปกติ


106

ฉันจะแปลงการแจกแจงแบบสม่ำเสมอได้อย่างไร (เนื่องจากเครื่องกำเนิดตัวเลขสุ่มส่วนใหญ่สร้างขึ้นเช่นระหว่าง 0.0 ถึง 1.0) เป็นการแจกแจงปกติ จะเกิดอะไรขึ้นถ้าฉันต้องการค่าเฉลี่ยและส่วนเบี่ยงเบนมาตรฐานที่ฉันเลือก?


3
คุณมีข้อกำหนดด้านภาษาหรือไม่หรือเป็นเพียงคำถามทั่วไปเกี่ยวกับอัลกอริทึม
Bill the Lizard

3
คำถามทั่วไปเกี่ยวกับอัลกอริทึม ฉันไม่สนใจว่าภาษาไหน แต่ฉันอยากให้คำตอบไม่ได้ขึ้นอยู่กับฟังก์ชันเฉพาะที่มีเฉพาะในภาษานั้น ๆ
Terhorst

คำตอบ:


47

อัลกอริทึม Zigguratมีประสิทธิภาพสวยสำหรับนี้แม้ว่าBox-Muller เปลี่ยนเป็นเรื่องง่ายที่จะใช้จากรอยขีดข่วน (และไม่บ้าช้า)


7
คำเตือนตามปกติเกี่ยวกับเครื่องกำเนิดไฟฟ้าที่สอดคล้องกันเชิงเส้นใช้กับทั้งสองวิธีนี้ดังนั้นให้ใช้เครื่องกำเนิดไฟฟ้าที่เหมาะสม ไชโย
dmckee --- อดีตผู้ดูแลลูกแมว

3
เช่น Mersenee Twister หรือคุณมีคำแนะนำอื่น ๆ ?
Gregg Lind

47

มีหลายวิธี:

  • ไม่ได้ใช้กล่องมุลเลอร์ โดยเฉพาะอย่างยิ่งถ้าคุณวาดตัวเลข gaussian หลายตัว Box Muller ให้ผลลัพธ์ที่ยึดระหว่าง -6 ถึง 6 (สมมติว่ามีความแม่นยำสองเท่าสิ่งที่แย่ลงเมื่อลอย) และมีประสิทธิภาพน้อยกว่าวิธีอื่น ๆ
  • Ziggurat ใช้ได้ดี แต่ต้องการการค้นหาตาราง (และการปรับแต่งเฉพาะบางแพลตฟอร์มเนื่องจากปัญหาขนาดแคช)
  • Ratio-of-uniforms เป็นสิ่งที่ฉันชอบมีเพียงการบวก / การคูณเพียงเล็กน้อยและบันทึก 1/50 ของเวลา (เช่นดูที่นั่น )
  • การเปลี่ยน CDF นั้นมีประสิทธิภาพ (และมองข้ามไปทำไม) คุณสามารถใช้งานได้อย่างรวดเร็วหากคุณค้นหาใน Google เป็นข้อบังคับสำหรับตัวเลขกึ่งสุ่ม

2
คุณแน่ใจเกี่ยวกับการหนีบ [-6,6] หรือไม่? นี่เป็นประเด็นสำคัญทีเดียวถ้าเป็นจริง (และควรค่าแก่การจดบันทึกไว้ในหน้าวิกิพีเดีย)
redcalx

1
@locster: นี่คือสิ่งที่ครูของฉันบอกฉัน (เขาศึกษาเครื่องกำเนิดไฟฟ้าดังกล่าวและฉันเชื่อในคำพูดของเขา) ฉันอาจสามารถหาข้อมูลอ้างอิงให้คุณได้
Alexandre C.

7
@locster: คุณสมบัติที่ไม่พึงปรารถนานี้ยังถูกแชร์โดยเมธอด CDF ผกผัน ดูcimat.mx/~src/prope08/randomgauss.pdf สิ่งนี้สามารถบรรเทาได้โดยใช้ RNG ที่สม่ำเสมอซึ่งมีความน่าจะเป็นที่ไม่ใช่ศูนย์ที่จะให้เลขทศนิยมใกล้เคียงกับศูนย์มาก RNG ส่วนใหญ่ไม่ทำเนื่องจากสร้างจำนวนเต็ม (โดยทั่วไปคือ 64 บิต) ซึ่งจะถูกจับคู่กับ [0,1] สิ่งนี้ทำให้วิธีการเหล่านั้นไม่เหมาะสำหรับการสุ่มตัวอย่างหางของตัวแปรเกาส์เซียน (ลองนึกถึงตัวเลือกการกำหนดราคาต่ำ / สูงในการเงินเชิงคำนวณ)
Alexandre C.

6
@AlexandreC. เพื่อให้ชัดเจนในสองจุดโดยใช้ตัวเลข 64 บิตหางจะออกไปเป็น 8.57 หรือ 9.41 (ค่าต่ำกว่าที่สอดคล้องกับการแปลงเป็น [0,1) ก่อนที่จะบันทึก) แม้ว่าจะยึดไว้ที่ [-6, 6] โอกาสที่จะอยู่นอกช่วงนี้จะอยู่ที่ประมาณ 1.98e-9 ซึ่งดีพอสำหรับคนส่วนใหญ่แม้แต่ในวงการวิทยาศาสตร์ สำหรับตัวเลข 8.57 และ 9.41 จะกลายเป็น 1.04e-17 และ 4.97e-21 ตัวเลขเหล่านี้มีขนาดเล็กมากจนความแตกต่างระหว่างการสุ่มตัวอย่างแบบบ็อกซ์มุลเลอร์และการสุ่มตัวอย่างแบบเกาส์เซียนที่แท้จริงในแง่ของขีด จำกัด ดังกล่าวแทบจะเป็นเรื่องวิชาการเท่านั้น หากคุณต้องการสิ่งที่ดีกว่าให้บวกสี่ของมันแล้วหารด้วย 2
CrazyCasta

6
ฉันคิดว่าคำแนะนำที่จะไม่ใช้การแปลง Box Muller นั้นทำให้ผู้ใช้ส่วนใหญ่เข้าใจผิด เป็นเรื่องดีที่ทราบเกี่ยวกับข้อ จำกัด แต่อย่างที่ CrazyCasta ชี้ให้เห็นสำหรับแอปพลิเคชันส่วนใหญ่ที่ไม่ได้ขึ้นอยู่กับค่าผิดปกติคุณอาจไม่ต้องกังวลเกี่ยวกับเรื่องนี้ ตัวอย่างเช่นถ้าคุณเคยได้ขึ้นอยู่กับการสุ่มตัวอย่างจากปกติใช้ numpy คุณได้ขึ้นอยู่กับกล่องแปลงมุลเลอร์ (พิกัดเชิงขั้วแบบฟอร์ม) github.com/numpy/numpy/blob/...
Andreas Grivas

31

การเปลี่ยนการกระจายของฟังก์ชันไปเป็นฟังก์ชันอื่นเกี่ยวข้องกับการใช้ฟังก์ชันผกผันที่คุณต้องการ

กล่าวอีกนัยหนึ่งคือถ้าคุณมุ่งเป้าไปที่ฟังก์ชันความน่าจะเป็นเฉพาะ p (x) คุณจะได้รับการแจกแจงโดยการรวมทับ -> d (x) = ปริพันธ์ (p (x)) และใช้ผกผัน: Inv (d (x)) . ตอนนี้ใช้ฟังก์ชันความน่าจะเป็นแบบสุ่ม (ซึ่งมีการแจกแจงแบบสม่ำเสมอ) และส่งค่าผลลัพธ์ผ่านฟังก์ชัน Inv (d (x)) คุณควรจะได้รับค่าสุ่มที่มีการกระจายตามฟังก์ชันที่คุณเลือก

นี่คือวิธีการคำนวณแบบทั่วไปโดยใช้วิธีนี้คุณสามารถเลือกฟังก์ชันความน่าจะเป็นหรือการแจกแจงใด ๆ ที่คุณมีได้ตราบเท่าที่มีการประมาณแบบผกผันหรือผกผันที่ดี

หวังว่านี่จะช่วยได้และขอบคุณสำหรับข้อสังเกตเล็ก ๆ เกี่ยวกับการใช้การแจกแจงไม่ใช่ความน่าจะเป็น


4
+1 นี่เป็นวิธีที่ถูกมองข้ามในการสร้างตัวแปรแบบเกาส์ซึ่งได้ผลดีมาก CDF ผกผันสามารถคำนวณได้อย่างมีประสิทธิภาพด้วยวิธี Newton ในกรณีนี้ (อนุพันธ์คือ e ^ {- t ^ 2}) การประมาณเริ่มต้นนั้นง่ายต่อการหาเศษส่วนอย่างมีเหตุผลดังนั้นคุณต้องมีการประเมินค่า erf และ exp 3-4 ครั้ง มีผลบังคับใช้หากคุณใช้ตัวเลขกึ่งสุ่มซึ่งเป็นกรณีที่คุณต้องใช้ตัวเลขที่เหมือนกันเพียงตัวเดียวจึงจะได้ตัวแบบเสียน
อเล็กซานเดรค.

9
โปรดทราบว่าคุณต้องสลับฟังก์ชันการแจกแจงสะสมไม่ใช่ฟังก์ชันการแจกแจงความน่าจะเป็น Alexandre บอกเป็นนัย ๆ แต่ฉันคิดว่าการพูดถึงมันอย่างชัดเจนมากกว่านี้อาจไม่เจ็บ - เนื่องจากคำตอบดูเหมือนจะแนะนำ PDF
ltjax

คุณสามารถใช้ PDF หากคุณเตรียมสุ่มเลือกทิศทางที่สัมพันธ์กับค่าเฉลี่ย ฉันเข้าใจใช่ไหม
Mark McKenna


1
นี่คือคำถามที่เกี่ยวข้องใน SE พร้อมคำตอบทั่วไปพร้อมคำอธิบายที่ดี
มืดมน

23

นี่คือการใช้งานจาวาสคริปต์โดยใช้รูปแบบเชิงขั้วของการแปลง Box-Muller

/*
 * Returns member of set with a given mean and standard deviation
 * mean: mean
 * standard deviation: std_dev 
 */
function createMemberInNormalDistribution(mean,std_dev){
    return mean + (gaussRandom()*std_dev);
}

/*
 * Returns random number in normal distribution centering on 0.
 * ~95% of numbers returned should fall between -2 and 2
 * ie within two standard deviations
 */
function gaussRandom() {
    var u = 2*Math.random()-1;
    var v = 2*Math.random()-1;
    var r = u*u + v*v;
    /*if outside interval [0,1] start over*/
    if(r == 0 || r >= 1) return gaussRandom();

    var c = Math.sqrt(-2*Math.log(r)/r);
    return u*c;

    /* todo: optimize this algorithm by caching (v*c) 
     * and returning next time gaussRandom() is called.
     * left out for simplicity */
}

5

ใช้ทฤษฎีบทขีด จำกัด กลางวิกิพีเดียรายการ รายการ Mathworldเพื่อประโยชน์ของคุณ

สร้าง n ของจำนวนที่กระจายสม่ำเสมอรวมกันลบ n * 0.5 และคุณได้ผลลัพธ์ของการแจกแจงปกติโดยประมาณที่มีค่าเฉลี่ยเท่ากับ 0 และความแปรปรวนเท่ากับ(1/12) * (1/sqrt(N))(ดูวิกิพีเดียเกี่ยวกับการแจกแจงแบบสม่ำเสมอสำหรับตัวสุดท้ายนั้น)

n = 10 ให้บางอย่างเร็วครึ่งหนึ่ง หากคุณต้องการบางสิ่งที่ดีกว่าครึ่งหนึ่งสำหรับโซลูชัน Tylers (ตามที่ระบุไว้ในรายการวิกิพีเดียเกี่ยวกับการแจกแจงปกติ )


1
สิ่งนี้จะไม่ให้ค่าใกล้เคียงปกติเป็นพิเศษ ("ก้อย" หรือจุดสิ้นสุดจะไม่ใกล้เคียงกับการแจกแจงปกติจริง) Box-Muller ดีกว่าอย่างที่คนอื่นแนะนำ
Peter K.

1
Box Muller มีหางผิดด้วย (ส่งกลับตัวเลขระหว่าง -6 ถึง 6 ในความแม่นยำสองเท่า)
Alexandre C.

n = 12 (รวมตัวเลขสุ่ม 12 ตัวในช่วง 0 ถึง 1 และลบ 6) ผลลัพธ์เป็น stddev = 1 และค่าเฉลี่ย = 0 จากนั้นสามารถใช้เพื่อสร้างการแจกแจงปกติ เพียงแค่คูณผลลัพธ์ด้วย stddev ที่ต้องการแล้วบวกค่าเฉลี่ย
JerryM

3

ฉันจะใช้ Box-Muller สองสิ่งเกี่ยวกับสิ่งนี้:

  1. คุณจะได้ค่าสองค่าต่อการวนซ้ำ
    โดยปกติคุณจะแคชค่าหนึ่งค่าและส่งคืนค่าอื่น ในการเรียกตัวอย่างครั้งต่อไปคุณจะส่งคืนค่าที่แคชไว้
  2. Box-Muller ให้คะแนน Z
    คุณต้องปรับขนาดคะแนน Z ตามค่าเบี่ยงเบนมาตรฐานและเพิ่มค่าเฉลี่ยเพื่อให้ได้ค่าเต็มในการแจกแจงปกติ

คุณจะปรับขนาดคะแนน Z ได้อย่างไร?
Terhorst

3
scaled = mean + stdDev * zScore // ให้ค่าปกติ (ค่าเฉลี่ย stdDev ^ 2)
yoyoyoyosef

2

โดยที่ R1, R2 เป็นตัวเลขเครื่องแบบสุ่ม:

การกระจายตามปกติโดยมี SD เป็น 1: sqrt (-2 * log (R1)) * cos (2 * pi * R2)

นี่คือสิ่งที่แน่นอน ... ไม่จำเป็นต้องทำลูปช้าทั้งหมด!


ก่อนที่จะมีคนแก้ไขฉัน ... นี่คือค่าประมาณที่ฉันคิดขึ้นมา: (1.5- (R1 + R2 + R3)) * 1.88 ฉันชอบมันเหมือนกัน.
Erik Aronesty

2

ดูเหมือนจะเหลือเชื่อที่ฉันสามารถเพิ่มบางอย่างลงในสิ่งนี้ได้หลังจากแปดปี แต่สำหรับกรณีของ Java ฉันต้องการชี้ให้ผู้อ่านใช้เมธอด Random.nextGaussian ()ซึ่งสร้างการแจกแจงแบบเกาส์ที่มีค่าเฉลี่ย 0.0 และค่าเบี่ยงเบนมาตรฐาน 1.0 สำหรับคุณ

การบวกและ / หรือการคูณอย่างง่ายจะเปลี่ยนค่าเฉลี่ยและส่วนเบี่ยงเบนมาตรฐานตามความต้องการของคุณ


1

สุ่มโมดูลไลบรารี Python มาตรฐานมีสิ่งที่คุณต้องการ:

normalvariate (mu, sigma)
การแจกแจงปกติ mu คือค่าเฉลี่ยและซิกม่าคือส่วนเบี่ยงเบนมาตรฐาน

สำหรับอัลกอริทึมเองให้ดูที่ฟังก์ชันใน random.py ในไลบรารี Python

รายการคู่มืออยู่ที่นี่


2
น่าเสียดายที่ไลบรารีของ python ใช้ Kinderman, AJ และ Monahan, JF, "การสร้างตัวแปรสุ่มด้วยคอมพิวเตอร์โดยใช้อัตราส่วนของค่าเบี่ยงเบนที่สม่ำเสมอ", ซอฟต์แวร์ ACM Trans Math, 3, (1977), pp257-260 สิ่งนี้ใช้ตัวแปรสุ่มสองตัวแปรในการสร้างค่าปกติแทนที่จะเป็นตัวแปรเดียวดังนั้นจึงไม่ชัดเจนว่าจะใช้มันเป็นการแมปที่ OP ต้องการอย่างไร
เอียน

1

นี่คือการใช้งาน JavaScript ของฉันในอัลกอริทึม P ( วิธีเชิงขั้วสำหรับการเบี่ยงเบนปกติ ) จากส่วน 3.4.1 ของหนังสือของ Donald Knuth The Art of Computer Programming :

function normal_random(mean,stddev)
{
    var V1
    var V2
    var S
    do{
        var U1 = Math.random() // return uniform distributed in [0,1[
        var U2 = Math.random()
        V1 = 2*U1-1
        V2 = 2*U2-1
        S = V1*V1+V2*V2
    }while(S >= 1)
    if(S===0) return 0
    return mean+stddev*(V1*Math.sqrt(-2*Math.log(S)/S))
}

0

ฉันควรลองสิ่งนี้ใน EXCEL: =norminv(rand();0;1)Excel: สิ่งนี้จะผลิตผลของตัวเลขสุ่มซึ่งปกติควรจะกระจายด้วยค่าเฉลี่ยศูนย์และความแปรปรวนรวมกัน สามารถระบุ "0" ด้วยค่าใดก็ได้เพื่อให้ตัวเลขเป็นค่าเฉลี่ยที่ต้องการและเมื่อเปลี่ยน "1" คุณจะได้ค่าความแปรปรวนเท่ากับกำลังสองของข้อมูลที่คุณป้อน

ตัวอย่างเช่น: =norminv(rand();50;3)จะให้ผลกับตัวเลขที่กระจายตามปกติโดยมี MEAN = 50 VARIANCE = 9


0

ถามฉันจะแปลงการแจกแจงแบบสม่ำเสมอ (เนื่องจากเครื่องกำเนิดตัวเลขสุ่มส่วนใหญ่สร้างขึ้นเช่นระหว่าง 0.0 ถึง 1.0) เป็นการแจกแจงแบบปกติได้อย่างไร

  1. สำหรับการใช้งานซอฟต์แวร์ฉันรู้จักชื่อเครื่องกำเนิดไฟฟ้าแบบสุ่มสองชื่อซึ่งให้ลำดับสุ่มหลอกที่เหมือนกันใน [0,1] (Mersenne Twister, Linear Congruate Generator) เรียกมันว่า U (x)

  2. มีพื้นที่ทางคณิตศาสตร์ซึ่งเรียกว่าทฤษฎีความน่าจะเป็น สิ่งแรก: หากคุณต้องการสร้างโมเดล rv ด้วยการแจกแจงแบบรวม F คุณสามารถลองประเมิน F ^ -1 (U (x)) ใน pr ทฤษฎีได้รับการพิสูจน์แล้วว่า rv ดังกล่าวจะมีการแจกแจงแบบรวม F

  3. ขั้นตอนที่ 2 สามารถใช้ได้ในการสร้าง rv ~ F โดยไม่ต้องใช้วิธีการนับใด ๆ เมื่อ F ^ -1 สามารถรับการวิเคราะห์ได้โดยไม่มีปัญหา (เช่น exp.distribution)

  4. ในการจำลองการแจกแจงแบบปกติคุณสามารถคำนวณ y1 * cos (y2) โดยที่ y1 ~ มีความสม่ำเสมอใน [0,2pi] และ y2 คือการแจกแจงแบบรีรี

ถาม: ถ้าฉันต้องการค่าเฉลี่ยและส่วนเบี่ยงเบนมาตรฐานที่ฉันเลือก?

คุณสามารถคำนวณ sigma * N (0,1) + m

แสดงได้ว่าการขยับและการปรับขนาดดังกล่าวนำไปสู่ ​​N (m, ซิกม่า)


0

นี่คือการนำ Matlab ไปใช้โดยใช้รูปแบบเชิงขั้วของการแปลงBox-Muller :

ฟังก์ชันrandn_box_muller.m:

function [values] = randn_box_muller(n, mean, std_dev)
    if nargin == 1
       mean = 0;
       std_dev = 1;
    end

    r = gaussRandomN(n);
    values = r.*std_dev - mean;
end

function [values] = gaussRandomN(n)
    [u, v, r] = gaussRandomNValid(n);

    c = sqrt(-2*log(r)./r);
    values = u.*c;
end

function [u, v, r] = gaussRandomNValid(n)
    r = zeros(n, 1);
    u = zeros(n, 1);
    v = zeros(n, 1);

    filter = r==0 | r>=1;

    % if outside interval [0,1] start over
    while n ~= 0
        u(filter) = 2*rand(n, 1)-1;
        v(filter) = 2*rand(n, 1)-1;
        r(filter) = u(filter).*u(filter) + v(filter).*v(filter);

        filter = r==0 | r>=1;
        n = size(r(filter),1);
    end
end

และการเรียกใช้histfit(randn_box_muller(10000000),100);นี่คือผลลัพธ์: Box-Muller Matlab Histfit

เห็นได้ชัดว่ามันจะไม่มีประสิทธิภาพจริงๆเมื่อเทียบกับ Matlab ในตัวrandn



0

นอกจากนี้ยังง่ายกว่าที่จะใช้ฟังก์ชัน rnorm () ที่ใช้งานได้เนื่องจากเร็วกว่าการเขียนตัวสร้างตัวเลขสุ่มสำหรับการแจกแจงปกติ ดูรหัสต่อไปนี้เป็นข้อพิสูจน์

n <- length(z)
t0 <- Sys.time()
z <- rnorm(n)
t1 <- Sys.time()
t1-t0

-2
function distRandom(){
  do{
    x=random(DISTRIBUTION_DOMAIN);
  }while(random(DISTRIBUTION_RANGE)>=distributionFunction(x));
  return x;
}

ไม่รับประกันว่าจะกลับมาอีกหรือไม่? ;-)
Peter K.

5
ตัวเลขสุ่มสำคัญเกินกว่าที่จะปล่อยให้มีโอกาส
Drew Noakes

ไม่ตอบคำถาม - การแจกแจงปกติมีโดเมนไม่สิ้นสุด
แมตต์
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.