diff --git a/pkg/apis/controller/experiments/v1beta1/experiment_types.go b/pkg/apis/controller/experiments/v1beta1/experiment_types.go index e8c8f55114b..edb87ec408a 100644 --- a/pkg/apis/controller/experiments/v1beta1/experiment_types.go +++ b/pkg/apis/controller/experiments/v1beta1/experiment_types.go @@ -199,11 +199,19 @@ const ( ParameterTypeCategorical ParameterType = "categorical" ) +type ParameterDistribution string + +const ( + Uniform ParameterDistribution = "uniform" + LogUniform ParameterDistribution = "log-uniform" +) + type FeasibleSpace struct { Max string `json:"max,omitempty"` Min string `json:"min,omitempty"` List []string `json:"list,omitempty"` Step string `json:"step,omitempty"` + Distribution ParameterDistribution `json:"distribution,omitempty"` } // TrialTemplate describes structure of trial template diff --git a/pkg/suggestion/v1beta1/goptuna/converter.go b/pkg/suggestion/v1beta1/goptuna/converter.go index 0a925851b81..85f23ea6c85 100644 --- a/pkg/suggestion/v1beta1/goptuna/converter.go +++ b/pkg/suggestion/v1beta1/goptuna/converter.go @@ -122,9 +122,16 @@ func toGoptunaSearchSpace(parameters []*api_v1_beta1.ParameterSpec) (map[string] stepstr := p.GetFeasibleSpace().GetStep() if stepstr == "" { - searchSpace[p.Name] = goptuna.UniformDistribution{ - High: high, - Low: low, + if p.GetFeasibleSpace().GetDistribution() == "uniform"{ + searchSpace[p.Name] = goptuna.UniformDistribution{ + High: high, + Low: low, + } + }else if p.GetFeasibleSpace().GetDistribution() == "log-uniform"{ + searchSpace[p.Name] = goptuna.LogUniformDistribution{ + High: high, + Low: low, + } } } else { step, err := strconv.ParseFloat(stepstr, 64) diff --git a/pkg/suggestion/v1beta1/hyperopt/base_service.py b/pkg/suggestion/v1beta1/hyperopt/base_service.py index 0c88d7dd4c6..734c38d3a00 100644 --- a/pkg/suggestion/v1beta1/hyperopt/base_service.py +++ b/pkg/suggestion/v1beta1/hyperopt/base_service.py @@ -62,10 +62,16 @@ def create_hyperopt_domain(self): float(param.max), float(param.step)) elif param.type == DOUBLE: - hyperopt_search_space[param.name] = hyperopt.hp.uniform( - param.name, - float(param.min), - float(param.max)) + if param.distribution == "uniform": + hyperopt_search_space[param.name] = hyperopt.hp.uniform( + param.name, + float(param.min), + float(param.max)) + elif param.distribution == "log-uniform": + hyperopt_search_space[param.name] = hyperopt.hp.loguniform( + param.name, + float(param.min), + float(param.max)) elif param.type == CATEGORICAL or param.type == DISCRETE: hyperopt_search_space[param.name] = hyperopt.hp.choice( param.name, param.list) diff --git a/pkg/suggestion/v1beta1/internal/search_space.py b/pkg/suggestion/v1beta1/internal/search_space.py index 590b22c3ce3..a72b4c459b8 100644 --- a/pkg/suggestion/v1beta1/internal/search_space.py +++ b/pkg/suggestion/v1beta1/internal/search_space.py @@ -52,7 +52,8 @@ def convertParameter(p): step = p.feasible_space.step return HyperParameter.int(p.name, p.feasible_space.min, p.feasible_space.max, step) elif p.parameter_type == api.DOUBLE: - return HyperParameter.double(p.name, p.feasible_space.min, p.feasible_space.max, p.feasible_space.step) + return HyperParameter.double(p.name, p.feasible_space.min, p.feasible_space.max, p.feasible_space.step, + p.feasible_space.distribution) elif p.parameter_type == api.CATEGORICAL: return HyperParameter.categorical(p.name, p.feasible_space.list) elif p.parameter_type == api.DISCRETE: @@ -63,34 +64,38 @@ def convertParameter(p): class HyperParameter(object): - def __init__(self, name, type_, min_, max_, list_, step): + def __init__(self, name, type_, min_, max_, list_, step, distribution): self.name = name self.type = type_ self.min = min_ self.max = max_ self.list = list_ self.step = step + self.distribution = distribution def __str__(self): - if self.type == INTEGER or self.type == DOUBLE: + if self.type == INTEGER: return "HyperParameter(name: {}, type: {}, min: {}, max: {}, step: {})".format( self.name, self.type, self.min, self.max, self.step) + elif self.type == DOUBLE: + return "HyperParameter(name: {}, type: {}, min: {}, max: {}, step: {}, distribution: {})".format( + self.name, self.type, self.min, self.max, self.step, self.distribution) else: return "HyperParameter(name: {}, type: {}, list: {})".format( self.name, self.type, ", ".join(self.list)) @staticmethod def int(name, min_, max_, step): - return HyperParameter(name, INTEGER, min_, max_, [], step) + return HyperParameter(name, INTEGER, min_, max_, [], step, None) @staticmethod - def double(name, min_, max_, step): - return HyperParameter(name, DOUBLE, min_, max_, [], step) + def double(name, min_, max_, step, distribution): + return HyperParameter(name, DOUBLE, min_, max_, [], step, distribution) @staticmethod def categorical(name, lst): - return HyperParameter(name, CATEGORICAL, 0, 0, [str(e) for e in lst], 0) + return HyperParameter(name, CATEGORICAL, 0, 0, [str(e) for e in lst], 0, None) @staticmethod def discrete(name, lst): - return HyperParameter(name, DISCRETE, 0, 0, [str(e) for e in lst], 0) + return HyperParameter(name, DISCRETE, 0, 0, [str(e) for e in lst], 0, None) diff --git a/pkg/suggestion/v1beta1/optuna/service.py b/pkg/suggestion/v1beta1/optuna/service.py index c7bbff613b2..7f6e17dd1c6 100644 --- a/pkg/suggestion/v1beta1/optuna/service.py +++ b/pkg/suggestion/v1beta1/optuna/service.py @@ -151,7 +151,12 @@ def _get_optuna_search_space(self): if param.type == INTEGER: search_space[param.name] = optuna.distributions.IntUniformDistribution(int(param.min), int(param.max)) elif param.type == DOUBLE: - search_space[param.name] = optuna.distributions.UniformDistribution(float(param.min), float(param.max)) + if param.distribution == "uniform": + search_space[param.name] = optuna.distributions.UniformDistribution(float(param.min), + float(param.max)) + elif param.distribution == "log-uniform": + search_space[param.name] = optuna.distributions.LogUniformDistribution(float(param.min), + float(param.max)) elif param.type == CATEGORICAL or param.type == DISCRETE: search_space[param.name] = optuna.distributions.CategoricalDistribution(param.list) return search_space diff --git a/pkg/suggestion/v1beta1/skopt/base_service.py b/pkg/suggestion/v1beta1/skopt/base_service.py index e0203ee57e7..4f63b76acc1 100644 --- a/pkg/suggestion/v1beta1/skopt/base_service.py +++ b/pkg/suggestion/v1beta1/skopt/base_service.py @@ -55,7 +55,7 @@ def create_optimizer(self): int(param.min), int(param.max), name=param.name)) elif param.type == DOUBLE: skopt_search_space.append(skopt.space.Real( - float(param.min), float(param.max), "log-uniform", name=param.name)) + float(param.min), float(param.max), prior=param.distribution, name=param.name)) elif param.type == CATEGORICAL or param.type == DISCRETE: skopt_search_space.append( skopt.space.Categorical(param.list, name=param.name)) diff --git a/pkg/webhook/v1beta1/experiment/validator/validator.go b/pkg/webhook/v1beta1/experiment/validator/validator.go index 09a80d6f5d3..d30a9a041c4 100644 --- a/pkg/webhook/v1beta1/experiment/validator/validator.go +++ b/pkg/webhook/v1beta1/experiment/validator/validator.go @@ -207,6 +207,11 @@ func (g *DefaultValidator) validateParameters(parameters []experimentsv1beta1.Pa if param.FeasibleSpace.Max == "" && param.FeasibleSpace.Min == "" { return fmt.Errorf("feasibleSpace.max or feasibleSpace.min must be specified for parameterType: %v in spec.parameters[%v]: %v", param.ParameterType, i, param) } + if param.ParameterType == experimentsv1beta1.ParameterTypeDouble && param.FeasibleSpace.Distribution == ""{ + return fmt.Errorf("feasibleSpace.distribution must be specified for parameterType: double in spec.parameters[%v]: %v", i, param) + }else if param.ParameterType == experimentsv1beta1.ParameterTypeDouble && param.FeasibleSpace.Distribution != "uniform" && param.FeasibleSpace.Distribution != "log-uniform"{ + return fmt.Errorf("feasibleSpace.distribution must be one of 'uniform', 'log-uniform' for parameterType: double in spec.parameters[%v]: %v", i, param) + } } else if param.ParameterType == experimentsv1beta1.ParameterTypeCategorical || param.ParameterType == experimentsv1beta1.ParameterTypeDiscrete { if param.FeasibleSpace.Max != "" || param.FeasibleSpace.Min != "" || param.FeasibleSpace.Step != "" {