การกระจายแบบสุ่มถ่วงน้ำหนักอย่างต่อเนื่องเอนเอียงไปทางปลายด้านหนึ่ง


28

ฉันกำลังมีส่วนร่วมในระบบอนุภาคสำหรับเกมของเราและพัฒนารูปทรงอีซีแอล

การกระจายแบบสุ่มที่สม่ำเสมอของฉันตามเส้นหรือตามพื้นที่สี่เหลี่ยมผืนผ้าทำงานได้ดี - ไม่มีปัญหา

แต่ตอนนี้ฉันอยากได้อะไรที่เหมือนกับการไล่ระดับสี 1 มิติในการแจกแจงนี้ สิ่งนี้จะหมายถึงตัวอย่างเช่นค่าที่ต่ำกว่านั้นเป็นเรื่องธรรมดามากกว่าค่าที่สูง

ฉันไม่รู้ว่าคำศัพท์ทางคณิตศาสตร์ที่เหมาะสมสำหรับปัญหานี้คืออะไรดังนั้นทักษะการค้นหาของฉันจึงค่อนข้างไร้ประโยชน์กับคำนี้ ฉันต้องการบางสิ่งที่เรียบง่ายในการคำนวณเนื่องจากระบบอนุภาคต้องมีประสิทธิภาพ



ไม่มีใครพูดถึงแคลคูลัสไหม
Alec Teal

คำตอบ:


42

ดูรูปนี้:

การทำแผนที่โค้ง

มันแสดงให้เห็นกระบวนการของการทำแผนที่ค่า (สุ่ม) กับเส้นโค้ง สมมติว่าคุณสร้างค่าสุ่มที่กระจายอย่างสม่ำเสมอ X ซึ่งมีค่าตั้งแต่ 0 ถึง 1 โดยการจับคู่ค่านี้กับเส้นโค้ง - หรืออีกนัยหนึ่งคือการใช้ f (X) แทน X - คุณสามารถบิดเบือนการกระจายของคุณในแบบที่คุณต้องการ .

ในภาพนี้เส้นโค้งแรกทำให้ค่าที่สูงกว่ามีแนวโน้มมากขึ้น ที่สองทำให้ค่าที่ต่ำกว่ามีแนวโน้มมากขึ้น และอันที่สามทำให้คลัสเตอร์ค่าอยู่ตรงกลาง สูตรที่แน่นอนของเส้นโค้งไม่สำคัญและสามารถเลือกได้ตามที่คุณต้องการ

ตัวอย่างเช่นเส้นโค้งแรกดูเหมือนบิตสแควร์รูทและสแควร์คล้ายสอง อันที่สามมีลักษณะคล้ายคิวบ์เพียงแปลเท่านั้น หากคุณคิดว่าสแควร์รูทช้าเกินไปเส้นโค้งแรกก็ดูเหมือน f (X) = 1- (1-X) ^ 2 - การผกผันของสแควร์ หรือไฮเปอร์โบล์: f (X) = 2X / (1 + X)

ในฐานะที่เป็นเส้นโค้งที่สี่แสดงให้เห็นว่าคุณสามารถใช้ตารางการค้นหาที่คำนวณล่วงหน้าได้ มันดูน่าเกลียดเหมือนเส้นโค้ง แต่อาจจะดีพอสำหรับระบบอนุภาค

เทคนิคทั่วไปนี้ง่ายและมีประสิทธิภาพมาก ไม่ว่าคุณต้องการการกระจายแบบใดลองจินตนาการถึงการทำแผนที่โค้งและคุณจะคิดสูตรในเวลาไม่นาน หรือหากเครื่องยนต์ของคุณมีโปรแกรมแก้ไขให้สร้างโปรแกรมแก้ไขภาพสำหรับเส้นโค้ง!


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

5
FYI: ฟังก์ชันเหล่านี้เรียกว่าฟังก์ชันควอนไทล์: en.wikipedia.org/wiki/Quantile_function
Neil G

8

คำอธิบายที่ยาวกว่า:

หากคุณมีการแจกแจงความน่าจะเป็นที่ต้องการเช่นการไล่ระดับ @didito ที่ต้องการคุณสามารถอธิบายได้ว่าเป็นฟังก์ชั่น สมมุติว่าคุณต้องการการกระจายแบบสามเหลี่ยมโดยที่ความน่าจะเป็นที่ 0 คือ 0.0 และคุณต้องการเลือกตัวเลขสุ่มจาก 0 ถึง 1 เราอาจเขียนมันเป็น y = x

