169 lines
6.6 KiB
Python
169 lines
6.6 KiB
Python
import random
|
|
import math
|
|
from typing import List, Dict, Any, Tuple
|
|
from itertools import product
|
|
|
|
class SmartSearch:
|
|
def __init__(self, ea_config):
|
|
self.ea_config = ea_config
|
|
self.total_combinations = ea_config.get_total_combinations()
|
|
|
|
def select_strategy(self) -> str:
|
|
if self.ea_config.search_strategy != 'auto':
|
|
return self.ea_config.search_strategy
|
|
if self.total_combinations <= 10000:
|
|
return 'grid'
|
|
elif self.total_combinations <= 500000:
|
|
return 'latin'
|
|
else:
|
|
return 'genetic'
|
|
|
|
def generate_combinations(self, max_samples: int = 2000) -> List[Dict]:
|
|
strategy = self.select_strategy()
|
|
print('Selected strategy: ' + strategy + ' for ' + str(self.total_combinations) + ' combinations')
|
|
|
|
if strategy == 'grid':
|
|
return self._grid_search()
|
|
elif strategy == 'random':
|
|
return self._random_search(max_samples)
|
|
elif strategy == 'latin':
|
|
return self._latin_hypercube(max_samples)
|
|
elif strategy == 'genetic':
|
|
return self._genetic_search(max_samples)
|
|
return []
|
|
|
|
def _grid_search(self) -> List[Dict]:
|
|
param_names = list(self.ea_config.parameters.keys())
|
|
param_values = [p.expand_values() for p in self.ea_config.parameters.values()]
|
|
combinations = list(product(*param_values))
|
|
return [dict(zip(param_names, combo)) for combo in combinations]
|
|
|
|
def _random_search(self, max_samples: int) -> List[Dict]:
|
|
all_values = {name: p.expand_values() for name, p in self.ea_config.parameters.items()}
|
|
samples = []
|
|
for _ in range(min(max_samples, self.total_combinations)):
|
|
sample = {name: random.choice(values) for name, values in all_values.items()}
|
|
if sample not in samples:
|
|
samples.append(sample)
|
|
return samples
|
|
|
|
def _latin_hypercube(self, max_samples: int) -> List[Dict]:
|
|
all_values = {name: p.expand_values() for name, p in self.ea_config.parameters.items()}
|
|
n_params = len(all_values)
|
|
samples = []
|
|
for i in range(min(max_samples, self.total_combinations)):
|
|
sample = {}
|
|
for j, (name, values) in enumerate(all_values.items()):
|
|
idx = int((i / max_samples) * len(values)) % len(values)
|
|
sample[name] = values[idx]
|
|
if sample not in samples:
|
|
samples.append(sample)
|
|
return samples
|
|
|
|
def _genetic_search(self, max_samples: int) -> List[Dict]:
|
|
pop_size = min(50, max_samples)
|
|
n_generations = max_samples // pop_size
|
|
all_values = {name: p.expand_values() for name, p in self.ea_config.parameters.items()}
|
|
|
|
population = []
|
|
for _ in range(pop_size):
|
|
individual = {name: random.choice(values) for name, values in all_values.items()}
|
|
population.append(individual)
|
|
|
|
for gen in range(n_generations):
|
|
population = self._evolve(population, all_values)
|
|
|
|
return population[:max_samples]
|
|
|
|
def _evolve(self, population: List[Dict], all_values: Dict) -> List[Dict]:
|
|
crossover_rate = 0.8
|
|
mutation_rate = 0.15
|
|
|
|
offspring = []
|
|
for _ in range(len(population)):
|
|
parent1, parent2 = random.sample(population, 2)
|
|
if random.random() < crossover_rate:
|
|
child = self._crossover(parent1, parent2)
|
|
else:
|
|
child = parent1.copy()
|
|
|
|
if random.random() < mutation_rate:
|
|
child = self._mutate(child, all_values)
|
|
|
|
offspring.append(child)
|
|
|
|
return population[:5] + offspring[:len(population)-5]
|
|
|
|
def _crossover(self, parent1: Dict, parent2: Dict) -> Dict:
|
|
child = {}
|
|
for key in parent1.keys():
|
|
if random.random() < 0.5:
|
|
child[key] = parent1[key]
|
|
else:
|
|
child[key] = parent2[key]
|
|
return child
|
|
|
|
def _mutate(self, individual: Dict, all_values: Dict) -> Dict:
|
|
key = random.choice(list(all_values.keys()))
|
|
individual[key] = random.choice(all_values[key])
|
|
return individual
|
|
|
|
|
|
class GeneticOptimizer:
|
|
def __init__(self, ea_config, population_size: int = 50, generations: int = 30):
|
|
self.ea_config = ea_config
|
|
self.population_size = population_size
|
|
self.generations = generations
|
|
self.all_values = {name: p.expand_values() for name, p in ea_config.parameters.items()}
|
|
|
|
def create_individual(self) -> Dict:
|
|
return {name: random.choice(values) for name, values in self.all_values.items()}
|
|
|
|
def evaluate(self, individual: Dict) -> float:
|
|
return random.random() * 10
|
|
|
|
def tournament_select(self, population: List[Dict], k: int = 3) -> Dict:
|
|
tournament = random.sample(population, k)
|
|
return max(tournament, key=self.evaluate)
|
|
|
|
def crossover(self, parent1: Dict, parent2: Dict) -> Tuple[Dict, Dict]:
|
|
child1, child2 = {}, {}
|
|
for key in parent1.keys():
|
|
if random.random() < 0.5:
|
|
child1[key] = parent1[key]
|
|
child2[key] = parent2[key]
|
|
else:
|
|
child1[key] = parent2[key]
|
|
child2[key] = parent1[key]
|
|
return child1, child2
|
|
|
|
def mutate(self, individual: Dict, rate: float = 0.15) -> Dict:
|
|
for key in individual.keys():
|
|
if random.random() < rate:
|
|
individual[key] = random.choice(self.all_values[key])
|
|
return individual
|
|
|
|
def run(self) -> List[Dict]:
|
|
population = [self.create_individual() for _ in range(self.population_size)]
|
|
best_individuals = []
|
|
|
|
for gen in range(self.generations):
|
|
fitness_scores = [(ind, self.evaluate(ind)) for ind in population]
|
|
fitness_scores.sort(key=lambda x: x[1], reverse=True)
|
|
|
|
elite = [ind for ind, _ in fitness_scores[:5]]
|
|
best_individuals.extend(elite)
|
|
|
|
new_population = elite.copy()
|
|
while len(new_population) < self.population_size:
|
|
parent1 = self.tournament_select(population)
|
|
parent2 = self.tournament_select(population)
|
|
child1, child2 = self.crossover(parent1, parent2)
|
|
child1 = self.mutate(child1)
|
|
child2 = self.mutate(child2)
|
|
new_population.extend([child1, child2])
|
|
|
|
population = new_population[:self.population_size]
|
|
|
|
return best_individuals
|