Esempio n. 1
0
    def setUp(self, list_mock):
        """Instantiate the ``Tunable`` and it's ``Hyperparameters`` that we will be using."""
        self.bhp = MagicMock()
        self.chp = MagicMock()
        self.ihp = MagicMock()

        list_mock.return_value = ['bhp', 'chp', 'ihp']

        hyperparams = {
            'bhp': self.bhp,
            'chp': self.chp,
            'ihp': self.ihp,
        }

        self.instance = Tunable(hyperparams)
Esempio n. 2
0
    def test_get_defaults(self):
        # setup
        bhp = MagicMock(default=True)
        chp = MagicMock(default='test')
        ihp = MagicMock(default=1)

        hyperparams = {
            'bhp': bhp,
            'chp': chp,
            'ihp': ihp,
        }

        self.instance = Tunable(hyperparams)

        # run
        result = self.instance.get_defaults()

        # assert
        assert result == {'bhp': True, 'chp': 'test', 'ihp': 1}
Esempio n. 3
0
    def test_from_dict(self, mock_bool, mock_cat, mock_float, mock_int):
        # setup
        mock_bool.return_value.dimensions = 1
        mock_cat.return_value .dimensions = 1
        mock_float.return_value.dimensions = 1
        mock_int.return_value.dimensions = 1

        mock_bool.return_value.cardinality = 1
        mock_cat.return_value .cardinality = 1
        mock_float.return_value.cardinality = 1
        mock_int.return_value.cardinality = 1

        # run
        hyperparameters = {
            'bhp': {
                'type': 'bool',
                'default': False
            },
            'chp': {
                'type': 'str',
                'default': 'cat',
                'range': ['a', 'b', 'cat']
            },
            'fhp': {
                'type': 'float',
                'default': None,
                'range': [0.1, 1.0]
            },
            'ihp': {
                'type': 'int',
                'default': 5,
                'range': [1, 10]
            }
        }

        result = Tunable.from_dict(hyperparameters)

        # assert
        mock_bool.assert_called_once_with(default=False)
        mock_cat.assert_called_once_with(choices=['a', 'b', 'cat'], default='cat')
        mock_float.assert_called_once_with(min=0.1, max=1.0, default=None)
        mock_int.assert_called_once_with(min=1, max=10, default=5)

        expected_tunable_hp = {
            'bhp': mock_bool.return_value,
            'chp': mock_cat.return_value,
            'fhp': mock_float.return_value,
            'ihp': mock_int.return_value
        }

        assert result.hyperparams == expected_tunable_hp
        assert result.dimensions == 4
        assert result.cardinality == 1
Esempio n. 4
0
    def __getitem__(self, key):
        value = super().__getitem__(key)
        if value is not None:
            return value

        path = os.path.join(self._templates_dir, key)
        if not path.endswith('.json'):
            path += '.json'

        template, tunable_hp = load_pipeline(path)
        self[key] = template

        return Tunable(tunable_hp)
Esempio n. 5
0
def _tuning_function(tuner_class, scoring_function, tunable_hyperparameters,
                     iterations):
    tunable = Tunable.from_dict(tunable_hyperparameters)
    tuner = tuner_class(tunable)
    best_score = -np.inf

    for _ in range(iterations):
        proposal = tuner.propose()
        score = scoring_function(**proposal)
        tuner.record(proposal, score)
        best_score = max(score, best_score)

    return best_score
