def _compute_model(self, skip_optimization: bool = None):
     args = self._model_args
     if skip_optimization is None:
         skip_optimization = self.skip_optimization(self._state)
     fit_parameters = not skip_optimization
     if fit_parameters and self._candidate_evaluations:
         # Did the labeled data really change since the last recent refit?
         # If not, skip the refitting
         if self._state.candidate_evaluations == self._candidate_evaluations:
             fit_parameters = False
             logger.warning(
                 "Skipping the refitting of GP hyperparameters, since the "
                 "labeled data did not change since the last recent fit")
     self._model = GPMXNetModel(
         state=self._state,
         active_metric=args.active_metric,
         random_seed=args.random_seed,
         gpmodel=self._gpmodel,
         fit_parameters=fit_parameters,
         num_fantasy_samples=args.num_fantasy_samples,
         normalize_targets=args.normalize_targets,
         profiler=self._profiler,
         debug_log=self._debug_log)
     # Note: This may be different than self._gpmodel.get_params(), since
     # the GPMXNetModel may append additional info
     self._model_params = self._model.get_params()
     if fit_parameters:
         # Keep copy of labeled data in order to avoid unnecessary
         # refitting
         self._candidate_evaluations = copy.copy(
             self._state.candidate_evaluations)
Esempio n. 2
0
def test_gp_mcmc_fit(tuning_job_state):
    def tuning_job_state_mcmc(X, Y) -> TuningJobState:
        Y = [dictionarize_objective(y) for y in Y]

        return TuningJobState(
            HyperparameterRanges_Impl(
                HyperparameterRangeContinuous('x', -4., 4., LinearScaling())),
            [CandidateEvaluation(x, y) for x, y in zip(X, Y)], [], [])

    _set_seeds(0)

    def f(x):
        return 0.1 * np.power(x, 3)

    X = np.concatenate((np.random.uniform(-4., -1.,
                                          10), np.random.uniform(1., 4., 10)))
    Y = f(X)
    X_test = np.sort(np.random.uniform(-1., 1., 10))

    X = [(x, ) for x in X]
    X_test = [(x, ) for x in X_test]

    tuning_job_state = tuning_job_state_mcmc(X, Y)
    # checks if fitting is running
    random_seed = 0
    gpmodel = default_gpmodel_mcmc(tuning_job_state,
                                   random_seed,
                                   mcmc_config=DEFAULT_MCMC_CONFIG)
    model = GPMXNetModel(tuning_job_state,
                         DEFAULT_METRIC,
                         random_seed,
                         gpmodel,
                         fit_parameters=True,
                         num_fantasy_samples=20)

    X = [tuning_job_state.hp_ranges.to_ndarray(x) for x in X]
    predictions = model.predict(np.array(X))

    Y_std_list = [stds for means, stds in predictions]
    Y_mean_list = [means for means, stds in predictions]
    Y_mean = np.mean(Y_mean_list, axis=0)
    Y_std = np.mean(Y_std_list, axis=0)

    assert np.all(np.abs(Y_mean - Y) < 1e-1), \
        "in a noiseless setting, mean of GP should coincide closely with outputs at training points"

    X_test = [tuning_job_state.hp_ranges.to_ndarray(x) for x in X_test]

    predictions_test = model.predict(np.array(X_test))
    Y_std_test_list = [stds for means, stds in predictions_test]
    Y_std_test = np.mean(Y_std_test_list, axis=0)
    assert np.max(Y_std) < np.min(Y_std_test), \
        "Standard deviation on un-observed points should be greater than at observed ones"
