Skip to content

Commit c32dec0

Browse files
uditbhatialaurenyu
authored andcommitted
Add support for automatic model tuner's warm start jobs (#489)
1 parent 501eced commit c32dec0

File tree

7 files changed

+751
-42
lines changed

7 files changed

+751
-42
lines changed

CHANGELOG.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ CHANGELOG
1010
* feature: Add APIs to export Airflow training and tuning config
1111
* doc-fix: Fix typos in tensorflow serving documentation
1212
* doc-fix: Add estimator base classes to API docs
13+
* feature: HyperparameterTuner: add support for Automatic Model Tuning's Warm Start Jobs
1314

1415
1.14.2
1516
======

src/sagemaker/session.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,8 @@ def train(self, image, input_mode, input_config, role, job_name, output_config,
279279
def tune(self, job_name, strategy, objective_type, objective_metric_name,
280280
max_jobs, max_parallel_jobs, parameter_ranges,
281281
static_hyperparameters, image, input_mode, metric_definitions,
282-
role, input_config, output_config, resource_config, stop_condition, tags):
282+
role, input_config, output_config, resource_config, stop_condition, tags,
283+
warm_start_config):
283284
"""Create an Amazon SageMaker hyperparameter tuning job
284285
285286
Args:
@@ -323,6 +324,8 @@ def tune(self, job_name, strategy, objective_type, objective_metric_name,
323324
stop_condition (dict): When training should finish, e.g. ``MaxRuntimeInSeconds``.
324325
tags (list[dict]): List of tags for labeling the tuning job. For more, see
325326
https://docs.aws.amazon.com/sagemaker/latest/dg/API_Tag.html.
327+
warm_start_config (dict): Configuration defining the type of warm start and
328+
other required configurations.
326329
"""
327330
tune_request = {
328331
'HyperParameterTuningJobName': job_name,
@@ -352,6 +355,9 @@ def tune(self, job_name, strategy, objective_type, objective_metric_name,
352355
}
353356
}
354357

358+
if warm_start_config:
359+
tune_request['WarmStartConfig'] = warm_start_config
360+
355361
if metric_definitions is not None:
356362
tune_request['TrainingJobDefinition']['AlgorithmSpecification']['MetricDefinitions'] = metric_definitions
357363

src/sagemaker/tuner.py

Lines changed: 245 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import importlib
1616
import inspect
1717
import json
18+
from enum import Enum
1819

1920
from sagemaker.amazon.amazon_estimator import AmazonAlgorithmEstimatorBase, RecordSet
2021
from sagemaker.amazon.hyperparameter import Hyperparameter as hp # noqa
@@ -35,6 +36,9 @@
3536
'knn': 'KNN',
3637
'object2vec': 'Object2Vec',
3738
}
39+
HYPERPARAMETER_TUNING_JOB_NAME = 'HyperParameterTuningJobName'
40+
PARENT_HYPERPARAMETER_TUNING_JOBS = 'ParentHyperParameterTuningJobs'
41+
WARM_START_TYPE = 'WarmStartType'
3842

3943

4044
class _ParameterRange(object):
@@ -134,6 +138,113 @@ class IntegerParameter(_ParameterRange):
134138
__name__ = 'Integer'
135139

136140