Esempio n. 6
0
File: session.py Progetto: csala/BTB
    def propose(self):
        """Propose a new configuration to score.

        Every time ``propose`` is called, a new tunable will be selected and a new
        hyperparameter proposal will be generated for it.

        At the begining, the default hyperparameters of each one of the tunables
        will be returned sequencially in the same order as they were passed to
        the ``BTBSession``.

        After that, once each tunable has been scored at least once, the tunable
        used to generate the new proposals will be selected optimally each time
        by the selector.

        If a tunable runs out of proposals, it will be discarded from the list and will
        not be proposed again.

        Finally, when all the tunables have ran out of proposals, a ``StopTuning`` exception
        will be raised.

        Returns:
            tuple (str, dict):
                * Name of the tunable to try next.
                * Hyperparameters proposal.

        Raises:
            StopTuning:
                If the ``BTBSession`` has run out of proposals to generate.
        """
        if not self._tunables:
            raise StopTuning('There are no tunables left to try.')

        if len(self._tuners) < len(self._tunable_names):
            tunable_name = self._tunable_names[len(self._tuners)]
            tunable = self._tunables[tunable_name]

            if isinstance(tunable, dict):
                LOGGER.info('Creating Tunable instance from dict.')
                tunable = Tunable.from_dict(tunable)

            if not isinstance(tunable, Tunable):
                raise TypeError(
                    'Tunable can only be an instance of btb.tuning.Tunable or dict'
                )

            LOGGER.info('Obtaining default configuration for %s', tunable_name)
            config = tunable.get_defaults()

            if tunable.cardinality == 1:
                LOGGER.warn(
                    'Skipping tuner creation for Tunable %s with cardinality 1',
                    tunable_name)
                tuner = None
            else:
                tuner = self._tuner_class(tunable)

            self._tuners[tunable_name] = tuner

        else:
            tunable_name = self._get_next_tunable_name()
            tuner = self._tuners[tunable_name]

            try:
                if tuner is None:
                    raise StopTuning(
                        'Tunable %s has no tunable hyperparameters',
                        tunable_name)

                LOGGER.info('Generating new proposal configuration for %s',
                            tunable_name)
                config = tuner.propose(1)

            except StopTuning:
                LOGGER.info('%s has no more configs to propose.', tunable_name)
                self._remove_tunable(tunable_name)
                tunable_name, config = self.propose()

        proposal_id = self._make_id(tunable_name, config)
        self.proposals[proposal_id] = {
            'id': proposal_id,
            'name': tunable_name,
            'config': config
        }

        return tunable_name, config
Esempio n. 7
0
File: btb.py Progetto: pythiac/BTB
 def tuning_function(scoring_function, tunable_hyperparameters, iterations):
     tunable = Tunable.from_dict(tunable_hyperparameters)
     tuner = tuner_class(tunable, **tuner_kwargs)
     return tune(tuner, scoring_function, iterations)
