Skip to content

Commit

Permalink
Group Parameter containers in container module (#1044)
Browse files Browse the repository at this point in the history
  • Loading branch information
jrapin authored Feb 10, 2021
1 parent 1823650 commit 1880ea2
Show file tree
Hide file tree
Showing 8 changed files with 222 additions and 185 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
[#1036](https://github.com/facebookresearch/nevergrad/pull/1036)
[#1038](https://github.com/facebookresearch/nevergrad/pull/1038)
[#1043](https://github.com/facebookresearch/nevergrad/pull/1043)
[#1044](https://github.com/facebookresearch/nevergrad/pull/1044)
and more to come), please open an issue if you encounter any problem. The midterm aim is to allow for simpler constraint management.

## 0.4.3 (2021-01-28)
Expand Down
3 changes: 2 additions & 1 deletion nevergrad/optimization/es.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,8 @@ class EvolutionStrategy(base.ConfiguredOptimizer):
"""Experimental evolution-strategy-like algorithm
The API is going to evolve
Parameters:
Parameters
----------
recombination_ratio: float
probability of using a recombination (after the mutation) for generating new offsprings
popsize: int
Expand Down
24 changes: 12 additions & 12 deletions nevergrad/parametrization/choice.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from . import discretization
from . import utils
from . import core
from .container import Tuple
from . import container
from .data import Array

# weird pylint issue on "Descriptors"
Expand All @@ -34,7 +34,7 @@ def as_tag(cls, param: core.Parameter) -> "ChoiceTag":
return cls(type(param), arity)


class BaseChoice(core.Container):
class BaseChoice(container.Container):

ChoiceTag = ChoiceTag

Expand All @@ -46,7 +46,7 @@ def __init__(
lchoices = list(choices) # unroll iterables (includig Tuple instances
if not lchoices:
raise ValueError("{self._class__.__name__} received an empty list of options.")
super().__init__(choices=Tuple(*lchoices), **kwargs)
super().__init__(choices=container.Tuple(*lchoices), **kwargs)

def _compute_descriptors(self) -> utils.Descriptors:
deterministic = getattr(self, "_deterministic", True)
Expand Down Expand Up @@ -80,7 +80,7 @@ def indices(self) -> np.ndarray:
raise NotImplementedError # TODO remove index?

@property
def choices(self) -> Tuple:
def choices(self) -> container.Tuple:
"""The different options, as a Tuple Parameter"""
return self["choices"] # type: ignore

Expand All @@ -89,18 +89,18 @@ def _get_value(self) -> tp.Any:
return core.as_parameter(self.choices[self.index]).value
return tuple(core.as_parameter(self.choices[ind]).value for ind in self.indices)

def _set_value(self, values: tp.List[tp.Any]) -> np.ndarray:
def _set_value(self, value: tp.List[tp.Any]) -> np.ndarray:
"""Must be adapted to each class
This handles a list of values, not just one
""" # TODO this is currenlty very messy, may need some improvement
values = [values] if self._repetitions is None else values
values = [value] if self._repetitions is None else value
self._check_frozen()
indices: np.ndarray = -1 * np.ones(len(values), dtype=int)
# try to find where to put this
for i, value in enumerate(values):
for i, val in enumerate(values):
for k, choice in enumerate(self.choices):
try:
choice.value = value
choice.value = val
indices[i] = k
break
except Exception: # pylint: disable=broad-except
Expand Down Expand Up @@ -197,8 +197,8 @@ def probabilities(self) -> np.ndarray:
exp = np.exp(self.weights.value)
return exp / np.sum(exp) # type: ignore

def _set_value(self, values: tp.Any) -> np.ndarray:
indices = super()._set_value(values)
def _set_value(self, value: tp.Any) -> np.ndarray:
indices = super()._set_value(value)
self._indices = indices
# force new probabilities
arity = self.weights.value.shape[1]
Expand Down Expand Up @@ -274,8 +274,8 @@ def __init__(
def indices(self) -> np.ndarray:
return np.minimum(len(self) - 1e-9, self.positions.value).astype(int) # type: ignore

def _set_value(self, values: tp.Any) -> np.ndarray:
indices = super()._set_value(values) # only one value for this class
def _set_value(self, value: tp.Any) -> np.ndarray:
indices = super()._set_value(value) # only one value for this class
self._set_index(indices)
return indices

Expand Down
156 changes: 152 additions & 4 deletions nevergrad/parametrization/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,164 @@
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.

import operator
import functools
from collections import OrderedDict
import nevergrad.common.typing as tp
from .core import Dict as Dict # Dict needs to be implemented in core since it's used in the base class
import numpy as np
from . import utils
from . import core


Ins = tp.TypeVar("Ins", bound="Instrumentation")
ArgsKwargs = tp.Tuple[tp.Tuple[tp.Any, ...], tp.Dict[str, tp.Any]]
D = tp.TypeVar("D", bound="Container")


class Tuple(core.Container):
class Container(core.Parameter):
"""Parameter which can hold other parameters.
This abstract implementation is based on a dictionary.
Parameters
----------
**parameters: Any
the objects or Parameter which will provide values for the dict
Note
----
This is the base structure for all container Parameters, and it is
used to hold the internal/model parameters for all Parameter classes.
"""

def __init__(self, **parameters: tp.Any) -> None:
super().__init__()
self._subobjects = utils.Subobjects(self, base=core.Parameter, attribute="_content")
self._content: tp.Dict[tp.Any, core.Parameter] = {
k: core.as_parameter(p) for k, p in parameters.items()
}
self._sizes: tp.Optional[tp.Dict[str, int]] = None
self._sanity_check(list(self._content.values()))
self._ignore_in_repr: tp.Dict[
str, str
] = {} # hacky undocumented way to bypass boring representations

def _sanity_check(self, parameters: tp.List[core.Parameter]) -> None:
"""Check that all parameters are different"""
# TODO: this is first order, in practice we would need to test all the different
# parameter levels together
if parameters:
assert all(isinstance(p, core.Parameter) for p in parameters)
ids = {id(p) for p in parameters}
if len(ids) != len(parameters):
raise ValueError("Don't repeat twice the same parameter")

def _compute_descriptors(self) -> utils.Descriptors:
init = utils.Descriptors()
return functools.reduce(operator.and_, [p.descriptors for p in self._content.values()], init)

def __getitem__(self, name: tp.Any) -> core.Parameter:
return self._content[name]

def __len__(self) -> int:
return len(self._content)

def _get_parameters_str(self) -> str:
raise NotImplementedError

def _get_name(self) -> str:
return f"{self.__class__.__name__}({self._get_parameters_str()})"

def get_value_hash(self) -> tp.Hashable:
return tuple(sorted((x, y.get_value_hash()) for x, y in self._content.items()))

def _internal_get_standardized_data(self: D, reference: D) -> np.ndarray:
data = {k: self[k].get_standardized_data(reference=p) for k, p in reference._content.items()}
if self._sizes is None:
self._sizes = OrderedDict(sorted((x, y.size) for x, y in data.items()))
assert self._sizes is not None
data_list = [data[k] for k in self._sizes]
if not data_list:
return np.array([])
return data_list[0] if len(data_list) == 1 else np.concatenate(data_list) # type: ignore

def _internal_set_standardized_data(
self: D, data: np.ndarray, reference: D, deterministic: bool = False
) -> None:
if self._sizes is None:
self.get_standardized_data(reference=self)
assert self._sizes is not None
if data.size != sum(v for v in self._sizes.values()):
raise ValueError(
f"Unexpected shape {data.shape} for {self} with dimension {self.dimension}:\n{data}"
)
data = data.ravel()
start, end = 0, 0
for name, size in self._sizes.items():
end = start + size
self._content[name].set_standardized_data(
data[start:end], reference=reference[name], deterministic=deterministic
)
start = end
assert end == len(data), f"Finished at {end} but expected {len(data)}"

def sample(self: D) -> D:
child = self.spawn_child()
child._content = {k: p.sample() for k, p in self._content.items()}
child.heritage["lineage"] = child.uid
return child


class Dict(Container):
"""Dictionary-valued parameter. This Parameter can contain other Parameters,
its value is a dict, with keys the ones provided as input, and corresponding values are
either directly the provided values if they are not Parameter instances, or the value of those
Parameters. It also implements a getter to access the Parameters directly if need be.
Parameters
----------
**parameters: Any
the objects or Parameter which will provide values for the dict
Note
----
This is the base structure for all container Parameters, and it is
used to hold the internal/model parameters for all Parameter classes.
"""

value: core.ValueProperty[tp.Dict[str, tp.Any]] = core.ValueProperty()

def __iter__(self) -> tp.Iterator[str]:
return iter(self.keys())

def keys(self) -> tp.KeysView[str]:
return self._content.keys()

def items(self) -> tp.ItemsView[str, core.Parameter]:
return self._content.items()

def values(self) -> tp.ValuesView[core.Parameter]:
return self._content.values()

def _get_value(self) -> tp.Dict[str, tp.Any]:
return {k: p.value for k, p in self.items()}

def _set_value(self, value: tp.Dict[str, tp.Any]) -> None:
cls = self.__class__.__name__
if not isinstance(value, dict):
raise TypeError(f"{cls} value must be a dict, got: {value}\nCurrent value: {self.value}")
if set(value) != set(self):
raise ValueError(
f"Got input keys {set(value)} for {cls} but expected {set(self._content)}\nCurrent value: {self.value}"
)
for key, val in value.items():
self._content[key].value = val

def _get_parameters_str(self) -> str:
params = sorted(
(k, p.name) for k, p in self.items() if p.name != self._ignore_in_repr.get(k, "#ignoredrepr#")
)
return ",".join(f"{k}={n}" for k, n in params)


class Tuple(Container):
"""Tuple-valued parameter. This Parameter can contain other Parameters,
its value is tuple which values are either directly the provided values
if they are not Parameter instances, or the value of those Parameters.
Expand Down
Loading

0 comments on commit 1880ea2

Please sign in to comment.