def _get_kernel_from_markov_model(self, model): """ Computes the Gibbs transition models from a Markov Network. 'Probabilistic Graphical Model Principles and Techniques', Koller and Friedman, Section 12.3.3 pp 512-513. Parameters: ----------- model: MarkovModel The model from which probabilities will be computed. """ self.variables = np.array(model.nodes()) factors_dict = {var: [] for var in self.variables} for factor in model.get_factors(): for var in factor.scope(): factors_dict[var].append(factor) # Take factor product factors_dict = {var: factor_product(*factors) if len(factors) > 1 else factors[0] for var, factors in factors_dict.items()} self.cardinalities = {var: factors_dict[var].get_cardinality([var])[var] for var in self.variables} for var in self.variables: other_vars = [v for v in self.variables if var != v] other_cards = [self.cardinalities[v] for v in other_vars] kernel = {} factor = factors_dict[var] scope = set(factor.scope()) for tup in itertools.product(*[range(card) for card in other_cards]): states = [State(var, s) for var, s in zip(other_vars, tup) if var in scope] reduced_factor = factor.reduce(states, inplace=False) kernel[tup] = reduced_factor.values / sum(reduced_factor.values) self.transition_models[var] = kernel
def _get_kernel_from_bayesian_model(self, model): """ Computes the Gibbs transition models from a Bayesian Network. 'Probabilistic Graphical Model Principles and Techniques', Koller and Friedman, Section 12.3.3 pp 512-513. Parameters: ----------- model: BayesianModel The model from which probabilities will be computed. """ self.variables = np.array(model.nodes()) self.cardinalities = {var: model.get_cpds(var).variable_card for var in self.variables} for var in self.variables: other_vars = [v for v in self.variables if var != v] other_cards = [self.cardinalities[v] for v in other_vars] cpds = [cpd for cpd in model.cpds if var in cpd.scope()] prod_cpd = factor_product(*cpds) kernel = {} scope = set(prod_cpd.scope()) for tup in itertools.product(*[range(card) for card in other_cards]): states = [State(v, s) for v, s in zip(other_vars, tup) if v in scope] prod_cpd_reduced = prod_cpd.reduce(states, inplace=False) kernel[tup] = prod_cpd_reduced.values / sum(prod_cpd_reduced.values) self.transition_models[var] = kernel
def map_query(self, variables=None, evidence=None, elimination_order=None): """ Computes the MAP Query over the variables given the evidence. Parameters ---------- variables: list list of variables over which we want to compute the max-marginal. evidence: dict a dict key, value pair as {var: state_of_var_observed} None if no evidence elimination_order: list order of variable eliminations (if nothing is provided) order is computed automatically Examples -------- >>> from pgmpy.inference import VariableElimination >>> from pgmpy.models import BayesianModel >>> import numpy as np >>> import pandas as pd >>> values = pd.DataFrame(np.random.randint(low=0, high=2, size=(1000, 5)), ... columns=['A', 'B', 'C', 'D', 'E']) >>> model = BayesianModel([('A', 'B'), ('C', 'B'), ('C', 'D'), ('B', 'E')]) >>> model.fit(values) >>> inference = VariableElimination(model) >>> phi_query = inference.map_query(['A', 'B']) """ elimination_variables = set(self.variables) - set( evidence.keys()) if evidence else set() final_distribution = self._variable_elimination( elimination_variables, 'maximize', evidence=evidence, elimination_order=elimination_order) # To handle the case when no argument is passed then # _variable_elimination returns a dict. if isinstance(final_distribution, dict): final_distribution = final_distribution.values() distribution = factor_product(*final_distribution) argmax = np.argmax(distribution.values) assignment = distribution.assignment([argmax])[0] map_query_results = {} for var_assignment in assignment: var, value = var_assignment map_query_results[var] = value if not variables: return map_query_results else: return_dict = {} for var in variables: return_dict[var] = map_query_results[var] return return_dict
def map_query(self, variables=None, evidence=None, elimination_order=None): """ Computes the MAP Query over the variables given the evidence. Parameters ---------- variables: list list of variables over which we want to compute the max-marginal. evidence: dict a dict key, value pair as {var: state_of_var_observed} None if no evidence elimination_order: list order of variable eliminations (if nothing is provided) order is computed automatically Examples -------- >>> from pgmpy.inference import VariableElimination >>> from pgmpy.models import BayesianModel >>> import numpy as np >>> import pandas as pd >>> values = pd.DataFrame(np.random.randint(low=0, high=2, size=(1000, 5)), ... columns=['A', 'B', 'C', 'D', 'E']) >>> model = BayesianModel([('A', 'B'), ('C', 'B'), ('C', 'D'), ('B', 'E')]) >>> model.fit(values) >>> inference = VariableElimination(model) >>> phi_query = inference.map_query(['A', 'B']) """ elimination_variables = set(self.variables) - set(evidence.keys()) if evidence else set() final_distribution = self._variable_elimination(elimination_variables, 'maximize', evidence=evidence, elimination_order=elimination_order) # To handle the case when no argument is passed then # _variable_elimination returns a dict. if isinstance(final_distribution, dict): final_distribution = final_distribution.values() distribution = factor_product(*final_distribution) argmax = np.argmax(distribution.values) assignment = distribution.assignment([argmax])[0] map_query_results = {} for var_assignment in assignment: var, value = var_assignment map_query_results[var] = value if not variables: return map_query_results else: return_dict = {} for var in variables: return_dict[var] = map_query_results[var] return return_dict
def _get_factor(self, belief_prop, evidence): """ Extracts the required factor from the junction tree. Parameters: ---------- belief_prop: Belief Propagation Belief Propagation which needs to be updated. evidence: dict a dict key, value pair as {var: state_of_var_observed} """ final_factor = factor_product(*belief_prop.junction_tree.get_factors()) if evidence: for var in evidence: if var in final_factor.scope(): final_factor.reduce([(var, evidence[var])]) return final_factor
def max_marginal(self, variables=None, evidence=None, elimination_order=None): """ Computes the max-marginal over the variables given the evidence. Parameters ---------- variables: list list of variables over which we want to compute the max-marginal. evidence: dict a dict key, value pair as {var: state_of_var_observed} None if no evidence elimination_order: list order of variable eliminations (if nothing is provided) order is computed automatically Examples -------- >>> import numpy as np >>> import pandas as pd >>> from pgmpy.models import BayesianModel >>> from pgmpy.inference import VariableElimination >>> values = pd.DataFrame(np.random.randint(low=0, high=2, size=(1000, 5)), ... columns=['A', 'B', 'C', 'D', 'E']) >>> model = BayesianModel([('A', 'B'), ('C', 'B'), ('C', 'D'), ('B', 'E')]) >>> model.fit(values) >>> inference = VariableElimination(model) >>> phi_query = inference.max_marginal(['A', 'B']) """ if not variables: variables = [] final_distribution = self._variable_elimination( variables, 'maximize', evidence=evidence, elimination_order=elimination_order) # To handle the case when no argument is passed then # _variable_elimination returns a dict. if isinstance(final_distribution, dict): final_distribution = final_distribution.values() return np.max(factor_product(*final_distribution).values)
def query(self, variables, conditions, elimination_order=None): """ Examples -------- >>> from pgmpy.BayesianModel import BayesianModel >>> bm = BayesianModel([('a', 'b'), ('b', 'c'), ('c', 'd'), ('d', 'e')]) >>> # add cpds >>> VariableElimination(bm).query(variables={'c':{}}) """ if not elimination_order: elimination_order = list(set(self.variables) - set(variables.keys()) - set(conditions.keys())) for node in elimination_order: working_factors = self.factors phi = factor_product(*working_factors[node]).marginalize(node) del working_factors[node] for var in phi.variables: try: working_factors[var].append(phi) except KeyError: working_factors[var] = [phi]
def max_marginal(self, variables=None, evidence=None, elimination_order=None): """ Computes the max-marginal over the variables given the evidence. Parameters ---------- variables: list list of variables over which we want to compute the max-marginal. evidence: dict a dict key, value pair as {var: state_of_var_observed} None if no evidence elimination_order: list order of variable eliminations (if nothing is provided) order is computed automatically Examples -------- >>> import numpy as np >>> import pandas as pd >>> from pgmpy.models import BayesianModel >>> from pgmpy.inference import VariableElimination >>> values = pd.DataFrame(np.random.randint(low=0, high=2, size=(1000, 5)), ... columns=['A', 'B', 'C', 'D', 'E']) >>> model = BayesianModel([('A', 'B'), ('C', 'B'), ('C', 'D'), ('B', 'E')]) >>> model.fit(values) >>> inference = VariableElimination(model) >>> phi_query = inference.max_marginal(['A', 'B']) """ if not variables: variables = [] final_distribution = self._variable_elimination(variables, 'maximize', evidence=evidence, elimination_order=elimination_order) # To handle the case when no argument is passed then # _variable_elimination returns a dict. if isinstance(final_distribution, dict): final_distribution = final_distribution.values() return np.max(factor_product(*final_distribution).values)
def _variable_elimination(self, variables, operation, evidence=None, elimination_order=None): """ Implementation of a generalized variable elimination. Parameters ---------- variables: list, array-like variables that are not to be eliminated. operation: str ('marginalize' | 'maximize') The operation to do for eliminating the variable. evidence: dict a dict key, value pair as {var: state_of_var_observed} None if no evidence elimination_order: list, array-like list of variables representing the order in which they are to be eliminated. If None order is computed automatically. """ # Dealing with the case when variables is not provided. if not variables: all_factors = [] for factor_li in self.factors.values(): all_factors.extend(factor_li) return set(all_factors) eliminated_variables = set() working_factors = {node: {factor for factor in self.factors[node]} for node in self.factors} # Dealing with evidence. Reducing factors over it before VE is run. if evidence: for evidence_var in evidence: for factor in working_factors[evidence_var]: factor_reduced = factor.reduce([(evidence_var, evidence[evidence_var])], inplace=False) for var in factor_reduced.scope(): working_factors[var].remove(factor) working_factors[var].add(factor_reduced) del working_factors[evidence_var] # TODO: Modify it to find the optimal elimination order if not elimination_order: elimination_order = list(set(self.variables) - set(variables) - set(evidence.keys() if evidence else [])) elif any(var in elimination_order for var in set(variables).union(set(evidence.keys() if evidence else []))): raise ValueError("Elimination order contains variables which are in" " variables or evidence args") for var in elimination_order: # Removing all the factors containing the variables which are # eliminated (as all the factors should be considered only once) factors = [factor for factor in working_factors[var] if not set(factor.variables).intersection(eliminated_variables)] phi = factor_product(*factors) phi = getattr(phi, operation)([var], inplace=False) del working_factors[var] for variable in phi.variables: working_factors[variable].add(phi) eliminated_variables.add(var) final_distribution = set() for node in working_factors: factors = working_factors[node] for factor in factors: if not set(factor.variables).intersection(eliminated_variables): final_distribution.add(factor) query_var_factor = {} for query_var in variables: phi = factor_product(*final_distribution) query_var_factor[query_var] = phi.marginalize(list(set(variables) - set([query_var])), inplace=False).normalize(inplace=False) return query_var_factor
def query(self, query, evidence=None, elimination_order=None): if not isinstance(query, list): query = [query] ### DEBUG # print(">>>>>--------------------------------------<<<<<<") # print(">>> Query: %s" % query) # print(">>> Evidence: %s" % evidence) # print(">>> Saved MTs:") # for mt in self.marginal_trees: # print("--> Nodes: %s" % mt.nodes()) # print(mt.evidence) ### --- DEBUG # See if it is possible to reuse saved MTs marginal_tree = None reuse_mts = self.find_reusable(query, evidence) ### DEBUG # print(">>> Reusable MTs:") # for mt in reuse_mts: # print("--> Nodes: %s" % mt.nodes()) # print(mt.evidence) ### --- DEBUG factors = [] if len(reuse_mts) > 0: # TODO: choose MT by the size of it # Choose one MT and rebuild it for the new query and evidence marginal_tree = reuse_mts[0] ### DEBUG # print(">>> Nodes before evidence") # print(marginal_tree.nodes()) # print(">>> Messages before evidence") # for s in marginal_tree.separators: # print("--> Separator %s" % s.__str__()) # for f in marginal_tree.separators[s]: # print(f) ### --- DEBUG # Reduce new evidences new_evidence = {k: evidence[k] for k in evidence if k not in marginal_tree.evidence} marginal_tree.set_evidence(new_evidence) ### DEBUG # print(">>> Nodes after evidence") # print(marginal_tree.nodes()) # print(">>> Messages before evidence") # for s in marginal_tree.separators: # print("--> Separator %s" % s.__str__()) # for f in marginal_tree.separators[s]: # print(f) ### --- DEBUG # Rebuild MT to answer new query marginal_tree = marginal_tree.rebuild(query, evidence) # Perform one way propagation self.partial_one_way_propagation(marginal_tree) else: marginal_tree = self._build(query, evidence, elimination_order) ### DEBUG # print(">>> Saving marginal tree") # print(marginal_tree.nodes()) # print(marginal_tree.evidence) ### --- DEBUG # Save the new MT self.marginal_trees.append(marginal_tree) # Answer the query node = marginal_tree.root # Define the variables to marginalize marginalize = set(node) - set(query) # Collect factors for the node neighbors = marginal_tree.neighbors(node) # Collect incoming messages for neighbor in neighbors: if (neighbor, node) in marginal_tree.separators: factors.extend([f for f in marginal_tree.separators[(neighbor, node)] if len(f.variables) > 0]) ### DEBUG # print(">>> Root: %s" % marginal_tree.root.__str__()) # print("incoming...") # for f in factors: # print(f) ### --- DEBUG # Collect assigned Factors factors.extend([f for f in marginal_tree.factor_node_assignment[node] if len(f.variables) > 0]) ### DEBUG # print("assigned...") # for f in marginal_tree.factor_node_assignment[node]: # print(f) # print("going to SUM OUT %s" % marginalize.__str__()) # for f in factors: # print(f) ### --- DEBUG result = None # Sum out variables from factors if len(factors) > 0: result = Inference.sum_out(marginalize, factors) ### DEBUG # print(">>> After SUM OUT") # for f in result: # print(f) ### --- DEBUG # Multiply all remaining CPDs if len(result) > 0: result = factor_product(*result) ### DEBUG # print(">>> After PRODUCT") # print(result) ### --- DEBUG # Normalize result.normalize() ### DEBUG # print(">>> After Normalize") # print(result) # marginal_tree.draw() ### --- DEBUG return result
def _build(self, variables, evidence=None, elimination_order=None): if not isinstance(variables, list): variables = [variables] # Removing barren and independent variables generate sub-models # (a modified version of the model). # Then, a copy is used to do not disturb the original model. model_copy = self.model.copy() factors_copy = self.factors.copy() # Load all factors used in this session of Variable Elimination working_factors = {node: {factor for factor in factors_copy[node]} for node in factors_copy} # Reduce working factors if evidence: for evidence_var in evidence: for factor in working_factors[evidence_var].copy(): factor_reduced = factor.reduce( '{evidence_var}_{state}' .format(evidence_var=evidence_var, state=evidence[evidence_var]), inplace=False) for var in factor_reduced.scope(): working_factors[var].remove(factor) working_factors[var].add(factor_reduced) del working_factors[evidence_var] if not elimination_order: # If is BayesianModel, find a good elimination ordering # using Weighted-Min-Fill heuristic. if isinstance(model_copy, BayesianModel): elim_ord = EliminationOrdering(model_copy) elimination_order = elim_ord.find_elimination_ordering( list(set(model_copy.nodes()) - set(variables) - set(evidence.keys() if evidence else [])), elim_ord.weighted_min_fill) else: elimination_order = list(set(self.variables) - set(variables) - set(evidence.keys() if evidence else [])) elif any(var in elimination_order for var in set(variables).union( set(evidence.keys() if evidence else []))): raise ValueError("Elimination order contains variables" " which are in variables or evidence args") # Perform elimination ordering while constructing new Marginal Tree marginal_tree = MarginalTree() eliminated_variables = set() messages = [] # Variables to keep the last "phi" message and last created "node" phi = None node = None for var in elimination_order: # Removing all the factors containing the variables which are # eliminated (as all the factors should be considered only once) ### DEBUG # print(">>> *** Eliminating %s ***" % var) ### --- DEBUG factors = [factor for factor in working_factors[var] if not set(factor.variables).intersection( eliminated_variables)] ### DEBUG # print(">>> Factors involved") # for f in factors: # print(f) ### --- DEBUG phi = factor_product(*factors) ### DEBUG # print(">>> Product") # print(phi) ### --- DEBUG phi = phi.marginalize(var, inplace=False) ### DEBUG # print(">>> Marginalize") # print(phi) ### --- DEBUG del working_factors[var] for variable in phi.variables: working_factors[variable].add(phi) eliminated_variables.add(var) # Save new message messages.append(phi) # Build a Marginal Tree node node = set() for f in factors: node = node.union(f.scope()) node = tuple(node) marginal_tree.add_node(node) messages_intersection = set(factors).intersection(set(messages)) marginal_tree.add_factors_to_node( list(set(factors) - messages_intersection), node) # Connect nodes, if past messages are used messages_used = [] for m in list(messages_intersection): for separator in marginal_tree.separators.copy(): if m in marginal_tree.separators[separator]: marginal_tree.add_edge(separator[0], node) new_separator = (separator[0], node) marginal_tree.add_messages_to_separator( m, new_separator) del marginal_tree.separators[separator] messages_used.append(m) ### DEBUG # print(">>> Messages used") # print(marginal_tree.separators[separator]) ### --- DEBUG # If message wasn't used to create the new message, # point it to the "empty node". if phi not in messages_used: marginal_tree.add_messages_to_separator(phi, (node,)) ### DEBUG # print("===> Remaining Factors") # for var in working_factors: # print("===> var %s" % var) # for f in working_factors[var]: # print(f) # print(">>> Last message") # print(phi.variables) ### --- DEBUG # If var was eliminated, thus no message created if not phi.variables: used_factors = [] for var in working_factors: for factor in working_factors[var]: if factor not in used_factors: used_factors.append(factor) phi = factor_product(*used_factors) # Create the query node (where the query is answered) query_node = tuple(phi.variables) marginal_tree.add_node(query_node) ### DEBUG # print(">>> Query node") # print(query_node) # print(">>> All Remaining Factors") # for var in working_factors: # print(">>> All Remaining for var %s" % var) # for f in working_factors[var]: # if not set(f.variables).intersection( # eliminated_variables): # if f in messages: # print("---> A message.") # else: # print("---> An original factor.") # print(f) ### --- DEBUG # Add remaining original factors to the query node remaining_assignment_factors = [] remaining_message_factors = [] for var in working_factors: for factor in working_factors[var]: if not set(factor.variables).intersection( eliminated_variables): if factor in messages: remaining_message_factors.append(factor) else: remaining_assignment_factors.append(factor) # ### DEBUG # print("===> Collected ASSIGMENT remaining Factors") # for f in remaining_assignment_factors: # print(f) # print("===> Collected MESSAGES remaining Factors") # for f in remaining_message_factors: # print(f) ### --- DEBUG # Redirect remaining message factors to query node for message in remaining_message_factors: for separator in marginal_tree.separators.copy(): if message in marginal_tree.separators[separator]: marginal_tree.add_edge(separator[0], query_node) new_separator = (separator[0], query_node) marginal_tree.add_messages_to_separator( message, new_separator) del marginal_tree.separators[separator] # TO REMOVE # marginal_tree.add_edge(node, query_node) # marginal_tree.add_messages_to_separator( # phi, (node, query_node)) # Add remaining original factors to query node marginal_tree.add_factors_to_node( remaining_assignment_factors, query_node) # Define the root node as the query node. marginal_tree.root = query_node # Update evidence variables of the Marginal tree for k in evidence: marginal_tree.evidence[k] = evidence[k] marginal_tree.observed.extend(list(evidence.keys())) return marginal_tree
def _variable_elimination(self, variables, operation, evidence=None, elimination_order=None): """ Implementation of a generalized variable elimination. Parameters ---------- variables: list, array-like variables that are not to be eliminated. operation: str ('marginalize' | 'maximize') The operation to do for eliminating the variable. evidence: dict a dict key, value pair as {var: state_of_var_observed} None if no evidence elimination_order: list, array-like list of variables representing the order in which they are to be eliminated. If None order is computed automatically. """ # Dealing with the case when variables is not provided. if not variables: all_factors = [] for factor_li in self.factors.values(): all_factors.extend(factor_li) return set(all_factors) eliminated_variables = set() working_factors = { node: {factor for factor in self.factors[node]} for node in self.factors } # Dealing with evidence. Reducing factors over it before VE is run. if evidence: for evidence_var in evidence: for factor in working_factors[evidence_var]: factor_reduced = factor.reduce( [(evidence_var, evidence[evidence_var])], inplace=False) for var in factor_reduced.scope(): working_factors[var].remove(factor) working_factors[var].add(factor_reduced) del working_factors[evidence_var] # TODO: Modify it to find the optimal elimination order if not elimination_order: elimination_order = list( set(self.variables) - set(variables) - set(evidence.keys() if evidence else [])) elif any(var in elimination_order for var in set(variables).union( set(evidence.keys() if evidence else []))): raise ValueError( "Elimination order contains variables which are in" " variables or evidence args") for var in elimination_order: # Removing all the factors containing the variables which are # eliminated (as all the factors should be considered only once) factors = [ factor for factor in working_factors[var] if not set(factor.variables).intersection(eliminated_variables) ] phi = factor_product(*factors) phi = getattr(phi, operation)([var], inplace=False) del working_factors[var] for variable in phi.variables: working_factors[variable].add(phi) eliminated_variables.add(var) final_distribution = set() for node in working_factors: factors = working_factors[node] for factor in factors: if not set( factor.variables).intersection(eliminated_variables): final_distribution.add(factor) query_var_factor = {} for query_var in variables: phi = factor_product(*final_distribution) query_var_factor[query_var] = phi.marginalize( list(set(variables) - set([query_var])), inplace=False).normalize(inplace=False) return query_var_factor
def query(self, query, evidence=None, elimination_order=None): if not isinstance(query, list): query = [query] ### DEBUG # print(">>>>>--------------------------------------<<<<<<") # print(">>> Query: %s" % query) # print(">>> Evidence: %s" % evidence) # print(">>> Saved MTs:") # for mt in self.marginal_trees: # print("--> Nodes: %s" % mt.nodes()) # print(mt.evidence) ### --- DEBUG # See if it is possible to reuse saved MTs marginal_tree = None reuse_mts = self.find_reusable(query, evidence) ### DEBUG # print(">>> Reusable MTs:") # for mt in reuse_mts: # print("--> Nodes: %s" % mt.nodes()) # print(mt.evidence) ### --- DEBUG factors = [] if len(reuse_mts) > 0: # TODO: choose MT by the size of it # Choose one MT and rebuild it for the new query and evidence marginal_tree = reuse_mts[0] ### DEBUG # print(">>> Nodes before evidence") # print(marginal_tree.nodes()) # print(">>> Messages before evidence") # for s in marginal_tree.separators: # print("--> Separator %s" % s.__str__()) # for f in marginal_tree.separators[s]: # print(f) ### --- DEBUG # Reduce new evidences new_evidence = { k: evidence[k] for k in evidence if k not in marginal_tree.evidence } marginal_tree.set_evidence(new_evidence) ### DEBUG # print(">>> Nodes after evidence") # print(marginal_tree.nodes()) # print(">>> Messages before evidence") # for s in marginal_tree.separators: # print("--> Separator %s" % s.__str__()) # for f in marginal_tree.separators[s]: # print(f) ### --- DEBUG # Rebuild MT to answer new query marginal_tree = marginal_tree.rebuild(query, evidence) # Perform one way propagation self.partial_one_way_propagation(marginal_tree) else: marginal_tree = self._build(query, evidence, elimination_order) ### DEBUG # print(">>> Saving marginal tree") # print(marginal_tree.nodes()) # print(marginal_tree.evidence) ### --- DEBUG # Save the new MT self.marginal_trees.append(marginal_tree) # Answer the query node = marginal_tree.root # Define the variables to marginalize marginalize = set(node) - set(query) # Collect factors for the node neighbors = marginal_tree.neighbors(node) # Collect incoming messages for neighbor in neighbors: if (neighbor, node) in marginal_tree.separators: factors.extend([ f for f in marginal_tree.separators[(neighbor, node)] if len(f.variables) > 0 ]) ### DEBUG # print(">>> Root: %s" % marginal_tree.root.__str__()) # print("incoming...") # for f in factors: # print(f) ### --- DEBUG # Collect assigned Factors factors.extend([ f for f in marginal_tree.factor_node_assignment[node] if len(f.variables) > 0 ]) ### DEBUG # print("assigned...") # for f in marginal_tree.factor_node_assignment[node]: # print(f) # print("going to SUM OUT %s" % marginalize.__str__()) # for f in factors: # print(f) ### --- DEBUG result = None # Sum out variables from factors if len(factors) > 0: result = Inference.sum_out(marginalize, factors) ### DEBUG # print(">>> After SUM OUT") # for f in result: # print(f) ### --- DEBUG # Multiply all remaining CPDs if len(result) > 0: result = factor_product(*result) ### DEBUG # print(">>> After PRODUCT") # print(result) ### --- DEBUG # Normalize result.normalize() ### DEBUG # print(">>> After Normalize") # print(result) # marginal_tree.draw() ### --- DEBUG return result
def _build(self, variables, evidence=None, elimination_order=None): if not isinstance(variables, list): variables = [variables] # Removing barren and independent variables generate sub-models # (a modified version of the model). # Then, a copy is used to do not disturb the original model. model_copy = self.model.copy() factors_copy = self.factors.copy() # Load all factors used in this session of Variable Elimination working_factors = { node: {factor for factor in factors_copy[node]} for node in factors_copy } # Reduce working factors if evidence: for evidence_var in evidence: for factor in working_factors[evidence_var].copy(): factor_reduced = factor.reduce( '{evidence_var}_{state}'.format( evidence_var=evidence_var, state=evidence[evidence_var]), inplace=False) for var in factor_reduced.scope(): working_factors[var].remove(factor) working_factors[var].add(factor_reduced) del working_factors[evidence_var] if not elimination_order: # If is BayesianModel, find a good elimination ordering # using Weighted-Min-Fill heuristic. if isinstance(model_copy, BayesianModel): elim_ord = EliminationOrdering(model_copy) elimination_order = elim_ord.find_elimination_ordering( list( set(model_copy.nodes()) - set(variables) - set(evidence.keys() if evidence else [])), elim_ord.weighted_min_fill) else: elimination_order = list( set(self.variables) - set(variables) - set(evidence.keys() if evidence else [])) elif any(var in elimination_order for var in set(variables).union( set(evidence.keys() if evidence else []))): raise ValueError("Elimination order contains variables" " which are in variables or evidence args") # Perform elimination ordering while constructing new Marginal Tree marginal_tree = MarginalTree() eliminated_variables = set() messages = [] # Variables to keep the last "phi" message and last created "node" phi = None node = None for var in elimination_order: # Removing all the factors containing the variables which are # eliminated (as all the factors should be considered only once) ### DEBUG # print(">>> *** Eliminating %s ***" % var) ### --- DEBUG factors = [ factor for factor in working_factors[var] if not set(factor.variables).intersection(eliminated_variables) ] ### DEBUG # print(">>> Factors involved") # for f in factors: # print(f) ### --- DEBUG phi = factor_product(*factors) ### DEBUG # print(">>> Product") # print(phi) ### --- DEBUG phi = phi.marginalize(var, inplace=False) ### DEBUG # print(">>> Marginalize") # print(phi) ### --- DEBUG del working_factors[var] for variable in phi.variables: working_factors[variable].add(phi) eliminated_variables.add(var) # Save new message messages.append(phi) # Build a Marginal Tree node node = set() for f in factors: node = node.union(f.scope()) node = tuple(node) marginal_tree.add_node(node) messages_intersection = set(factors).intersection(set(messages)) marginal_tree.add_factors_to_node( list(set(factors) - messages_intersection), node) # Connect nodes, if past messages are used messages_used = [] for m in list(messages_intersection): for separator in marginal_tree.separators.copy(): if m in marginal_tree.separators[separator]: marginal_tree.add_edge(separator[0], node) new_separator = (separator[0], node) marginal_tree.add_messages_to_separator( m, new_separator) del marginal_tree.separators[separator] messages_used.append(m) ### DEBUG # print(">>> Messages used") # print(marginal_tree.separators[separator]) ### --- DEBUG # If message wasn't used to create the new message, # point it to the "empty node". if phi not in messages_used: marginal_tree.add_messages_to_separator(phi, (node, )) ### DEBUG # print("===> Remaining Factors") # for var in working_factors: # print("===> var %s" % var) # for f in working_factors[var]: # print(f) # print(">>> Last message") # print(phi.variables) ### --- DEBUG # If var was eliminated, thus no message created if not phi.variables: used_factors = [] for var in working_factors: for factor in working_factors[var]: if factor not in used_factors: used_factors.append(factor) phi = factor_product(*used_factors) # Create the query node (where the query is answered) query_node = tuple(phi.variables) marginal_tree.add_node(query_node) ### DEBUG # print(">>> Query node") # print(query_node) # print(">>> All Remaining Factors") # for var in working_factors: # print(">>> All Remaining for var %s" % var) # for f in working_factors[var]: # if not set(f.variables).intersection( # eliminated_variables): # if f in messages: # print("---> A message.") # else: # print("---> An original factor.") # print(f) ### --- DEBUG # Add remaining original factors to the query node remaining_assignment_factors = [] remaining_message_factors = [] for var in working_factors: for factor in working_factors[var]: if not set( factor.variables).intersection(eliminated_variables): if factor in messages: remaining_message_factors.append(factor) else: remaining_assignment_factors.append(factor) # ### DEBUG # print("===> Collected ASSIGMENT remaining Factors") # for f in remaining_assignment_factors: # print(f) # print("===> Collected MESSAGES remaining Factors") # for f in remaining_message_factors: # print(f) ### --- DEBUG # Redirect remaining message factors to query node for message in remaining_message_factors: for separator in marginal_tree.separators.copy(): if message in marginal_tree.separators[separator]: marginal_tree.add_edge(separator[0], query_node) new_separator = (separator[0], query_node) marginal_tree.add_messages_to_separator( message, new_separator) del marginal_tree.separators[separator] # TO REMOVE # marginal_tree.add_edge(node, query_node) # marginal_tree.add_messages_to_separator( # phi, (node, query_node)) # Add remaining original factors to query node marginal_tree.add_factors_to_node(remaining_assignment_factors, query_node) # Define the root node as the query node. marginal_tree.root = query_node # Update evidence variables of the Marginal tree for k in evidence: marginal_tree.evidence[k] = evidence[k] marginal_tree.observed.extend(list(evidence.keys())) return marginal_tree