เวอร์ชันถ่วงน้ำหนักของ random.choice


245

ฉันต้องการเขียน random.choice แบบถ่วงน้ำหนัก (แต่ละองค์ประกอบในรายการมีความน่าจะเป็นที่แตกต่างกันสำหรับการเลือก) นี่คือสิ่งที่ฉันมาด้วย:

def weightedChoice(choices):
    """Like random.choice, but each element can have a different chance of
    being selected.

    choices can be any iterable containing iterables with two items each.
    Technically, they can have more than two items, the rest will just be
    ignored.  The first item is the thing being chosen, the second item is
    its weight.  The weights can be any numeric values, what matters is the
    relative differences between them.
    """
    space = {}
    current = 0
    for choice, weight in choices:
        if weight > 0:
            space[current] = choice
            current += weight
    rand = random.uniform(0, current)
    for key in sorted(space.keys() + [current]):
        if rand < key:
            return choice
        choice = space[key]
    return None

ฟังก์ชั่นนี้ดูซับซ้อนเกินไปสำหรับฉันและน่าเกลียด ฉันหวังว่าทุกคนที่นี่สามารถให้คำแนะนำในการปรับปรุงหรือวิธีการอื่นในการทำเช่นนี้ ประสิทธิภาพไม่ได้สำคัญเท่ากับฉันในเรื่องความสะอาดและการอ่านรหัส

คำตอบ:


297

ตั้งแต่รุ่น 1.7.0 NumPy มีchoiceฟังก์ชันที่รองรับการแจกแจงความน่าจะเป็น

from numpy.random import choice
draw = choice(list_of_candidates, number_of_items_to_pick,
              p=probability_distribution)

ทราบว่าเป็นลำดับในลำดับเดียวกันของprobability_distribution list_of_candidatesนอกจากนี้คุณยังสามารถใช้คำหลักreplace=Falseเพื่อเปลี่ยนพฤติกรรมเพื่อไม่ให้แทนที่รายการที่วาด


11
โดยการทดสอบของฉันนี่เป็นลำดับความสำคัญช้ากว่าrandom.choicesการโทรแต่ละครั้ง number_of_items_to_pickหากคุณต้องการมากของผลการสุ่มมันเป็นสิ่งสำคัญมากที่จะเลือกพวกเขาทั้งหมดในครั้งเดียวโดยการปรับ ถ้าคุณทำเช่นนั้นมันเป็นลำดับความสำคัญเร็วขึ้น
jpmc26

2
สิ่งนี้ใช้ไม่ได้กับ tuples เป็นต้น ("ValueError: a ต้องเป็น 1 มิติ") ดังนั้นในกรณีนั้นเราสามารถขอให้ numpy เลือกดัชนีในรายการได้เช่นlen(list_of_candidates)และจากนั้นทำlist_of_candidates[draw]
xjcl

218

ตั้งแต่ Python 3.6 มีเมธอดchoicesจากrandomโมดูล

Python 3.6.1 (v3.6.1:69c0db5050, Mar 21 2017, 01:21:04)
Type 'copyright', 'credits' or 'license' for more information
IPython 6.0.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: import random

In [2]: random.choices(
...:     population=[['a','b'], ['b','a'], ['c','b']],
...:     weights=[0.2, 0.2, 0.6],
...:     k=10
...: )

Out[2]:
[['c', 'b'],
 ['c', 'b'],
 ['b', 'a'],
 ['c', 'b'],
 ['c', 'b'],
 ['b', 'a'],
 ['c', 'b'],
 ['b', 'a'],
 ['c', 'b'],
 ['c', 'b']]

โปรดทราบว่าrandom.choicesจะสุ่มตัวอย่างด้วยการเปลี่ยนตามเอกสาร :

ส่งคืนkรายการขนาดขององค์ประกอบที่เลือกจากประชากรพร้อมการแทนที่

หากคุณต้องการตัวอย่างโดยไม่มีการแทนที่ดังนั้นในขณะที่สถานะคำตอบที่ยอดเยี่ยมของ @ ronan-paixãoคุณสามารถใช้numpy.choiceซึ่งreplaceอาร์กิวเมนต์จะควบคุมพฤติกรรมดังกล่าว


