usar probabilidad normal funcion distribucion densidad condicional como python algorithm random distribution probability

normal - Distribución de probabilidad en Python



funcion de probabilidad python (12)

(Un año después) el método alias de Walker para objetos aleatorios con diferentes probabilidades es muy rápido y muy simple

Tengo un montón de llaves que tienen una variable de improbabilidad. Quiero elegir al azar una de estas claves, pero quiero que sea menos probable que sea poco probable (clave, valores) que un objeto menos improbable (más probable). Me pregunto si tendrías alguna sugerencia, preferentemente un módulo de python existente que podría usar, de lo contrario tendré que hacerlo yo mismo.

He revisado el módulo aleatorio; no parece proporcionar esto.

Tengo que tomar esas decisiones millones de veces para 1000 diferentes conjuntos de objetos, cada uno con 2.455 objetos. Cada conjunto intercambiará objetos entre sí, por lo que el selector aleatorio debe ser dinámico. Con 1000 conjuntos de 2,433 objetos, eso es 2,433 millones de objetos; el bajo consumo de memoria es crucial. Y dado que estas opciones no son la mayor parte del algoritmo, necesito que este proceso sea bastante rápido; El tiempo de CPU es limitado.

Gracias

Actualizar:

Ok, traté de considerar sus sugerencias sabiamente, pero el tiempo es muy limitado ...

Miré el enfoque del árbol de búsqueda binario y parece demasiado arriesgado (complejo y complicado). Las otras sugerencias se parecen a la receta de ActiveState. Lo tomé y lo modifiqué un poco con la esperanza de hacer más eficiente:

def windex(dict, sum, max): ''''''an attempt to make a random.choose() function that makes weighted choices accepts a dictionary with the item_key and certainty_value as a pair like: >>> x = [(''one'', 20), (''two'', 2), (''three'', 50)], the maximum certainty value (max) and the sum of all certainties.'''''' n = random.uniform(0, 1) sum = max*len(list)-sum for key, certainty in dict.iteritems(): weight = float(max-certainty)/sum if n < weight: break n = n - weight return key

Espero obtener una ganancia de eficiencia manteniendo dinámicamente la suma de certezas y la máxima certeza. Cualquier sugerencia adicional es bienvenida. Ustedes me ahorran mucho tiempo y esfuerzo, mientras aumentan mi efectividad, es una locura. ¡Gracias! ¡Gracias! ¡Gracias!

Actualización2:

Decidí hacerlo más eficiente al permitirle elegir más opciones a la vez. Esto dará como resultado una pérdida de precisión aceptable en mi algoritmo, ya que es de naturaleza dinámica. De todos modos, esto es lo que tengo ahora:

def weightedChoices(dict, sum, max, choices=10): ''''''an attempt to make a random.choose() function that makes weighted choices accepts a dictionary with the item_key and certainty_value as a pair like: >>> x = [(''one'', 20), (''two'', 2), (''three'', 50)], the maximum certainty value (max) and the sum of all certainties.'''''' list = [random.uniform(0, 1) for i in range(choices)] (n, list) = relavate(list.sort()) keys = [] sum = max*len(list)-sum for key, certainty in dict.iteritems(): weight = float(max-certainty)/sum if n < weight: keys.append(key) if list: (n, list) = relavate(list) else: break n = n - weight return keys def relavate(list): min = list[0] new = [l - min for l in list[1:]] return (min, new)

No lo he probado todavía Si tiene algún comentario / sugerencia, no dude. ¡Gracias!

Actualización3:

He estado trabajando todo el día en una versión adaptada a las tareas de la respuesta de Rex Logan. En lugar de 2 matrices de objetos y pesos, en realidad es una clase de diccionario especial; lo que hace las cosas bastante complejas ya que el código de Rex genera un índice aleatorio ... También codifiqué un caso de prueba que se parece a lo que sucederá en mi algo (¡pero no puedo saberlo hasta que lo intente!). El principio básico es que: cuanto más se genera aleatoriamente una clave a menudo, más improbable es que se genere de nuevo:

