Source code for alts.modules.query.query_optimizer
from __future__ import annotations
from typing import TYPE_CHECKING
from dataclasses import dataclass, field
import numpy as np
from scipy.optimize import differential_evolution
from alts.core.query.query_optimizer import QueryOptimizer
from alts.modules.query.selection_criteria import NoSelectionCriteria
from alts.core.configuration import Required, is_set, init, pre_init
if TYPE_CHECKING:
from typing import Dict, Generator
from alts.core.query.query_sampler import QuerySampler
from alts.core.query.selection_criteria import SelectionCriteria
from alts.core.experiment_modules import ExperimentModules
from typing_extensions import Self # type: ignore
[docs]
@dataclass
class NoQueryOptimizer(QueryOptimizer):
"""
NoQueryOptimizer(selection_criteria, query_sampler)
| **Description**
| Selects the first queries from the query sample
:param selection_criteria: Scores the queries for the optimizer
:type selection_criteria: SelectionCriteria
:param query_sampler: Samples queries to work with
:type selection_criteria: QuerySampler
"""
selection_criteria: SelectionCriteria = init(default_factory=NoSelectionCriteria)
query_sampler: QuerySampler = init()
[docs]
def post_init(self):
"""
post_init(self) -> None
| **Description**
| Initializes the query_sampler
"""
super().post_init()
self.query_sampler = self.query_sampler(exp_modules = self.exp_modules)
[docs]
def select(self):
"""
select(self) -> queries, scores
| **Description**
| Selects the first sampled queries regardless of score
:return: queries and associated scores scores
:rtype: queries, `NDArray[float] <https://numpy.org/doc/stable/reference/arrays.ndarray.html>`_
"""
queries = self.query_sampler.sample()
queries, scores = self.selection_criteria.query(queries)
return queries, scores
[docs]
@dataclass
class GAQueryOptimizer(QueryOptimizer):
"""
GAQueryOptimizer()
| **Description**
| The Genetic Algortihm Query Optimizer tries to maximize the query scores through Differential Evolution
"""
[docs]
def select(self):
"""
select(self) -> queries, scores
| **Description**
| Tries to find the score maximizing queries through heuristic methods.
:return: queries, scores
:rtype: Iterable over `NDArrays <https://numpy.org/doc/stable/reference/arrays.ndarray.html>`_, Iterable over `NDArrays <https://numpy.org/doc/stable/reference/arrays.ndarray.html>`_
"""
def opt_func(x):
"""
#TODO Correct
opt_func(queries's) -> scores
| **Description**
| Returns the scores to all given queries
:param x: queries's
:type x: Iterable over iterables over `NDArrays <https://numpy.org/doc/stable/reference/arrays.ndarray.html>`_
:return: Scores of all queries
:rtype: Iterable over `NDArrays <https://numpy.org/doc/stable/reference/arrays.ndarray.html>`_
"""
queries = x[:,None]
queries, scores = self.selection_criteria.query(queries)
return scores[0]
res = differential_evolution(opt_func, bounds=np.repeat(self.oracles.query_constrain().ranges, 2, axis=0))
queries = res.x[:,None]
queries, scores = self.selection_criteria.query(queries)
return queries, scores
[docs]
@dataclass
class MCQueryOptimizer(QueryOptimizer):
"""
MCQueryOptimizer(query_sampler, num_tries=100)
| **Description**
| The Monte Carlo Query Optimizer works by sampling ``num_tries`` many times and chosing one of those.
:param query_sampler: The query sampler to use
:type query_sampler: QuerySampler
:param num_tries: Amount of samples to get (default=100)
:type query_sampler: int
"""
query_sampler: QuerySampler = init()
num_tries: int = init(default=100)
[docs]
def post_init(self):
"""
post_init(self) -> None
| **Description**
| Initializes the query sampler
"""
super().post_init()
self.query_sampler = self.query_sampler(exp_modules=self.exp_modules)
[docs]
@dataclass
class MaxMCQueryOptimizer(MCQueryOptimizer):
"""
MaxMCQueryOptimizer(query_sampler, num_tries=100)
| **Description**
| The Maximizing Monte Carlo Query Optimizer samples ``num_tries`` many times and then choses the best queries.
:param query_sampler: The query sampler to use
:type query_sampler: QuerySampler
:param num_tries: Amount of samples to get (default=100)
:type query_sampler: int
"""
[docs]
def select(self):
query_candidates = self.query_sampler.sample(self.num_tries)
query_candidates, candidate_scores = self.selection_criteria.query(query_candidates)
num_queries = self.query_sampler.num_queries
ind = np.argpartition(candidate_scores, -num_queries, axis=0)[-num_queries:]
ind = ind[:, 0]
queries = query_candidates[ind, ...]
scores = candidate_scores[ind, ...]
return queries, scores
[docs]
@dataclass
class ProbWeightedMCQueryOptimizer(MCQueryOptimizer):
_rng: Generator = pre_init(default_factory = np.random.default_rng)
[docs]
def select(self):
query_candidates = self.query_sampler.sample(self.num_tries)
scores = self.selection_criteria.query(query_candidates)
scores_zero = np.nan_to_num(scores)
scores_weight = np.exp(scores_zero)
score_sum = np.sum(scores_weight)
weight = scores_weight / score_sum
num_queries = self.query_sampler.num_queries
indexes = np.arange(num_queries)
if np.count_nonzero(np.isnan(weight)) > 0:
idx = self._rng.choice(a=indexes, size=num_queries, replace=False)
else:
idx = self._rng.choice(a=indexes, size=num_queries, replace=False, p=weight)
return query_candidates[idx], scores[idx]