diff --git a/src/Microsoft.ML.AutoML/Tuner/PipelineProposer.cs b/src/Microsoft.ML.AutoML/Tuner/PipelineProposer.cs index 2d63b680ea..c3e2aab751 100644 --- a/src/Microsoft.ML.AutoML/Tuner/PipelineProposer.cs +++ b/src/Microsoft.ML.AutoML/Tuner/PipelineProposer.cs @@ -101,6 +101,13 @@ public string ProposeSearchSpace() { var probabilities = _pipelineSchemas.Select(id => _eci[id]).ToArray(); probabilities = ArrayMath.Inverse(probabilities); + // _eci (estimator improve cost) might be infinity, which means the estimator cost for finding an improvement is positive infinity + // in that case, we used to set the probability to be a very small number, in some cases it can be zero ( 1 / double.infinity), so that it will be very unlikely to be picked. + // however, there's a special situation where all the estimators have infinity eci + // which could happen when all the estimators have been tried, all retrieves perfect loss and no improvement can be made. + // in which case, all probablities will be zero and in that case, we will never be able to pick any of them. + // Therefore, we need to make sure non of the probabilities is zero, and we can do that by adding a very small number (double.epsilon) to each of them after inverse. + probabilities = probabilities.Select(p => p + double.Epsilon).ToArray(); probabilities = ArrayMath.Normalize(probabilities); // sample diff --git a/test/Microsoft.ML.AutoML.Tests/TunerTests.cs b/test/Microsoft.ML.AutoML.Tests/TunerTests.cs index be10b6856e..24ad39ab83 100644 --- a/test/Microsoft.ML.AutoML.Tests/TunerTests.cs +++ b/test/Microsoft.ML.AutoML.Tests/TunerTests.cs @@ -140,7 +140,8 @@ public void EciCfo_should_handle_trial_result_with_nan_value() SearchSpace = searchSpace, Seed = 1, }); - var invalidLosses = new[] { double.NaN, double.NegativeInfinity, double.PositiveInfinity }; + var invalidLosses = Enumerable.Repeat(new[] { double.NaN, double.NegativeInfinity, double.PositiveInfinity }, 100) + .SelectMany(loss => loss); var id = 0; foreach (var loss in invalidLosses) { @@ -155,7 +156,42 @@ public void EciCfo_should_handle_trial_result_with_nan_value() { TrialSettings = trialSetting, DurationInMilliseconds = 10000, - Loss = double.NaN, + Loss = loss, + }; + tuner.Update(trialResult); + } + } + + [Fact] + public void EciCfo_should_handle_trial_result_with_no_improvements_over_losses() + { + // this test verify if tuner can find max value for LSE. + var context = new MLContext(1); + var pipeline = this.CreateDummySweepablePipeline(context); + var searchSpace = new SearchSpace.SearchSpace(); + searchSpace["_pipeline_"] = pipeline.SearchSpace; + var tuner = new EciCostFrugalTuner(pipeline, new AutoMLExperiment.AutoMLExperimentSettings + { + SearchSpace = searchSpace, + Seed = 1, + }); + var zeroLosses = Enumerable.Repeat(0.0, 100); + var randomLosses = Enumerable.Range(0, 100).Select(i => i * 0.1); + var id = 0; + foreach (var loss in zeroLosses.Concat(randomLosses)) + { + var trialSetting = new TrialSettings + { + TrialId = id++, + Parameter = Parameter.CreateNestedParameter(), + }; + var parameter = tuner.Propose(trialSetting); + trialSetting.Parameter = parameter; + var trialResult = new TrialResult + { + TrialSettings = trialSetting, + DurationInMilliseconds = 10000, + Loss = loss, }; tuner.Update(trialResult); }