Esempio n. 8
0
class TestTunable(TestCase):
    """Unit test for the class ``Tunable``."""

    @patch('btb.tuning.tunable.list')
    def setUp(self, list_mock):
        """Instantiate the ``Tunable`` and it's ``Hyperparameters`` that we will be using."""
        self.bhp = MagicMock()
        self.chp = MagicMock()
        self.ihp = MagicMock()

        list_mock.return_value = ['bhp', 'chp', 'ihp']

        hyperparams = {
            'bhp': self.bhp,
            'chp': self.chp,
            'ihp': self.ihp,
        }

        self.instance = Tunable(hyperparams)

    def test___init__with_given_names(self):
        """Test that the names are being generated correctly."""
        # assert
        assert self.instance.names == ['bhp', 'chp', 'ihp']

    def test_transform_valid_dict(self):
        """Test transform method with a dictionary that has all the hyperparameters."""
        # setup
        self.bhp.transform.return_value = [[1]]
        self.chp.transform.return_value = [[0]]
        self.ihp.transform.return_value = [[1]]

        values_dict = {
            'bhp': True,
            'chp': 'cat',
            'ihp': 1
        }

        # run
        result = self.instance.transform(values_dict)

        # assert
        self.bhp.transform.assert_called_once_with(True)
        self.chp.transform.assert_called_once_with('cat')
        self.ihp.transform.assert_called_once_with(1)

        np.testing.assert_array_equal(result, np.array([[1, 0, 1]]))

    def test_transform_empty_dict(self):
        """Test transform method with a dictionary that has a missing hyperparameters."""
        # run / assert
        with self.assertRaises(KeyError):
            self.instance.transform({})

    def test_transform_invalid_dict_one_missing(self):
        """Test transform method with a dictionary that has a missing hyperparameters."""
        # run / assert
        values = {
            'bhp': True,
            'chp': 'cat'
        }

        with self.assertRaises(KeyError):
            self.instance.transform(values)

    def test_transform_list_of_dicts(self):
        """Test transform method with a list of dictionaries."""
        # setup
        self.bhp.transform.return_value = [[1], [0]]
        self.chp.transform.return_value = [[0], [1]]
        self.ihp.transform.return_value = [[1], [1]]

        values_list_dict = [
            {'bhp': True, 'chp': 'cat', 'ihp': 2},
            {'bhp': False, 'chp': 'cat', 'ihp': 3}
        ]

        # run
        results = self.instance.transform(values_list_dict)

        # assert
        assert_called_with_np_array(self.bhp.transform.call_args_list, [call([True, False])])
        assert_called_with_np_array(self.chp.transform.call_args_list, [call(['cat', 'cat'])])
        assert_called_with_np_array(self.ihp.transform.call_args_list, [call([2, 3])])

        np.testing.assert_array_equal(results, np.array([[1, 0, 1], [0, 1, 1]]))

    def test_transform_list_of_invalid_dicts(self):
        """Test transform method with a list of dictionaries where one of them does not have
        the categorical value."""

        # setup
        self.bhp.transform.return_value = [[1], [0]]

        # Here we create a CHP so we can raise an value error as there will be a NaN inside the
        # pandas.DataFrame.
        self.chp = CategoricalHyperParam(['cat', 'dog'])
        self.ihp.transform.return_value = [[1], [1]]

        values_list_dict = [
            {'bhp': True, 'ihp': 2},
            {'bhp': False, 'chp': 'cat', 'ihp': 3}
        ]

        # run / assert
        with self.assertRaises(ValueError):
            self.instance.transform(values_list_dict)

    def test_transform_empty_list(self):
        """Test transform method with an empty list."""
        # run / assert
        with self.assertRaises(IndexError):
            self.instance.transform(list())

    def test_transform_valid_pandas_series(self):
        """Test transform method over a valid ``pandas.Series`` object."""
        # setup
        self.bhp.transform.return_value = [[1]]
        self.chp.transform.return_value = [[0]]
        self.ihp.transform.return_value = [[1]]

        values = pd.Series([False, 'cat', 1], index=['bhp', 'chp', 'ihp'])

        # run
        result = self.instance.transform(values)

        # assert
        self.bhp.transform.assert_called_once_with(False)
        self.chp.transform.assert_called_once_with('cat')
        self.ihp.transform.assert_called_once_with(1)

        np.testing.assert_array_equal(result, np.array([[1, 0, 1]]))

    def test_transform_invalid_pandas_series(self):
        """Test transform method over a ``pandas.Series`` object that does not have index."""
        # setup
        values = pd.Series([False, 'cat', 1])

        # run
        with self.assertRaises(KeyError):
            self.instance.transform(values)

    def test_transform_array_like_list(self):
        """Test transform a valid array like list."""
        # setup
        self.bhp.transform.return_value = [[1]]
        self.chp.transform.return_value = [[0]]
        self.ihp.transform.return_value = [[1]]

        values = [[True, 'dog', 2], [False, 'cat', 3]]

        # run
        result = self.instance.transform(values)

        # assert
        assert_called_with_np_array(
            self.bhp.transform.call_args_list,
            [call(np.array([True, False]))]
        )
        assert_called_with_np_array(
            self.chp.transform.call_args_list,
            [call(np.array(['dog', 'cat']))]
        )
        assert_called_with_np_array(
            self.ihp.transform.call_args_list,
            [call(np.array([2, 3]))]
        )

        np.testing.assert_array_equal(result, np.array([[1, 0, 1]]))

    def test_transform_simple_list(self):
        """Test that the method transform performs a transformation over a list with a single
        combination of hyperparameter valid values.
        """
        # setup
        self.bhp.transform.return_value = [[1]]
        self.chp.transform.return_value = [[0]]
        self.ihp.transform.return_value = [[1]]

        values = [True, 'dog', 2]

        # run
        result = self.instance.transform(values)

        # assert
        self.bhp.transform.assert_called_once_with(True)
        self.chp.transform.assert_called_once_with('dog')
        self.ihp.transform.assert_called_once_with(2)

        np.testing.assert_array_equal(result, np.array([[1, 0, 1]]))

    def test_transform_pd_df(self):
        """Test that the method transform performs a transformation over a ``pandas.DataFrame``
        with a single combination of hyperparameter valid values.
        """
        # setup
        self.bhp.transform.return_value = [[1]]
        self.chp.transform.return_value = [[0]]
        self.ihp.transform.return_value = [[1]]

        values = pd.DataFrame([[True, 'dog', 2]], columns=['bhp', 'chp', 'ihp'])

        # run
        result = self.instance.transform(values)

        # assert
        self.bhp.transform.assert_called_once_with(True)
        self.chp.transform.assert_called_once_with('dog')
        self.ihp.transform.assert_called_once_with(2)

        np.testing.assert_array_equal(result, np.array([[1, 0, 1]]))

    def test_transform_simple_invalid_list(self):
        """Test that the method transform does not transform a list with a single combination
        of invalid hyperparameter values.
        """
        # run / assert
        with self.assertRaises(TypeError):
            self.instance.transform([[True], 1, 2])

    def test_inverse_transform_valid_data(self):
        """Test that the inverse transform method is calling the hyperparameters."""
        # setup
        self.bhp.K = 1
        self.chp.K = 1
        self.ihp.K = 1

        self.bhp.inverse_transform.return_value = [[True]]
        self.chp.inverse_transform.return_value = [['cat']]
        self.ihp.inverse_transform.return_value = [[1]]

        values = [[1, 0, 1]]

        # run
        result = self.instance.inverse_transform(values)

        # assert
        expected_result = pd.DataFrame(
            {
                'bhp': [True],
                'chp': ['cat'],
                'ihp': [1]
            },
            dtype=object
        )

        self.bhp.inverse_transform.assert_called_once_with([1])
        self.chp.inverse_transform.assert_called_once_with([0])
        self.ihp.inverse_transform.assert_called_once_with([1])
        pd.testing.assert_frame_equal(result, expected_result)

    def test_inverse_transform_invalid_data(self):
        """Test that the a ``TypeError`` is being raised when calling with the invalid data."""
        # setup
        values = [1, 0, 1]

        # run
        with self.assertRaises(TypeError):
            self.instance.inverse_transform(values)

    def test_sample(self):
        """Test that the method sample generates data from all the ``hyperparams``."""

        # setup
        # Values have been changed to ensure that each one of them is being called.
        self.bhp.sample.return_value = [['a']]
        self.chp.sample.return_value = [['b']]
        self.ihp.sample.return_value = [['c']]

        # run
        result = self.instance.sample(1)

        # assert
        expected_result = np.array([['a', 'b', 'c']])

        assert set(result.flat) == set(expected_result.flat)
        self.bhp.sample.assert_called_once_with(1)
        self.chp.sample.assert_called_once_with(1)
        self.ihp.sample.assert_called_once_with(1)