import random, time import psyco psyco.full() class ProbDict(): """ Modified version of Rex Logans RandomObject class. The more a key is randomly chosen, the more unlikely it will further be randomly chosen. """ def __init__(self,keys_weights_values={}): self._kw=keys_weights_values self._keys=self._kw.keys() self._len=len(self._keys) self._findSeniors() self._effort = 0.15 self._fails = 0 def __iter__(self): return self.next() def __getitem__(self, key): return self._kw[key] def __setitem__(self, key, value): self.append(key, value) def __len__(self): return self._len def next(self): key=self._key() while key: yield key key = self._key() def __contains__(self, key): return key in self._kw def items(self): return self._kw.items() def pop(self, key): try: (w, value) = self._kw.pop(key) self._len -=1 if w == self._seniorW: self._seniors -= 1 if not self._seniors: #costly but unlikely: self._findSeniors() return [w, value] except KeyError: return None def popitem(self): return self.pop(self._key()) def values(self): values = [] for key in self._keys: try: values.append(self._kw[key][1]) except KeyError: pass return values def weights(self): weights = [] for key in self._keys: try: weights.append(self._kw[key][0]) except KeyError: pass return weights def keys(self, imperfect=False): if imperfect: return self._keys return self._kw.keys() def append(self, key, value=None): if key not in self._kw: self._len +=1 self._kw[key] = [0, value] self._keys.append(key) else: self._kw[key][1]=value def _key(self): for i in range(int(self._effort*self._len)): ri=random.randint(0,self._len-1) #choose a random object rx=random.uniform(0,self._seniorW) rkey = self._keys[ri] try: w = self._kw[rkey][0] if rx >= w: # test to see if that is the value we want w += 1 self._warnSeniors(w) self._kw[rkey][0] = w return rkey except KeyError: self._keys.pop(ri) # if you do not find one after 100 tries then just get a random one self._fails += 1 #for confirming effectiveness only for key in self._keys: if key in self._kw: w = self._kw[key][0] + 1 self._warnSeniors(w) self._kw[key][0] = w return key return None def _findSeniors(self): ''''''this function finds the seniors, counts them and assess their age. It is costly but unlikely.'''''' seniorW = 0 seniors = 0 for w in self._kw.itervalues(): if w >= seniorW: if w == seniorW: seniors += 1 else: seniorsW = w seniors = 1 self._seniors = seniors self._seniorW = seniorW def _warnSeniors(self, w): #a weight can only be incremented...good if w >= self._seniorW: if w == self._seniorW: self._seniors+=1 else: self._seniors = 1 self._seniorW = w def test(): #test code iterations = 200000 size = 2500 nextkey = size pd = ProbDict(dict([(i,[0,i]) for i in xrange(size)])) start = time.clock() for i in xrange(iterations): key=pd._key() w=pd[key][0] if random.randint(0,1+pd._seniorW-w): #the heavier the object, the more unlikely it will be removed pd.pop(key) probAppend = float(500+(size-len(pd)))/1000 if random.uniform(0,1) < probAppend: nextkey+=1 pd.append(nextkey) print (time.clock()-start)*1000/iterations, "msecs / iteration with", pd._fails, "failures /", iterations, "iterations" weights = pd.weights() weights.sort() print "avg weight:", float(sum(weights))/pd._len, max(weights), pd._seniorW, pd._seniors, len(pd), len(weights) print weights test()

Cualquier comentario es bienvenido @Darius: tus árboles binarios son demasiado complejos y complicados para mí; y no creo que sus hojas se puedan eliminar de manera eficiente ... Thx all


Aquí hay una mejor respuesta para una distribución de probabilidad especial, en la que la respuesta de Rex Logan parece estar orientada. La distribución es así: cada objeto tiene un peso entero entre 0 y 100, y su probabilidad es proporcional a su peso. Dado que esa es la respuesta actualmente aceptada, creo que vale la pena pensar en esto.

Por lo tanto, mantenga una selección de 101 contenedores. Cada contenedor contiene una lista de todos los objetos con su peso particular. Cada contenedor también conoce el peso total de todos sus objetos.

Para muestrear: elija un cubo al azar en proporción a su peso total. (Use una de las recetas estándar para esto - búsqueda lineal o binaria.) Luego, elija un objeto del contenedor uniformemente al azar.