4
มันเร็วกว่า numpy.random.choice มาก การเลือกจากรายการ 8 รายการที่มีน้ำหนัก 10,000 ครั้ง numpy.random.choice ใช้เวลา 0.3286 วินาทีโดยที่ random.choices ใช้เวลา 0.0416 วินาทีเร็วขึ้น 8 เท่า
รหัสแอนตัน

@AntonCodes ตัวอย่างนี้เป็นเชอร์รี่ที่เลือก numpy กำลังจะมีโอเวอร์เฮดเวลาคงที่ที่random.choicesไม่แน่นอนดังนั้นมันจะช้ากว่าในรายการย่อ 8 รายการและถ้าคุณเลือก 10k ครั้งจากรายการคุณก็พูดถูก แต่ในบางกรณีเมื่อรายการมีขนาดใหญ่ขึ้น (ขึ้นอยู่กับว่าคุณทดสอบอย่างไรฉันเห็นจุดแตกหักระหว่างองค์ประกอบ 100-300) np.random.choiceเริ่มมีประสิทธิภาพเหนือกว่าrandom.choicesด้วยช่องว่างที่ค่อนข้างกว้าง ตัวอย่างเช่นรวมถึงขั้นตอนการทำให้เป็นมาตรฐานพร้อมกับการเรียก numpy ฉันได้รับการเร่งความเร็วเกือบ 4x random.choicesสำหรับรายการองค์ประกอบ 10k
ggorlen

นี่ควรเป็นคำตอบใหม่ตามการปรับปรุงประสิทธิภาพที่ @AntonCodes รายงาน
Wayne Workman

132
def weighted_choice(choices):
   total = sum(w for c, w in choices)
   r = random.uniform(0, total)
   upto = 0
   for c, w in choices:
      if upto + w >= r:
         return c
      upto += w
   assert False, "Shouldn't get here"

10
คุณสามารถวางการดำเนินงานและประหยัดเศษไม้ของเวลาโดยการกลับงบภายในห่วง:upto +=w; if upto > r
knite

5
บันทึกตัวแปรโดยการลบเกินและเพียงแค่ลด r โดยน้ำหนักในแต่ละครั้ง การเปรียบเทียบนั้นif r < 0
JnBrymn

@JnBrymn r <= 0คุณต้องตรวจสอบ พิจารณาชุดอินพุต 1 รายการและม้วน 1.0 การยืนยันจะล้มเหลว ฉันแก้ไขข้อผิดพลาดนั้นในคำตอบ
moooeeeep

1
@ Sardathrion คุณสามารถใช้ pragma เพื่อทำเครื่องหมาย for loop เป็นบางส่วน:# pragma: no branch
Ned Batchelder

1
@ mLstudent33 ฉันไม่ได้ใช้ Udacity
รหัส Anton

70
  1. จัดน้ำหนักให้เป็นการกระจายแบบสะสม
  2. ใช้random.random ()0.0 <= x < totalเพื่อเลือกลอยสุ่ม
  3. ค้นหากระจายโดยใช้bisect.bisectดังแสดงในตัวอย่างที่http://docs.python.org/dev/library/bisect.html#other-examples
from random import random
from bisect import bisect

def weighted_choice(choices):
    values, weights = zip(*choices)
    total = 0
    cum_weights = []
    for w in weights:
        total += w
        cum_weights.append(total)
    x = random() * total
    i = bisect(cum_weights, x)
    return values[i]

>>> weighted_choice([("WHITE",90), ("RED",8), ("GREEN",2)])
'WHITE'

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


5
นี่มีประสิทธิภาพมากกว่าคำตอบของเน็ด โดยพื้นฐานแล้วแทนที่จะทำการค้นหาเชิงเส้น (O (n)) ผ่านทางเลือกเขากำลังทำการค้นหาแบบไบนารี่ (O (ล็อก n)) +1!
NHDaly

tuple ดัชนีอยู่นอกช่วงถ้าสุ่ม () เกิดขึ้นเพื่อส่งคืน 1.0
Jon Vaughan

10
สิ่งนี้ยังคงทำงานอยู่O(n)เนื่องจากการคำนวณการแจกแจงสะสม
Lev Levitsky

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