ขั้นตอนต่อไปคือการคำนวณอินทิกรัลของฟังก์ชันนี้ ในกรณีนี้มันเป็น2} ประเมินจาก 0 ถึง 1 นั่นคือ½ มันสมเหตุสมผล - มันคือสามเหลี่ยมที่มีฐาน 1 และสูง 1 ดังนั้นพื้นที่ของมันคือ½x=1x2

จากนั้นคุณเลือกจุดสุ่มอย่างสม่ำเสมอจาก 0 ถึงพื้นที่ (½ในตัวอย่างของเรา) ลองเรียกนี่ว่า z (เราเลือกอย่างสม่ำเสมอจากการแจกแจงสะสม )

ขั้นตอนต่อไปคือย้อนกลับไปหาค่า x (เราจะเรียกมันว่า x̂) ตรงกับพื้นที่ของ z เรากำลังหา , ประเมินจาก 0 ถึง x̂, เท่ากับ z เมื่อคุณแก้ปัญหาสำหรับคุณจะได้รับ{}x=1x21x̂2=zx̂=2z

ในตัวอย่างนี้คุณเลือก Z จาก 0 ถึง½แล้วสุ่มหมายเลขที่ต้องการเป็น{} ง่ายขึ้นคุณสามารถเขียนเป็น - สิ่งที่ธุรกิจ eBusiness แนะนำ2zrand(0,1)


ขอบคุณสำหรับการป้อนข้อมูลที่มีค่าของคุณ ฉันมักจะชอบที่จะได้ยินว่าคนที่มีทักษะแก้ปัญหาได้อย่างไร แต่ฉันยังคงต้องตัดหัวของฉันรอบมันจะซื่อสัตย์ ...
didito

นี่มันเจ๋งมาก. ฉันทำมาsqrt(random())ทั้งชีวิตของฉันเสมอแต่ฉันได้มาซึ่งประจักษ์ กำลังพยายามผูกตัวเลขสุ่มกับเส้นโค้งและมันใช้งานได้ ตอนนี้ฉันมีทักษะคณิตศาสตร์มากขึ้นการรู้ว่าทำไมการทำงานถึงมีค่ามาก!
Gustavo Maciel

5

คุณอาจได้ใกล้เคียงกับสิ่งที่คุณต้องการโดยใช้ระบบเลขชี้กำลัง

ทำให้ x ขึ้นอยู่กับสิ่งที่ต้องการ 1- (ค่า rnd ^) (สมมติว่า rnd อยู่ระหว่าง 0 ถึง 1) และคุณจะได้รับพฤติกรรมที่แตกต่างกันของการเอียงซ้ายไปขวาตามการใช้งานของคุณ มูลค่าที่สูงขึ้นจะทำให้คุณกระจายการบิดเบือนมากขึ้น

คุณสามารถใช้เครื่องมือสร้างกราฟออนไลน์เพื่อให้ได้แนวคิดคร่าวๆเกี่ยวกับพฤติกรรมของสมการที่แตกต่างกันซึ่งจะให้คุณก่อนที่จะวางลงไปหรือคุณเพียงแค่เล่นซอกับสมการในระบบอนุภาคของคุณโดยตรง

แก้ไข

สำหรับบางสิ่งบางอย่างเช่นระบบอนุภาคที่เวลา CPU ต่ออนุภาคมีความสำคัญมากการใช้ Math.Pow (หรือเทียบเท่าภาษา) โดยตรงอาจทำให้ประสิทธิภาพลดลง หากต้องการประสิทธิภาพเพิ่มขึ้นและไม่มีการเปลี่ยนแปลงค่าในเวลาทำงานให้ลองเปลี่ยนไปใช้ฟังก์ชันที่เทียบเท่าเช่น x * x แทนที่จะเป็น x ^ 2

(เลขชี้กำลังเศษส่วนอาจเป็นปัญหามากกว่า แต่มีบางคนที่มีภูมิหลังทางคณิตศาสตร์ที่แข็งแกร่งกว่าที่ฉันอาจคิดวิธีที่ดีในการสร้างฟังก์ชั่นการประมาณ)