Para transferir un objeto: eliminarlo de su contenedor, colocarlo en su contenedor en el objetivo y actualizar los pesos de ambos contenedores. (Si está utilizando la búsqueda binaria para el muestreo, también debe actualizar las sumas en ejecución que utiliza. Esto todavía es razonablemente rápido ya que no hay muchos contenedores).


En comentarios sobre la publicación original, Nicholas Leonard sugiere que tanto el intercambio como el muestreo deben ser rápidos. Aquí hay una idea para ese caso; No lo he intentado.

Si solo el muestreo tuviera que ser rápido, podríamos usar una matriz de valores junto con la suma acumulativa de sus probabilidades, y hacer una búsqueda binaria en la suma en ejecución (con la clave siendo un número aleatorio uniforme) - un O (log ( n)) operación. Pero un intercambio requeriría actualizar todos los valores de suma acumulada que aparecen después de las entradas intercambiadas: una operación O (n). (¿Podrías optar por intercambiar solo los artículos cerca del final de sus listas? Asumo que no).

Así que apuntemos a O (log (n)) en ambas operaciones. En lugar de una matriz, guarde un árbol binario para cada conjunto de donde se muestreará. Una hoja contiene el valor de la muestra y su probabilidad (no normalizada). Un nodo de rama tiene la probabilidad total de sus hijos.

Para muestrear, genere un número aleatorio uniforme x entre 0 y la probabilidad total de la raíz, y descienda del árbol. En cada rama, elija el niño de la izquierda si el niño de la izquierda tiene una probabilidad total <= x . De lo contrario, reste la probabilidad del niño de la izquierda de x vaya a la derecha. Devuelve el valor de la hoja que alcanzas.

Para intercambiar, retire la hoja de su árbol y ajuste las ramas que conducen a ella (disminuyendo su probabilidad total, y cortando cualquier nodo de rama hijo único). Inserte la hoja en el árbol de destino: puede elegir dónde colocarla, así que manténgala equilibrada. Escoger un niño al azar en cada nivel probablemente sea lo suficientemente bueno: ahí es donde comenzaría. Aumente la probabilidad de cada nodo padre, haga una copia de seguridad en la raíz.

Ahora tanto el muestreo como el intercambio son O (log (n)) en promedio. (Si necesita un equilibrio garantizado, una forma simple es agregar otro campo a los nodos de la rama que contienen el recuento de hojas en todo el subárbol. Al agregar una hoja, en cada nivel elija al niño con menos hojas. Esto deja la posibilidad de un árbol que se desequilibra solo por eliminaciones; esto no puede ser un problema si hay tráfico razonablemente parejo entre los conjuntos, pero si lo es, entonces elija rotaciones durante la eliminación utilizando la información de conteo de hojas en cada nodo en su recorrido).

Actualización: a pedido, aquí hay una implementación básica. No lo he ajustado en absoluto. Uso:

>>> t1 = build_tree([(''one'', 20), (''two'', 2), (''three'', 50)]) >>> t1 Branch(Leaf(20, ''one''), Branch(Leaf(2, ''two''), Leaf(50, ''three''))) >>> t1.sample() Leaf(50, ''three'') >>> t1.sample() Leaf(20, ''one'') >>> t2 = build_tree([(''four'', 10), (''five'', 30)]) >>> t1a, t2a = transfer(t1, t2) >>> t1a Branch(Leaf(20, ''one''), Leaf(2, ''two'')) >>> t2a Branch(Leaf(10, ''four''), Branch(Leaf(30, ''five''), Leaf(50, ''three'')))

Código:

import random def build_tree(pairs): tree = Empty() for value, weight in pairs: tree = tree.add(Leaf(weight, value)) return tree def transfer(from_tree, to_tree): """Given a nonempty tree and a target, move a leaf from the former to the latter. Return the two updated trees.""" leaf, from_tree1 = from_tree.extract() return from_tree1, to_tree.add(leaf) class Tree: def add(self, leaf): "Return a new tree holding my leaves plus the given leaf." abstract def sample(self): "Pick one of my leaves at random in proportion to its weight." return self.sampling(random.uniform(0, self.weight)) def extract(self): """Pick one of my leaves and return it along with a new tree holding my leaves minus that one leaf.""" return self.extracting(random.uniform(0, self.weight)) class Empty(Tree): weight = 0 def __repr__(self): return ''Empty()'' def add(self, leaf): return leaf def sampling(self, weight): raise Exception("You can''t sample an empty tree") def extracting(self, weight): raise Exception("You can''t extract from an empty tree") class Leaf(Tree): def __init__(self, weight, value): self.weight = weight self.value = value def __repr__(self): return ''Leaf(%r, %r)'' % (self.weight, self.value) def add(self, leaf): return Branch(self, leaf) def sampling(self, weight): return self def extracting(self, weight): return self, Empty() def combine(left, right): if isinstance(left, Empty): return right if isinstance(right, Empty): return left return Branch(left, right) class Branch(Tree): def __init__(self, left, right): self.weight = left.weight + right.weight self.left = left self.right = right def __repr__(self): return ''Branch(%r, %r)'' % (self.left, self.right) def add(self, leaf): # Adding to a random branch as a clumsy way to keep an # approximately balanced tree. if random.random() < 0.5: return combine(self.left.add(leaf), self.right) return combine(self.left, self.right.add(leaf)) def sampling(self, weight): if weight < self.left.weight: return self.left.sampling(weight) return self.right.sampling(weight - self.left.weight) def extracting(self, weight): if weight < self.left.weight: leaf, left1 = self.left.extracting(weight) return leaf, combine(left1, self.right) leaf, right1 = self.right.extracting(weight - self.left.weight) return leaf, combine(self.left, right1)

Actualización 2: Al responder a otro problema , Jason Orendorff señala que los árboles binarios se pueden mantener perfectamente equilibrados al representarlos en una matriz al igual que la estructura de montón clásica. (Esto ahorra el espacio que se gasta en punteros, también.) Consulte mis comentarios a esa respuesta para saber cómo adaptar su código a este problema.


Esta es una forma clásica de hacerlo, en pseudocódigo, donde random.random () te da un flotante aleatorio de 0 a 1.

let z = sum of all the convictions let choice = random.random() * z iterate through your objects: choice = choice - the current object''s conviction if choice <= 0, return this object return the last object

Por ejemplo: imagina que tienes dos objetos, uno con peso 2, otro con peso 4. Generas un número de 0 a 6. Si la choice está entre 0 y 2, lo que sucederá con 2/6 = 1/3 de probabilidad, luego se restará por 2 y se elegirá el primer objeto. Si la elección es entre 2 y 6, que ocurrirá con 4/6 = 2/3 de probabilidad, entonces la primera resta aún tendrá una opción que sea> 0, y la segunda resta hará que el segundo objeto sea elegido.



Lo más simple es usar random.choice (que usa una distribución uniforme) y variar la frecuencia de ocurrencia en el objeto en la colección fuente.

>>> random.choice([1, 2, 3, 4]) 4

... vs:

>>> random.choice([1, 1, 1, 1, 2, 2, 2, 3, 3, 4]) 2

Por lo tanto, sus objetos pueden tener una tasa de incidencia base (n) y entre 1 y n objetos se agregan a la colección de origen en función de la tasa de condenas. Este método es realmente simple; sin embargo, puede tener una sobrecarga significativa si la cantidad de objetos distintos es grande o si la tasa de condenas debe ser de grano muy fino.

Alternativamente, si genera más de un número aleatorio usando una distribución uniforme y los suma, los números que ocurren cerca de la media son más probables que los que ocurren cerca de los extremos (piense en tirar dos dados y la probabilidad de obtener 7 versus 12 o 2). A continuación, puede ordenar los objetos por tasa de condenas y generar un número utilizando múltiples tiradas de dado que usa para calcular e indexar en los objetos. Use números cercanos al promedio para indexar objetos de baja convicción y números cercanos a los extremos para indexar artículos de alta convicción. Puedes variar la probabilidad precisa de que un objeto dado sea seleccionado cambiando el "número de lados" y el número de tus "dados" (puede ser más simple poner los objetos en cubos y usar dados con un pequeño número de lados en lugar de tratando de asociar cada objeto con un resultado específico):