1
@JonVaughan random() ไม่สามารถส่งคืนได้ 1.0 สำหรับเอกสารนั้นจะส่งคืนผลลัพธ์ในช่วงครึ่งเวลาที่เปิด[0.0, 1.0)ซึ่งอาจกล่าวได้ว่าสามารถส่งคืนได้อย่างแน่นอน 0.0 แต่ไม่สามารถคืนค่าที่แน่นอนได้ 1.0 ค่าที่ใหญ่ที่สุดที่สามารถส่งคืนได้คือ 0.999999999999999887769753748434595763683319091796875 (ซึ่ง Python พิมพ์เป็น 0.9999999999999999 และเป็น 64 บิตที่ใหญ่ที่สุดที่น้อยกว่า 1)
Mark Amery

21

หากคุณไม่ทราบใช้ numpy คุณสามารถใช้numpy.random.choice

ตัวอย่างเช่น:

import numpy

items  = [["item1", 0.2], ["item2", 0.3], ["item3", 0.45], ["item4", 0.05]
elems = [i[0] for i in items]
probs = [i[1] for i in items]

trials = 1000
results = [0] * len(items)
for i in range(trials):
    res = numpy.random.choice(items, p=probs)  #This is where the item is selected!
    results[items.index(res)] += 1
results = [r / float(trials) for r in results]
print "item\texpected\tactual"
for i in range(len(probs)):
    print "%s\t%0.4f\t%0.4f" % (items[i], probs[i], results[i])

หากคุณทราบจำนวนตัวเลือกที่คุณต้องการล่วงหน้าคุณสามารถทำได้โดยไม่ต้องวนซ้ำเช่นนี้:

numpy.random.choice(items, trials, p=probs)

15

ดิบ แต่อาจเพียงพอ:

import random
weighted_choice = lambda s : random.choice(sum(([v]*wt for v,wt in s),[]))

ใช้งานได้หรือไม่

# define choices and relative weights
choices = [("WHITE",90), ("RED",8), ("GREEN",2)]

# initialize tally dict
tally = dict.fromkeys(choices, 0)

# tally up 1000 weighted choices
for i in xrange(1000):
    tally[weighted_choice(choices)] += 1

print tally.items()

พิมพ์:

[('WHITE', 904), ('GREEN', 22), ('RED', 74)]

สมมติว่าน้ำหนักทั้งหมดเป็นจำนวนเต็ม พวกเขาไม่จำเป็นต้องเพิ่มมากถึง 100 ฉันเพิ่งทำเช่นนั้นเพื่อให้ผลการทดสอบตีความได้ง่าย (หากน้ำหนักเป็นตัวเลขทศนิยมให้คูณด้วย 10 ซ้ำ ๆ จนกระทั่งน้ำหนักทั้งหมด> = 1)

weights = [.6, .2, .001, .199]
while any(w < 1.0 for w in weights):
    weights = [w*10 for w in weights]
weights = map(int, weights)

1
ดีฉันไม่แน่ใจว่าฉันสามารถสรุปน้ำหนักทั้งหมดเป็นจำนวนเต็มได้
โคลิน

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

Primitives จะถูกทำซ้ำ แต่วัตถุจะมีการอ้างอิงที่ซ้ำกันเท่านั้นไม่ใช่วัตถุเอง (นี่คือเหตุผลที่คุณไม่สามารถสร้างรายการของรายการที่ใช้[[]]*10- องค์ประกอบทั้งหมดในรายการด้านนอกชี้ไปที่รายการเดียวกัน
PaulMcG

@PaulMcG ไม่; ไม่มีอะไรนอกจากการอ้างอิงจะถูกทำซ้ำ ระบบการพิมพ์แบบของ Python ไม่มีแนวความคิดพื้นฐาน คุณสามารถยืนยันได้ว่าแม้แต่กับตัวอย่างที่intคุณยังคงได้รับการอ้างอิงจำนวนมากไปยังวัตถุเดียวกันโดยทำสิ่งที่ชอบ[id(x) for x in ([99**99] * 100)]และสังเกตว่าidส่งคืนที่อยู่หน่วยความจำเดียวกันในทุกการโทร
Mark Amery

14

หากคุณมีพจนานุกรมถ่วงน้ำหนักแทนรายการคุณสามารถเขียนสิ่งนี้

items = { "a": 10, "b": 5, "c": 1 } 
random.choice([k for k in items for dummy in range(items[k])])

โปรดทราบว่า[k for k in items for dummy in range(items[k])]ผลิตรายการนี้['a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'c', 'b', 'b', 'b', 'b', 'b']


10
สิ่งนี้ใช้ได้กับค่าประชากรทั้งหมดเพียงเล็กน้อย แต่ไม่ใช่สำหรับชุดข้อมูลขนาดใหญ่ (เช่นประชากรของสหรัฐฯโดยรัฐจะสิ้นสุดการสร้างรายการทำงานที่มี 300 ล้านรายการในนั้น)
Ryan

@ Ryan แน่นอน นอกจากนี้ยังใช้งานไม่ได้กับน้ำหนักที่ไม่เป็นจำนวนเต็มซึ่งเป็นอีกสถานการณ์ที่เป็นจริง (เช่นหากคุณมีน้ำหนักที่แสดงเป็นความน่าจะเป็นของการเลือก)
Mark Amery

12

ในฐานะของงูหลามv3.6, random.choicesสามารถใช้ในการกลับมาเป็นlistขององค์ประกอบของขนาดระบุจากประชากรที่มีน้ำหนักตัวเลือก

random.choices(population, weights=None, *, cum_weights=None, k=1)

  • ประชากร : listมีข้อสังเกตที่ไม่ซ้ำใคร (ถ้าว่างให้เพิ่มIndexError)

  • ตุ้มน้ำหนัก : ตุ้มน้ำหนักสัมพัทธ์ที่แม่นยำยิ่งขึ้นเพื่อทำการเลือก

  • cum_weights : น้ำหนักสะสมที่จำเป็นสำหรับการเลือก

  • k : ขนาด ( len) ของที่listจะเอาท์พุท (ค่าเริ่มต้นlen()=1)


ไม่กี่ Caveats:

1) ใช้การสุ่มตัวอย่างแบบถ่วงน้ำหนักพร้อมการเปลี่ยนดังนั้นรายการที่วาดจะถูกแทนที่ภายหลัง ค่าในลำดับของน้ำหนักในตัวมันไม่สำคัญ แต่อัตราส่วนสัมพัทธ์ของมันนั้น

ซึ่งแตกต่างจากnp.random.choiceที่สามารถใช้กับความน่าจะเป็นน้ำหนักและยังต้องให้แน่ใจว่าการรวมตัวของความน่าจะเป็นแต่ละคนไม่เกิน 1 เกณฑ์ไม่มีกฎระเบียบดังกล่าวที่นี่ ตราบใดที่ยังเป็นประเภทตัวเลข ( int/float/fractionยกเว้นDecimalประเภท) สิ่งเหล่านี้จะยังคงทำงานต่อไป

>>> import random
# weights being integers
>>> random.choices(["white", "green", "red"], [12, 12, 4], k=10)
['green', 'red', 'green', 'white', 'white', 'white', 'green', 'white', 'red', 'white']
# weights being floats
>>> random.choices(["white", "green", "red"], [.12, .12, .04], k=10)
['white', 'white', 'green', 'green', 'red', 'red', 'white', 'green', 'white', 'green']
# weights being fractions
>>> random.choices(["white", "green", "red"], [12/100, 12/100, 4/100], k=10)
['green', 'green', 'white', 'red', 'green', 'red', 'white', 'green', 'green', 'green']

2) หากไม่ได้ระบุน้ำหนักหรือcum_weightsจะทำการเลือกโดยมีความน่าจะเป็นที่เท่ากัน หากมีการจัดลำดับน้ำหนักมันจะต้องมีความยาวเท่ากับลำดับประชากร

ระบุทั้งน้ำหนักและcum_weightsTypeErrorยก

>>> random.choices(["white", "green", "red"], k=10)
['white', 'white', 'green', 'red', 'red', 'red', 'white', 'white', 'white', 'green']

3) cum_weightsมักจะเป็นผลมาจากitertools.accumulateฟังก์ชั่นซึ่งมีประโยชน์จริง ๆ ในสถานการณ์เช่นนี้