1
แทนที่จะใช้โปรแกรมสร้างกราฟคุณสามารถพล็อตการแจกแจงแบบเบต้าเนื่องจากเป็นกรณีพิเศษ สำหรับค่าที่ระบุvalueนี่คือเบต้า (ค่า 1)
Neil G

ขอบคุณ. ฉันพยายามพล็อตกราฟและคิดว่ามันจะทำให้ฉันได้ที่ที่ฉันต้องการ
didito

@ Neil G ขอบคุณสำหรับเคล็ดลับด้วย "การแจกแจงแบบเบต้า" - ฟังดูน่าสนใจและมีประโยชน์ ... ฉันจะทำวิจัยบางอย่างเกี่ยวกับหัวข้อนั้น
didito

3

คำที่คุณกำลังมองหาคือWeighted Random Numbersอัลกอริธึมส่วนใหญ่ที่ฉันเคยเห็นใช้ฟังก์ชั่นตรีโกณมิติ แต่ฉันคิดว่าฉันคิดหาวิธีที่จะมีประสิทธิภาพ:

สร้างตาราง / array / List (อะไรก็ตาม) ที่เก็บค่าตัวคูณสำหรับฟังก์ชันสุ่ม กรอกด้วยมือหรือเขียนโปรแกรม ...

randMulti= {.1,.1,.1,.1,.1,.1,.2,.2,.3,.3,.9,1,1,1,} 

... จากนั้นคูณrandomด้วยการสุ่มเลือกrandMultiและท้ายที่สุดด้วยค่าสูงสุดของการกระจาย ...

weightedRandom = math.random()*randMulti[Math.random(randMulti.length)]*maxValue

ฉันเชื่อว่าสิ่งนี้จะเร็วกว่าการใช้sqrtงานหรือฟังก์ชั่นการคำนวณที่ซับซ้อนมากขึ้นและจะอนุญาตให้ใช้รูปแบบการจัดกลุ่มที่กำหนดเองมากขึ้น


2
หากคุณสามารถเสียสละหน่วยความจำได้ตาราง 100 ค่าที่คำนวณล่วงหน้าจะเร็วขึ้น (และแม่นยำยิ่งขึ้นเล็กน้อย) ฉันสงสัยว่าผู้ใช้จะสามารถแยกแยะความแตกต่างระหว่างเวอร์ชันเต็มและเวอร์ชั่นที่คำนวณล่วงหน้าได้
Daniel Blezek

@Daniel มันจะเร็วกว่า แต่ด้วยค่า 100 ค่ามันเป็นเรื่องง่ายมากที่จะเห็นรูปแบบการทำซ้ำ
AttackHobo

เพียงเพราะดูเหมือนว่าจะมีรูปแบบการทำซ้ำไม่ได้หมายความว่ามันไม่ได้สุ่ม สาระสำคัญของการสุ่มคือการคาดเดาไม่ได้ซึ่งหมายความว่าเท่าที่เราไม่สามารถคาดเดาได้ว่าจะไม่มีรูปแบบหนึ่งก็ไม่สามารถทำนายได้ว่าอาจมีอย่างใดอย่างหนึ่ง (อย่างน้อยในช่วงเวลาสั้น ๆ ) คุณจะต้องทำการทดสอบบางอย่าง แต่หากคุณพบรูปแบบที่มีการทดสอบหลายรายการโดยใช้เมล็ดที่แตกต่างกันดังนั้นขั้นตอนวิธีในการสร้างตัวเลขสุ่มหลอกอาจต้องได้รับการตรวจสอบ
Randolf Richardson

@ AttackingHobo ขอบคุณสำหรับเคล็ดลับนั้น ฉันชอบการใช้ LUT และสูตรนั้นค่อนข้างเข้าใจง่าย ฉันไม่เคยคิดแบบนี้มาก่อน ไม่เห็นไม้สำหรับต้นไม้ ... :) ฉันคิดว่าควรหลีกเลี่ยงรูปแบบการทำซ้ำ แต่อาจจะไม่ได้รับการยอมรับในกรณีนี้ แต่ถึงกระนั้นการคำนวณค่าล่วงหน้าทั้งหมดจะส่งผลกระทบต่อประสบการณ์ด้านภาพ แล้วขอบคุณสำหรับการเตือนฉันว่านี้เป็นปัจจัยที่จะต้องพิจารณาในเรื่องของการสุ่ม ...
didito

ขอขอบคุณสำหรับการเพิ่มคำว่า "ตัวเลขสุ่ม" แบบถ่วงน้ำหนัก!
didito

