python dictionary data-structures recommendation-engine fuzzy-logic

Diccionario python Multivalue no determinista Multikey



dictionary data-structures (4)

Ya hay un dict multi-clave en python y también un dict multivaluado. Necesitaba un diccionario de Python que sea ambos:

ejemplo:

# probabilistically fetch any one of baloon, toy or car d[''red'',''blue'',''green'']== "baloon" or "car" or "toy"

La probabilidad de d [''rojo''] == d [''verde''] es alta y la probabilidad de d [''rojo'']! = D [''rojo''] es baja pero posible

el único valor de salida debe determinarse probabilísticamente (borroso) basado en una regla de teclas, por ejemplo: en el caso anterior la regla podría ser si las teclas tienen "rojo" y "azul", entonces devuelve "globo" 80% de tiempo si solo es azul y luego regresa "juguete" 15% de tiempo "automóvil" el 5% de tiempo.

El método setitem debe diseñarse de tal manera que el siguiente sea posible:

d["red", "blue"] =[ ("baloon",haseither(''red'',''green''),0.8), ("toy",.....) ,.... ]

Arriba asigna múltiples valores al diccionario con una función de predicado y la probabilidad correspondiente. Y en lugar de la lista de asignaciones anterior, incluso un diccionario como asignación sería preferible:

d["red", "blue"] ={ "baloon": haseither(''red'',''green'',0.8), "toy": hasonly("blue",0.15), "car": default(0.05) }

En el globo de arriba se devolverá el 80% del tiempo si está presente "rojo" o verde, devuelva el juguete 15% de tiempo si está presente en azul y devuelva el vehículo el 5% de tiempo sin ninguna condición.

¿Hay alguna estructura de datos existente que ya satisfaga los requisitos anteriores en python? si no, ¿cómo se puede modificar el código multikeydict para cumplir con los requisitos anteriores en python?

si se utiliza el diccionario, puede haber un archivo de configuración o el uso de decoradores anidados apropiados que configure las lógicas de predicados probabilísticas anteriores sin tener que codificar las sentencias if / else.

Nota: Above es un autómata útil para una aplicación de respuesta automática basada en reglas; por lo tanto, ¿me permite saber si hay un marco similar basado en reglas disponible en python, incluso si no utiliza la estructura del diccionario?


Si es posible cambiar la estructura de datos, sería más simple tener una función que devuelva los datos que necesita. Esto será completamente flexible y podría acomodar cualquier tipo de datos, en caso de que necesite cambiarlos más tarde.

import random def myfunc(*args): if ''red'' in args: return ''blue'' elif ''green'' in args or ''violet'' in args: return ''violet'' else: r = random.random() if 0 < r < 0.2: return ''blue'' else: return ''green'' print(myfunc(''green'', ''blue'')) print(myfunc(''yellow''))

salida (la segunda línea obviamente cambia):

violet blue


el único valor de salida debe determinarse probabilísticamente (borroso) basado en una regla de teclas, por ejemplo: en el caso anterior la regla podría ser si las teclas tienen "rojo" y "azul", entonces devuelve "globo" 80% de tiempo si solo es azul y luego regresa "juguete" 15% de tiempo "automóvil" el 5% de tiempo.

Sin tener en cuenta que el análisis de su caso no está completo, y es ambiguo, pero puede hacer lo siguiente "en espíritu" (dando cuerpo a los resultados deseados):

import random def randomly_return(*colors): colors = set(*colors) if ''red'' in colors and ''blue'' in colors: if random.random() < 0.8: # 80 % of the time return "baloon" if ''blue'' in colors and len(colors) == 1: # only blue in colors if random.random() < 0.15: return "toy" else: if random.random() < 0.05: return "car" # other cases to consider

Lo mantendría como una función, ¡porque es una función! Pero si insistes en hacerlo como un dictado, entonces Python te permite hacer esto anulando __getitem__ (IMO no es pitónico).

class RandomlyReturn(object): def __getitem__(self, *colors): return randomly_return(*colors) >>> r = RandomlyReturn() >>> r["red", "blue"] # 80% of the time it''ll return "baloon" "baloon"

A partir de su aclaración, OP quiere pasar y generar:

randreturn ((haseither (rojo, azul), baloon: 0.8), ((hasonly (azul), toy: 0.15)), (por defecto (), coche: 0.05)))

quieres generar una función de la siguiente manera:

funcs = {"haseither": lambda needles, haystack: any(n in haystack for n in needles), "hasonly": lambda needles, haystack: len(needles) == 1 and needles[1] in haystack} def make_random_return(crits, default): def random_return(*colors): colors = set(*colors) for c in crits: if funcs[c["func"]](c["args"], colors) and random.random() > c["with_prob"]: return c["return_value"] return default return random_return

donde el crítico y el incumplimiento en este caso serían:

crit = [{"func": "haseither", "args": ("red", "blue"), "return_value": "baloon", "with_prob": 0.8}, ...] default = "car" # ?? my_random_return = make_random_return(crits, default)

Como digo, tus probabilidades son ambiguas / no suman, así que lo más probable es que tengas que modificar esto ...

Puede extender la definición de clase pasando crítico y por defecto en instanciación:

class RandomlyReturn(object): def __init__(self, crit, default): self.randomly_return = make_random_return(crit, default) def __getitem__(self, *colors): return self.randomly_return(*colors) >>> r = RandomlyReturn(crit, default) >>> r["red", "blue"] # 80% of the time it''ll return "baloon" "baloon"


El OP quiere lo siguiente,

d["red", "blue"] ={ "baloon": haseither(''red'',''green'',0.8), "toy": hasonly("blue",0.15), "car": default(0.05) }

pero esto es información con lógica embebida. Es muy tedioso definir una función para cada valor. Lo que sugiero es separar los datos y la lógica.

Python tiene un tipo de datos para esto, eso es class . Se puede asignar una instancia invocable de una class al dict y dejar que el dict pase las teclas y llamar al objeto para devolver el resultado.

He heredado y extendido multiple_key_dict para admitir la recuperación de varias teclas y para pasar claves al objeto y llamar al objeto que se ha almacenado en el dict.

Supongo que los datos se vuelven a calcular por regla. Esta es la clase de Rule , tiene una lista de reglas. Una regla es una expresión de Python y tiene acceso a la función len y a la lista de keys . Entonces uno puede escribir una regla como len(keys) == 1 and ''blue'' in keys .

class Rule(object): def __init__(self, rule, data): self.rule = rule self.data = data

Esta es la clase de Data que tiene ambos conjuntos de datos y reglas.

class Data(object): def __init__(self, rules): self.rules= rules def make_choice(self, data): data = tuple(self.make_list_of_values(data)) return random.choice(data) def make_list_of_values(self, data): for val, weight in data: percent = int(weight * 100) for v in [val] * percent: yield v def __call__(self, keys): for rule in self.rules: if eval(rule.rule,dict(keys=keys)): return self.make_choice(rule.data)

Esto es RuleDict , pero los no RuleDict no se pueden recuperar.

class RuleDict(multi_key_dict): def __init__(self, *args, **kwargs): multi_key_dict.__init__(self, *args, **kwargs) def __getitem__(self, keys): if isinstance(keys, str): keys = (keys, ) keys_set = frozenset(keys) for key in self.keys(): key = frozenset(key) if keys_set <= key: return multi_key_dict.__getitem__(self,keys[0])(keys) raise KeyError(keys)

ejemplo de uso,

d = RuleDict() rule1 = Rule(''"red" in keys and "green" in keys'',((''baloon'',0.8), (''car'',0.05), (''toy'',0.15))) rule2 = Rule(''len(keys) ==1 and "blue" in keys'',((''baloon'',0.25), (''car'',0.35), (''toy'',0.15))) data = Data((rule1, rule2)) d[''red'',''blue'',''green''] = data print(d[''red'',''green''])

d[''red'',''green''] llama al objeto, con las teclas, que se le asignó y devuelve el resultado.

Otro enfoque es hacer que la dict invocable. Éste parece un enfoque sensato, porque los datos y la lógica están separados. Con esto, pasas las claves y la lógica, un invocable, al dict y devuelves el resultado. fe,

def f(keys, data): pass # do the logic and return data d[''red'',''blue'',''green''] = (''baloon'', ''car'', ''toy'')