>>> die = lambda sides : random.randint(1, sides) >>> die(6) 3 >>> die(6) + die(6) + die(6) 10


Me necesitaban en funciones más rápidas, para números no muy grandes. Así que aquí está, en Visual C ++:

#undef _DEBUG // disable linking with python25_d.dll #include <Python.h> #include <malloc.h> #include <stdlib.h> static PyObject* dieroll(PyObject *, PyObject *args) { PyObject *list; if (!PyArg_ParseTuple(args, "O:decompress", &list)) return NULL; if (!PyList_Check(list)) return PyErr_Format(PyExc_TypeError, "list of numbers expected (''%s'' given)", list->ob_type->tp_name), NULL; int size = PyList_Size(list); if (size < 1) return PyErr_Format(PyExc_TypeError, "got empty list"), NULL; long *array = (long*)alloca(size*sizeof(long)); long sum = 0; for (int i = 0; i < size; i++) { PyObject *o = PyList_GetItem(list, i); if (!PyInt_Check(o)) return PyErr_Format(PyExc_TypeError, "list of ints expected (''%s'' found)", o->ob_type->tp_name), NULL; long n = PyInt_AsLong(o); if (n == -1 && PyErr_Occurred()) return NULL; if (n < 0) return PyErr_Format(PyExc_TypeError, "list of positive ints expected (negative found)"), NULL; sum += n; //NOTE: integer overflow array[i] = sum; } if (sum <= 0) return PyErr_Format(PyExc_TypeError, "sum of numbers is not positive"), NULL; int r = rand() * (sum-1) / RAND_MAX; //NOTE: rand() may be too small (0x7fff). rand() * sum may result in integer overlow. assert(array[size-1] == sum); assert(r < sum && r < array[size-1]); for (int i = 0; i < size; ++i) { if (r < array[i]) return PyInt_FromLong(i); } return PyErr_Format(PyExc_TypeError, "internal error."), NULL; } static PyMethodDef module_methods[] = { {"dieroll", (PyCFunction)dieroll, METH_VARARGS, "random index, beased on weights" }, {NULL} /* Sentinel */ }; PyMODINIT_FUNC initdieroll(void) { PyObject *module = Py_InitModule3("dieroll", module_methods, "dieroll"); if (module == NULL) return; }


Una manera muy fácil y simple de hacer esto es establecer pesos para cada uno de los valores, y no requeriría mucha memoria.

Probablemente puedas usar un hash / diccionario para hacer esto.

Lo que querrá hacer es tener el número aleatorio, x , multiplicado y sumado sobre todo el conjunto de cosas que desea seleccionar, y dividir ese resultado sobre la cantidad de objetos en su conjunto.

Pseudo-código:

objectSet = [(object1, weight1), ..., (objectN, weightN)] sum = 0 rand = random() for obj, weight in objectSet sum = sum+weight*rand choice = objectSet[floor(sum/objectSet.size())]

EDITAR : Solo pensé en lo lento que sería mi código con conjuntos muy grandes (es O (n)). El siguiente pseudocódigo es O (log (n)), y básicamente está utilizando una búsqueda binaria.

objectSet = [(object1, weight1), ..., (objectN, weightN)] sort objectSet from less to greater according to weights choice = random() * N # where N is the number of objects in objectSet do a binary search until you have just one answer

Hay implementaciones de búsqueda binaria en Python en toda la red, por lo que no es necesario repetir aquí.


Usted quiere dar a cada objeto un peso. Cuanto mayor sea el peso, más probable es que suceda. Más precisamente, probx = weight / sum_all_weights.

Luego, genere un número aleatorio en el rango de 0 a suma_todos los pesos y asígnele un mapa a cada objeto.

Este código le permite generar un índice aleatorio y se asigna cuando el objeto se crea para la velocidad. Si todos tus conjuntos de objetos tienen la misma distribución, entonces puedes salir adelante con solo un objeto RandomIndex.

