aws / aws/amazon-sagemaker-feedback

Allow singleton interval for integer or continuous range hyperparameter and duplicate values for categorical range parameter of tuning job.

Open
#195 3 comments 5 reactions 0 assignees View on GitHub
feature-request
Dominant language
No language data
Stars
10
Forks
3
PR merge metrics
No merged PRs in 30d

Description

### Product Version

- [ ] Amazon SageMaker Studio Classic
- [ ] Amazon SageMaker Studio
- [x] It is not related to SageMaker Studio

### Product Category

Jobs

### Description

I would like to be able to pass a singleton range parameter to HyperparameterTuner, e.g. IntegerParameter(1,1), ContinousParameter(1,1) or CategoricalParameter(["cat1"]) or even .CategoricalParameter(["cat1", "cat1"]). Other tuning frameworks, such as ray tune, support such parameter ranges. I would like the following code to work by setting for each training job: `integer-fixed-hyperparam=1`, `float-fixed-hyperparam=1.0`, `categorical-fixed-hyperparam="val1`", and `continous-hyperparam` randomly from range `[0,1]`.

```python
from sagemaker.pytorch import PyTorch
from sagemaker.tuner import (
HyperparameterTuner,
CategoricalParameter,
ContinuousParameter,
IntegerParameter
)
from sagemaker.session import Session

if __name__ == "__main__":
estimator = PyTorch(
sagemaker_session=Session(),
instance_type='ml.m5.large',
instance_count=1,
framework_version="2.3",
py_version="py311",
source_dir='source',
entry_point='main.py',
metric_definitions=[
{'Name': 'valid:loss', 'Regex': 'valid_loss=([0-9]+\\.?[0-9]*)'}
]
)

tuner = HyperparameterTuner(
estimator=estimator,
objective_metric_name='valid:loss',
objective_type='Minimize',
hyperparameter_ranges={
"continous-hyperparam": ContinuousParameter(0.0, 1.0),
"fixed-continous-hparam": ContinuousParameter(1.0, 1.0),
"fixed-integer-hparam": IntegerParameter(1, 1),
"fixed-categorical-hparam": CategoricalParameter(["val1", "val1"])
},
max_jobs=2,
max_parallel_jobs=1,
base_tuning_job_name='test-tuning',
strategy='Random',
metric_definitions=estimator.metric_definitions,
)
print(tuner.fit())
```

Sample source/main.py
```python
import argparse

if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--continous-hyperparam", type=float, required=True)
parser.add_argument("--fixed-continous-hyperparam", type=float, required=True)
parser.add_argument("--fixed-integer-hyperparam", type=int, required=True)
parser.add_argument("--fixed-categorical-hyperparam", type=str, required=True)
args, _ = parser.parse_known_args()
print(f"Hparams: {args}")
print("valid_loss=0.1")
```

Why do I need this, and why can't I simply pass fixed hyperparameters as static hyperparameters to the estimator? I'd like to have an ML pipeline for tuning various hyperparameters, with a user-defined search space via pipeline parameters. In some cases, the user might want to tune a subset of hyperparameters while keeping other hyperparameters constant, while in other cases, they might want to tune a different subset of hyperparameters. The following code illustrates this:

```python
from sagemaker.pytorch import PyTorch
from sagemaker.tuner import (
HyperparameterTuner,
ContinuousParameter,
IntegerParameter
)
from sagemaker.workflow.parameters import (
ParameterString,
ParameterInteger,
ParameterFloat
)
from sagemaker.workflow.steps import TuningStep
from sagemaker.workflow.pipeline import Pipeline
from sagemaker.workflow.pipeline_context import (
PipelineSession
)

if __name__ == "__main__":
pipeline_session = PipelineSession()

estimator = PyTorch(
sagemaker_session=pipeline_session,
instance_type='ml.m5.large',
instance_count=1,
framework_version="2.3",
py_version="py311",
source_dir='source',
entry_point='main.py',
metric_definitions=[
{'Name': 'valid:loss', 'Regex': 'valid_loss=([0-9]+\\.?[0-9]*)'}
]
)

min_integer_hparam_group1 = ParameterInteger("MinIntegerHparamGroup1")
max_integer_hparam_group1 = ParameterInteger("MaxIntegerHparamGroup1")
min_float_hparam_group1 = ParameterFloat("MinFloatHparamGroup1")
max_float_hparam_group1 = ParameterFloat("MaxFloatHparamGroup1")
option1_categorical_hparam_group1 = ParameterString("Option1CategoricalHparamGroup1")
option2_categorical_hparam_group1 = ParameterString("Option2CategoricalHparamGroup1")

min_integer_hparam_group2 = ParameterInteger("MinIntegerHparamGroup2")
max_integer_hparam_group2 = ParameterInteger("MaxIntegerHparamGroup2")
min_float_hparam_group2 = ParameterFloat("MinFloatHparamGroup2")
max_float_hparam_group2 = ParameterFloat("MaxFloatHparamGroup2")
option1_categorical_hparam_group2 = ParameterString("Option1CategoricalHparamGroup2")
option2_categorical_hparam_group2 = ParameterString("Option2CategoricalHparamGroup2")

tuner = HyperparameterTuner(
estimator=estimator,
objective_metric_name='valid:loss',
objective_type='Minimize',
hyperparameter_ranges={
"integer-hparam-group1": IntegerParameter(
min_integer_hparam_group1,
max_integer_hparam_group1
),
"float-hparam-group1": ContinuousParameter(
min_float_hparam_group1,
max_float_hparam_group1
),
# "categorical-hparam-group1": CategoricalParameter([
# option1_categorical_hparam_group1,
# option2_categorical_hparam_group1
# ]), # that can not work due to that issue: https://github.com/aws/sagemaker-python-sdk/issues/5240
"integer-hparam-group2": IntegerParameter(
min_integer_hparam_group2,
max_integer_hparam_group2
),
"float-hparam-group2": ContinuousParameter(
min_float_hparam_group2,
max_float_hparam_group2
),
# "categorical-hparam-group2": CategoricalParameter([
# option1_categorical_hparam_group2,
# option2_categorical_hparam_group2
# ]) # that can not work due to that issue: https://github.com/aws/sagemaker-python-sdk/issues/5240
},
max_jobs=2,
max_parallel_jobs=1,
strategy='Random',
metric_definitions=estimator.metric_definitions,
)
print(tuner.fit())

tuning_step = TuningStep(
name="Tuning",
step_args=tuner.fit(),
)

pipeline = Pipeline(
name="TestTuningPipeline",
parameters=[
min_integer_hparam_group1,
max_integer_hparam_group1,
min_integer_hparam_group2,
max_integer_hparam_group2,
min_float_hparam_group1,
max_float_hparam_group1,
min_float_hparam_group2,
max_float_hparam_group2,
option1_categorical_hparam_group1,
option2_categorical_hparam_group1,
option1_categorical_hparam_group2,
option2_categorical_hparam_group2
],
steps=[tuning_step],
sagemaker_session=pipeline_session
)
pipeline.create()

pipeline.start(
{
"MinIntegerHparamGroup1": 0,
"MaxIntegerHparamGroup1": 1,
"MinFloatHparamGroup1": 0.0,
"MaxFloatHparamGroup1": 1.0,
"Option1CategoricalHparamGroup1": "val1",
"Option2CategoricalHparamGroup1": "val2",
"MinIntegerHparamGroup2": 0,
"MaxIntegerHparamGroup2": 0,
"MinFloatHparamGroup2": 0.0,
"MaxFloatHparamGroup2": 0.0,
"Option1CategoricalHparamGroup2": "val",
"Option2CategoricalHparamGroup2": "val"
}
)

pipeline.start(
{
"MinIntegerHparamGroup1": 0,
"MaxIntegerHparamGroup1": 0,
"MinFloatHparamGroup1": 0.0,
"MaxFloatHparamGroup1": 0.0,
"Option1CategoricalHparamGroup1": "val",
"Option2CategoricalHparamGroup1": "val",
"MinIntegerHparamGroup2": 0,
"MaxIntegerHparamGroup2": 1,
"MinFloatHparamGroup2": 0.0,
"MaxFloatHparamGroup2": 1.0,
"Option1CategoricalHparamGroup2": "val1",
"Option2CategoricalHparamGroup2": "val2",
}
)
```

Sample source/main.py
```python
import argparse

if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--integer-hparam-group1", type=int, required=True)
parser.add_argument("--float-hparam-group1", type=float, required=True)
# parser.add_argument("--categorical-hparam-group1", type=str, required=True)
parser.add_argument("--integer-hparam-group2", type=int, required=True)
parser.add_argument("--float-hparam-group2", type=float, required=True)
# parser.add_argument("--categorical-hparam-group2", type=str, required=True)
args, _ = parser.parse_known_args()
print(f"Hparams: {args}")
print("valid_loss=0.1")
```

I'd like the above code to create a pipeline with a tuning step and run it the first time by tuning the hyperparameters with the `hparam-group1` suffix, leaving the hyperparameters with the `hparam-group2` suffix constant. The second time, it would do the opposite, tuning the hyperparameters with the `hparam-group2` suffix while leaving the hyperparameters with the `hparam-group1` suffix constant.
Unfortunately, both of these pipeline executions will fail almost immediately with an error, failing to run any training jobs.

An alternative solution that would satisfy me: allow to define additional dictionary for hyperparameter tuner that keys-values define which hyperparameters are tunable and each are static. However solution with supporting singleton intervals seems to me more natural, consistent with other tuning framework (as ray tune), and probably simpler to implement.

### Other Details

_No response_

Contributor guide

Open the contributing guide

Research direction

Start with HyperparameterTuner and the IntegerParameter, ContinuousParameter, CategoricalParameter, and TuningStep entry points shown in the examples. Reproduce both pipeline.start calls, then verify that singleton numeric ranges and duplicate categorical values are accepted and that the tuning jobs launch with the intended fixed parameters.

Written by the indexing model from the issue text.

Assessment

Tech stack
aws, python
Domain
cloud, machine-learning
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.