Esempio n. 3
0
def default_models() -> List[GPMXNetModel]:
    X = [
        (0.0, 0.0),
        (1.0, 0.0),
        (0.0, 1.0),
        (1.0, 1.0),
        (0.0, 0.0
         ),  # same evals are added multiple times to force GP to unlearn prior
        (1.0, 0.0),
        (0.0, 1.0),
        (1.0, 1.0),
        (0.0, 0.0),
        (1.0, 0.0),
        (0.0, 1.0),
        (1.0, 1.0),
    ]
    Y = [dictionarize_objective(np.sum(x) * 10.0) for x in X]

    state = TuningJobState(
        HyperparameterRanges_Impl(
            HyperparameterRangeContinuous('x', 0.0, 1.0, LinearScaling()),
            HyperparameterRangeContinuous('y', 0.0, 1.0, LinearScaling()),
        ),
        [CandidateEvaluation(x, y) for x, y in zip(X, Y)],
        [],
        [],
    )
    random_seed = 0

    gpmodel = default_gpmodel(state,
                              random_seed=random_seed,
                              optimization_config=DEFAULT_OPTIMIZATION_CONFIG)

    gpmodel_mcmc = default_gpmodel_mcmc(state,
                                        random_seed=random_seed,
                                        mcmc_config=DEFAULT_MCMC_CONFIG)

    return [
        GPMXNetModel(state,
                     DEFAULT_METRIC,
                     random_seed,
                     gpmodel,
                     fit_parameters=True,
                     num_fantasy_samples=20),
        GPMXNetModel(state,
                     DEFAULT_METRIC,
                     random_seed,
                     gpmodel_mcmc,
                     fit_parameters=True,
                     num_fantasy_samples=20)
    ]
Esempio n. 4
0
def default_models(do_mcmc=True) -> List[GPMXNetModel]:
    X = [
        (0.0, 0.0),
        (1.0, 0.0),
        (0.0, 1.0),
        (1.0, 1.0),
    ]
    Y = [dictionarize_objective(np.sum(x) * 10.0) for x in X]

    state = TuningJobState(
        HyperparameterRanges_Impl(
            HyperparameterRangeContinuous('x', 0.0, 1.0, LinearScaling()),
            HyperparameterRangeContinuous('y', 0.0, 1.0, LinearScaling()),
        ), [CandidateEvaluation(x, y) for x, y in zip(X, Y)], [], [])
    random_seed = 0

    gpmodel = default_gpmodel(state,
                              random_seed=random_seed,
                              optimization_config=DEFAULT_OPTIMIZATION_CONFIG)
    result = [
        GPMXNetModel(state,
                     DEFAULT_METRIC,
                     random_seed,
                     gpmodel,
                     fit_parameters=True,
                     num_fantasy_samples=20)
    ]
    if do_mcmc:
        gpmodel_mcmc = default_gpmodel_mcmc(state,
                                            random_seed=random_seed,
                                            mcmc_config=DEFAULT_MCMC_CONFIG)
        result.append(
            GPMXNetModel(state,
                         DEFAULT_METRIC,
                         random_seed,
                         gpmodel_mcmc,
                         fit_parameters=True,
                         num_fantasy_samples=20))
    return result
Esempio n. 5
0
def test_gp_fit(tuning_job_state):
    _set_seeds(0)
    X = [
        (0.0, 0.0),
        (1.0, 0.0),
        (0.0, 1.0),
        (1.0, 1.0),
    ]
    Y = [np.sum(x) * 10.0 for x in X]

    # checks if fitting is running
    random_seed = 0
    gpmodel = default_gpmodel(tuning_job_state,
                              random_seed,
                              optimization_config=DEFAULT_OPTIMIZATION_CONFIG)
    model = GPMXNetModel(tuning_job_state,
                         DEFAULT_METRIC,
                         random_seed,
                         gpmodel,
                         fit_parameters=True,
                         num_fantasy_samples=20)

    X = [tuning_job_state.hp_ranges.to_ndarray(x) for x in X]
    Y_mean, Y_std = model.predict(np.array(X))[0]

    assert np.all(np.abs(Y_mean - Y) < 1e-1), \
        "in a noiseless setting, mean of GP should coincide closely with outputs at training points"

    X_test = [
        (0.2, 0.2),
        (0.4, 0.2),
        (0.1, 0.9),
        (0.5, 0.5),
    ]
    X_test = [tuning_job_state.hp_ranges.to_ndarray(x) for x in X_test]

    Y_mean_test, Y_std_test = model.predict(np.array(X_test))[0]
    assert np.min(Y_std) < np.min(Y_std_test), \
        "Standard deviation on un-observed points should be greater than at observed ones"