Ahora llama al dict

d((''red'',''blue''),f)

Este es un dict se puede dict . Si no se proporciona ningún llamable, simplemente devuelve la información completa.

class callable_mkd(multi_key_dict): def __init__(self, *args, **kwargs): multi_key_dict.__init__(self, *args, **kwargs) def __call__(self, keys, process=None): keys_set = frozenset(keys) for key in self.keys(): key = frozenset(key) if keys_set <= key: if process: return process(keys, self[keys[0]]) return self[keys[0]] raise KeyError(keys)


Diccionario MultiKey Simulado

multi_key_dict no permitió __getitem__() con múltiples claves en onces ...

(por ejemplo, d["red", "green"] )

Se puede simular una clave múltiple con tuple o set claves. Si el orden no importa, set parece el mejor (en realidad, el conjunto frozen set cargar, de modo que [ "red", "blue"] sea ​​el mismo a ["blue", "red"] .

Diccionario MultiVal Simulado

Los valores múltiples son inherentes al uso de ciertos tipos de datos, puede ser cualquier elemento de almacenamiento que pueda indexarse ​​convenientemente. Un dict estándar debería proporcionar eso.

No determinismo

Usando una distribución de probabilidad definida por las reglas y suposiciones 1 , la selección no determinista se realiza usando esta receta de los documentos de python.

MultiKeyMultiValNonDeterministicDict Class

Que nombre. / o / -nice!

Esta clase toma múltiples claves que definen un conjunto de reglas probabilísticas de valores múltiples. Durante la creación del elemento ( __setitem__() ) todas las probabilidades de valor son precalculadas para todas las combinaciones de teclas 1 . Durante el acceso al elemento ( __getitem__() ) se selecciona la distribución de probabilidad precalculada y el resultado se evalúa en función de una selección ponderada aleatoria.

Definición

import random import operator import bisect import itertools # or use itertools.accumulate in python 3 def accumulate(iterable, func=operator.add): ''Return running totals'' # accumulate([1,2,3,4,5]) --> 1 3 6 10 15 # accumulate([1,2,3,4,5], operator.mul) --> 1 2 6 24 120 it = iter(iterable) try: total = next(it) except StopIteration: return yield total for element in it: total = func(total, element) yield total class MultiKeyMultiValNonDeterministicDict(dict): def key_combinations(self, keys): """get all combinations of keys""" return [frozenset(subset) for L in range(0, len(keys)+1) for subset in itertools.combinations(keys, L)] def multi_val_rule_prob(self, rules, rule): """ assign probabilities for each value, spreading undefined result probabilities uniformly over the leftover results not defined by rule. """ all_results = set([result for result_probs in rules.values() for result in result_probs]) prob = rules[rule] leftover_prob = 1.0 - sum([x for x in prob.values()]) leftover_results = len(all_results) - len(prob) for result in all_results: if result not in prob: # spread undefined prob uniformly over leftover results prob[result] = leftover_prob/leftover_results return prob def multi_key_rule_prob(self, key, val): """ assign probability distributions for every combination of keys, using the default for combinations not defined in rule set """ combo_probs = {} for combo in self.key_combinations(key): if combo in val: result_probs = self.multi_val_rule_prob(val, combo).items() else: result_probs = self.multi_val_rule_prob(val, frozenset([])).items() combo_probs[combo] = result_probs return combo_probs def weighted_random_choice(self, weighted_choices): """make choice from weighted distribution""" choices, weights = zip(*weighted_choices) cumdist = list(accumulate(weights)) return choices[bisect.bisect(cumdist, random.random() * cumdist[-1])] def __setitem__(self, key, val): """ set item in dictionary, assigns values to keys with precomputed probability distributions """ precompute_val_probs = self.multi_key_rule_prob(key, val) # use to show ALL precomputed probabilities for key''s rule set # print precompute_val_probs dict.__setitem__(self, frozenset(key), precompute_val_probs) def __getitem__(self, key): """ get item from dictionary, randomly select value based on rule probability """ key = frozenset([key]) if isinstance(key, str) else frozenset(key) val = None weighted_val = None if key in self.keys(): val = dict.__getitem__(self, key) weighted_val = val[key] else: for k in self.keys(): if key.issubset(k): val = dict.__getitem__(self, k) weighted_val = val[key] # used to show probabality for key # print weighted_val if weighted_val: prob_results = self.weighted_random_choice(weighted_val) else: prob_results = None return prob_results

Uso

d = MultiKeyMultiValNonDeterministicDict() d["red","blue","green"] = { # {rule_set} : {result: probability} frozenset(["red", "green"]): {"ballon": 0.8}, frozenset(["blue"]): {"toy": 0.15}, frozenset([]): {"car": 0.05} }

Pruebas

Verifica las probabilidades

N = 10000 red_green_test = {''car'':0.0, ''toy'':0.0, ''ballon'':0.0} red_blue_test = {''car'':0.0, ''toy'':0.0, ''ballon'':0.0} blue_test = {''car'':0.0, ''toy'':0.0, ''ballon'':0.0} red_blue_green_test = {''car'':0.0, ''toy'':0.0, ''ballon'':0.0} default_test = {''car'':0.0, ''toy'':0.0, ''ballon'':0.0} for _ in xrange(N): red_green_test[d["red","green"]] += 1.0 red_blue_test[d["red","blue"]] += 1.0 blue_test[d["blue"]] += 1.0 default_test[d["green"]] += 1.0 red_blue_green_test[d["red","blue","green"]] += 1.0 print ''red,green test ='', '' ''.join(''{0}: {1:05.2f}%''.format(key, 100.0*val/N) for key, val in red_green_test.items()) print ''red,blue test ='', '' ''.join(''{0}: {1:05.2f}%''.format(key, 100.0*val/N) for key, val in red_blue_test.items()) print ''blue test ='', '' ''.join(''{0}: {1:05.2f}%''.format(key, 100.0*val/N) for key, val in blue_test.items()) print ''default test ='', '' ''.join(''{0}: {1:05.2f}%''.format(key, 100.0*val/N) for key, val in default_test.items()) print ''red,blue,green test ='', '' ''.join(''{0}: {1:05.2f}%''.format(key, 100.0*val/N) for key, val in red_blue_green_test.items())

red,green test = car: 09.89% toy: 10.06% ballon: 80.05% red,blue test = car: 05.30% toy: 47.71% ballon: 46.99% blue test = car: 41.69% toy: 15.02% ballon: 43.29% default test = car: 05.03% toy: 47.16% ballon: 47.81% red,blue,green test = car: 04.85% toy: 49.20% ballon: 45.95%

¡Las probabilidades coinciden con las reglas!

Notas a pie de página

  1. Asunción de distribución

    Dado que el conjunto de reglas no está completamente definido, se hacen suposiciones sobre las distribuciones de probabilidad, la mayoría de esto se hace en multi_val_rule_prob() . Básicamente, cualquier probabilidad indefinida se distribuirá uniformemente sobre los valores restantes. Esto se hace para todas las combinaciones de teclas, y crea una interfaz de clave generalizada para la selección ponderada aleatoriamente.

    Dado el conjunto de reglas de ejemplo

    d["red","blue","green"] = { # {rule_set} : {result: probability} frozenset(["red", "green"]): {"ballon": 0.8}, frozenset(["blue"]): {"toy": 0.15}, frozenset([]): {"car": 0.05} }

    esto creará las siguientes distribuciones

    ''red'' = [(''car'', 0.050), (''toy'', 0.475), (''ballon'', 0.475)] ''green'' = [(''car'', 0.050), (''toy'', 0.475), (''ballon'', 0.475)] ''blue'' = [(''car'', 0.425), (''toy'', 0.150), (''ballon'', 0.425)] ''blue,red'' = [(''car'', 0.050), (''toy'', 0.475), (''ballon'', 0.475)] ''green,red'' = [(''car'', 0.098), (''toy'', 0.098), (''ballon'', 0.800)] ''blue,green'' = [(''car'', 0.050), (''toy'', 0.475), (''ballon'', 0.475)] ''blue,green,red''= [(''car'', 0.050), (''toy'', 0.475), (''ballon'', 0.475)] default = [(''car'', 0.050), (''toy'', 0.475), (''ballon'', 0.475)]

    Si esto es incorrecto, por favor avise.