Beispiel #1
0
 def test_get_pending_observation_features_based_on_trial_status(self):
     # Pending observations should be none if there aren't any as trial is
     # candidate.
     self.assertTrue(self.trial.status.is_candidate)
     self.assertIsNone(get_pending_observation_features(self.experiment))
     self.trial.mark_staged()
     # Now that the trial is staged, it should become a pending trial on the
     # experiment and appear as pending for all metrics.
     self.assertEqual(
         get_pending_observation_features(self.experiment),
         {"tracking": [self.obs_feat], "m2": [self.obs_feat], "m1": [self.obs_feat]},
     )
     # Same should be true for running trial.
     # NOTE: Can't mark a staged trial running unless it uses a runner that
     # specifically requires staging; hacking around that here since the marking
     # logic does not matter for this test.
     self.trial._status = TrialStatus.RUNNING
     # Now that the trial is staged, it should become a pending trial on the
     # experiment and appear as pending for all metrics.
     self.assertEqual(
         get_pending_observation_features(self.experiment),
         {"tracking": [self.obs_feat], "m2": [self.obs_feat], "m1": [self.obs_feat]},
     )
     # When a trial is marked failed, it should no longer appear in pending.
     self.trial.mark_failed()
     self.assertIsNone(get_pending_observation_features(self.experiment))
     # And if the trial is abandoned, it should always appear in pending features.
     self.trial._status = TrialStatus.ABANDONED  # Cannot re-mark a failed trial.
     self.assertEqual(
         get_pending_observation_features(
             self.experiment, include_failed_as_pending=True
         ),
         {"tracking": [self.obs_feat], "m2": [self.obs_feat], "m1": [self.obs_feat]},
     )
Beispiel #2
0
 def test_pending_observations_as_array(self):
     # Mark a trial dispatched so that there are pending observations.
     self.trial.mark_running(no_runner_required=True)
     # If outcome names are respected, unlisted metrics should be filtered out.
     self.assertEqual(
         [
             x.tolist() for x in pending_observations_as_array(
                 pending_observations=get_pending_observation_features(
                     self.experiment),
                 outcome_names=["m2", "m1"],
                 param_names=["x", "y", "z", "w"],
             )
         ],
         [[["1", "foo", "True", "4"]], [["1", "foo", "True", "4"]]],
     )
     self.experiment.attach_data(
         Data.from_evaluations({self.trial.arm.name: {
             "m2": (1, 0)
         }},
                               trial_index=self.trial.index))
     # There should be no pending observations for metric m2 now, since the
     # only trial there is, has been updated with data for it.
     self.assertEqual(
         [
             x.tolist() for x in pending_observations_as_array(
                 pending_observations=get_pending_observation_features(
                     self.experiment),
                 outcome_names=["m2", "m1"],
                 param_names=["x", "y", "z", "w"],
             )
         ],
         [[], [["1", "foo", "True", "4"]]],
     )
Beispiel #3
0
    def test_get_pending_observation_features(self):
        # Pending observations should be none if there aren't any.
        self.assertIsNone(get_pending_observation_features(self.experiment))

        self.trial.mark_dispatched()
        # Now that the trial is deployed, it should become a pending trial on the
        # experiment and appear as pending for all metrics.
        self.assertEqual(
            get_pending_observation_features(self.experiment),
            {"tracking": [self.obs_feat], "m2": [self.obs_feat], "m1": [self.obs_feat]},
        )
        self.experiment.attach_data(
            Data.from_evaluations(
                {self.trial.arm.name: {"m2": (1, 0)}}, trial_index=self.trial.index
            )
        )
        # Not m2 should have empty pending features, since the trial was updated
        # for m2.
        self.assertEqual(
            get_pending_observation_features(self.experiment),
            {"tracking": [self.obs_feat], "m2": [], "m1": [self.obs_feat]},
        )
        # When a trial is marked failed, it should no longer appear in pending...
        self.trial.mark_failed()
        self.assertIsNone(get_pending_observation_features(self.experiment))
        # ... unless specified to include failed trials in pending observations.
        self.assertEqual(
            get_pending_observation_features(
                self.experiment, include_failed_as_pending=True
            ),
            {"tracking": [self.obs_feat], "m2": [self.obs_feat], "m1": [self.obs_feat]},
        )
 def run_trial(self) -> None:
     """Run a single step of the optimization plan."""
     if self.current_trial >= self.total_trials:
         raise ValueError(
             f"Optimization is complete, cannot run another trial.")
     logger.info(f"Running optimization trial {self.current_trial + 1}...")
     arms_per_trial = self.arms_per_trial
     dat = (self.experiment._fetch_trial_data(self.current_trial - 1)
            if self.current_trial > 0 else None)
     if arms_per_trial == 1:
         trial = self.experiment.new_trial(
             generator_run=self.generation_strategy.gen(
                 experiment=self.experiment,
                 new_data=dat,
                 pending_observations=get_pending_observation_features(
                     experiment=self.experiment),
             ))
     elif arms_per_trial > 1:
         trial = self.experiment.new_batch_trial(
             generator_run=self.generation_strategy.gen(
                 experiment=self.experiment, new_data=dat,
                 n=arms_per_trial))
     else:  # pragma: no cover
         raise ValueError(
             f"Invalid number of arms per trial: {arms_per_trial}")
     trial.fetch_data()
     self.current_trial += 1
