Necesitaba escribir una versión ponderada de random.choice (cada elemento en la lista tiene una probabilidad diferente de ser seleccionado). Esto es lo que se me ocurrió:
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
Esta función me parece demasiado compleja y fea. Espero que todos aquí puedan ofrecer algunas sugerencias para mejorarlo o formas alternativas de hacerlo. La eficiencia no es tan importante para mí como la limpieza y la legibilidad del código.
python
optimization
Colin
fuente
fuente
random.choices
para llamadas individuales. Si necesita muchos resultados aleatorios, es realmente importante elegirlos todos a la vez ajustandonumber_of_items_to_pick
. Si lo hace, es un orden de magnitud más rápido.len(list_of_candidates)
, y luego lo hagalist_of_candidates[draw]
Desde Python 3.6 hay un método
choices
desde elrandom
módulo.Tenga en cuenta que
random.choices
se muestra con reemplazo , según los documentos :Si necesita muestrear sin reemplazo, entonces, como dice la brillante respuesta de @ ronan-paixão , puede usar
numpy.choice
, cuyoreplace
argumento controla dicho comportamiento.fuente
random.choices
no lo hace, por lo que, por supuesto, es más lento en una lista minúscula de 8 elementos, y si elige 10k veces de esa lista, tiene razón. Pero para los casos en que la lista es más grande (dependiendo de cómo esté probando, veo puntos de ruptura entre 100-300 elementos),np.random.choice
comienza a superarrandom.choices
un intervalo bastante amplio. Por ejemplo, incluyendo el paso de normalización junto con la llamada numpy, obtengo una aceleración de casi 4xrandom.choices
para obtener una lista de 10k elementos.fuente
upto +=w; if upto > r
if r < 0
r <= 0
. Considere un conjunto de entrada de 1 elementos y un rollo de 1.0. La afirmación fallará entonces. Corregí ese error en la respuesta.# pragma: no branch
0.0 <= x < total
.Si necesita hacer más de una elección, divídalo en dos funciones, una para construir los pesos acumulativos y otra para dividir en bisectos a un punto aleatorio.
fuente
O(n)
debido al cálculo de distribución acumulativa.random()
no puede devolver 1.0. Según los documentos, devuelve un resultado en el intervalo medio abierto[0.0, 1.0)
, lo que significa que puede devolver exactamente 0.0, pero no puede devolver exactamente 1.0. El valor más grande que puede devolver es 0.99999999999999988897769753748434595763683319091796875 (que Python imprime como 0.9999999999999999, y es el flotante de 64 bits más grande de menos de 1).Si no le importa usar numpy, puede usar numpy.random.choice .
Por ejemplo:
Si sabe cuántas selecciones necesita hacer de antemano, puede hacerlo sin un ciclo como este:
fuente
Crudo, pero puede ser suficiente:
¿Funciona?
Huellas dactilares:
Asume que todos los pesos son enteros. No tienen que sumar 100, solo hice eso para que los resultados de la prueba sean más fáciles de interpretar. (Si los pesos son números de coma flotante, multiplíquelos todos por 10 repetidamente hasta que todos los pesos> = 1.)
fuente
[[]]*10
- todos los elementos en la lista externa apuntan a la misma lista.int
, todavía obtiene muchas referencias al mismo objeto haciendo algo como[id(x) for x in ([99**99] * 100)]
y observar queid
devuelve la misma dirección de memoria en cada llamada.Si tiene un diccionario ponderado en lugar de una lista, puede escribir esto
Tenga en cuenta que
[k for k in items for dummy in range(items[k])]
produce esta lista['a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'c', 'b', 'b', 'b', 'b', 'b']
fuente
A partir de Python
v3.6
,random.choices
podría usarse para devolver unlist
elemento de tamaño específico de la población dada con pesos opcionales.población :
list
contiene observaciones únicas. (Si está vacío, subeIndexError
)pesos : más precisamente, los pesos relativos necesarios para realizar selecciones.
cum_weights : pesos acumulativos necesarios para realizar selecciones.
k : tamaño (
len
) de lalist
salida. (Predeterminadolen()=1
)Pocas advertencias:
1) Utiliza muestreo ponderado con reemplazo para que los artículos extraídos sean reemplazados más tarde. Los valores en la secuencia de pesos en sí mismos no importan, pero su relación relativa sí.
A diferencia de lo
np.random.choice
que solo puede asumir las probabilidades como ponderaciones y también lo que debe garantizar la suma de las probabilidades individuales hasta 1 criterio, aquí no existen tales regulaciones. Mientras pertenezcan a tipos numéricos (int/float/fraction
excepto elDecimal
tipo), estos seguirían funcionando.2) Si no se especifican ni pesos ni cum_weights , las selecciones se realizan con la misma probabilidad. Si se proporciona una secuencia de pesos , debe tener la misma longitud que la secuencia de población .
Especificar pesos y cum_weights plantea a
TypeError
.3) cum_weights son típicamente el resultado de una
itertools.accumulate
función que es realmente útil en tales situaciones.Por lo tanto, el suministro
weights=[12, 12, 4]
ocum_weights=[12, 24, 28]
para nuestro caso artificial produce el mismo resultado y este último parece ser más rápido / eficiente.fuente
Aquí está la versión que se incluye en la biblioteca estándar para Python 3.6:
Fuente: https://hg.python.org/cpython/file/tip/Lib/random.py#l340
fuente
fuente
Probablemente sea demasiado tarde para contribuir con algo útil, pero aquí hay un fragmento simple, breve y muy eficiente:
No es necesario ordenar sus probabilidades o crear un vector con su cmf, y termina una vez que encuentra su elección. Memoria: O (1), tiempo: O (N), con tiempo de ejecución promedio ~ N / 2.
Si tiene pesas, simplemente agregue una línea:
fuente
np.random.choice
. Pero lo más interesante es que hay un modo de falla donde esto genera una excepción. Hacerprobabilities = weights / sum(weights)
no garantiza queprobabilities
sumarán 1; por ejemplo, siweights
es,[1,1,1,1,1,1,1]
entoncesprobabilities
solo sumarán 0.9999999999999998, más pequeño que el mayor valor de retorno posible derandom.random
(que es 0.9999999999999999). Entonceschoice <= cmf
nunca se quedará satisfecho.Si su lista de opciones ponderadas es relativamente estática y desea un muestreo frecuente, puede hacer un paso de preprocesamiento de O (N) y luego hacer la selección en O (1), utilizando las funciones en esta respuesta relacionada .
fuente
Miré el otro hilo puntiagudo y se me ocurrió esta variación en mi estilo de codificación, esto devuelve el índice de elección para el recuento, pero es simple devolver la cadena (alternativa de devolución comentada):
fuente
Depende de cuántas veces desee muestrear la distribución.
Suponga que desea muestrear la distribución K veces. Entonces, la complejidad de tiempo que se usa
np.random.choice()
cada vez esO(K(n + log(n)))
cuándon
es el número de elementos en la distribución.En mi caso, necesitaba muestrear la misma distribución varias veces del orden de 10 ^ 3 donde n es del orden de 10 ^ 6. Usé el siguiente código, que calcula previamente la distribución acumulativa y la muestra
O(log(n))
. La complejidad general del tiempo esO(n+K*log(n))
.fuente
Si tiene Python 3 y tiene miedo de instalar
numpy
o escribir sus propios bucles, puede hacer lo siguiente:¡Porque puede construir cualquier cosa con una bolsa de adaptadores de plomería! Aunque ... debo admitir que la respuesta de Ned, aunque un poco más larga, es más fácil de entender.
fuente
Una solución general:
fuente
Aquí hay otra versión de weighted_choice que usa numpy. Pase el vector de pesos y devolverá una matriz de 0 que contiene un 1 que indica qué bin fue elegido. El código predeterminado es solo hacer un sorteo único, pero puede pasar el número de sorteos que se realizarán y se devolverán los recuentos por sorteo.
Si el vector de pesos no suma 1, se normalizará para que lo haga.
fuente
Otra forma de hacerlo, suponiendo que tengamos pesos en el mismo índice que los elementos en la matriz de elementos.
Ahora supongamos que tenemos que probar 3 elementos en 1 prueba. Puede suponer que hay tres bolas R, G, B presentes en gran cantidad en relación con sus pesos dados por la matriz de peso, el siguiente resultado podría ser posible:
También puede pensar en el número de elementos que se seleccionarán como número de ensayos binomiales / multinomiales dentro de un conjunto. Entonces, el ejemplo anterior todavía puede funcionar como
fuente
Sebastien Thurn da una conferencia sobre esto en el curso gratuito Udacity AI for Robotics. Básicamente, hace una matriz circular de los pesos indexados utilizando el operador mod
%
, establece una variable beta en 0, elige aleatoriamente un índice, para bucles a través de N donde N es el número de índices y en el bucle for en primer lugar incrementa beta por la fórmula:beta = beta + muestra uniforme de {0 ... 2 * Weight_max}
y luego anidado en el ciclo for, un ciclo while por debajo:
Luego, al siguiente índice para volver a muestrear en función de las probabilidades (o probabilidad normalizada en el caso presentado en el curso).
El enlace de la conferencia: https://classroom.udacity.com/courses/cs373/lessons/48704330/concepts/487480820923
Estoy conectado a Udacity con la cuenta de mi escuela, por lo que si el enlace no funciona, es la Lección 8, video número 21 de Inteligencia Artificial para Robótica, donde está dando conferencias sobre filtros de partículas.
fuente
Una forma es aleatorizar el total de todos los pesos y luego usar los valores como puntos límite para cada var. Aquí hay una implementación cruda como generador.
fuente
Usando numpy
fuente
np.random.choice
, como se menciona en la respuesta aceptada que ha estado aquí desde 2014. ¿Cuál es el punto de lanzar la tuya?Necesitaba hacer algo como esto realmente rápido, muy simple, desde la búsqueda de ideas finalmente construí esta plantilla. La idea es recibir los valores ponderados en forma de un json de la API, que aquí es simulada por el dict.
Luego, conviértalo en una lista en la que cada valor se repita proporcionalmente a su peso, y simplemente use random.choice para seleccionar un valor de la lista.
Lo intenté con 10, 100 y 1000 iteraciones. La distribución parece bastante sólida.
fuente
No me encantó la sintaxis de ninguno de esos. Realmente quería especificar cuáles eran los artículos y cuál era el peso de cada uno. Me doy cuenta de que podría haber usado,
random.choices
pero en su lugar, escribí rápidamente la clase a continuación.fuente
Proporcione random.choice () con una lista pre ponderada:
Solución y prueba:
Salida:
fuente