import random class RandomIndex: def __init__(self, wlist): self._wi=[] self._rsize=sum(wlist)-1 self._m={} i=0 s=wlist[i] for n in range(self._rsize+1): if n == s: i+=1 s+=wlist[i] self._m[n]=i def i(self): rn=random.randint(0,self._rsize) return self._m[rn] sx=[1,2,3,4] wx=[1,10,100,1000] #weight list ri=RandomIndex(wx) cnt=[0,0,0,0] for i in range(1000): cnt[ri.i()] +=1 #keep track of number of times each index was generated print(cnt)


Yo usaría esta receta . Necesitará agregar un peso a sus objetos, pero eso es solo una razón simple y ponerlos en una lista de tuplas (objeto, convicción / (suma de convicciones)). Esto debería ser fácil de hacer usando una lista de comprensión.


Esta receta de estado activo brinda un enfoque fácil de seguir, específicamente la versión en los comentarios que no requiere que prenormalice sus ponderaciones:

import random def weighted_choice(items): """items is a list of tuples in the form (item, weight)""" weight_total = sum((item[1] for item in items)) n = random.uniform(0, weight_total) for item, weight in items: if n < weight: return item n = n - weight return item

Esto será lento si tiene una lista grande de artículos. Una búsqueda binaria probablemente sería mejor en ese caso ... pero también sería más complicado de escribir, con poca ganancia si tiene un tamaño de muestra pequeño. Aquí hay un ejemplo del enfoque de búsqueda binaria en python si desea seguir esa ruta.

(Recomiendo hacer algunas pruebas de rendimiento rápido de ambos métodos en su conjunto de datos. El rendimiento de los diferentes enfoques de este tipo de algoritmo es a menudo poco intuitivo).

Editar: tomé mi propio consejo, ya que tenía curiosidad e hice algunas pruebas.

Comparé cuatro enfoques:

La función weighted_choice arriba.

Una función de búsqueda binaria como esta:

def weighted_choice_bisect(items): added_weights = [] last_sum = 0 for item, weight in items: last_sum += weight added_weights.append(last_sum) return items[bisect.bisect(added_weights, random.random() * last_sum)][0]

Una versión de compilación de 1:

def weighted_choice_compile(items): """returns a function that fetches a random item from items items is a list of tuples in the form (item, weight)""" weight_total = sum((item[1] for item in items)) def choice(uniform = random.uniform): n = uniform(0, weight_total) for item, weight in items: if n < weight: return item n = n - weight return item return choice

Una versión de compilación de 2:

def weighted_choice_bisect_compile(items): """Returns a function that makes a weighted random choice from items.""" added_weights = [] last_sum = 0 for item, weight in items: last_sum += weight added_weights.append(last_sum) def choice(rnd=random.random, bis=bisect.bisect): return items[bis(added_weights, rnd() * last_sum)][0] return choice

Luego construí una gran lista de opciones como esta:

choices = [(random.choice("abcdefg"), random.uniform(0,50)) for i in xrange(2500)]

Y una función de creación de perfiles excesivamente simple:

def profiler(f, n, *args, **kwargs): start = time.time() for i in xrange(n): f(*args, **kwargs) return time.time() - start

Los resultados:

(Segundos tomados para 1,000 llamadas a la función).

  • Simple sin compilar: 0.918624162674
  • Binario sin compilar: 1.01497793198
  • Compilado simple: 0.287325024605
  • Compilado binario: 0.00327413797379

Los resultados "compilados" incluyen el tiempo promedio necesario para compilar la función de elección una vez. (Calculé 1,000 compilaciones, luego dividí ese tiempo entre 1,000 y agregué el resultado al tiempo de la función de elección).

Entonces: si tiene una lista de elementos + pesos que cambian muy raramente, el método compilado binario es, con mucho, el más rápido.


Alrededor de 3 años después ...

Si usa numpy, quizás la opción más simple es usar np.random.choice , que toma una lista de valores posibles, y una secuencia opcional de probabilidades asociadas con cada valor:

import numpy as np values = (''A'', ''B'', ''C'', ''D'') weights = (0.5, 0.1, 0.2, 0.2) print ''''.join(np.random.choice(values, size=60, replace=True, p=weights)) # ACCADAACCDACDBACCADCAAAAAAADACCDCAADDDADAAACCAAACBAAADCADABA