จากเอกสารที่เชื่อมโยง:

ภายในน้ำหนักที่สัมพันธ์กันจะถูกแปลงเป็นน้ำหนักสะสมก่อนทำการเลือกดังนั้นการจัดหาตุ้มน้ำหนักสะสมจะบันทึกการทำงาน

ดังนั้นการจัดหาweights=[12, 12, 4]หรือcum_weights=[12, 24, 28]กรณีที่เราประดิษฐ์จะให้ผลลัพธ์ที่เหมือนกันและดูเหมือนว่าจะเร็วกว่า / มีประสิทธิภาพมากกว่า


11

นี่คือเวอร์ชั่นที่รวมอยู่ในไลบรารี่มาตรฐานสำหรับ Python 3.6:

import itertools as _itertools
import bisect as _bisect

class Random36(random.Random):
    "Show the code included in the Python 3.6 version of the Random class"

    def choices(self, population, weights=None, *, cum_weights=None, k=1):
        """Return a k sized list of population elements chosen with replacement.

        If the relative weights or cumulative weights are not specified,
        the selections are made with equal probability.

        """
        random = self.random
        if cum_weights is None:
            if weights is None:
                _int = int
                total = len(population)
                return [population[_int(random() * total)] for i in range(k)]
            cum_weights = list(_itertools.accumulate(weights))
        elif weights is not None:
            raise TypeError('Cannot specify both weights and cumulative weights')
        if len(cum_weights) != len(population):
            raise ValueError('The number of weights does not match the population')
        bisect = _bisect.bisect
        total = cum_weights[-1]
        return [population[bisect(cum_weights, random() * total)] for i in range(k)]