Beispiel #5
0
 def test_get_pending_observation_features_batch_trial(self):
     # Check the same functionality for batched trials.
     self.assertIsNone(get_pending_observation_features(self.experiment_2))
     self.batch_trial.mark_running(no_runner_required=True)
     sq_obs_feat = ObservationFeatures.from_arm(
         self.batch_trial.arms_by_name.get("status_quo"),
         trial_index=self.batch_trial.index,
     )
     self.assertEqual(
         get_pending_observation_features(self.experiment_2),
         {
             "tracking": [self.obs_feat, sq_obs_feat],
             "m2": [self.obs_feat, sq_obs_feat],
             "m1": [self.obs_feat, sq_obs_feat],
         },
     )
Beispiel #6
0
 def test_get_pending_observation_features(self):
     # Pending observations should be none if there aren't any.
     self.assertIsNone(get_pending_observation_features(self.experiment))
     self.trial.mark_running(no_runner_required=True)
     # Now that the trial is deployed, it should become a pending trial on the
     # experiment and appear as pending for all metrics.
     self.assertEqual(
         get_pending_observation_features(self.experiment),
         {
             "tracking": [self.obs_feat],
             "m2": [self.obs_feat],
             "m1": [self.obs_feat]
         },
     )
     # With `fetch_data` on trial returning data for metric "m2", that metric
     # should no longer have pending observation features.
     with patch.object(
             self.trial,
             "fetch_data",
             return_value=Data.from_evaluations(
                 {self.trial.arm.name: {
                     "m2": (1, 0)
                 }},
                 trial_index=self.trial.index),
     ):
         self.assertEqual(
             get_pending_observation_features(self.experiment),
             {
                 "tracking": [self.obs_feat],
                 "m2": [],
                 "m1": [self.obs_feat]
             },
         )
     # When a trial is marked failed, it should no longer appear in pending...
     self.trial.mark_failed()
     self.assertIsNone(get_pending_observation_features(self.experiment))
     # ... unless specified to include failed trials in pending observations.
     self.assertEqual(
         get_pending_observation_features(self.experiment,
                                          include_failed_as_pending=True),
         {
             "tracking": [self.obs_feat],
             "m2": [self.obs_feat],
             "m1": [self.obs_feat]
         },
     )
Beispiel #7
0
 def _get_new_trial(self) -> BaseTrial:
     if self.arms_per_trial == 1:
         return self.experiment.new_trial(
             generator_run=self.generation_strategy.gen(
                 experiment=self.experiment,
                 pending_observations=get_pending_observation_features(
                     experiment=self.experiment),
             ))
     elif self.arms_per_trial > 1:
         return self.experiment.new_batch_trial(
             generator_run=self.generation_strategy.gen(
                 experiment=self.experiment, n=self.arms_per_trial))
     else:
         raise UserInputError(
             f"Invalid number of arms per trial: {self.arms_per_trial}")
Beispiel #8
0
    def _suggest_new_trial(self) -> Trial:
        """
        Suggest new candidate for this experiment.

        Args:
            n: Number of candidates to generate.

        Returns:
            Trial with candidate.
        """
        new_data = self._get_new_data()
        generator_run = not_none(self.generation_strategy).gen(
            experiment=self.experiment,
            new_data=new_data,
            pending_observations=get_pending_observation_features(
                experiment=self.experiment),
        )
        return self.experiment.new_trial(generator_run=generator_run)