Esempio n. 9
0
    def propose(self):
        """Propose a new configuration for a tunable.

        ``BTBSession``, ensures that  every tunable has been scored atleast once. The
        following proposals use the ``self.selector`` in order to select the ``tunable``
        from which a proposal is generated.

        If the ``tuner`` can not propose more configurations it will return
        ``None`` and will remove the ``tunable`` from the list.

        Returns:
            tuple (str, dict):
                Returns a tuple with the name of the tunable and the proposal as a dictionary.
            None:
                ``None`` is being returned When the ``tunable`` has no more combinations to be
                evaluated.

        Raises:
            ValueError:
                A ``ValueErorr`` is being raised if ``self.tunables`` is empty.
        """
        if not self.tunables:
            raise ValueError('All the tunables failed.')

        if len(self._normalized_scores) < len(self._tunable_names):
            tunable_name = self._tunable_names[len(self._normalized_scores)]
            tunable = self.tunables[tunable_name]

            if isinstance(tunable, dict):
                LOGGER.info('Creating Tunable instance from dict.')
                tunable = Tunable.from_dict(tunable)

            if not isinstance(tunable, Tunable):
                raise TypeError(
                    'Tunable can only be an instance of btb.tuning.Tunable or dict'
                )

            LOGGER.info('Obtaining default configuration for %s', tunable_name)
            config = tunable.get_defaults()

            self._tuners[tunable_name] = self.tuner(tunable)

        else:
            tunable_name = self.selector.select(self._normalized_scores)
            tuner = self._tuners[tunable_name]
            try:
                LOGGER.info('Generating new proposal configuration for %s',
                            tunable_name)
                config = tuner.propose(1)

            except StopTuning:
                LOGGER.info('%s has no more configs to propose.' %
                            tunable_name)
                self._normalized_scores.pop(tunable_name, None)
                self._tunable_names.remove(tunable_name)
                tunable_name, config = self.propose()

        proposal_id = self._make_id(tunable_name, config)
        self.proposals[proposal_id] = {
            'id': proposal_id,
            'name': tunable_name,
            'config': config
        }

        return tunable_name, config
