Skip to content

Commit

Permalink
Avoid casting to tuple in base Optimizer (facebookresearch#133)
Browse files Browse the repository at this point in the history
  • Loading branch information
jrapin authored Mar 4, 2019
1 parent bac5bff commit bbcf5c2
Show file tree
Hide file tree
Showing 6 changed files with 28 additions and 27 deletions.
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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/
30 changes: 14 additions & 16 deletions nevergrad/optimization/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -125,40 +125,38 @@ 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)
if not isinstance(value, Real):
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:
self.archive[x].add_evaluation(value)
# 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

Expand All @@ -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()
Expand All @@ -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
Expand Down
13 changes: 7 additions & 6 deletions nevergrad/optimization/differentialevolution.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion nevergrad/optimization/recaster.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion nevergrad/optimization/recastlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
6 changes: 4 additions & 2 deletions nevergrad/optimization/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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<x: {}, mean: {}, count: {}>".format(self.x, self.mean, self.count)
Expand Down

0 comments on commit bbcf5c2

Please sign in to comment.