Musiałem napisać ważoną wersję random.choice (każdy element na liście ma inne prawdopodobieństwo wyboru). Oto co wymyśliłem:
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
Ta funkcja wydaje mi się zbyt skomplikowana i brzydka. Mam nadzieję, że wszyscy tutaj będą mogli zaproponować kilka ulepszeń lub alternatywne sposoby na zrobienie tego. Wydajność nie jest dla mnie tak ważna, jak czystość i czytelność kodu.
python
optimization
Colin
źródło
źródło
random.choices
przypadku pojedynczych połączeń. Jeśli potrzebujesz wielu losowych wyników, bardzo ważne jest, aby wybrać je wszystkie jednocześnie, dostosowującnumber_of_items_to_pick
. Jeśli to zrobisz, będzie to rząd wielkości szybszy.len(list_of_candidates)
I wtedylist_of_candidates[draw]
Od wersji Python 3.6 istnieje metoda
choices
zrandom
modułu.Pamiętaj, że
random.choices
będzie próbkować z zamiennikiem , według dokumentów :Jeśli musisz próbkować bez zamiany, wówczas jako genialną odpowiedź @ ronan-paixão można użyć
numpy.choice
, któregoreplace
argument kontroluje takie zachowanie.źródło
random.choices
nie ma, więc oczywiście jest wolniejszy na maleńkiej liście 8 przedmiotów, a jeśli wybierasz 10 000 razy z takiej listy, masz rację. Ale w przypadkach, gdy lista jest większa (w zależności od tego, jak testujesz, widzę punkty przerwania między 100-300 elementów),np.random.choice
zaczyna osiągać lepsze wynikirandom.choices
od dość szerokiej luki. Na przykład, włączając krok normalizacyjny wraz z wywołaniem numpy, otrzymuję prawie 4x przyspieszenierandom.choices
dla listy 10k elementów.źródło
upto +=w; if upto > r
if r < 0
r <= 0
. Rozważ zestaw wejściowy z 1 przedmiotów i rzut 1,0. Twierdzenie wtedy się nie powiedzie. Poprawiłem ten błąd w odpowiedzi.# pragma: no branch
0.0 <= x < total
.Jeśli musisz dokonać więcej niż jednego wyboru, podziel to na dwie funkcje, jedną do zbudowania skumulowanych wag, a drugą do podzielenia na losowe punkty.
źródło
O(n)
powodu obliczenia skumulowanego rozkładu.random()
nie może zwrócić 1.0. Według dokumentów zwraca wynik w półotwartym interwale[0.0, 1.0)
, co oznacza, że może zwrócić dokładnie 0,0, ale nie może zwrócić dokładnie 1,0. Największa wartość, jaką może zwrócić, to 0,99999999999999988897769753748434595763683319091796875 (która Python drukuje jako 0,999999999999999999 i jest największą liczbą zmiennoprzecinkową 64-bit mniejszą niż 1).Jeśli nie masz nic przeciwko użyciu numpy, możesz użyć numpy.random.choice .
Na przykład:
Jeśli wiesz, ile wyborów musisz zrobić z góry, możesz to zrobić bez takiej pętli:
źródło
Surowy, ale może być wystarczający:
Czy to działa?
Wydruki:
Zakłada, że wszystkie wagi są liczbami całkowitymi. Nie muszą sumować się do 100, właśnie to zrobiłem, aby wyniki testu były łatwiejsze do interpretacji. (Jeśli wagi są liczbami zmiennoprzecinkowymi, należy pomnożyć je wszystkie przez 10 razy, aż wszystkie wagi> = 1.)
źródło
[[]]*10
- wszystkie elementy na liście zewnętrznej wskazują na tę samą listę.int
Wciąż otrzymujesz wiele odniesień do tego samego obiektu, robiąc coś podobnego[id(x) for x in ([99**99] * 100)]
i obserwując, żeid
zwraca ten sam adres pamięci przy każdym wywołaniu.Jeśli masz ważoną słownik zamiast listy, możesz to napisać
Zauważ, że
[k for k in items for dummy in range(items[k])]
tworzy tę listę['a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'c', 'b', 'b', 'b', 'b', 'b']
źródło
Począwszy od Pythona
v3.6
,random.choices
można go użyć do zwrócenialist
elementów o określonym rozmiarze z danej populacji z opcjonalnymi wagami.populacja :
list
zawierająca unikalne obserwacje. (Jeśli pusty, podnosiIndexError
)wagi : Aby dokonać selekcji, wymagane są bardziej dokładne wagi względne.
cum_weights : skumulowane wagi wymagane do dokonania selekcji.
k : rozmiar (
len
),list
który ma być wyprowadzony. (Domyślnielen()=1
)Kilka ostrzeżeń:
1) Wykorzystuje ważenie próbkowania z wymianą, aby narysowane elementy zostały później zastąpione. Wartości w sekwencji wag same w sobie nie mają znaczenia, ale ich względny stosunek ma znaczenie.
W przeciwieństwie do tego,
np.random.choice
który może przyjmować jedynie prawdopodobieństwa jako wagi, a także który musi zapewniać sumowanie indywidualnych prawdopodobieństw do 1 kryteriów, nie ma tutaj takich przepisów. Tak długo, jak należą do typów numerycznych (int/float/fraction
opróczDecimal
typu), będą one nadal działać.2) Jeśli nie podano ani wag, ani cum_weights , wyborów dokonuje się z jednakowym prawdopodobieństwem. Jeśli podano sekwencję wag , musi ona być tej samej długości co sekwencja populacji .
Określenie zarówno wag, jak i cum_weights podnosi a
TypeError
.3) Cum_wagi są zazwyczaj wynikiem
itertools.accumulate
funkcji, które są naprawdę przydatne w takich sytuacjach.Zatem dostarczanie
weights=[12, 12, 4]
lubcum_weights=[12, 24, 28]
w naszym przemyślanym przypadku daje ten sam rezultat, a ten drugi wydaje się być szybszy / bardziej wydajny.źródło
Oto wersja, która jest zawarta w standardowej bibliotece Python 3.6:
Źródło: https://hg.python.org/cpython/file/tip/Lib/random.py#l340
źródło
źródło
Prawdopodobnie spóźniłem się, by przekazać coś przydatnego, ale oto prosty, krótki i bardzo skuteczny fragment:
Nie musisz sortować swoich prawdopodobieństw ani tworzyć wektora za pomocą cmf, i kończy się, gdy znajdzie swój wybór. Pamięć: O (1), czas: O (N), ze średnim czasem pracy ~ N / 2.
Jeśli masz wagi, po prostu dodaj jedną linię:
źródło
np.random.choice
. Co ciekawsze, istnieje tryb awaryjny, w którym pojawia się wyjątek. Postępowanieprobabilities = weights / sum(weights)
nie gwarantuje, żeprobabilities
suma będzie równa 1; na przykład ifweights
jest[1,1,1,1,1,1,1]
wtedyprobabilities
sumą tylko 0,9999999999999998, mniejszą niż największa możliwa wartość zwracanarandom.random
(czyli 0,9999999999999999). Wtedychoice <= cmf
nigdy nie będzie zadowolony.Jeśli twoja lista ważonych wyborów jest względnie statyczna i chcesz często próbkować, możesz wykonać jeden etap wstępnego przetwarzania O (N), a następnie dokonać wyboru w O (1), korzystając z funkcji w tej pokrewnej odpowiedzi .
źródło
Spojrzałem na wskazany drugi wątek i wymyśliłem tę odmianę w moim stylu kodowania, to zwraca indeks wyboru do celów liczenia, ale łatwo jest zwrócić ciąg (skomentowana opcja powrotu):
źródło
To zależy od tego, ile razy chcesz próbkować rozkład.
Załóżmy, że chcesz próbkować rozkład K razy. Następnie złożonością czasową używaną za
np.random.choice()
każdym razem jest,O(K(n + log(n)))
kiedyn
jest liczba elementów w rozkładzie.W moim przypadku musiałem próbować ten sam rozkład wiele razy rzędu 10 ^ 3, gdzie n jest rzędu 10 ^ 6. Użyłem poniższego kodu, który wstępnie oblicza skumulowany rozkład i próbkuje go
O(log(n))
. Ogólna złożoność czasu wynosiO(n+K*log(n))
.źródło
Jeśli masz Python 3 i boisz się instalować
numpy
lub pisać własne pętle, możesz:Ponieważ możesz zbudować wszystko z torby adapterów hydraulicznych! Chociaż ... Muszę przyznać, że odpowiedź Neda, choć nieco dłuższa, jest łatwiejsza do zrozumienia.
źródło
Ogólne rozwiązanie:
źródło
Oto kolejna wersja weighted_choice, która używa numpy. Przekaż wektor wag, a zwróci tablicę zer zawierającą 1 wskazującą, który bin został wybrany. Kod domyślnie wykonuje tylko jedno losowanie, ale możesz przekazać liczbę losowań, które mają zostać wykonane, a liczba losowanych bin zostanie zwrócona.
Jeśli wektor wag nie sumuje się do 1, zostanie znormalizowany, a więc tak.
źródło
Innym sposobem jest założenie, że mamy wagi o tym samym indeksie co elementy w tablicy elementów.
Załóżmy teraz, że musimy wypróbować 3 elementy w 1 próbie. Można założyć, że występują trzy kule R, G, B w dużych ilościach w stosunku ich ciężarów podanych w układzie wagowym, może być możliwy następujący wynik:
możesz również pomyśleć o liczbie elementów do wyboru jako liczbie prób dwumianowych / wielomianowych w zestawie. Tak więc powyższy przykład może nadal działać jako
źródło
Wykład na ten temat przeprowadził Sebastien Thurn w bezpłatnym kursie Udacity AI for Robotics. Zasadniczo tworzy tablicę kołową indeksowanych wag za pomocą operatora mod
%
, ustawia zmienną beta na 0, losowo wybiera indeks, dla pętli przez N, gdzie N jest liczbą indeksów, aw pętli for najpierw zwiększa beta według wzoru:beta = beta + jednolita próbka z {0 ... 2 * Weight_max}
a następnie zagnieżdżone w pętli for, pętla while na poniżej:
Następnie przejdź do następnego indeksu do ponownego próbkowania w oparciu o prawdopodobieństwa (lub znormalizowane prawdopodobieństwo w przypadku przedstawionym w trakcie).
Link do wykładu: https://classroom.udacity.com/courses/cs373/lessons/48704330/concepts/487480820923
Jestem zalogowany do konta Udacity na moim szkolnym koncie, więc jeśli link nie działa, to lekcja 8, numer wideo 21 sztucznej inteligencji dla robotyki, gdzie wykłada filtry cząstek stałych.
źródło
Jednym ze sposobów jest randomizacja sumy wszystkich wag, a następnie wykorzystanie wartości jako punktów granicznych dla każdej zmiennej. Oto prymitywna implementacja jako generator.
źródło
Korzystanie z numpy
źródło
np.random.choice
, jak wspomniano w przyjętej odpowiedzi, która jest dostępna od 2014 roku. Jaki jest sens tworzenia własnych?Musiałem zrobić coś takiego bardzo szybko, naprawdę prosto, od szukania pomysłów w końcu zbudowałem ten szablon. Chodzi o to, aby otrzymać ważone wartości w postaci json z interfejsu API, który tutaj jest symulowany przez dyktando.
Następnie przetłumacz go na listę, w której każda wartość powtarza się proporcjonalnie do swojej wagi, i po prostu użyj random.choice, aby wybrać wartość z listy.
Próbowałem go uruchomić z 10, 100 i 1000 iteracjami. Rozkład wydaje się dość solidny.
źródło
Nie podobała mi się składnia żadnego z nich. Naprawdę chciałem tylko sprecyzować, jakie były przedmioty i jaka była waga każdego z nich. Zdaję sobie sprawę, że mogłem skorzystać,
random.choices
ale zamiast tego szybko napisałem klasę poniżej.źródło
Podaj random.choice () z wstępnie ważoną listą:
Rozwiązanie i test:
Wynik:
źródło