From bbcf5c24f06e59e93360737a4c4efecfc4615d3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my?= Date: Mon, 4 Mar 2019 11:10:21 +0100 Subject: [PATCH] Avoid casting to tuple in base Optimizer (#133) --- .pre-commit-config.yaml | 2 +- nevergrad/optimization/base.py | 30 +++++++++---------- .../optimization/differentialevolution.py | 13 ++++---- nevergrad/optimization/recaster.py | 2 +- nevergrad/optimization/recastlib.py | 2 +- nevergrad/optimization/utils.py | 6 ++-- 6 files changed, 28 insertions(+), 27 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 61a11ad14..eda556647 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,7 +8,7 @@ repos: - id: autopep8-wrapper args: ['-i', '--max-line-length=140'] - repo: https://github.com/pre-commit/mirrors-pylint - sha: v2.1.1 + sha: v2.3.0 hooks: - id: pylint exclude: ^scripts/ diff --git a/nevergrad/optimization/base.py b/nevergrad/optimization/base.py index e13a4204e..ec1e7858f 100644 --- a/nevergrad/optimization/base.py +++ b/nevergrad/optimization/base.py @@ -70,7 +70,7 @@ def __init__(self, dimension: int, budget: Optional[int] = None, num_workers: in self.name = self.__class__.__name__ # printed name in repr # keep a record of evaluations, and current bests which are updated at each new evaluation self.archive = utils.Archive() # dict like structure taking np.ndarray as keys and Value as values - self.current_bests = {x: utils.Point(tuple(0. for _ in range(dimension)), utils.Value(np.inf)) + self.current_bests = {x: utils.Point(np.zeros(dimension, dtype=np.float), utils.Value(np.inf)) for x in ["optimistic", "pessimistic", "average"]} # instance state self._num_ask = 0 @@ -125,11 +125,12 @@ def tell(self, x: ArrayLike, value: float) -> None: Parameters ---------- - x: tuple/np.ndarray + x: np.ndarray point where the function was evaluated value: float value of the function """ + x = np.array(x, copy=False) # call callbacks for logging etc... for callback in self._callbacks.get("tell", []): callback(self, x, value) @@ -137,7 +138,6 @@ def tell(self, x: ArrayLike, value: float) -> None: raise TypeError(f'"tell" method only supports float values but the passed value was: {value} (type: {type(value)}.') if np.isnan(value) or value == np.inf: warnings.warn(f"Updating fitness with {value} value") - x = tuple(x) if x not in self.archive: self.archive[x] = utils.Value(value) # better not to stock the position as a Point (memory) else: @@ -145,20 +145,18 @@ def tell(self, x: ArrayLike, value: float) -> None: # update current best records # this may have to be improved if we want to keep more kinds of best values for name in ["optimistic", "pessimistic", "average"]: - if x == self.current_bests[name].x: # reboot - if isinstance(self.archive, utils.Archive): - # currently, cast to tuple for compatibility reason (comparing tuples and np.ndarray fails) - y: ArrayLike = tuple(np.frombuffer( - min(self.archive.bytesdict, key=lambda x, n=name: self.archive.bytesdict[x].get_estimation(n)))) - else: - y = min(self.archive, key=lambda x, n=name: self.archive[x].get_estimation(n)) + if np.array_equal(x, self.current_bests[name].x): # reboot + y = min(self.archive.bytesdict, key=lambda z, n=name: self.archive.bytesdict[z].get_estimation(n)) # rebuild best point may change, and which value did not track the updated value anyway - self.current_bests[name] = utils.Point(y, self.archive[y]) + self.current_bests[name] = utils.Point(np.frombuffer(y), self.archive.bytesdict[y]) else: if self.archive[x].get_estimation(name) <= self.current_bests[name].get_estimation(name): self.current_bests[name] = utils.Point(x, self.archive[x]) if not (np.isnan(value) or value == np.inf): - assert self.current_bests[name].x in self.archive, "Best value should exist in the archive" + if self.current_bests[name].x not in self.archive: + y = np.frombuffer( + min(self.archive.bytesdict, key=lambda z, n=name: self.archive.bytesdict[z].get_estimation(n))) + assert self.current_bests[name].x in self.archive, "Best value should exist in the archive" self._internal_tell(x, value) self._num_tell += 1 @@ -174,12 +172,12 @@ def ask(self) -> Tuple[float, ...]: self._num_ask += 1 return suggestion - def provide_recommendation(self) -> Tuple[float, ...]: + def provide_recommendation(self) -> np.ndarray: """Provides the best point to use as a minimum, given the budget that was used """ return self.recommend() # duplicate method - def recommend(self) -> Tuple[float, ...]: + def recommend(self) -> np.ndarray: """Provides the best point to use as a minimum, given the budget that was used """ return self._internal_provide_recommendation() @@ -192,13 +190,13 @@ def _internal_tell(self, x: ArrayLike, value: float) -> None: def _internal_ask(self) -> Tuple[float, ...]: raise NotImplementedError("Optimizer undefined.") - def _internal_provide_recommendation(self) -> Tuple[float, ...]: + def _internal_provide_recommendation(self) -> np.ndarray: return self.current_bests["pessimistic"].x def optimize(self, objective_function: Callable[[Any], float], executor: Optional[ExecutorLike] = None, batch_mode: bool = False, - verbosity: int = 0) -> Tuple[float, ...]: + verbosity: int = 0) -> np.ndarray: """Optimization (minimization) procedure Parameters diff --git a/nevergrad/optimization/differentialevolution.py b/nevergrad/optimization/differentialevolution.py index df541e29c..1af29dcff 100644 --- a/nevergrad/optimization/differentialevolution.py +++ b/nevergrad/optimization/differentialevolution.py @@ -49,19 +49,20 @@ def llambda(self) -> int: return self._llambda def match_population_size_to_lambda(self) -> None: - if len(self.population) < self.llambda: - self.candidates += [None] * (self.llambda - len(self.population)) - self.population_fitnesses += [None] * (self.llambda - len(self.population)) - self.population += [None] * (self.llambda - len(self.population)) + current_pop = len(self.population) + if current_pop < self.llambda: + self.candidates.extend([None] * (self.llambda - current_pop)) + self.population_fitnesses.extend([None] * (self.llambda - current_pop)) + self.population.extend([None] * (self.llambda - current_pop)) - def _internal_provide_recommendation(self) -> Tuple[float, ...]: # This is NOT the naive version. We deal with noise. + def _internal_provide_recommendation(self) -> np.ndarray: # This is NOT the naive version. We deal with noise. if self._parameters.recommendation != "noisy": return self.current_bests[self._parameters.recommendation].x med_fitness = np.median([f for f in self.population_fitnesses if f is not None]) good_guys = [p for p, f in zip(self.population, self.population_fitnesses) if f is not None and f < med_fitness] if not good_guys: return self.current_bests["pessimistic"].x - return sum([np.array(g) for g in good_guys]) / len(good_guys) # type: ignore + return sum([np.array(g) for g in good_guys]) / len(good_guys) def _internal_ask(self) -> Tuple[float, ...]: init = self._parameters.initialization diff --git a/nevergrad/optimization/recaster.py b/nevergrad/optimization/recaster.py index 044ae986f..9d504766d 100644 --- a/nevergrad/optimization/recaster.py +++ b/nevergrad/optimization/recaster.py @@ -208,7 +208,7 @@ def _internal_ask(self) -> base.ArrayLike: def _check_error(self) -> None: if self._messaging_thread is not None: if self._messaging_thread.error is not None: - raise RuntimeError("Recast optimizer raised an error") from self._messaging_thread.error + raise RuntimeError(f"Recast optimizer raised an error:\n{self._messaging_thread.error}") from self._messaging_thread.error def _internal_tell(self, x: base.ArrayLike, value: float) -> None: """Returns value for a point which was "asked" diff --git a/nevergrad/optimization/recastlib.py b/nevergrad/optimization/recastlib.py index a0b34a62d..819c3fe53 100644 --- a/nevergrad/optimization/recastlib.py +++ b/nevergrad/optimization/recastlib.py @@ -34,7 +34,7 @@ def _optimization_function(self, objective_function: Callable[[base.ArrayLike], best_res = np.inf best_x = np.zeros(self.dimension) if self.initial_guess is not None: - best_x = self.initial_guess + best_x = np.array(self.initial_guess, copy=True) # copy, just to make sure it is not modified remaining = budget - self._num_ask while remaining > 0: # try to restart if budget is not elapsed options: Dict[str, int] = {} if self.budget is None else {"maxiter": remaining} diff --git a/nevergrad/optimization/utils.py b/nevergrad/optimization/utils.py index 30859673c..78fa500a7 100644 --- a/nevergrad/optimization/utils.py +++ b/nevergrad/optimization/utils.py @@ -88,11 +88,13 @@ class Point(Value): the value estimation instance """ - def __init__(self, x: Tuple[float, ...], value: Value) -> None: + def __init__(self, x: np.ndarray, value: Value) -> None: assert isinstance(value, Value) super().__init__(value.mean) self.__dict__.update(value.__dict__) - self.x = x + assert not isinstance(x, (str, bytes)) + self.x = np.array(x, copy=True) # copy to avoid interfering with algorithms + self.x.flags.writeable = False # make sure it is not modified! def __repr__(self) -> str: return "Point".format(self.x, self.mean, self.count)