141+
class WarmStartTypes(Enum):
142+
"""Warm Start Configuration type. There can be two types of warm start jobs:
143+
* IdenticalDataAndAlgorithm: Type of warm start that allows users to reuse training results from existing
144+
tuning jobs that have the same algorithm code and datasets.
145+
* TransferLearning: Type of warm start that allows users to reuse training results from existing tuning jobs
146+
that have similar algorithm code and datasets.
147+
"""
148+
IDENTICAL_DATA_AND_ALGORITHM = "IdenticalDataAndAlgorithm"
149+
TRANSFER_LEARNING = "TransferLearning"
150+
151+
152+
class WarmStartConfig(object):
153+
"""Warm Start Configuration which defines the nature of the warm start ``HyperparameterTuner``, with type and
154+
parents for warm start.
155+
156+
Examples:
157+
>>> warm_start_config = WarmStartConfig(type=WarmStartTypes.TransferLearning, parents={"p1","p2"})
158+
>>> warm_start_config.type
159+
"TransferLearning"
160+
>>> warm_start_config.parents
161+
{"p1","p2"}
162+
"""
163+
164+
def __init__(self, warm_start_type, parents):
165+
"""Initializes the ``WarmStartConfig`` with the provided ``WarmStartTypes`` and parents.
166+
167+
Args:
168+
warm_start_type (sagemaker.tuner.WarmStartTypes): This should be one of the supported warm start types
169+
in WarmStartType
170+
parents (set{str}): Set of parent tuning jobs which will be used to warm start the new tuning job.
171+
"""
172+
173+
if warm_start_type not in WarmStartTypes:
174+
raise ValueError(
175+
"Invalid type: {}, valid warm start types are: [{}]".format(warm_start_type,
176+
[t for t in WarmStartTypes]))
177+
178+
if not parents:
179+
raise ValueError("Invalid parents: {}, parents should not be None/empty".format(parents))
180+
181+
self.type = warm_start_type
182+
self.parents = set(parents)
183+
184+
@classmethod
185+
def from_job_desc(cls, warm_start_config):
186+
"""Creates an instance of ``WarmStartConfig`` class, from warm start configuration response from
187+
DescribeTrainingJob.
188+
189+
Args:
190+
warm_start_config (dict): The expected format of the ``warm_start_config`` contains two first-class
191+
fields:
192+
* "type": Type of warm start tuner, currently two supported types - "IdenticalDataAndAlgorithm" and
193+
"TransferLearning".
194+
* "parents": List of tuning job names from which the warm start should be done.
195+
196+
Returns:
197+
sagemaker.tuner.WarmStartConfig: De-serialized instance of WarmStartConfig containing the type and parents
198+
provided as part of ``warm_start_config``.
199+
200+
Examples:
201+
>>> warm_start_config = WarmStartConfig.from_job_desc(warm_start_config={
202+
>>> "WarmStartType":"TransferLearning",
203+
>>> "ParentHyperParameterTuningJobs": [
204+
>>> {'HyperParameterTuningJobName': "p1"},
205+
>>> {'HyperParameterTuningJobName': "p2"},
206+
>>> ]
207+
>>>})
208+
>>> warm_start_config.type
209+
"TransferLearning"
210+
>>> warm_start_config.parents
211+
["p1","p2"]
212+
"""
213+
if not warm_start_config or \
214+
WARM_START_TYPE not in warm_start_config or \
215+
PARENT_HYPERPARAMETER_TUNING_JOBS not in warm_start_config:
216+
return None
217+
218+
parents = []
219+
for parent in warm_start_config[PARENT_HYPERPARAMETER_TUNING_JOBS]:
220+
parents.append(parent[HYPERPARAMETER_TUNING_JOB_NAME])
221+
222+
return cls(warm_start_type=WarmStartTypes(warm_start_config[WARM_START_TYPE]),
223+
parents=parents)
224+
225+
def to_input_req(self):
226+
"""Converts the ``self`` instance to the desired input request format.
227+
228+
Returns:
229+
dict: Containing the "WarmStartType" and "ParentHyperParameterTuningJobs" as the first class fields.
230+
231+
Examples:
232+
>>> warm_start_config = WarmStartConfig(warm_start_type=WarmStartTypes.TransferLearning,parents=["p1,p2"])
233+
>>> warm_start_config.to_input_req()
234+
{
235+
"WarmStartType":"TransferLearning",
236+
"ParentHyperParameterTuningJobs": [
237+
{'HyperParameterTuningJobName': "p1"},
238+
{'HyperParameterTuningJobName': "p2"},
239+
]
240+
}
241+
"""
242+
return {
243+
WARM_START_TYPE: self.type.value,
244+
PARENT_HYPERPARAMETER_TUNING_JOBS: [{HYPERPARAMETER_TUNING_JOB_NAME: parent} for parent in self.parents]
245+
}
246+
247+
137248
class HyperparameterTuner(object):
138249
"""A class for creating and interacting with Amazon SageMaker hyperparameter tuning jobs, as well as
139250
deploying the resulting model(s).
@@ -148,7 +259,7 @@ class HyperparameterTuner(object):
148259

149260
def __init__(self, estimator, objective_metric_name, hyperparameter_ranges, metric_definitions=None,
150261
strategy='Bayesian', objective_type='Maximize', max_jobs=1, max_parallel_jobs=1,
151-
tags=None, base_tuning_job_name=None):
262+
tags=None, base_tuning_job_name=None, warm_start_config=None):
152263
"""Initialize a ``HyperparameterTuner``. It takes an estimator to obtain configuration information
153264
for training jobs that are created as the result of a hyperparameter tuning job.
154265
@@ -175,6 +286,8 @@ def __init__(self, estimator, objective_metric_name, hyperparameter_ranges, metr
175286
base_tuning_job_name (str): Prefix for the hyperparameter tuning job name when the
176287
:meth:`~sagemaker.tuner.HyperparameterTuner.fit` method launches. If not specified,
177288
a default job name is generaged, based on the training image name and current timestamp.
289+
warm_start_config (sagemaker.tuner.WarmStartConfig): A ``WarmStartConfig`` object that has been initialized
290+
with the configuration defining the nature of warm start tuning job.
178291
"""
179292
self._hyperparameter_ranges = hyperparameter_ranges
180293
if self._hyperparameter_ranges is None or len(self._hyperparameter_ranges) == 0:
@@ -194,6 +307,7 @@ def __init__(self, estimator, objective_metric_name, hyperparameter_ranges, metr
194307
self.base_tuning_job_name = base_tuning_job_name
195308
self._current_job_name = None
196309
self.latest_tuning_job = None
310+
self.warm_start_config = warm_start_config
197311

198312
def _prepare_for_training(self, job_name=None, include_cls_metadata=True):
199313
if job_name is not None:
@@ -419,6 +533,7 @@ def _prepare_init_params_from_job_description(cls, job_details):
419533
'strategy': tuning_config['Strategy'],
420534
'max_jobs': tuning_config['ResourceLimits']['MaxNumberOfTrainingJobs'],
421535
'max_parallel_jobs': tuning_config['ResourceLimits']['MaxParallelTrainingJobs'],
536+
'warm_start_config': WarmStartConfig.from_job_desc(job_details.get('WarmStartConfig', None))
422537
}
423538

424539
@classmethod
@@ -489,6 +604,82 @@ def _validate_parameter_ranges(self):
489604
except KeyError:
490605
pass
491606

607+
def transfer_learning_tuner(self, additional_parents=None, estimator=None):
608+
"""Creates a new ``HyperparameterTuner`` by copying the request fields from the provided parent to the new
609+
instance of ``HyperparameterTuner``. Followed by addition of warm start configuration with the type as
610+
"TransferLearning" and parents as the union of provided list of ``additional_parents`` and the ``self``.
611+
Also, training image in the new tuner's estimator is updated with the provided ``training_image``.
612+
613+
Args:
614+
additional_parents (set{str}): Set of additional parents along with the self to be used in warm starting
615+
the transfer learning tuner.
616+
estimator (sagemaker.estimator.EstimatorBase): An estimator object that has been initialized with
617+
the desired configuration. There does not need to be a training job associated with this instance.
618+
619+
Returns:
620+
sagemaker.tuner.HyperparameterTuner: ``HyperparameterTuner`` instance which can be used to launch transfer
621+
learning tuning job.
622+
623+
Examples:
624+
>>> parent_tuner = HyperparameterTuner.attach(tuning_job_name="parent-job-1")
625+
>>> transfer_learning_tuner = parent_tuner.transfer_learning_tuner(additional_parents={"parent-job-2"})
626+
Later On:
627+
>>> transfer_learning_tuner.fit(inputs={})
628+
"""
629+
630+
return self._create_warm_start_tuner(additional_parents=additional_parents,
631+
warm_start_type=WarmStartTypes.TRANSFER_LEARNING,
632+
estimator=estimator)
633+
634+
def identical_dataset_and_algorithm_tuner(self, additional_parents=None):
635+
"""Creates a new ``HyperparameterTuner`` by copying the request fields from the provided parent to the new
636+
instance of ``HyperparameterTuner``. Followed by addition of warm start configuration with the type as
637+
"IdenticalDataAndAlgorithm" and parents as the union of provided list of ``additional_parents`` and the ``self``
638+
639+
Args:
640+
additional_parents (set{str}): Set of additional parents along with the self to be used in warm starting
641+
the identical dataset and algorithm tuner.
642+
643+
Returns:
644+
sagemaker.tuner.HyperparameterTuner: HyperparameterTuner instance which can be used to launch identical
645+
dataset and algorithm tuning job.
646+
647+
Examples:
648+
>>> parent_tuner = HyperparameterTuner.attach(tuning_job_name="parent-job-1")
649+
>>> identical_dataset_algo_tuner = parent_tuner.identical_dataset_and_algorithm_tuner(
650+
>>> additional_parents={"parent-job-2"})
651+
Later On:
652+
>>> identical_dataset_algo_tuner.fit(inputs={})
653+
"""
654+
655+
return self._create_warm_start_tuner(additional_parents=additional_parents,
656+
warm_start_type=WarmStartTypes.IDENTICAL_DATA_AND_ALGORITHM)
657+
658+
def _create_warm_start_tuner(self, additional_parents, warm_start_type, estimator=None):
659+
"""Creates a new ``HyperparameterTuner`` with ``WarmStartConfig``, where type will be equal to
660+
``warm_start_type`` and``parents`` would be equal to union of ``additional_parents`` and self.
661+
662+
Args:
663+
additional_parents (set{str}): Additional parents along with self, to be used for warm starting.
664+
warm_start_type (sagemaker.tuner.WarmStartTypes): Type of warm start job.
665+
666+
Returns:
667+
sagemaker.tuner.HyperparameterTuner: Instance with the request fields copied from self along with the
668+
warm start configuration
669+
"""
670+
all_parents = {self.latest_tuning_job.name}
671+
if additional_parents:
672+
all_parents = all_parents.union(additional_parents)
673+
674+
return HyperparameterTuner(estimator=estimator if estimator else self.estimator,
675+
objective_metric_name=self.objective_metric_name,
676+
hyperparameter_ranges=self._hyperparameter_ranges,
677+
objective_type=self.objective_type,
678+
max_jobs=self.max_jobs,
679+
max_parallel_jobs=self.max_parallel_jobs,
680+
warm_start_config=WarmStartConfig(warm_start_type=warm_start_type,
681+
parents=all_parents))
682+
492683

493684
class _TuningJob(_Job):
494685
@classmethod
@@ -504,6 +695,10 @@ def start_new(cls, tuner, inputs):
504695
"""
505696
config = _Job._load_config(inputs, tuner.estimator)
506697

698+
warm_start_config_req = None
699+
if tuner.warm_start_config:
700+
warm_start_config_req = tuner.warm_start_config.to_input_req()
701+
507702
tuner.estimator.sagemaker_session.tune(job_name=tuner._current_job_name, strategy=tuner.strategy,
508703
objective_type=tuner.objective_type,
509704
objective_metric_name=tuner.objective_metric_name,
@@ -516,7 +711,8 @@ def start_new(cls, tuner, inputs):
516711
role=(config['role']), input_config=(config['input_config']),
517712
output_config=(config['output_config']),
518713
resource_config=(config['resource_config']),
519-
stop_condition=(config['stop_condition']), tags=tuner.tags)
714+
stop_condition=(config['stop_condition']), tags=tuner.tags,
715+
warm_start_config=warm_start_config_req)
520716