Beispiel #9
0
    def _gen_new_generator_run(self, n: int = 1) -> GeneratorRun:
        """Generate new generator run for this experiment.

        Args:
            n: Number of arms to generate.
        """
        # If random seed is not set for this optimization, context manager does
        # nothing; otherwise, it sets the random seed for torch, but only for the
        # scope of this call. This is important because torch seed is set globally,
        # so if we just set the seed without the context manager, it can have
        # serious negative impact on the performance of the models that employ
        # stochasticity.
        with manual_seed(seed=self._random_seed) and warnings.catch_warnings():
            # Filter out GPYTorch warnings to avoid confusing users.
            warnings.simplefilter("ignore")
            return not_none(self.generation_strategy).gen(
                experiment=self.experiment,
                n=n,
                pending_observations=get_pending_observation_features(
                    experiment=self.experiment),
            )
Beispiel #10
0
 def test_get_pending_observation_features(self):
     # Pending observations should be none if there aren't any.
     self.assertIsNone(get_pending_observation_features(self.experiment))
     self.trial.mark_running(no_runner_required=True)
     # Now that the trial is deployed, it should become a pending trial on the
     # experiment and appear as pending for all metrics.
     self.assertEqual(
         get_pending_observation_features(self.experiment),
         {
             "tracking": [self.obs_feat],
             "m2": [self.obs_feat],
             "m1": [self.obs_feat]
         },
     )
     # With `fetch_data` on trial returning data for metric "m2", that metric
     # should no longer have pending observation features.
     with patch.object(
             self.trial,
             "lookup_data",
             return_value=Data.from_evaluations(
                 {self.trial.arm.name: {
                     "m2": (1, 0)
                 }},
                 trial_index=self.trial.index),
     ):
         self.assertEqual(
             get_pending_observation_features(self.experiment),
             {
                 "tracking": [self.obs_feat],
                 "m2": [],
                 "m1": [self.obs_feat]
             },
         )
     # When a trial is marked failed, it should no longer appear in pending...
     self.trial.mark_failed()
     self.assertIsNone(get_pending_observation_features(self.experiment))
     # ... unless specified to include failed trials in pending observations.
     self.assertEqual(
         get_pending_observation_features(self.experiment,
                                          include_failed_as_pending=True),
         {
             "tracking": [self.obs_feat],
             "m2": [self.obs_feat],
             "m1": [self.obs_feat]
         },
     )
     # When a trial is abandoned, it should appear in pending features whether
     # or not there is data for it.
     self.trial._status = TrialStatus.ABANDONED  # Cannot re-mark a failed trial.
     self.assertEqual(
         get_pending_observation_features(self.experiment,
                                          include_failed_as_pending=True),
         {
             "tracking": [self.obs_feat],
             "m2": [self.obs_feat],
             "m1": [self.obs_feat]
         },
     )
     # When an arm is abandoned, it should appear in pending features whether
     # or not there is data for it.
     self.batch_trial.mark_arm_abandoned(arm_name="0_0")
     # Checking with data for all metrics.
     with patch.object(
             self.batch_trial,
             "fetch_data",
             return_value=Data.from_evaluations(
                 {
                     self.batch_trial.arms[0].name: {
                         "m1": (1, 0),
                         "m2": (1, 0),
                         "tracking": (1, 0),
                     }
                 },
                 trial_index=self.trial.index,
             ),
     ):
         self.assertEqual(
             get_pending_observation_features(
                 self.experiment, include_failed_as_pending=True),
             {
                 "tracking": [self.obs_feat],
                 "m2": [self.obs_feat],
                 "m1": [self.obs_feat],
             },
         )
     # Checking with data for all metrics.
     with patch.object(
             self.trial,
             "fetch_data",
             return_value=Data.from_evaluations(
                 {
                     self.trial.arm.name: {
                         "m1": (1, 0),
                         "m2": (1, 0),
                         "tracking": (1, 0)
                     }
                 },
                 trial_index=self.trial.index,
             ),
     ):
         self.assertEqual(
             get_pending_observation_features(self.experiment),
             {
                 "tracking": [self.obs_feat],
                 "m2": [self.obs_feat],
                 "m1": [self.obs_feat],
             },
         )
