Source code for QHyper.solvers.hyper_optimizer
from dataclasses import dataclass, field
from numpy.typing import NDArray
from typing import Any
from QHyper.optimizers import OptimizationResult, Optimizer, OptimizationParameter
from QHyper.solvers import Solver, SolverResult
from QHyper.problems import Problem
from QHyper.util import weighted_avg_evaluation
class HyperOptimizerProperty:
def __init__(self, name: str, min_value: list[float],
max_value: list[float], initial_value: list[float]) -> None:
self.name = name
self.min_value = min_value
self.max_value = max_value
self.initial_value = initial_value
if len(min_value) != len(max_value):
raise ValueError(
"min_value and max_value must have the same length")
if len(min_value) != len(initial_value):
raise ValueError(
"min_value and initial_value must have the same length")
def get_bounds(self) -> list[tuple[float, float]]:
return list(zip(self.min_value, self.max_value))
[docs]
@dataclass
class HyperOptimizer:
""" Class for hyperparameter optimization
HyperOptimizer is a class that allows to use the optimizers and
find the best parameters for a given solver.
Parameters
----------
optimizer : Optimizer
The optimizer to use for optimization
solver : Solver
The solver to use for optimization
properties : dict[str, OptimizationParameter]
The properties to optimize. Their keys must match the properties
of the solver.
history : list[SolverResult]
The history of the optimization process
best_params : OptimizationResult
The best parameters found during optimization
"""
optimizer: Optimizer
solver: Solver
properties: dict[str, OptimizationParameter] = field(default_factory=dict)
history: list[SolverResult] = field(default_factory=list)
best_params: OptimizationResult = field(init=False)
def __init__(self, optimizer: Optimizer, solver: Solver, **properties: Any) -> None:
self.optimizer = optimizer
self.solver = solver
self.properties = {}
self.history = []
for property, values in properties.items():
self.properties[property] = OptimizationParameter(**values)
@property
def problem(self) -> Problem:
return self.solver.problem
def parse_params(self, params: NDArray) -> dict[str, list[float]]:
parsed_params = {}
param_index = 0
for property, opt_param in self.properties.items():
parsed_params[property] = params[
param_index:param_index + len(opt_param)]
param_index += len(opt_param)
return parsed_params
def run_solver(self, params: NDArray) -> SolverResult:
return self.solver.solve(**self.parse_params(params))
def _optimization_function(self, params: NDArray) -> OptimizationResult:
result = self.run_solver(params)
value = weighted_avg_evaluation(
result.probabilities, self.solver.problem.get_score,
)
self.history.append(result)
return OptimizationResult(
value=value,
params=params,
history=[]
)
def solve(self) -> OptimizationResult:
initial_params = sum(self.properties.values(), OptimizationParameter())
self.best_params = self.optimizer.minimize(
self._optimization_function, initial_params)
return self.best_params
def run_with_best_params(self) -> SolverResult:
if self.best_params is None:
raise ValueError(
"Run hyper optimization first. Call solve() method")
return self.run_solver(self.best_params.params)