def fit_predict_ours(data: dict,
                     random_seed: int,
                     optimization_config: OptimizationConfig,
                     test_intermediates: Optional[dict] = None) -> dict:
    # Create surrogate model
    num_dims = len(data['ss_limits'])
    _gpmodel = GaussianProcessRegression(
        kernel=Matern52(num_dims, ARD=True),
        mean=ZeroMeanFunction(),  # Instead of ScalarMeanFunction
        optimization_config=optimization_config,
        random_seed=random_seed,
        test_intermediates=test_intermediates)
    model = GPMXNetModel(data['state'],
                         DEFAULT_METRIC,
                         random_seed,
                         _gpmodel,
                         fit_parameters=True,
                         num_fantasy_samples=20)
    model_params = model.get_params()
    print('Hyperparameters: {}'.format(model_params))
    # Prediction
    means, stddevs = model.predict(data['test_inputs'])[0]
    return {'means': means, 'stddevs': stddevs}
Esempio n. 7
0
def test_gp_fantasizing():
    """
    Compare whether acquisition function evaluations (values, gradients) with
    fantasizing are the same as averaging them by hand.
    """
    random_seed = 4567
    _set_seeds(random_seed)
    num_fantasy_samples = 10
    num_pending = 5

    hp_ranges = HyperparameterRanges_Impl(
        HyperparameterRangeContinuous('x', 0.0, 1.0, LinearScaling()),
        HyperparameterRangeContinuous('y', 0.0, 1.0, LinearScaling()))
    X = [
        (0.0, 0.0),
        (1.0, 0.0),
        (0.0, 1.0),
        (1.0, 1.0),
    ]
    num_data = len(X)
    Y = [
        dictionarize_objective(np.random.randn(1, 1)) for _ in range(num_data)
    ]
    # Draw fantasies. This is done for a number of fixed pending candidates
    # The model parameters are fit in the first iteration, when there are
    # no pending candidates

    # Note: It is important to not normalize targets, because this would be
    # done on the observed targets only, not the fantasized ones, so it
    # would be hard to compare below.
    pending_evaluations = []
    for _ in range(num_pending):
        pending_cand = tuple(np.random.rand(2, ))
        pending_evaluations.append(PendingEvaluation(pending_cand))
    state = TuningJobState(hp_ranges,
                           [CandidateEvaluation(x, y) for x, y in zip(X, Y)],
                           failed_candidates=[],
                           pending_evaluations=pending_evaluations)
    gpmodel = default_gpmodel(state,
                              random_seed,
                              optimization_config=DEFAULT_OPTIMIZATION_CONFIG)
    model = GPMXNetModel(state,
                         DEFAULT_METRIC,
                         random_seed,
                         gpmodel,
                         fit_parameters=True,
                         num_fantasy_samples=num_fantasy_samples,
                         normalize_targets=False)
    fantasy_samples = model.fantasy_samples
    # Evaluate acquisition function and gradients with fantasizing
    num_test = 50
    X_test = np.vstack([
        hp_ranges.to_ndarray(tuple(np.random.rand(2, )))
        for _ in range(num_test)
    ])
    acq_func = EIAcquisitionFunction(model)
    fvals, grads = acq_func.compute_acq_with_gradients(X_test)
    # Do the same computation by averaging by hand
    fvals_cmp = np.empty((num_fantasy_samples, ) + fvals.shape)
    grads_cmp = np.empty((num_fantasy_samples, ) + grads.shape)
    X_full = X + state.pending_candidates
    for it in range(num_fantasy_samples):
        Y_full = Y + [
            dictionarize_objective(eval.fantasies[DEFAULT_METRIC][:, it])
            for eval in fantasy_samples
        ]
        state2 = TuningJobState(
            hp_ranges,
            [CandidateEvaluation(x, y) for x, y in zip(X_full, Y_full)],
            failed_candidates=[],
            pending_evaluations=[])
        # We have to skip parameter optimization here
        model2 = GPMXNetModel(state2,
                              DEFAULT_METRIC,
                              random_seed,
                              gpmodel,
                              fit_parameters=False,
                              num_fantasy_samples=num_fantasy_samples,
                              normalize_targets=False)
        acq_func2 = EIAcquisitionFunction(model2)
        fvals_, grads_ = acq_func2.compute_acq_with_gradients(X_test)
        fvals_cmp[it, :] = fvals_
        grads_cmp[it, :] = grads_
    # Comparison
    fvals2 = np.mean(fvals_cmp, axis=0)
    grads2 = np.mean(grads_cmp, axis=0)
    assert np.allclose(fvals, fvals2)
    assert np.allclose(grads, grads2)