ที่มา: https://hg.python.org/cpython/file/tip/Lib/random.py#l340



2

ฉันอาจสายเกินไปที่จะมีส่วนร่วมในสิ่งที่มีประโยชน์ แต่นี่เป็นตัวอย่างสั้น ๆ ง่าย ๆ และมีประสิทธิภาพมาก:

def choose_index(probabilies):
    cmf = probabilies[0]
    choice = random.random()
    for k in xrange(len(probabilies)):
        if choice <= cmf:
            return k
        else:
            cmf += probabilies[k+1]

ไม่จำเป็นต้องเรียงลำดับความน่าจะเป็นของคุณหรือสร้างเวกเตอร์ด้วย cmf ของคุณและมันจะสิ้นสุดลงเมื่อพบทางเลือก หน่วยความจำ: O (1), เวลา: O (N), ที่มีเวลาทำงานเฉลี่ย ~ N / 2

หากคุณมีน้ำหนักเพียงเพิ่มหนึ่งบรรทัด:

def choose_index(weights):
    probabilities = weights / sum(weights)
    cmf = probabilies[0]
    choice = random.random()
    for k in xrange(len(probabilies)):
        if choice <= cmf:
            return k
        else:
            cmf += probabilies[k+1]

1
มีหลายสิ่งที่ผิดกับสิ่งนี้ เผิน ๆ มีบางชื่อตัวแปร typoed np.random.choiceและมีเหตุผลไม่ได้รับสำหรับการใช้ในช่วงนี้พูด แต่น่าสนใจยิ่งกว่าคือมีโหมดความล้มเหลวซึ่งสิ่งนี้จะทำให้เกิดข้อยกเว้น การทำprobabilities = weights / sum(weights)ไม่ได้รับประกันว่าprobabilitiesจะรวมเป็น 1; ตัวอย่างเช่นหากweightsเป็น[1,1,1,1,1,1,1]แล้วprobabilitiesจะสรุปผลเพื่อ 0.9999999999999998 มีขนาดเล็กกว่าค่าตอบแทนที่ใหญ่ที่สุดที่เป็นไปได้random.random(ซึ่งเป็น 0.9999999999999999) จากนั้นchoice <= cmfไม่เคยพอใจ
Mark Amery

2

