Exemplo n.º 1
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)
Exemplo n.º 2
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