Esempio n. 10
0
 def test_from_dict_not_a_dict(self):
     # run
     with self.assertRaises(TypeError):
         Tunable.from_dict(1)
Esempio n. 11
0
class TestTunable(TestCase):
    """Unit test for the class ``Tunable``."""

    @patch('btb.tuning.tunable.list')
    def setUp(self, list_mock):
        """Instantiate the ``Tunable`` and it's ``Hyperparameters`` that we will be using."""
        self.bhp = MagicMock(spec_set=BooleanHyperParam)
        self.chp = MagicMock(spec_set=CategoricalHyperParam)
        self.ihp = MagicMock(spec_set=IntHyperParam)

        list_mock.return_value = ['bhp', 'chp', 'ihp']

        hyperparams = {
            'bhp': self.bhp,
            'chp': self.chp,
            'ihp': self.ihp,
        }

        self.instance = Tunable(hyperparams)

    def test___init__with_given_names(self):
        """Test that the names are being generated correctly."""
        # assert
        assert self.instance.names == ['bhp', 'chp', 'ihp']

    def test_transform_valid_dict(self):
        """Test transform method with a dictionary that has all the hyperparameters."""
        # setup
        self.bhp.transform.return_value = [[1]]
        self.chp.transform.return_value = [[0]]
        self.ihp.transform.return_value = [[1]]

        values_dict = {
            'bhp': True,
            'chp': 'cat',
            'ihp': 1
        }

        # run
        result = self.instance.transform(values_dict)

        # assert
        self.bhp.transform.assert_called_once_with(True)
        self.chp.transform.assert_called_once_with('cat')
        self.ihp.transform.assert_called_once_with(1)

        np.testing.assert_array_equal(result, np.array([[1, 0, 1]]))

    def test_transform_empty_dict(self):
        """Test transform method with a dictionary that has a missing hyperparameters."""
        # run / assert
        with self.assertRaises(KeyError):
            self.instance.transform({})

    def test_transform_invalid_dict_one_missing(self):
        """Test transform method with a dictionary that has a missing hyperparameters."""
        # run / assert
        values = {
            'bhp': True,
            'chp': 'cat'
        }

        with self.assertRaises(KeyError):
            self.instance.transform(values)

    def test_transform_list_of_dicts(self):
        """Test transform method with a list of dictionaries."""
        # setup
        self.bhp.transform.return_value = [[1], [0]]
        self.chp.transform.return_value = [[0], [1]]
        self.ihp.transform.return_value = [[1], [1]]

        values_list_dict = [
            {'bhp': True, 'chp': 'cat', 'ihp': 2},
            {'bhp': False, 'chp': 'cat', 'ihp': 3}
        ]

        # run
        results = self.instance.transform(values_list_dict)

        # assert
        assert_called_with_np_array(self.bhp.transform.call_args_list, [call([True, False])])
        assert_called_with_np_array(self.chp.transform.call_args_list, [call(['cat', 'cat'])])
        assert_called_with_np_array(self.ihp.transform.call_args_list, [call([2, 3])])

        np.testing.assert_array_equal(results, np.array([[1, 0, 1], [0, 1, 1]]))

    def test_transform_list_of_invalid_dicts(self):
        """Test transform method with a list of dictionaries where one of them does not have
        the categorical value."""

        # setup
        self.bhp.transform.return_value = [[1], [0]]

        # Here we create a CHP so we can raise an value error as there will be a NaN inside the
        # pandas.DataFrame.
        self.chp = CategoricalHyperParam(['cat', 'dog'])
        self.ihp.transform.return_value = [[1], [1]]

        values_list_dict = [
            {'bhp': True, 'ihp': 2},
            {'bhp': False, 'chp': 'cat', 'ihp': 3}
        ]

        # run / assert
        with self.assertRaises(ValueError):
            self.instance.transform(values_list_dict)

    def test_transform_empty_list(self):
        """Test transform method with an empty list."""
        # run / assert
        with self.assertRaises(IndexError):
            self.instance.transform(list())

    def test_transform_valid_pandas_series(self):
        """Test transform method over a valid ``pandas.Series`` object."""
        # setup
        self.bhp.transform.return_value = [[1]]
        self.chp.transform.return_value = [[0]]
        self.ihp.transform.return_value = [[1]]

        values = pd.Series([False, 'cat', 1], index=['bhp', 'chp', 'ihp'])

        # run
        result = self.instance.transform(values)

        # assert
        self.bhp.transform.assert_called_once_with(False)
        self.chp.transform.assert_called_once_with('cat')
        self.ihp.transform.assert_called_once_with(1)

        np.testing.assert_array_equal(result, np.array([[1, 0, 1]]))

    def test_transform_invalid_pandas_series(self):
        """Test transform method over a ``pandas.Series`` object that does not have index."""
        # setup
        values = pd.Series([False, 'cat', 1])

        # run
        with self.assertRaises(KeyError):
            self.instance.transform(values)

    def test_transform_array_like_list(self):
        """Test transform a valid array like list."""
        # setup
        self.bhp.transform.return_value = [[1]]
        self.chp.transform.return_value = [[0]]
        self.ihp.transform.return_value = [[1]]

        values = [[True, 'dog', 2], [False, 'cat', 3]]

        # run
        result = self.instance.transform(values)

        # assert
        assert_called_with_np_array(
            self.bhp.transform.call_args_list,
            [call(np.array([True, False]))]
        )
        assert_called_with_np_array(
            self.chp.transform.call_args_list,
            [call(np.array(['dog', 'cat']))]
        )
        assert_called_with_np_array(
            self.ihp.transform.call_args_list,
            [call(np.array([2, 3]))]
        )

        np.testing.assert_array_equal(result, np.array([[1, 0, 1]]))

    def test_transform_simple_list(self):
        """Test that the method transform performs a transformation over a list with a single
        combination of hyperparameter valid values.
        """
        # setup
        self.bhp.transform.return_value = [[1]]
        self.chp.transform.return_value = [[0]]
        self.ihp.transform.return_value = [[1]]

        values = [True, 'dog', 2]

        # run
        result = self.instance.transform(values)

        # assert
        self.bhp.transform.assert_called_once_with(True)
        self.chp.transform.assert_called_once_with('dog')
        self.ihp.transform.assert_called_once_with(2)

        np.testing.assert_array_equal(result, np.array([[1, 0, 1]]))

    def test_transform_pd_df(self):
        """Test that the method transform performs a transformation over a ``pandas.DataFrame``
        with a single combination of hyperparameter valid values.
        """
        # setup
        self.bhp.transform.return_value = [[1]]
        self.chp.transform.return_value = [[0]]
        self.ihp.transform.return_value = [[1]]

        values = pd.DataFrame([[True, 'dog', 2]], columns=['bhp', 'chp', 'ihp'])

        # run
        result = self.instance.transform(values)

        # assert
        self.bhp.transform.assert_called_once_with(True)
        self.chp.transform.assert_called_once_with('dog')
        self.ihp.transform.assert_called_once_with(2)

        np.testing.assert_array_equal(result, np.array([[1, 0, 1]]))

    def test_transform_simple_invalid_list(self):
        """Test that the method transform does not transform a list with a single combination
        of invalid hyperparameter values.
        """
        # run / assert
        with self.assertRaises(TypeError):
            self.instance.transform([[True], 1, 2])

    def test_inverse_transform_valid_data(self):
        """Test that the inverse transform method is calling the hyperparameters."""
        # setup
        self.bhp.inverse_transform.return_value = [[True]]
        self.chp.inverse_transform.return_value = [['cat']]
        self.ihp.inverse_transform.return_value = [[1]]

        values = [[1, 0, 1]]

        # run
        result = self.instance.inverse_transform(values)

        # assert
        expected_result = pd.DataFrame(
            {
                'bhp': [True],
                'chp': ['cat'],
                'ihp': [1]
            },
            dtype=object
        )

        self.bhp.inverse_transform.assert_called_once_with([1])
        self.chp.inverse_transform.assert_called_once_with([0])
        self.ihp.inverse_transform.assert_called_once_with([1])
        pd.testing.assert_frame_equal(result, expected_result)

    def test_inverse_transform_invalid_data(self):
        """Test that the a ``TypeError`` is being raised when calling with the invalid data."""
        # setup
        values = [1, 0, 1]

        # run
        with self.assertRaises(TypeError):
            self.instance.inverse_transform(values)

    def test_sample(self):
        """Test that the method sample generates data from all the ``hyperparams``."""

        # setup
        # Values have been changed to ensure that each one of them is being called.
        self.bhp.sample.return_value = [['a']]
        self.chp.sample.return_value = [['b']]
        self.ihp.sample.return_value = [['c']]

        # run
        result = self.instance.sample(1)

        # assert
        expected_result = np.array([['a', 'b', 'c']])

        assert set(result.flat) == set(expected_result.flat)
        self.bhp.sample.assert_called_once_with(1)
        self.chp.sample.assert_called_once_with(1)
        self.ihp.sample.assert_called_once_with(1)

    def test_get_defaults(self):
        # setup
        bhp = MagicMock(default=True)
        chp = MagicMock(default='test')
        ihp = MagicMock(default=1)

        hyperparams = {
            'bhp': bhp,
            'chp': chp,
            'ihp': ihp,
        }

        self.instance = Tunable(hyperparams)

        # run
        result = self.instance.get_defaults()

        # assert
        assert result == {'bhp': True, 'chp': 'test', 'ihp': 1}

    def test_from_dict_not_a_dict(self):
        # run
        with self.assertRaises(TypeError):
            Tunable.from_dict(1)

    @patch('btb.tuning.tunable.IntHyperParam')
    @patch('btb.tuning.tunable.FloatHyperParam')
    @patch('btb.tuning.tunable.CategoricalHyperParam')
    @patch('btb.tuning.tunable.BooleanHyperParam')
    def test_from_dict(self, mock_bool, mock_cat, mock_float, mock_int):
        # setup
        mock_bool.return_value.dimensions = 1
        mock_cat.return_value .dimensions = 1
        mock_float.return_value.dimensions = 1
        mock_int.return_value.dimensions = 1

        mock_bool.return_value.cardinality = 1
        mock_cat.return_value .cardinality = 1
        mock_float.return_value.cardinality = 1
        mock_int.return_value.cardinality = 1

        # run
        hyperparameters = {
            'bhp': {
                'type': 'bool',
                'default': False
            },
            'chp': {
                'type': 'str',
                'default': 'cat',
                'range': ['a', 'b', 'cat']
            },
            'fhp': {
                'type': 'float',
                'default': None,
                'range': [0.1, 1.0]
            },
            'ihp': {
                'type': 'int',
                'default': 5,
                'range': [1, 10]
            }
        }

        result = Tunable.from_dict(hyperparameters)

        # assert
        mock_bool.assert_called_once_with(default=False)
        mock_cat.assert_called_once_with(choices=['a', 'b', 'cat'], default='cat')
        mock_float.assert_called_once_with(min=0.1, max=1.0, default=None)
        mock_int.assert_called_once_with(min=1, max=10, default=5)

        expected_tunable_hp = {
            'bhp': mock_bool.return_value,
            'chp': mock_cat.return_value,
            'fhp': mock_float.return_value,
            'ihp': mock_int.return_value
        }

        assert result.hyperparams == expected_tunable_hp
        assert result.dimensions == 4
        assert result.cardinality == 1