หากรายการตัวเลือกถ่วงน้ำหนักของคุณค่อนข้างคงที่และคุณต้องการสุ่มตัวอย่างบ่อยครั้งคุณสามารถทำหนึ่งขั้นตอนการประมวลผลล่วงหน้า O (N) จากนั้นทำการเลือกใน O (1) โดยใช้ฟังก์ชันในคำตอบที่เกี่ยวข้องนี้

# run only when `choices` changes.
preprocessed_data = prep(weight for _,weight in choices)

# O(1) selection
value = choices[sample(preprocessed_data)][0]

1

ฉันดูเธรดอื่น ๆ ที่แหลมและเกิดขึ้นกับรูปแบบการเขียนโค้ดของฉันซึ่งจะส่งคืนดัชนีของตัวเลือกสำหรับจุดประสงค์ในการรับทราบ แต่มันง่ายที่จะคืนสตริง (คอมเม้นต์ return)

import random
import bisect

try:
    range = xrange
except:
    pass

def weighted_choice(choices):
    total, cumulative = 0, []
    for c,w in choices:
        total += w
        cumulative.append((total, c))
    r = random.uniform(0, total)
    # return index
    return bisect.bisect(cumulative, (r,))
    # return item string
    #return choices[bisect.bisect(cumulative, (r,))][0]

# define choices and relative weights
choices = [("WHITE",90), ("RED",8), ("GREEN",2)]

tally = [0 for item in choices]

n = 100000
# tally up n weighted choices
for i in range(n):
    tally[weighted_choice(choices)] += 1

print([t/sum(tally)*100 for t in tally])

1

ขึ้นอยู่กับจำนวนครั้งที่คุณต้องการสุ่มตัวอย่างการแจกแจง

สมมติว่าคุณต้องการตัวอย่างการกระจาย K คูณ จากนั้นความซับซ้อนของเวลาที่ใช้np.random.choice()ในแต่ละครั้งคือO(K(n + log(n)))เมื่อnมีจำนวนรายการในการกระจาย

ในกรณีของฉันฉันต้องการตัวอย่างการแจกแจงแบบเดียวกันหลายเท่าของลำดับ 10 ^ 3 โดยที่ n คือลำดับ 10 ^ 6 ฉันใช้รหัสด้านล่างซึ่ง precomputes O(log(n))การกระจายสะสมและตัวอย่างใน O(n+K*log(n))ความซับซ้อนของเวลาโดยรวมคือ

import numpy as np

n,k = 10**6,10**3

# Create dummy distribution
a = np.array([i+1 for i in range(n)])
p = np.array([1.0/n]*n)

cfd = p.cumsum()
for _ in range(k):
    x = np.random.uniform()
    idx = cfd.searchsorted(x, side='right')
    sampled_element = a[idx]

1

หากคุณมี Python 3 และกลัวที่จะติดตั้งnumpyหรือเขียนลูปของคุณเองคุณสามารถทำได้:

import itertools, bisect, random

def weighted_choice(choices):
   weights = list(zip(*choices))[1]
   return choices[bisect.bisect(list(itertools.accumulate(weights)),
                                random.uniform(0, sum(weights)))][0]

เพราะคุณสามารถสร้างอะไรก็ได้จากถุงอะแดปเตอร์ไฟฟ้า! แม้ว่า ... ฉันต้องยอมรับว่าคำตอบของเน็ดในขณะที่อีกต่อไปเล็กน้อยจะเข้าใจได้ง่ายขึ้น


0

วิธีแก้ปัญหาทั่วไป:

import random
def weighted_choice(choices, weights):
    total = sum(weights)
    treshold = random.uniform(0, total)
    for k, weight in enumerate(weights):
        total -= weight
        if total < treshold:
            return choices[k]

0

นี่เป็นอีกเวอร์ชันหนึ่งของ weighted_choice ที่ใช้จำนวนมาก ส่งผ่านเวกเตอร์ตุ้มน้ำหนักและจะส่งคืนอาร์เรย์ของ 0 ที่มี 1 ซึ่งระบุว่าถังใดถูกเลือก รหัสเริ่มต้นของการวาดเพียงครั้งเดียว แต่คุณสามารถผ่านจำนวนการดึงที่จะทำและจำนวนนับต่อการดึงถังจะถูกส่งกลับ

