desenhar elemento aleatório em numpy
Pergunta
Eu tenho uma série de probabilidades de elementos, digamos [0.1, 0.2, 0.5, 0.2]
.A matriz soma 1,0.
Usando Python simples ou numpy, quero desenhar elementos proporcionais à sua probabilidade:o primeiro elemento cerca de 10% do tempo, o segundo 20%, o terceiro 50% etc.O “draw” deve retornar o índice do elemento desenhado.
Eu descobri isso:
def draw(probs):
cumsum = numpy.cumsum(probs / sum(probs)) # sum up to 1.0, just in case
return len(numpy.where(numpy.random.rand() >= cumsum)[0])
Funciona, mas é muito complicado, deve haver uma maneira melhor.Obrigado.
Solução
import numpy as np
def random_pick(choices, probs):
'''
>>> a = ['Hit', 'Out']
>>> b = [.3, .7]
>>> random_pick(a,b)
'''
cutoffs = np.cumsum(probs)
idx = cutoffs.searchsorted(np.random.uniform(0, cutoffs[-1]))
return choices[idx]
Como funciona:
In [22]: import numpy as np
In [23]: probs = [0.1, 0.2, 0.5, 0.2]
Calcule a soma cumulativa:
In [24]: cutoffs = np.cumsum(probs)
In [25]: cutoffs
Out[25]: array([ 0.1, 0.3, 0.8, 1. ])
Calcule um número aleatório uniformemente distribuído no intervalo semiaberto [0, cutoffs[-1])
:
In [26]: np.random.uniform(0, cutoffs[-1])
Out[26]: 0.9723114393023948
Usar pesquisa ordenada para encontrar o índice onde o número aleatório seria inserido cutoffs
:
In [27]: cutoffs.searchsorted(0.9723114393023948)
Out[27]: 3
Retornar choices[idx]
, onde idx
é esse índice.
Outras dicas
Você deseja obter uma amostra da distribuição categórica, que não é implementada em numpy.No entanto, o multinomial distribuição é uma generalização do categórico distribuição e pode ser usado para esse fim.
>>> import numpy as np
>>>
>>> def sampleCategory(p):
... return np.flatnonzero( np.random.multinomial(1,p,1) )[0]
...
>>> sampleCategory( [0.1,0.5,0.4] )
1
usar numpy.random.multinomial
- mais eficiente
Nunca usei numpy, mas presumo que meu código abaixo (somente python) faça a mesma coisa que você realizou em uma linha.Estou colocando aqui caso você queira.
Parece muito c-ish, então peço desculpas por não ser muito pitônico.
peso_total seria 1 para você.
def draw(probs)
r = random.randrange(weight_total)
running_total = 0
for i, p in enumerate(probs)
running_total += p
if running_total > r:
return i
usar bissectar
import bisect
import random
import numpy
def draw(probs):
cumsum=numpy.cumsum(probs/sum(probs))
return bisect.bisect_left(cumsum, numpy.random.rand())
deve fazer o truque.