2

ฉันคิดว่าสิ่งที่คุณขอคือการกระจายทำได้โดยใช้ฟังก์ชันรากที่สอง

[position] = sqrt(rand(0, 1))

สิ่งนี้จะให้การแจกแจงในฟิลด์มิติเดียว[0, 1]ที่ความน่าจะเป็นสำหรับตำแหน่งเทียบเท่ากับตำแหน่งนั้นคือ "การแจกแจงแบบสามเหลี่ยม"

รุ่นที่ไม่มี squareroot สำรอง:

[position] = 1-abs(rand(0, 1)-rand(0, 1))

รากที่สองในการใช้งานที่ดีที่สุดเป็นเพียงคำสั่งการคูณและผลรวมเพียงไม่กี่ที่ไม่มีสาขา (ดู: http://en.wikipedia.org/wiki/Fast_inverse_square_root ) ฟังก์ชันใดในสองฟังก์ชันนี้ที่เร็วกว่าอาจแตกต่างกันไปขึ้นอยู่กับแพลตฟอร์มและตัวสร้างแบบสุ่ม ตัวอย่างเช่นบนแพลตฟอร์ม x86 มันจะใช้เวลาเพียงไม่กี่สาขาที่ไม่แน่นอนในตัวสร้างแบบสุ่มเพื่อทำให้วิธีที่สองช้าลง


ความน่าจะเป็นของตำแหน่งจะไม่เท่ากับตำแหน่ง (ที่เป็นไปไม่ได้ทางคณิตศาสตร์ - เล็กน้อยโดเมนและช่วงของฟังก์ชั่นรวมทั้ง 0.50 และ 0.51) และไม่ได้เป็นการกระจายแบบสามเหลี่ยม ( en.wikipedia.org/wiki/Triangular_distribution )

1
ในขณะที่ sqrt ให้รูปแบบที่น่าสนใจบางส่วนระบบอนุภาคโดยทั่วไปจะต้องมีแสง CPU มากต่ออนุภาคดังนั้นฉันขอแนะนำให้หลีกเลี่ยงสแควร์รูท (ซึ่งคำนวณได้ช้า) หากเป็นไปได้ บางครั้งคุณสามารถหนีไปได้ด้วยการคำนวณล่วงหน้า แต่มันสามารถทำให้อนุภาคของคุณมี patters ที่สังเกตเห็นได้ตลอดเวลา
Lunin

1
@Joe Wreschnig คุณอ่านบทความของ Wikipedia ด้วยตัวเองแล้วว่าสิ่งที่ a = 0, b = 1, c = 1 ในสูตรการสร้างและคุณได้สูตรในโพสต์ของฉัน
aaaaaaaaaaaa

3
@Lunin ทำไมคุณบ่นเกี่ยวกับสแควร์รูทเมื่อคุณมีเลขชี้กำลังในคำตอบ
aaaaaaaaaaaa

1
@Lunin: ทฤษฎีการปฏิบัติงานเป็นสาขาที่ถูกทอดทิ้งสวยมากหลายคนคิดว่าพวกเขารู้ว่าถูกต้องประมาณ 30 ปีที่ผ่านมาเมื่อ ALUs มีราคาแพงและช้ามาก แม้แต่ฟังก์ชั่นเลขชี้กำลังที่คุณเพิ่งค้นพบว่าเป็นฟังก์ชั่นเลขคณิตที่ค่อนข้างช้าก็ไม่ค่อยจะเป็นคนบาปที่มีประสิทธิภาพสูงมากนัก การแยกสาขา (ใช้คำสั่ง if) และแคชหายไป (การอ่านข้อมูลบางส่วนที่ไม่ได้อยู่ในแคชในปัจจุบัน) โดยทั่วไปแล้วสิ่งที่คุ้มค่ากับประสิทธิภาพที่สุด
aaaaaaaaaaaa

1

เพียงใช้การแจกแจงแบบเบต้า:

  • เบต้า (1,1) แบน
  • เบต้า (1,2) เป็นเส้นไล่ระดับสี
  • เบต้า (1,3) เป็นกำลังสอง

เป็นต้น

พารามิเตอร์รูปร่างสองรูปไม่จำเป็นต้องเป็นจำนวนเต็ม


ขอบคุณสำหรับความช่วยเหลือของคุณ ตามที่ระบุไว้ข้างต้นการแจกแจงแบบเบต้านั้นน่าสนใจ แต่ฉันไม่สามารถเข้าใจเนื้อหาของหน้าวิกิพีเดียได้ หรือสูตร / รหัส ดีฉันยังไม่มีเวลาที่จะตรวจสอบเพิ่มเติม: ศรีเห็นว่าเพิ่มมีรหัสสำหรับการแจกแจงแบบเบต้า แต่นี่จะเป็น overkill ดีฉันคิดว่าฉันต้องผ่านมันก่อนแล้วเขียนเวอร์ชั่นที่เรียบง่ายของตัวเอง
didito

1
@didito: มันไม่ยาก คุณเพียงแค่เปลี่ยนของคุณโทรกับuniform_generator() gsl_ran_beta(rng, a, b)ดูที่นี่: gnu.org/software/gsl/manual/html_node/…
Neil G

ขอบคุณสำหรับคำใบ้ ฉันไม่ได้ใช้ GSL (จริง ๆ แล้วไม่เคยได้ยินมาก่อน) แต่เป็นการโทรที่ดี ฉันจะตรวจสอบแหล่งที่มา!
didito

@idito: ในกรณีนี้ฉันจะไปกับทางออกของ Lunin โชคดี.
Neil G

0

ยิ่งง่ายขึ้นขึ้นอยู่กับความเร็วของเครื่องกำเนิดไฟฟ้าแบบสุ่มคุณสามารถสร้างค่าสองค่าและหาค่าเฉลี่ย

หรือยิ่งง่ายที่ X เป็นผลมาจาก RNG ที่แรก,double y = double(1/x); x = y*[maximum return value of rng];สิ่งนี้จะทำให้จำนวนน้ำหนักเพิ่มขึ้นเป็นเลขชี้กำลังแทนจำนวน

สร้างและเฉลี่ยค่ามากขึ้นเพื่อเพิ่มโอกาสในการรับค่าที่อยู่ใกล้กับศูนย์

แน่นอนว่าสิ่งนี้ใช้ได้เฉพาะกับการกระจายตัวแบบโค้งมาตรฐานหรือรุ่น "พับ" ของมัน * แต่ด้วยตัวกำเนิดที่รวดเร็วมันอาจเร็วและง่ายกว่าการใช้ฟังก์ชันคณิตศาสตร์ต่างๆเช่น sqrt

คุณสามารถค้นหาการวิจัยทุกประเภทเกี่ยวกับเรื่องนี้สำหรับเส้นโค้งระฆังลูกเต๋า อันที่จริงแล้ว Anydice.com เป็นเว็บไซต์ที่ดีซึ่งสร้างกราฟสำหรับวิธีการทอยลูกเต๋าแบบต่างๆ แม้ว่าคุณกำลังใช้ RNG แต่หลักฐานก็เหมือนกันกับผลลัพธ์ ดังนั้นจึงเป็นจุดที่ดีสำหรับการดูการกระจายก่อนที่จะเข้ารหัส

* นอกจากนี้คุณยังสามารถ "พับ" การกระจายผลลัพธ์ไปตามแกนได้โดยการเอาแกนและลบผลลัพธ์เฉลี่ยแล้วจึงเพิ่มแกน ตัวอย่างเช่นคุณต้องการให้ค่าที่ต่ำกว่าเป็นค่าทั่วไปและสมมติว่าคุณต้องการให้ 15 เป็นค่าต่ำสุดของคุณและ 35 เป็นค่าสูงสุดของคุณซึ่งเป็นช่วงที่ 20 ดังนั้นคุณจึงสร้างและเฉลี่ยค่าสองค่าด้วยกันด้วยช่วง 20 สองเท่าของช่วงที่คุณต้องการ) ซึ่งจะให้เสียงระฆังตรงกึ่งกลางที่ 20 (เราลบห้าตอนท้ายเพื่อเปลี่ยนช่วงจาก 20 เป็น 40, 15 ถึง 35) นำตัวเลขที่สร้างขึ้น X และ Y

หมายเลขสุดท้าย

z =(x+y)/2;// average them
If (z<20){z = (20-z)+20;}// fold if below axis
return z-5;// return value adjusted to desired range

ถ้าศูนย์คือค่าต่ำสุดของคุณให้ทำสิ่งนี้แทน

z= (x+y)/2;
If (z<20){z = 20-z;}
else {z = z - 20;}
return z;
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.