521717
return cls(tuner.sagemaker_session, tuner._current_job_name)
522718

@@ -525,3 +721,50 @@ def stop(self):
525721

526722
def wait(self):
527723
self.sagemaker_session.wait_for_tuning_job(self.name)
724+
725+
726+
def create_identical_dataset_and_algorithm_tuner(parent, additional_parents=None, sagemaker_session=None):
727+
"""Creates a new tuner by copying the request fields from the provided parent to the new instance of
728+
``HyperparameterTuner`` followed by addition of warm start configuration with the type as
729+
"IdenticalDataAndAlgorithm" and ``parents`` as the union of provided list of ``additional_parents`` and the
730+
``parent``.
731+
732+
Args:
733+
parent (str): Primary parent tuning job's name from which the Tuner and Estimator configuration has to be copied
734+
additional_parents (set{str}): Set of additional parent tuning job's names along with the primary parent tuning
735+
job name to be used in warm starting the transfer learning tuner.
736+
sagemaker_session (sagemaker.session.Session): Session object which manages interactions with
737+
Amazon SageMaker APIs and any other AWS services needed. If not specified, one is created
738+
using the default AWS configuration chain.
739+
740+
Returns:
741+
sagemaker.tuner.HyperparameterTuner: a new ``HyperparameterTuner`` object for the warm-started
742+
hyperparameter tuning job
743+
"""
744+
745+
parent_tuner = HyperparameterTuner.attach(tuning_job_name=parent, sagemaker_session=sagemaker_session)
746+
return parent_tuner.identical_dataset_and_algorithm_tuner(additional_parents=additional_parents)
747+
748+
749+
def create_transfer_learning_tuner(parent, additional_parents=None, estimator=None, sagemaker_session=None):
750+
"""Creates a new ``HyperParameterTuner`` by copying the request fields from the provided parent to the new instance
751+
of ``HyperparameterTuner`` followed by addition of warm start configuration with the type as "TransferLearning"
752+
and ``parents`` as the union of provided list of ``additional_parents`` and the ``parent``.
753+
754+
Args:
755+
parent (str): Primary parent tuning job's name from which the Tuner and Estimator configuration has to be copied
756+
additional_parents (set{str}): Set of additional parent tuning job's names along with the primary parent tuning
757+
job name to be used in warm starting the identical dataset and algorithm tuner.
758+
estimator (sagemaker.estimator.EstimatorBase): An estimator object that has been initialized with
759+
the desired configuration. There does not need to be a training job associated with this instance.
760+
sagemaker_session (sagemaker.session.Session): Session object which manages interactions with
761+
Amazon SageMaker APIs and any other AWS services needed. If not specified, one is created
762+
using the default AWS configuration chain.
763+
764+
Returns:
765+
sagemaker.tuner.HyperparameterTuner: New instance of warm started HyperparameterTuner
766+
"""
767+
768+
parent_tuner = HyperparameterTuner.attach(tuning_job_name=parent, sagemaker_session=sagemaker_session)
769+
return parent_tuner.transfer_learning_tuner(additional_parents=additional_parents,
770+
estimator=estimator)

tests/data/local_mode_lock

Whitespace-only changes.

0 commit comments

Comments
 (0)