1
0
Fork 0
Old engine for Continuous Time Bayesian Networks. Superseded by reCTBN. 🐍 https://github.com/madlabunimib/PyCTBN
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests.
PyCTBN/main_package/classes/optimizers/tabu_search.py

199 lines
7.4 KiB

import itertools
import json
import typing
import networkx as nx
import numpy as np
from random import choice,sample
from abc import ABC
from .optimizer import Optimizer
from ..estimators.structure_estimator import StructureEstimator
from ..structure_graph.network_graph import NetworkGraph
import queue
class TabuSearch(Optimizer):
"""
Optimizer class that implement Tabu Search
:param node_id: current node's id
:type node_id: string
:param structure_estimator: a structure estimator object with the information about the net
:type structure_estimator: class:'StructureEstimator'
:param max_parents: maximum number of parents for each variable. If None, disabled, default to None
:type max_parents: int, optional
:param iterations_number: maximum number of optimization algorithm's iteration, default to 40
:type iterations_number: int, optional
:param patience: number of iteration without any improvement before to stop the search.If None, disabled, default to None
:type patience: int, optional
:param tabu_length: maximum lenght of the data structures used in the optimization process, default to None
:type tabu_length: int, optional
:param tabu_rules_duration: number of iterations in which each rule keeps its value, default to None
:type tabu_rules_duration: int, optional
"""
def __init__(self,
node_id:str,
structure_estimator: StructureEstimator,
max_parents:int = None,
iterations_number:int= 40,
patience:int = None,
tabu_length:int = None,
tabu_rules_duration = None
):
"""
Constructor
"""
super().__init__(node_id, structure_estimator)
self.max_parents = max_parents
self.iterations_number = iterations_number
self.patience = patience
self.tabu_length = tabu_length
self.tabu_rules_duration = tabu_rules_duration
def optimize_structure(self) -> typing.List:
"""
Compute Optimization process for a structure_estimator by using a Hill Climbing Algorithm
:return: the estimated structure for the node
:rtype: List
"""
print(f"tabu search is processing the structure of {self.node_id}")
'Create the graph for the single node'
graph = NetworkGraph(self.structure_estimator._sample_path.structure)
'get the index for the current node'
node_index = self.structure_estimator._sample_path._structure.get_node_indx(self.node_id)
'list of prior edges'
prior_parents = set()
'Add the edges from prior knowledge'
for i in range(len(self.structure_estimator._removable_edges_matrix)):
if not self.structure_estimator._removable_edges_matrix[i][node_index]:
parent_id= self.structure_estimator._sample_path._structure.get_node_id(i)
prior_parents.add(parent_id)
'Add the node to the starting structure'
graph.add_edges([(parent_id, self.node_id)])
'get all the possible parents'
other_nodes = set([node for node in
self.structure_estimator._sample_path.structure.nodes_labels if
node != self.node_id and
not prior_parents.__contains__(node)])
'calculate the score for the node without parents'
actual_best_score = self.structure_estimator.get_score_from_graph(graph,self.node_id)
'initialize tabu_length and tabu_rules_duration if None'
if self.tabu_length is None:
self.tabu_length = len(other_nodes)
if self.tabu_rules_duration is None:
self.tabu_tabu_rules_durationength = len(other_nodes)
'inizialize the data structures'
tabu_set = set()
tabu_queue = queue.Queue()
patince_count = 0
tabu_count = 0
for i in range(self.iterations_number):
current_possible_nodes = other_nodes.difference(tabu_set)
'choose a new random edge according to tabu restiction'
if(len(current_possible_nodes) > 0):
current_new_parent = sample(current_possible_nodes,k=1)[0]
else:
current_new_parent = tabu_queue.get()
tabu_set.remove(current_new_parent)
current_edge = (current_new_parent,self.node_id)
added = False
parent_removed = None
if graph.has_edge(current_edge):
graph.remove_edges([current_edge])
else:
'check the max_parents constraint'
if self.max_parents is not None:
parents_list = graph.get_parents_by_id(self.node_id)
if len(parents_list) >= self.max_parents :
parent_removed = (choice(parents_list), self.node_id)
graph.remove_edges([parent_removed])
graph.add_edges([current_edge])
added = True
#print('**************************')
current_score = self.structure_estimator.get_score_from_graph(graph,self.node_id)
# print("-------------------------------------------")
# print(f"Current new parent: {current_new_parent}")
# print(f"Current score: {current_score}")
# print(f"Current best score: {actual_best_score}")
# print(f"tabu list : {str(tabu_set)} length: {len(tabu_set)}")
# print(f"tabu queue : {str(tabu_queue)} length: {tabu_queue.qsize()}")
# print(f"graph edges: {graph.edges}")
# print("-------------------------------------------")
# input()
if current_score > actual_best_score:
'update current best score'
actual_best_score = current_score
patince_count = 0
'update tabu list'
else:
'undo the last update'
if added:
graph.remove_edges([current_edge])
'If a parent was removed, add it again to the graph'
if parent_removed is not None:
graph.add_edges([parent_removed])
else:
graph.add_edges([current_edge])
'update patience count'
patince_count += 1
if tabu_queue.qsize() >= self.tabu_length:
current_removed = tabu_queue.get()
tabu_set.remove(current_removed)
'Add the node on the tabu list'
tabu_queue.put(current_new_parent)
tabu_set.add(current_new_parent)
tabu_count += 1
'Every tabu_rules_duration step remove an item from the tabu list '
if tabu_count % self.tabu_rules_duration == 0:
if tabu_queue.qsize() > 0:
current_removed = tabu_queue.get()
tabu_set.remove(current_removed)
tabu_count = 0
else:
tabu_count = 0
if self.patience is not None and patince_count > self.patience:
break
print(f"finito variabile: {self.node_id}")
return graph.edges