Beispiel #11
0
    def test_get_pending_observation_features_hss(self):
        # Pending observations should be none if there aren't any.
        self.assertIsNone(get_pending_observation_features(self.hss_exp))
        self.hss_trial.mark_running(no_runner_required=True)
        # Now that the trial is deployed, it should become a pending trial on the
        # experiment and appear as pending for all metrics.
        pending = get_pending_observation_features(self.hss_exp)
        self.assertEqual(
            pending,
            {
                "m1": [self.hss_obs_feat],
                "m2": [self.hss_obs_feat],
            },
        )

        # Check that transforming observation features works correctly since this
        # is applying `Cast` transform, it should inject full parameterization into
        # resulting obs.feats.). Therefore, transforming the extracted pending features
        #  and observation features made from full parameterization should be the same.
        self.assertEqual(
            self.hss_sobol._transform_data(
                obs_feats=pending["m1"],
                obs_data=[],
                search_space=self.hss_exp.search_space,
                transforms=self.hss_sobol._raw_transforms,
                transform_configs=None,
            ),
            self.hss_sobol._transform_data(
                obs_feats=[self.hss_obs_feat_all_params.clone()],
                obs_data=[],
                search_space=self.hss_exp.search_space,
                transforms=self.hss_sobol._raw_transforms,
                transform_configs=None,
            ),
        )
        # With `fetch_data` on trial returning data for metric "m2", that metric
        # should no longer have pending observation features.
        with patch.object(
                self.hss_trial,
                "lookup_data",
                return_value=Data.from_evaluations(
                    {self.hss_trial.arm.name: {
                        "m2": (1, 0)
                    }},
                    trial_index=self.hss_trial.index,
                ),
        ):
            self.assertEqual(
                get_pending_observation_features(self.hss_exp),
                {
                    "m2": [],
                    "m1": [self.hss_obs_feat]
                },
            )
        # When a trial is marked failed, it should no longer appear in pending...
        self.hss_trial.mark_failed()
        self.assertIsNone(get_pending_observation_features(self.hss_exp))
        # ... unless specified to include failed trials in pending observations.
        self.assertEqual(
            get_pending_observation_features(self.hss_exp,
                                             include_failed_as_pending=True),
            {
                "m1": [self.hss_obs_feat],
                "m2": [self.hss_obs_feat],
            },
        )

        # When an arm is abandoned, it should appear in pending features whether
        # or not there is data for it.
        hss_exp = get_hierarchical_search_space_experiment()
        hss_batch_trial = hss_exp.new_batch_trial(generator_run=self.hss_gr)
        hss_batch_trial.mark_arm_abandoned(hss_batch_trial.arms[0].name)
        # Checking with data for all metrics.
        with patch.object(
                hss_batch_trial,
                "fetch_data",
                return_value=Data.from_evaluations(
                    {
                        hss_batch_trial.arms[0].name: {
                            "m1": (1, 0),
                            "m2": (1, 0),
                        }
                    },
                    trial_index=hss_batch_trial.index,
                ),
        ):
            pending = get_pending_observation_features(
                hss_exp, include_failed_as_pending=True)
            self.assertEqual(
                pending,
                {
                    "m1": [self.hss_obs_feat],
                    "m2": [self.hss_obs_feat],
                },
            )
            # Check that candidate metadata is property propagated for abandoned arm.
            self.assertEqual(
                self.hss_sobol._transform_data(
                    obs_feats=pending["m1"],
                    obs_data=[],
                    search_space=hss_exp.search_space,
                    transforms=self.hss_sobol._raw_transforms,
                    transform_configs=None,
                ),
                self.hss_sobol._transform_data(
                    obs_feats=[self.hss_obs_feat_all_params.clone()],
                    obs_data=[],
                    search_space=hss_exp.search_space,
                    transforms=self.hss_sobol._raw_transforms,
                    transform_configs=None,
                ),
            )
        # Checking with data for all metrics.
        with patch.object(
                hss_batch_trial,
                "fetch_data",
                return_value=Data.from_evaluations(
                    {
                        hss_batch_trial.arms[0].name: {
                            "m1": (1, 0),
                            "m2": (1, 0),
                        }
                    },
                    trial_index=hss_batch_trial.index,
                ),
        ):
            self.assertEqual(
                get_pending_observation_features(hss_exp),
                {
                    "m2": [self.hss_obs_feat],
                    "m1": [self.hss_obs_feat],
                },
            )