class GPMXNetPendingCandidateStateTransformer(PendingCandidateStateTransformer):
    """
    This class maintains the TuningJobState along an asynchronous GP-based
    HPO experiment, and manages the reaction to changes of this state.
    In particular, it provides a GPMXNetModel on demand, which encapsulates
    the GP posterior.

    Note: The GPMXNetModel can be accessed only once the state has at least
    one labeled case, since otherwise no posterior can be computed.

    skip_optimization is a predicate depending on TuningJobState, determining
    what is done at the next recent GPMXNetModel computation. If False, the
    GP hyperparameters are optimized. Otherwise, the current ones are not
    changed.

    Safeguard against multiple GP hyperparameter optimization while labeled
    data does not change:
    The posterior has to be recomputed every time the state changes, even if
    this only concerns pending evaluations. The expensive part of this is
    refitting the GP hyperparameters, which makes sense only when the labeled
    data in the state changes. We put a safeguard in place to avoid refitting
    when the labeled data is unchanged.

    """
    def __init__(
            self, gpmodel: GPModel, init_state: TuningJobState,
            model_args: GPMXNetModelArgs,
            skip_optimization: SkipOptimizationPredicate = None,
            profiler: GPMXNetSimpleProfiler = None,
            debug_log: Optional[DebugLogPrinter] = None):
        self._gpmodel = gpmodel
        self._state = copy.copy(init_state)
        self._model_args = model_args
        if skip_optimization is None:
            self.skip_optimization = NeverSkipPredicate()
        else:
            self.skip_optimization = skip_optimization
        self._profiler = profiler
        self._debug_log = debug_log
        # GPMXNetModel computed on demand
        self._model: GPMXNetModel = None
        self._candidate_evaluations = None
        # _model_params is returned by get_params. Careful: This is not just
        # self._gpmodel.get_params(), since the current GPMXNetModel may append
        # additional parameters
        self._model_params = gpmodel.get_params()

    @property
    def state(self) -> TuningJobState:
        return self._state

    def model(self, **kwargs) -> GPMXNetModel:
        """
        If skip_optimization is given, it overrides the self.skip_optimization
        predicate.

        :return: GPMXNetModel for current state

        """
        if self._model is None:
            skip_optimization = kwargs.get('skip_optimization')
            self._compute_model(skip_optimization=skip_optimization)
        return self._model

    def get_params(self):
        return self._model_params

    def set_params(self, param_dict):
        self._gpmodel.set_params(param_dict)
        self._model_params = self._gpmodel.get_params()

    def append_candidate(self, candidate: Candidate):
        """
        Appends new pending candidate to the state.

        :param candidate: New pending candidate

        """
        self._model = None  # Invalidate
        self._state.pending_evaluations.append(PendingEvaluation(candidate))

    @staticmethod
    def _find_candidate(candidate: Candidate, lst: List):
        try:
            pos = next(
                i for i, x in enumerate(lst)
                if x.candidate == candidate)
        except StopIteration:
            pos = -1
        return pos

    def drop_candidate(self, candidate: Candidate):
        """
        Drop candidate (labeled or pending) from state.

        :param candidate: Candidate to be dropped

        """
        # Candidate may be labeled or pending. First, try labeled
        pos = self._find_candidate(
            candidate, self._state.candidate_evaluations)
        if pos != -1:
            self._model = None  # Invalidate
            self._state.candidate_evaluations.pop(pos)
            if self._debug_log is not None:
                deb_msg = "[GPMXNetAsyncPendingCandidateStateTransformer.drop_candidate]\n"
                deb_msg += ("- len(candidate_evaluations) afterwards = {}".format(
                    len(self.state.candidate_evaluations)))
                logger.info(deb_msg)
        else:
            # Try pending
            pos = self._find_candidate(
                candidate, self._state.pending_evaluations)
            assert pos != -1, \
                "Candidate {} not registered (neither labeled, nor pending)".format(
                    candidate)
            self._model = None  # Invalidate
            self._state.pending_evaluations.pop(pos)
            if self._debug_log is not None:
                deb_msg = "[GPMXNetAsyncPendingCandidateStateTransformer.drop_candidate]\n"
                deb_msg += ("- len(pending_evaluations) afterwards = {}\n".format(
                    len(self.state.pending_evaluations)))
                logger.info(deb_msg)

    def label_candidate(self, data: CandidateEvaluation):
        """
        Adds a labeled candidate. If it was pending before, it is removed as
        pending candidate.

        :param data: New labeled candidate

        """
        pos = self._find_candidate(
            data.candidate, self._state.pending_evaluations)
        if pos != -1:
            self._state.pending_evaluations.pop(pos)
        self._state.candidate_evaluations.append(data)
        self._model = None  # Invalidate

    def filter_pending_evaluations(
            self, filter_pred: Callable[[PendingEvaluation], bool]):
        """
        Filters state.pending_evaluations with filter_pred.

        :param filter_pred Filtering predicate

        """
        new_pending_evaluations = list(filter(
            filter_pred, self._state.pending_evaluations))
        if len(new_pending_evaluations) != len(self._state.pending_evaluations):
            if self._debug_log is not None:
                deb_msg = "[GPMXNetAsyncPendingCandidateStateTransformer.filter_pending_evaluations]\n"
                deb_msg += ("- from len {} to {}".format(
                    len(self.state.pending_evaluations), len(new_pending_evaluations)))
                logger.info(deb_msg)
            self._model = None  # Invalidate
            del self._state.pending_evaluations[:]
            self._state.pending_evaluations.extend(new_pending_evaluations)

    def mark_candidate_failed(self, candidate: Candidate):
        self._state.failed_candidates.append(candidate)

    def _compute_model(self, skip_optimization: bool = None):
        args = self._model_args
        if skip_optimization is None:
            skip_optimization = self.skip_optimization(self._state)
        fit_parameters = not skip_optimization
        if fit_parameters and self._candidate_evaluations:
            # Did the labeled data really change since the last recent refit?
            # If not, skip the refitting
            if self._state.candidate_evaluations == self._candidate_evaluations:
                fit_parameters = False
                logger.warning(
                    "Skipping the refitting of GP hyperparameters, since the "
                    "labeled data did not change since the last recent fit")
        self._model = GPMXNetModel(
            state=self._state,
            active_metric=args.active_metric,
            random_seed=args.random_seed,
            gpmodel=self._gpmodel,
            fit_parameters=fit_parameters,
            num_fantasy_samples=args.num_fantasy_samples,
            normalize_targets=args.normalize_targets,
            profiler=self._profiler,
            debug_log=self._debug_log)
        # Note: This may be different than self._gpmodel.get_params(), since
        # the GPMXNetModel may append additional info
        self._model_params = self._model.get_params()
        if fit_parameters:
            # Keep copy of labeled data in order to avoid unnecessary
            # refitting
            self._candidate_evaluations = copy.copy(
                self._state.candidate_evaluations)