หากเวกเตอร์ตุ้มน้ำหนักไม่รวมเป็น 1 มันจะถูกทำให้เป็นมาตรฐานเพื่อให้เป็นเช่นนั้น

import numpy as np

def weighted_choice(weights, n=1):
    if np.sum(weights)!=1:
        weights = weights/np.sum(weights)

    draws = np.random.random_sample(size=n)

    weights = np.cumsum(weights)
    weights = np.insert(weights,0,0.0)

    counts = np.histogram(draws, bins=weights)
    return(counts[0])

0

อีกวิธีในการทำเช่นนี้โดยสมมติว่าเรามีน้ำหนักที่ดัชนีเดียวกันกับองค์ประกอบในอาร์เรย์องค์ประกอบ

import numpy as np
weights = [0.1, 0.3, 0.5] #weights for the item at index 0,1,2
# sum of weights should be <=1, you can also divide each weight by sum of all weights to standardise it to <=1 constraint.
trials = 1 #number of trials
num_item = 1 #number of items that can be picked in each trial
selected_item_arr = np.random.multinomial(num_item, weights, trials)
# gives number of times an item was selected at a particular index
# this assumes selection with replacement
# one possible output
# selected_item_arr
# array([[0, 0, 1]])
# say if trials = 5, the the possible output could be 
# selected_item_arr
# array([[1, 0, 0],
#   [0, 0, 1],
#   [0, 0, 1],
#   [0, 1, 0],
#   [0, 0, 1]])

ทีนี้สมมติว่าเราต้องสุ่ม 3 รายการในการทดลอง 1 ครั้ง คุณสามารถสันนิษฐานได้ว่ามีลูกบอลสามลูก R, G, B อยู่ในปริมาณมากในอัตราส่วนของน้ำหนักที่กำหนดโดยแถวน้ำหนักต่อไปนี้อาจเป็นไปได้ผลลัพธ์:

num_item = 3
trials = 1
selected_item_arr = np.random.multinomial(num_item, weights, trials)
# selected_item_arr can give output like :
# array([[1, 0, 2]])

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

num_binomial_trial = 5
weights = [0.1,0.9] #say an unfair coin weights for H/T
num_experiment_set = 1
selected_item_arr = np.random.multinomial(num_binomial_trial, weights, num_experiment_set)
# possible output
# selected_item_arr
# array([[1, 4]])
# i.e H came 1 time and T came 4 times in 5 binomial trials. And one set contains 5 binomial trails.

0

มีการบรรยายเรื่องนี้โดย Sebastien Thurn ในหลักสูตร Udacity AI ฟรีสำหรับวิทยาการหุ่นยนต์ โดยทั่วไปเขาสร้างอาร์เรย์แบบวงกลมของน้ำหนักที่จัดทำดัชนีโดยใช้ตัวดำเนินการ mod %ตั้งค่าตัวแปรเบต้าเป็น 0 สุ่มเลือกดัชนีสำหรับลูปผ่าน N โดยที่ N คือจำนวนดัชนีและในลูปเพิ่มขึ้นครั้งแรกโดยสูตรเบต้า:

beta = beta + ตัวอย่างสม่ำเสมอจาก {0 ... 2 * Weight_max}

และซ้อนใน for for a, a while loop ต่อด้านล่าง:

while w[index] < beta:
    beta = beta - w[index]
    index = index + 1

select p[index]

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

ลิงค์บรรยาย: https://classroom.udacity.com/courses/cs373/lessons/48704330/concepts/487480820923

ฉันลงชื่อเข้าใช้ Udacity ด้วยบัญชีโรงเรียนของฉันดังนั้นหากลิงก์ไม่ทำงานนั่นคือบทเรียนที่ 8 วิดีโอหมายเลข 21 ของปัญญาประดิษฐ์สำหรับวิทยาการหุ่นยนต์ที่เขาบรรยายเกี่ยวกับตัวกรองอนุภาค


-1

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

def rand_weighted(weights):
    """
    Generator which uses the weights to generate a
    weighted random values
    """
    sum_weights = sum(weights.values())
    cum_weights = {}
    current_weight = 0
    for key, value in sorted(weights.iteritems()):
        current_weight += value
        cum_weights[key] = current_weight
    while True:
        sel = int(random.uniform(0, 1) * sum_weights)
        for key, value in sorted(cum_weights.iteritems()):
            if sel < value:
                break
        yield key

