# -*- coding: utf-8 -*-
"""Package where the NumericalHyperParam class and it's childs are defined."""
import sys
import numpy as np
from btb.tuning.hyperparams.base import BaseHyperParam
[docs]class NumericalHyperParam(BaseHyperParam):
"""NumericalHyperParam class.
The NumericalHyperParam class defines an abstraction to hyperparameters which ranges are
defined by a numerical value and can take any number within that range.
"""
dimensions = 1
def _within_range(self, values, min=0, max=1):
if (values < min).any() or (values > max).any():
raise ValueError('Value not within range [{}, {}]: {}'.format(min, max, values))
[docs]class FloatHyperParam(NumericalHyperParam):
"""FloatHyperParam class.
The FloatHyperParam class represents a single hyperparameter within a range of ``float``
numbers, where ``min`` and ``max`` can take as value any float number within that range,
having ``min`` to be smaller than ``max``.
Hyperparameter space:
:math:`h_1, h_2,... h_n` where :math:`h_i = i * (max - min) + min`
Search space:
:math:`s_1, s_2,... s_n` where :math:`s_i = (i - min) / (max - min)`
Args:
min (float):
Float number to represent the minimum value that this hyperparameter can take,
by default is ``None`` which will take the system's minimum float value possible.
max (float):
Float number to represent the maximum value that this hyperparameter can take,
by default is ``None`` which will take the system's maximum float value possible.
default (float):
Float number that represents the default value for the hyperparameter. Defaults to
``self.min``
include_min (bool):
Either or not to include the minimum value in the search space.
include_max (bool):
Either or not to include the maximum value in the search space.
"""
cardinality = np.inf
def __init__(self, min=None, max=None, default=None, include_min=True, include_max=True):
self.include_min = include_min
self.include_max = include_max
if min is None or min == -np.inf:
min = sys.float_info.min
if max is None or max == np.inf:
max = sys.float_info.max
if min >= max:
raise ValueError('The ``min`` value can not be greater or equal to ``max`` value.')
if default is None:
self.default = float(min)
else:
self.default = float(default)
self.min = float(min)
self.max = float(max)
self.range = max - min
def _inverse_transform(self, values):
"""Invert one or more search space values.
Converts a ``numpy.ndarray`` with normalized values from the search space :math:`s_1,
s_2,... s_n` to the hyperparameter space of :math:`h_1, h_2,... h_n`. This is
being computed by multiplying the hyperparameter's range with the values to be denormalized
and adding the ``self.min`` value to them.
Args:
values (numpy.ndarray):
2D array with values from the search space.
Returns:
numpy.ndarray:
2D ``numpy.ndarray`` containing values from the original hyperparameter space.
Example:
The example below shows simple usage case where a FloatHyperParam is being created
with a range that goes from ``0.1`` to ``0.9`` and it's ``_inverse_transform`` method
is being called with a valid ``numpy.ndarray`` that contain values from the normalized
search space and values from the hyperparameter space are being returned.
>>> instance = FloatHyperParam(min=0.1, max=0.9)
>>> instance._inverse_transform(np.array([[0.], [1.]]))
array([[0.1],
[0.9]])
>>> instance._inverse_transform(np.array([[0.875]]))
array([[0.8]])
"""
return values * self.range + self.min
def _transform(self, values):
"""Transform one or more hyperparameter values.
Converts a ``numpy.ndarray`` with ``float`` values from the original hyperparameter space
:math:`h_1, h_2,... h_n` into the normalized search space :math:`s_1, s_2,... s_n`.
This is being computed by substracting the ``self.min`` value from the values to be
trasnformed and dividing them by the ``self.range`` of the hyperparameter.
Args:
values (numpy.ndarray):
2D array with values from the hyperparameter space to be converted into the
search space.
Returns:
numpy.ndarray:
2D ``numpy.ndarray`` of shape `(len(values), 1)` containing the search space
values.
Example:
The example below shows simple usage case where a FloatHyperParam is being created
with a range that goes from ``0.1`` to ``0.9`` and it's ``_transform`` method
is being called with a valid ``numpy.ndarray`` that contain values from the
hyperparameter space and values from the search space are being returned.
>>> instance = FloatHyperParam(min=0.1, max=0.9)
>>> instance._transform(np.array([[0.1], [0.9]]))
array([[0.],
[1.]])
>>> instance._transform(np.array([[0.8]]))
array([[0.875]])
"""
return (values - self.min) / self.range
[docs] def sample(self, n_samples):
"""Generate sample values in the hyperparameter search space :math:`{0, 1}`.
Args:
n_samples (int):
Number of values to sample.
Returns:
numpy.ndarray:
2D array with shape of `(n_samples, 1)` with normalized values inside the
search space :math:`{0, 1}`.
Example:
The example below shows simple usage case where a FloatHyperParam is being created
with a range that goes from ``0.1`` to ``0.9`` and it's ``sample`` method
is being called with a number of samples to be obtained. A ``numpy.ndarray`` with
values from the search space is being returned.
>>> instance = FloatHyperParam(min=0.1, max=0.9)
>>> instance.sample(2)
array([[0.52058728],
[0.00582452]])
"""
return np.random.random((n_samples, self.dimensions))
def __repr__(self):
args = (self.min, self.max, self.default, self.include_min, self.include_max)
args = 'min={}, max={}, default={}, include_min={}, include_max={}'.format(*args)
return 'FloatHyperParam({})'.format(args)
[docs]class IntHyperParam(NumericalHyperParam):
"""IntHyperParam class.
The IntHyperParam class represents a single hyperparameter within an range of ``int``
numbers, where ``min`` and ``max`` can take as value any ``int`` number that compose this range
having ``min`` to be smaller than ``max``.
Hyperparameter space:
:math:`h_1, h_2,... h_n` where :math:`h_i = min + (i - 1) * step`
Search space:
:math:`s_1, s_2,... s_n` where :math:`s_i = \\frac{interval}{2} + (i - 1) * interval`
Args:
min (int):
Integer number to represent the minimum value that this hyperparameter can take,
by default is ``None`` which will take the system's minimum int value possible.
max (int):
Integer number to represent the maximum value that this hyperparameter can take,
by default is ``None`` which will take the system's maximum int value possible.
default (int):
Integer number that represents the default value for the hyperparameter. Defaults to
``self.min``.
step (int):
Increase amount to take for each sample. Defaults to 1.
include_min (bool):
Either or not to include the minimum value in the search space.
include_max (bool):
Either or not to include the maximum value in the search space.
"""
dimensions = 1
def __init__(self, min=None, max=None, default=None,
include_min=True, include_max=True, step=1):
self.include_min = include_min
self.include_max = include_max
if min is None or min == -np.inf:
min = -(sys.maxsize / 2)
if max is None or max == np.inf:
max = sys.maxsize / 2
if min >= max:
raise ValueError('The `min` value can not be greater or equal to `max` value.')
if default is None:
self.default = min
else:
self.default = int(default)
self.min = int(min) if include_min else int(min) + 1
self.max = int(max) if include_max else int(max) - 1
self.step = step
self.cardinality = ((self.max - self.min) // step) + 1
if (self.max - self.min) % self.step:
raise ValueError(
"Invalid step of {} for values inside [{}, {}]".format(step, self.min, self.max)
)
self.interval = self.step / (self.max - self.min + self.step)
def _inverse_transform(self, values):
"""Invert one or more search space values.
Converts a ``numpy.ndarray`` with normalized values from the search space :math:`s_1,
s_2,... s_n` to the hyperparameter space of :math:`h_1, h_2,... h_n`. This is
being computed by divinding the hyperparameter's interval with the values to be inverted
and adding the ``self.min`` value to them and resting the 0.5 that has been added during
the transformation.
Args:
values (numpy.ndarray):
2D array with values from the search space.
Returns:
numpy.ndarray:
2D ``numpy.ndarray`` containing values from the original hyperparameter space.
Example:
The example below shows simple usage case where an IntHyperParam is being created
with a range that goes from ``1`` to ``4`` and it's ``_inverse_transform`` method
is being called with a valid ``numpy.ndarray`` that contain values from the
search space and values from the hyperparameter space are being returned.
>>> instance = IntHyperParam(min=1, max=4)
>>> instance._inverse_transform(np.array([[0.125], [0.875]]))
array([[1],
[4]])
>>> instance._inverse_trasnfrom(np.array([[0.625]]))
array([[3]])
"""
unscaled_values = values / self.interval - 0.5 + self.min
rounded = unscaled_values.round()
# Restrict to make sure that we stay within the valid range
restricted = np.minimum(np.maximum(rounded, self.min), self.max)
return restricted.astype(int)
def _transform(self, values):
"""Transform one or more hyperparameter values.
Converts a ``numpy.ndarray`` with ``int`` values from the original hyperparameter space
:math:`h_1, h_2,... h_n` into the normalized search space :math:`s_1, s_2,... s_n`.
This is being computed by substracting the ``min`` value that the hyperparameter can take
from the values to be trasnformed and adding them 0.5, then multiply by the interval.
Args:
values (numpy.ndarray):
2D array with values from the hyperparameter space to be converted into the
search space.
Returns:
numpy.ndarray:
2D ``numpy.ndarray`` of shape `(len(values), 1)` containing the search space
values.
Example:
The example below shows simple usage case where an IntHyperParam is being created
with a range that goes from ``1`` to ``4`` and it's ``_transform`` method
is being called with a valid ``numpy.ndarray`` that contain values from the
hyperparameter space and values from the search space are being returned.
>>> instance = IntHyperParam(min=1, max=4)
>>> instance._transform(np.array([[1], [4]]))
array([[0.125],
[0.875]])
>>> instance._trasnfrom(np.array([[3]]))
array([[0.625]])
"""
return ((values - self.min) / self.step + 0.5) * self.interval
[docs] def sample(self, n_samples):
"""Generate sample values in the hyperparameter search space of [0, 1).
Args:
n_samples (int):
Number of values to sample.
Returns:
numpy.ndarray:
2D array with shape of `(n_samples, 1)` with normalized values inside the
search space :math:`{0, 1}`.
Example:
The example below shows simple usage case where a IntHyperParam is being created
with a range that goes from ``1`` to ``4`` and it's ``sample`` method
is being called with a number of samples to be obtained. A ``numpy.ndarray`` with
values from the search space is being returned.
>>> instance = IntHyperParam(min=1, max=4)
>>> instance.sample(2)
array([[0.625],
[0.375]])
"""
sampled = np.random.random((n_samples, self.dimensions))
inverted = self._inverse_transform(sampled)
return self._transform(inverted)
def __repr__(self):
args = (self.min, self.max, self.default, self.include_min, self.include_max, self.step)
args = 'min={}, max={}, default={}, include_min={}, include_max={}, step={}'.format(*args)
return 'IntHyperParam({})'.format(args)