-1

ใช้งานเป็นก้อน

def choice(items, weights):
    return items[np.argmin((np.cumsum(weights) / sum(weights)) < np.random.rand())]

NumPy มีอยู่แล้วดังที่ได้np.random.choiceกล่าวไว้ในคำตอบที่ยอมรับมาตั้งแต่ปี 2014 จุดประสงค์ของคุณคืออะไร
Mark Amery

-1

ฉันต้องทำอะไรแบบนี้เร็วจริง ๆ ง่าย ๆ จากการค้นหาความคิดในที่สุดฉันก็สร้างเทมเพลตนี้ ความคิดจะได้รับค่าถ่วงน้ำหนักในรูปแบบของ json จาก api ซึ่งนี่คือการจำลองโดย dict

จากนั้นแปลเป็นรายการที่ค่าแต่ละค่ามีน้ำหนักซ้ำกันและใช้ random.choice เพื่อเลือกค่าจากรายการ

ฉันลองใช้ด้วยซ้ำ 10, 100 และ 1,000 การกระจายตัวค่อนข้างแข็งแกร่ง

def weighted_choice(weighted_dict):
    """Input example: dict(apples=60, oranges=30, pineapples=10)"""
    weight_list = []
    for key in weighted_dict.keys():
        weight_list += [key] * weighted_dict[key]
    return random.choice(weight_list)

-1

ฉันไม่ชอบไวยากรณ์ของสิ่งเหล่านี้ ฉันอยากจะระบุว่ารายการนั้นเป็นอะไรและน้ำหนักของแต่ละรายการเป็นเท่าไหร่ ฉันรู้ว่าฉันสามารถใช้งานได้random.choicesแต่ฉันเขียนชั้นล่างอย่างรวดเร็ว

import random, string
from numpy import cumsum

class randomChoiceWithProportions:
    '''
    Accepts a dictionary of choices as keys and weights as values. Example if you want a unfair dice:


    choiceWeightDic = {"1":0.16666666666666666, "2": 0.16666666666666666, "3": 0.16666666666666666
    , "4": 0.16666666666666666, "5": .06666666666666666, "6": 0.26666666666666666}
    dice = randomChoiceWithProportions(choiceWeightDic)

    samples = []
    for i in range(100000):
        samples.append(dice.sample())

    # Should be close to .26666
    samples.count("6")/len(samples)

    # Should be close to .16666
    samples.count("1")/len(samples)
    '''
    def __init__(self, choiceWeightDic):
        self.choiceWeightDic = choiceWeightDic
        weightSum = sum(self.choiceWeightDic.values())
        assert weightSum == 1, 'Weights sum to ' + str(weightSum) + ', not 1.'
        self.valWeightDict = self._compute_valWeights()

    def _compute_valWeights(self):
        valWeights = list(cumsum(list(self.choiceWeightDic.values())))
        valWeightDict = dict(zip(list(self.choiceWeightDic.keys()), valWeights))
        return valWeightDict

    def sample(self):
        num = random.uniform(0,1)
        for key, val in self.valWeightDict.items():
            if val >= num:
                return key

-1

ระบุ random.choice () พร้อมรายการน้ำหนักล่วงหน้า:

โซลูชัน & ทดสอบ:

import random

options = ['a', 'b', 'c', 'd']
weights = [1, 2, 5, 2]

weighted_options = [[opt]*wgt for opt, wgt in zip(options, weights)]
weighted_options = [opt for sublist in weighted_options for opt in sublist]
print(weighted_options)

# test

counts = {c: 0 for c in options}
for x in range(10000):
    counts[random.choice(weighted_options)] += 1

for opt, wgt in zip(options, weights):
    wgt_r = counts[opt] / 10000 * sum(weights)
    print(opt, counts[opt], wgt, wgt_r)

เอาท์พุท:

['a', 'b', 'b', 'c', 'c', 'c', 'c', 'c', 'd', 'd']
a 1025 1 1.025
b 1948 2 1.948
c 5019 5 5.019
d 2008 2 2.008
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.