Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,6 +5,7 @@ CHANGELOG
1.4.3dev
========
* feature: Allow Local Serving of Models in S3
* enhancement: Allow option for ``HyperparameterTuner`` to not include estimator metadata in job


1.4.2
Expand Down
8 changes: 8 additions & 0 deletions README.rst
Original file line numberDiff line numberDiff line change
Expand Up@@ -321,6 +321,14 @@ In addition, the ``fit()`` call uses a list of ``RecordSet`` objects instead of
# Start hyperparameter tuning job
my_tuner.fit([train_records, test_records])

To aid with attaching a previously-started hyperparameter tuning job with a ``HyperparameterTuner`` instance, ``fit()`` injects metadata in the hyperparameters by default.
If the algorithm you are using cannot handle unknown hyperparameters (e.g. an Amazon ML algorithm that does not have a custom estimator in the Python SDK), then you can set ``include_cls_metadata`` to ``False`` when calling fit:

.. code:: python

my_tuner.fit({'train': 's3://my_bucket/my_training_data', 'test': 's3://my_bucket_my_testing_data'},
include_cls_metadata=False)

There is also an analytics object associated with each ``HyperparameterTuner`` instance that presents useful information about the hyperparameter tuning job.
For example, the ``dataframe`` method gets a pandas dataframe summarizing the associated training jobs:

Expand Down
8 changes: 4 additions & 4 deletions src/sagemaker/tuner.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -204,7 +204,7 @@ def __init__(self, estimator, objective_metric_name, hyperparameter_ranges, metr
self._current_job_name = None
self.latest_tuning_job = None

def _prepare_for_training(self, job_name=None):
def _prepare_for_training(self, job_name=None, include_cls_metadata=True):
if job_name is not None:
self._current_job_name = job_name
else:
Expand All@@ -217,12 +217,12 @@ def _prepare_for_training(self, job_name=None):

# For attach() to know what estimator to use for non-1P algorithms
# (1P algorithms don't accept extra hyperparameters)
if not isinstance(self.estimator, AmazonAlgorithmEstimatorBase):
if include_cls_metadata and not isinstance(self.estimator, AmazonAlgorithmEstimatorBase):
self.static_hyperparameters[self.SAGEMAKER_ESTIMATOR_CLASS_NAME] = json.dumps(
self.estimator.__class__.__name__)
self.static_hyperparameters[self.SAGEMAKER_ESTIMATOR_MODULE] = json.dumps(self.estimator.__module__)

def fit(self, inputs, job_name=None, **kwargs):
def fit(self, inputs, job_name=None, include_cls_metadata=True, **kwargs):
"""Start a hyperparameter tuning job.

Args:
Expand DownExpand Up@@ -253,7 +253,7 @@ def fit(self, inputs, job_name=None, **kwargs):
else:
self.estimator._prepare_for_training(job_name)

self._prepare_for_training(job_name=job_name)
self._prepare_for_training(job_name=job_name, include_cls_metadata=include_cls_metadata)
self.latest_tuning_job = _TuningJob.start_new(self, inputs)

@classmethod
Expand Down
91 changes: 88 additions & 3 deletions tests/integ/test_tuner.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,19 +13,24 @@
from __future__ import absolute_import

import gzip
import io
import json
import os
import pickle
import sys
import time

import boto3
import numpy as np
import pytest

from sagemaker import LDA, RandomCutForest
from sagemaker.amazon.common import read_records
from sagemaker.amazon.kmeans import KMeans
from sagemaker import KMeans, LDA, RandomCutForest
from sagemaker.amazon.amazon_estimator import registry
from sagemaker.amazon.common import read_records, write_numpy_to_dense_tensor
from sagemaker.chainer import Chainer
from sagemaker.estimator import Estimator
from sagemaker.mxnet.estimator import MXNet
from sagemaker.predictor import json_deserializer
from sagemaker.tensorflow import TensorFlow
from sagemaker.tuner import IntegerParameter, ContinuousParameter, CategoricalParameter, HyperparameterTuner
from tests.integ import DATA_DIR
Expand DownExpand Up@@ -307,3 +312,83 @@ def test_tuning_chainer(sagemaker_session):
data = np.zeros((batch_size, 28, 28), dtype='float32')
output = predictor.predict(data)
assert len(output) == batch_size


@pytest.mark.continuous_testing
def test_tuning_byo_estimator(sagemaker_session):
"""Use Factorization Machines algorithm as an example here.

First we need to prepare data for training. We take standard data set, convert it to the
format that the algorithm can process and upload it to S3.
Then we create the Estimator and set hyperparamets as required by the algorithm.
Next, we can call fit() with path to the S3.
Later the trained model is deployed and prediction is called against the endpoint.
Default predictor is updated with json serializer and deserializer.
"""
image_name = registry(sagemaker_session.boto_session.region_name) + '/factorization-machines:1'

with timeout(minutes=15):
data_path = os.path.join(DATA_DIR, 'one_p_mnist', 'mnist.pkl.gz')
pickle_args = {} if sys.version_info.major == 2 else {'encoding': 'latin1'}

with gzip.open(data_path, 'rb') as f:
train_set, _, _ = pickle.load(f, **pickle_args)

# take 100 examples for faster execution
vectors = np.array([t.tolist() for t in train_set[0][:100]]).astype('float32')
labels = np.where(np.array([t.tolist() for t in train_set[1][:100]]) == 0, 1.0, 0.0).astype('float32')

buf = io.BytesIO()
write_numpy_to_dense_tensor(buf, vectors, labels)
buf.seek(0)

bucket = sagemaker_session.default_bucket()
prefix = 'test_byo_estimator'
key = 'recordio-pb-data'
boto3.resource('s3').Bucket(bucket).Object(os.path.join(prefix, 'train', key)).upload_fileobj(buf)
s3_train_data = 's3://{}/{}/train/{}'.format(bucket, prefix, key)

estimator = Estimator(image_name=image_name,
role='SageMakerRole', train_instance_count=1,
train_instance_type='ml.c4.xlarge',
sagemaker_session=sagemaker_session, base_job_name='test-byo')

estimator.set_hyperparameters(num_factors=10,
feature_dim=784,
mini_batch_size=100,
predictor_type='binary_classifier')

hyperparameter_ranges = {'mini_batch_size': IntegerParameter(100, 200)}

tuner = HyperparameterTuner(estimator=estimator, base_tuning_job_name='byo',
objective_metric_name='test:binary_classification_accuracy',
hyperparameter_ranges=hyperparameter_ranges,
max_jobs=2, max_parallel_jobs=2)

tuner.fit({'train': s3_train_data, 'test': s3_train_data}, include_cls_metadata=False)

print('Started hyperparameter tuning job with name:' + tuner.latest_tuning_job.name)

time.sleep(15)
tuner.wait()

best_training_job = tuner.best_training_job()
with timeout_and_delete_endpoint_by_name(best_training_job, sagemaker_session):
predictor = tuner.deploy(1, 'ml.m4.xlarge', endpoint_name=best_training_job)
predictor.serializer = _fm_serializer
predictor.content_type = 'application/json'
predictor.deserializer = json_deserializer

result = predictor.predict(train_set[0][:10])

assert len(result['predictions']) == 10
for prediction in result['predictions']:
assert prediction['score'] is not None


# Serializer for the Factorization Machines predictor (for BYO example)
def _fm_serializer(data):
js = {'instances': []}
for row in data:
js['instances'].append({'features': row.tolist()})
return json.dumps(js)
15 changes: 15 additions & 0 deletions tests/unit/test_tuner.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -159,6 +159,21 @@ def test_prepare_for_training(tuner):
assert tuner.static_hyperparameters['sagemaker_estimator_module'] == module


def test_prepare_for_training_with_amazon_estimator(tuner, sagemaker_session):
tuner.estimator = PCA(ROLE, TRAIN_INSTANCE_COUNT, TRAIN_INSTANCE_TYPE, NUM_COMPONENTS,
sagemaker_session=sagemaker_session)

tuner._prepare_for_training()
assert 'sagemaker_estimator_class_name' not in tuner.static_hyperparameters
assert 'sagemaker_estimator_module' not in tuner.static_hyperparameters


def test_prepare_for_training_dont_include_estimator_cls(tuner):
tuner._prepare_for_training(include_cls_metadata=False)
assert 'sagemaker_estimator_class_name' not in tuner.static_hyperparameters
assert 'sagemaker_estimator_module' not in tuner.static_hyperparameters


def test_prepare_for_training_with_job_name(tuner):
static_hyperparameters = {'validated': 1, 'another_one': 0}
tuner.estimator.set_hyperparameters(**static_hyperparameters)
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,6 +5,7 @@ CHANGELOG
1.4.3dev
========
* feature: Allow Local Serving of Models in S3
* enhancement: Allow option for ``HyperparameterTuner`` to not include estimator metadata in job


1.4.2
Expand Down
8 changes: 8 additions & 0 deletions README.rst
Original file line numberDiff line numberDiff line change
Expand Up@@ -321,6 +321,14 @@ In addition, the ``fit()`` call uses a list of ``RecordSet`` objects instead of
# Start hyperparameter tuning job
my_tuner.fit([train_records, test_records])

To aid with attaching a previously-started hyperparameter tuning job with a ``HyperparameterTuner`` instance, ``fit()`` injects metadata in the hyperparameters by default.
If the algorithm you are using cannot handle unknown hyperparameters (e.g. an Amazon ML algorithm that does not have a custom estimator in the Python SDK), then you can set ``include_cls_metadata`` to ``False`` when calling fit:

.. code:: python

my_tuner.fit({'train': 's3://my_bucket/my_training_data', 'test': 's3://my_bucket_my_testing_data'},
include_cls_metadata=False)

There is also an analytics object associated with each ``HyperparameterTuner`` instance that presents useful information about the hyperparameter tuning job.
For example, the ``dataframe`` method gets a pandas dataframe summarizing the associated training jobs:

Expand Down
8 changes: 4 additions & 4 deletions src/sagemaker/tuner.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -204,7 +204,7 @@ def __init__(self, estimator, objective_metric_name, hyperparameter_ranges, metr
self._current_job_name = None
self.latest_tuning_job = None

def _prepare_for_training(self, job_name=None):
def _prepare_for_training(self, job_name=None, include_cls_metadata=True):
if job_name is not None:
self._current_job_name = job_name
else:
Expand All@@ -217,12 +217,12 @@ def _prepare_for_training(self, job_name=None):

# For attach() to know what estimator to use for non-1P algorithms
# (1P algorithms don't accept extra hyperparameters)
if not isinstance(self.estimator, AmazonAlgorithmEstimatorBase):
if include_cls_metadata and not isinstance(self.estimator, AmazonAlgorithmEstimatorBase):
self.static_hyperparameters[self.SAGEMAKER_ESTIMATOR_CLASS_NAME] = json.dumps(
self.estimator.__class__.__name__)
self.static_hyperparameters[self.SAGEMAKER_ESTIMATOR_MODULE] = json.dumps(self.estimator.__module__)

def fit(self, inputs, job_name=None, **kwargs):
def fit(self, inputs, job_name=None, include_cls_metadata=True, **kwargs):
"""Start a hyperparameter tuning job.

Args:
Expand DownExpand Up@@ -253,7 +253,7 @@ def fit(self, inputs, job_name=None, **kwargs):
else:
self.estimator._prepare_for_training(job_name)

self._prepare_for_training(job_name=job_name)
self._prepare_for_training(job_name=job_name, include_cls_metadata=include_cls_metadata)
self.latest_tuning_job = _TuningJob.start_new(self, inputs)

@classmethod
Expand Down
91 changes: 88 additions & 3 deletions tests/integ/test_tuner.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,19 +13,24 @@
from __future__ import absolute_import

import gzip
import io
import json
import os
import pickle
import sys
import time

import boto3
import numpy as np
import pytest

from sagemaker import LDA, RandomCutForest
from sagemaker.amazon.common import read_records
from sagemaker.amazon.kmeans import KMeans
from sagemaker import KMeans, LDA, RandomCutForest
from sagemaker.amazon.amazon_estimator import registry
from sagemaker.amazon.common import read_records, write_numpy_to_dense_tensor
from sagemaker.chainer import Chainer
from sagemaker.estimator import Estimator
from sagemaker.mxnet.estimator import MXNet
from sagemaker.predictor import json_deserializer
from sagemaker.tensorflow import TensorFlow
from sagemaker.tuner import IntegerParameter, ContinuousParameter, CategoricalParameter, HyperparameterTuner
from tests.integ import DATA_DIR
Expand DownExpand Up@@ -307,3 +312,83 @@ def test_tuning_chainer(sagemaker_session):
data = np.zeros((batch_size, 28, 28), dtype='float32')
output = predictor.predict(data)
assert len(output) == batch_size


@pytest.mark.continuous_testing
def test_tuning_byo_estimator(sagemaker_session):
"""Use Factorization Machines algorithm as an example here.

First we need to prepare data for training. We take standard data set, convert it to the
format that the algorithm can process and upload it to S3.
Then we create the Estimator and set hyperparamets as required by the algorithm.
Next, we can call fit() with path to the S3.
Later the trained model is deployed and prediction is called against the endpoint.
Default predictor is updated with json serializer and deserializer.
"""
image_name = registry(sagemaker_session.boto_session.region_name) + '/factorization-machines:1'

with timeout(minutes=15):
data_path = os.path.join(DATA_DIR, 'one_p_mnist', 'mnist.pkl.gz')
pickle_args = {} if sys.version_info.major == 2 else {'encoding': 'latin1'}

with gzip.open(data_path, 'rb') as f:
train_set, _, _ = pickle.load(f, **pickle_args)

# take 100 examples for faster execution
vectors = np.array([t.tolist() for t in train_set[0][:100]]).astype('float32')
labels = np.where(np.array([t.tolist() for t in train_set[1][:100]]) == 0, 1.0, 0.0).astype('float32')

buf = io.BytesIO()
write_numpy_to_dense_tensor(buf, vectors, labels)
buf.seek(0)

bucket = sagemaker_session.default_bucket()
prefix = 'test_byo_estimator'
key = 'recordio-pb-data'
boto3.resource('s3').Bucket(bucket).Object(os.path.join(prefix, 'train', key)).upload_fileobj(buf)
s3_train_data = 's3://{}/{}/train/{}'.format(bucket, prefix, key)

estimator = Estimator(image_name=image_name,
role='SageMakerRole', train_instance_count=1,
train_instance_type='ml.c4.xlarge',
sagemaker_session=sagemaker_session, base_job_name='test-byo')

estimator.set_hyperparameters(num_factors=10,
feature_dim=784,
mini_batch_size=100,
predictor_type='binary_classifier')

hyperparameter_ranges = {'mini_batch_size': IntegerParameter(100, 200)}

tuner = HyperparameterTuner(estimator=estimator, base_tuning_job_name='byo',
objective_metric_name='test:binary_classification_accuracy',
hyperparameter_ranges=hyperparameter_ranges,
max_jobs=2, max_parallel_jobs=2)

tuner.fit({'train': s3_train_data, 'test': s3_train_data}, include_cls_metadata=False)

print('Started hyperparameter tuning job with name:' + tuner.latest_tuning_job.name)

time.sleep(15)
tuner.wait()

best_training_job = tuner.best_training_job()
with timeout_and_delete_endpoint_by_name(best_training_job, sagemaker_session):
predictor = tuner.deploy(1, 'ml.m4.xlarge', endpoint_name=best_training_job)
predictor.serializer = _fm_serializer
predictor.content_type = 'application/json'
predictor.deserializer = json_deserializer

result = predictor.predict(train_set[0][:10])

assert len(result['predictions']) == 10
for prediction in result['predictions']:
assert prediction['score'] is not None


# Serializer for the Factorization Machines predictor (for BYO example)
def _fm_serializer(data):
js = {'instances': []}
for row in data:
js['instances'].append({'features': row.tolist()})
return json.dumps(js)
15 changes: 15 additions & 0 deletions tests/unit/test_tuner.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -159,6 +159,21 @@ def test_prepare_for_training(tuner):
assert tuner.static_hyperparameters['sagemaker_estimator_module'] == module


def test_prepare_for_training_with_amazon_estimator(tuner, sagemaker_session):
tuner.estimator = PCA(ROLE, TRAIN_INSTANCE_COUNT, TRAIN_INSTANCE_TYPE, NUM_COMPONENTS,
sagemaker_session=sagemaker_session)

tuner._prepare_for_training()
assert 'sagemaker_estimator_class_name' not in tuner.static_hyperparameters
assert 'sagemaker_estimator_module' not in tuner.static_hyperparameters


def test_prepare_for_training_dont_include_estimator_cls(tuner):
tuner._prepare_for_training(include_cls_metadata=False)
assert 'sagemaker_estimator_class_name' not in tuner.static_hyperparameters
assert 'sagemaker_estimator_module' not in tuner.static_hyperparameters


def test_prepare_for_training_with_job_name(tuner):
static_hyperparameters = {'validated': 1, 'another_one': 0}
tuner.estimator.set_hyperparameters(**static_hyperparameters)
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,6 +5,7 @@ CHANGELOG
1.4.3dev
========
* feature: Allow Local Serving of Models in S3
* enhancement: Allow option for ``HyperparameterTuner`` to not include estimator metadata in job


1.4.2
Expand Down
8 changes: 8 additions & 0 deletions README.rst
Original file line numberDiff line numberDiff line change
Expand Up@@ -321,6 +321,14 @@ In addition, the ``fit()`` call uses a list of ``RecordSet`` objects instead of
# Start hyperparameter tuning job
my_tuner.fit([train_records, test_records])

To aid with attaching a previously-started hyperparameter tuning job with a ``HyperparameterTuner`` instance, ``fit()`` injects metadata in the hyperparameters by default.
If the algorithm you are using cannot handle unknown hyperparameters (e.g. an Amazon ML algorithm that does not have a custom estimator in the Python SDK), then you can set ``include_cls_metadata`` to ``False`` when calling fit:

.. code:: python

my_tuner.fit({'train': 's3://my_bucket/my_training_data', 'test': 's3://my_bucket_my_testing_data'},
include_cls_metadata=False)

There is also an analytics object associated with each ``HyperparameterTuner`` instance that presents useful information about the hyperparameter tuning job.
For example, the ``dataframe`` method gets a pandas dataframe summarizing the associated training jobs:

Expand Down
8 changes: 4 additions & 4 deletions src/sagemaker/tuner.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -204,7 +204,7 @@ def __init__(self, estimator, objective_metric_name, hyperparameter_ranges, metr
self._current_job_name = None
self.latest_tuning_job = None

def _prepare_for_training(self, job_name=None):
def _prepare_for_training(self, job_name=None, include_cls_metadata=True):
if job_name is not None:
self._current_job_name = job_name
else:
Expand All@@ -217,12 +217,12 @@ def _prepare_for_training(self, job_name=None):

# For attach() to know what estimator to use for non-1P algorithms
# (1P algorithms don't accept extra hyperparameters)
if not isinstance(self.estimator, AmazonAlgorithmEstimatorBase):
if include_cls_metadata and not isinstance(self.estimator, AmazonAlgorithmEstimatorBase):
self.static_hyperparameters[self.SAGEMAKER_ESTIMATOR_CLASS_NAME] = json.dumps(
self.estimator.__class__.__name__)
self.static_hyperparameters[self.SAGEMAKER_ESTIMATOR_MODULE] = json.dumps(self.estimator.__module__)

def fit(self, inputs, job_name=None, **kwargs):
def fit(self, inputs, job_name=None, include_cls_metadata=True, **kwargs):
"""Start a hyperparameter tuning job.

Args:
Expand DownExpand Up@@ -253,7 +253,7 @@ def fit(self, inputs, job_name=None, **kwargs):
else:
self.estimator._prepare_for_training(job_name)

self._prepare_for_training(job_name=job_name)
self._prepare_for_training(job_name=job_name, include_cls_metadata=include_cls_metadata)
self.latest_tuning_job = _TuningJob.start_new(self, inputs)

@classmethod
Expand Down
91 changes: 88 additions & 3 deletions tests/integ/test_tuner.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,19 +13,24 @@
from __future__ import absolute_import

import gzip
import io
import json
import os
import pickle
import sys
import time

import boto3
import numpy as np
import pytest

from sagemaker import LDA, RandomCutForest
from sagemaker.amazon.common import read_records
from sagemaker.amazon.kmeans import KMeans
from sagemaker import KMeans, LDA, RandomCutForest
from sagemaker.amazon.amazon_estimator import registry
from sagemaker.amazon.common import read_records, write_numpy_to_dense_tensor
from sagemaker.chainer import Chainer
from sagemaker.estimator import Estimator
from sagemaker.mxnet.estimator import MXNet
from sagemaker.predictor import json_deserializer
from sagemaker.tensorflow import TensorFlow
from sagemaker.tuner import IntegerParameter, ContinuousParameter, CategoricalParameter, HyperparameterTuner
from tests.integ import DATA_DIR
Expand DownExpand Up@@ -307,3 +312,83 @@ def test_tuning_chainer(sagemaker_session):
data = np.zeros((batch_size, 28, 28), dtype='float32')
output = predictor.predict(data)
assert len(output) == batch_size


@pytest.mark.continuous_testing
def test_tuning_byo_estimator(sagemaker_session):
"""Use Factorization Machines algorithm as an example here.

First we need to prepare data for training. We take standard data set, convert it to the
format that the algorithm can process and upload it to S3.
Then we create the Estimator and set hyperparamets as required by the algorithm.
Next, we can call fit() with path to the S3.
Later the trained model is deployed and prediction is called against the endpoint.
Default predictor is updated with json serializer and deserializer.
"""
image_name = registry(sagemaker_session.boto_session.region_name) + '/factorization-machines:1'

with timeout(minutes=15):
data_path = os.path.join(DATA_DIR, 'one_p_mnist', 'mnist.pkl.gz')
pickle_args = {} if sys.version_info.major == 2 else {'encoding': 'latin1'}

with gzip.open(data_path, 'rb') as f:
train_set, _, _ = pickle.load(f, **pickle_args)

# take 100 examples for faster execution
vectors = np.array([t.tolist() for t in train_set[0][:100]]).astype('float32')
labels = np.where(np.array([t.tolist() for t in train_set[1][:100]]) == 0, 1.0, 0.0).astype('float32')

buf = io.BytesIO()
write_numpy_to_dense_tensor(buf, vectors, labels)
buf.seek(0)

bucket = sagemaker_session.default_bucket()
prefix = 'test_byo_estimator'
key = 'recordio-pb-data'
boto3.resource('s3').Bucket(bucket).Object(os.path.join(prefix, 'train', key)).upload_fileobj(buf)
s3_train_data = 's3://{}/{}/train/{}'.format(bucket, prefix, key)

estimator = Estimator(image_name=image_name,
role='SageMakerRole', train_instance_count=1,
train_instance_type='ml.c4.xlarge',
sagemaker_session=sagemaker_session, base_job_name='test-byo')

estimator.set_hyperparameters(num_factors=10,
feature_dim=784,
mini_batch_size=100,
predictor_type='binary_classifier')

hyperparameter_ranges = {'mini_batch_size': IntegerParameter(100, 200)}

tuner = HyperparameterTuner(estimator=estimator, base_tuning_job_name='byo',
objective_metric_name='test:binary_classification_accuracy',
hyperparameter_ranges=hyperparameter_ranges,
max_jobs=2, max_parallel_jobs=2)

tuner.fit({'train': s3_train_data, 'test': s3_train_data}, include_cls_metadata=False)

print('Started hyperparameter tuning job with name:' + tuner.latest_tuning_job.name)

time.sleep(15)
tuner.wait()

best_training_job = tuner.best_training_job()
with timeout_and_delete_endpoint_by_name(best_training_job, sagemaker_session):
predictor = tuner.deploy(1, 'ml.m4.xlarge', endpoint_name=best_training_job)
predictor.serializer = _fm_serializer
predictor.content_type = 'application/json'
predictor.deserializer = json_deserializer

result = predictor.predict(train_set[0][:10])

assert len(result['predictions']) == 10
for prediction in result['predictions']:
assert prediction['score'] is not None


# Serializer for the Factorization Machines predictor (for BYO example)
def _fm_serializer(data):
js = {'instances': []}
for row in data:
js['instances'].append({'features': row.tolist()})
return json.dumps(js)
15 changes: 15 additions & 0 deletions tests/unit/test_tuner.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -159,6 +159,21 @@ def test_prepare_for_training(tuner):
assert tuner.static_hyperparameters['sagemaker_estimator_module'] == module


def test_prepare_for_training_with_amazon_estimator(tuner, sagemaker_session):
tuner.estimator = PCA(ROLE, TRAIN_INSTANCE_COUNT, TRAIN_INSTANCE_TYPE, NUM_COMPONENTS,
sagemaker_session=sagemaker_session)

tuner._prepare_for_training()
assert 'sagemaker_estimator_class_name' not in tuner.static_hyperparameters
assert 'sagemaker_estimator_module' not in tuner.static_hyperparameters


def test_prepare_for_training_dont_include_estimator_cls(tuner):
tuner._prepare_for_training(include_cls_metadata=False)
assert 'sagemaker_estimator_class_name' not in tuner.static_hyperparameters
assert 'sagemaker_estimator_module' not in tuner.static_hyperparameters


def test_prepare_for_training_with_job_name(tuner):
static_hyperparameters = {'validated': 1, 'another_one': 0}
tuner.estimator.set_hyperparameters(**static_hyperparameters)
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,6 +5,7 @@ CHANGELOG
1.4.3dev
========
* feature: Allow Local Serving of Models in S3
* enhancement: Allow option for ``HyperparameterTuner`` to not include estimator metadata in job


1.4.2
Expand Down
8 changes: 8 additions & 0 deletions README.rst
Original file line numberDiff line numberDiff line change
Expand Up@@ -321,6 +321,14 @@ In addition, the ``fit()`` call uses a list of ``RecordSet`` objects instead of
# Start hyperparameter tuning job
my_tuner.fit([train_records, test_records])

To aid with attaching a previously-started hyperparameter tuning job with a ``HyperparameterTuner`` instance, ``fit()`` injects metadata in the hyperparameters by default.
If the algorithm you are using cannot handle unknown hyperparameters (e.g. an Amazon ML algorithm that does not have a custom estimator in the Python SDK), then you can set ``include_cls_metadata`` to ``False`` when calling fit:

.. code:: python

my_tuner.fit({'train': 's3://my_bucket/my_training_data', 'test': 's3://my_bucket_my_testing_data'},
include_cls_metadata=False)

There is also an analytics object associated with each ``HyperparameterTuner`` instance that presents useful information about the hyperparameter tuning job.
For example, the ``dataframe`` method gets a pandas dataframe summarizing the associated training jobs:

Expand Down
8 changes: 4 additions & 4 deletions src/sagemaker/tuner.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -204,7 +204,7 @@ def __init__(self, estimator, objective_metric_name, hyperparameter_ranges, metr
self._current_job_name = None
self.latest_tuning_job = None

def _prepare_for_training(self, job_name=None):
def _prepare_for_training(self, job_name=None, include_cls_metadata=True):
if job_name is not None:
self._current_job_name = job_name
else:
Expand All@@ -217,12 +217,12 @@ def _prepare_for_training(self, job_name=None):

# For attach() to know what estimator to use for non-1P algorithms
# (1P algorithms don't accept extra hyperparameters)
if not isinstance(self.estimator, AmazonAlgorithmEstimatorBase):
if include_cls_metadata and not isinstance(self.estimator, AmazonAlgorithmEstimatorBase):
self.static_hyperparameters[self.SAGEMAKER_ESTIMATOR_CLASS_NAME] = json.dumps(
self.estimator.__class__.__name__)
self.static_hyperparameters[self.SAGEMAKER_ESTIMATOR_MODULE] = json.dumps(self.estimator.__module__)

def fit(self, inputs, job_name=None, **kwargs):
def fit(self, inputs, job_name=None, include_cls_metadata=True, **kwargs):
"""Start a hyperparameter tuning job.

Args:
Expand DownExpand Up@@ -253,7 +253,7 @@ def fit(self, inputs, job_name=None, **kwargs):
else:
self.estimator._prepare_for_training(job_name)

self._prepare_for_training(job_name=job_name)
self._prepare_for_training(job_name=job_name, include_cls_metadata=include_cls_metadata)
self.latest_tuning_job = _TuningJob.start_new(self, inputs)

@classmethod
Expand Down
91 changes: 88 additions & 3 deletions tests/integ/test_tuner.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,19 +13,24 @@
from __future__ import absolute_import

import gzip
import io
import json
import os
import pickle
import sys
import time

import boto3
import numpy as np
import pytest

from sagemaker import LDA, RandomCutForest
from sagemaker.amazon.common import read_records
from sagemaker.amazon.kmeans import KMeans
from sagemaker import KMeans, LDA, RandomCutForest
from sagemaker.amazon.amazon_estimator import registry
from sagemaker.amazon.common import read_records, write_numpy_to_dense_tensor
from sagemaker.chainer import Chainer
from sagemaker.estimator import Estimator
from sagemaker.mxnet.estimator import MXNet
from sagemaker.predictor import json_deserializer
from sagemaker.tensorflow import TensorFlow
from sagemaker.tuner import IntegerParameter, ContinuousParameter, CategoricalParameter, HyperparameterTuner
from tests.integ import DATA_DIR
Expand DownExpand Up@@ -307,3 +312,83 @@ def test_tuning_chainer(sagemaker_session):
data = np.zeros((batch_size, 28, 28), dtype='float32')
output = predictor.predict(data)
assert len(output) == batch_size


@pytest.mark.continuous_testing
def test_tuning_byo_estimator(sagemaker_session):
"""Use Factorization Machines algorithm as an example here.

First we need to prepare data for training. We take standard data set, convert it to the
format that the algorithm can process and upload it to S3.
Then we create the Estimator and set hyperparamets as required by the algorithm.
Next, we can call fit() with path to the S3.
Later the trained model is deployed and prediction is called against the endpoint.
Default predictor is updated with json serializer and deserializer.
"""
image_name = registry(sagemaker_session.boto_session.region_name) + '/factorization-machines:1'

with timeout(minutes=15):
data_path = os.path.join(DATA_DIR, 'one_p_mnist', 'mnist.pkl.gz')
pickle_args = {} if sys.version_info.major == 2 else {'encoding': 'latin1'}

with gzip.open(data_path, 'rb') as f:
train_set, _, _ = pickle.load(f, **pickle_args)

# take 100 examples for faster execution
vectors = np.array([t.tolist() for t in train_set[0][:100]]).astype('float32')
labels = np.where(np.array([t.tolist() for t in train_set[1][:100]]) == 0, 1.0, 0.0).astype('float32')

buf = io.BytesIO()
write_numpy_to_dense_tensor(buf, vectors, labels)
buf.seek(0)

bucket = sagemaker_session.default_bucket()
prefix = 'test_byo_estimator'
key = 'recordio-pb-data'
boto3.resource('s3').Bucket(bucket).Object(os.path.join(prefix, 'train', key)).upload_fileobj(buf)
s3_train_data = 's3://{}/{}/train/{}'.format(bucket, prefix, key)

estimator = Estimator(image_name=image_name,
role='SageMakerRole', train_instance_count=1,
train_instance_type='ml.c4.xlarge',
sagemaker_session=sagemaker_session, base_job_name='test-byo')

estimator.set_hyperparameters(num_factors=10,
feature_dim=784,
mini_batch_size=100,
predictor_type='binary_classifier')

hyperparameter_ranges = {'mini_batch_size': IntegerParameter(100, 200)}

tuner = HyperparameterTuner(estimator=estimator, base_tuning_job_name='byo',
objective_metric_name='test:binary_classification_accuracy',
hyperparameter_ranges=hyperparameter_ranges,
max_jobs=2, max_parallel_jobs=2)

tuner.fit({'train': s3_train_data, 'test': s3_train_data}, include_cls_metadata=False)

print('Started hyperparameter tuning job with name:' + tuner.latest_tuning_job.name)

time.sleep(15)
tuner.wait()

best_training_job = tuner.best_training_job()
with timeout_and_delete_endpoint_by_name(best_training_job, sagemaker_session):
predictor = tuner.deploy(1, 'ml.m4.xlarge', endpoint_name=best_training_job)
predictor.serializer = _fm_serializer
predictor.content_type = 'application/json'
predictor.deserializer = json_deserializer

result = predictor.predict(train_set[0][:10])

assert len(result['predictions']) == 10
for prediction in result['predictions']:
assert prediction['score'] is not None


# Serializer for the Factorization Machines predictor (for BYO example)
def _fm_serializer(data):
js = {'instances': []}
for row in data:
js['instances'].append({'features': row.tolist()})
return json.dumps(js)
15 changes: 15 additions & 0 deletions tests/unit/test_tuner.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -159,6 +159,21 @@ def test_prepare_for_training(tuner):
assert tuner.static_hyperparameters['sagemaker_estimator_module'] == module


def test_prepare_for_training_with_amazon_estimator(tuner, sagemaker_session):
tuner.estimator = PCA(ROLE, TRAIN_INSTANCE_COUNT, TRAIN_INSTANCE_TYPE, NUM_COMPONENTS,
sagemaker_session=sagemaker_session)

tuner._prepare_for_training()
assert 'sagemaker_estimator_class_name' not in tuner.static_hyperparameters
assert 'sagemaker_estimator_module' not in tuner.static_hyperparameters


def test_prepare_for_training_dont_include_estimator_cls(tuner):
tuner._prepare_for_training(include_cls_metadata=False)
assert 'sagemaker_estimator_class_name' not in tuner.static_hyperparameters
assert 'sagemaker_estimator_module' not in tuner.static_hyperparameters


def test_prepare_for_training_with_job_name(tuner):
static_hyperparameters = {'validated': 1, 'another_one': 0}
tuner.estimator.set_hyperparameters(**static_hyperparameters)
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,6 +5,7 @@ CHANGELOG
1.4.3dev
========
* feature: Allow Local Serving of Models in S3
* enhancement: Allow option for ``HyperparameterTuner`` to not include estimator metadata in job


1.4.2
Expand Down
8 changes: 8 additions & 0 deletions README.rst
Original file line numberDiff line numberDiff line change
Expand Up@@ -321,6 +321,14 @@ In addition, the ``fit()`` call uses a list of ``RecordSet`` objects instead of
# Start hyperparameter tuning job
my_tuner.fit([train_records, test_records])

To aid with attaching a previously-started hyperparameter tuning job with a ``HyperparameterTuner`` instance, ``fit()`` injects metadata in the hyperparameters by default.
If the algorithm you are using cannot handle unknown hyperparameters (e.g. an Amazon ML algorithm that does not have a custom estimator in the Python SDK), then you can set ``include_cls_metadata`` to ``False`` when calling fit:

.. code:: python

my_tuner.fit({'train': 's3://my_bucket/my_training_data', 'test': 's3://my_bucket_my_testing_data'},
include_cls_metadata=False)

There is also an analytics object associated with each ``HyperparameterTuner`` instance that presents useful information about the hyperparameter tuning job.
For example, the ``dataframe`` method gets a pandas dataframe summarizing the associated training jobs:

Expand Down
8 changes: 4 additions & 4 deletions src/sagemaker/tuner.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -204,7 +204,7 @@ def __init__(self, estimator, objective_metric_name, hyperparameter_ranges, metr
self._current_job_name = None
self.latest_tuning_job = None

def _prepare_for_training(self, job_name=None):
def _prepare_for_training(self, job_name=None, include_cls_metadata=True):
if job_name is not None:
self._current_job_name = job_name
else:
Expand All@@ -217,12 +217,12 @@ def _prepare_for_training(self, job_name=None):

# For attach() to know what estimator to use for non-1P algorithms
# (1P algorithms don't accept extra hyperparameters)
if not isinstance(self.estimator, AmazonAlgorithmEstimatorBase):
if include_cls_metadata and not isinstance(self.estimator, AmazonAlgorithmEstimatorBase):
self.static_hyperparameters[self.SAGEMAKER_ESTIMATOR_CLASS_NAME] = json.dumps(
self.estimator.__class__.__name__)
self.static_hyperparameters[self.SAGEMAKER_ESTIMATOR_MODULE] = json.dumps(self.estimator.__module__)

def fit(self, inputs, job_name=None, **kwargs):
def fit(self, inputs, job_name=None, include_cls_metadata=True, **kwargs):
"""Start a hyperparameter tuning job.

Args:
Expand DownExpand Up@@ -253,7 +253,7 @@ def fit(self, inputs, job_name=None, **kwargs):
else:
self.estimator._prepare_for_training(job_name)

self._prepare_for_training(job_name=job_name)
self._prepare_for_training(job_name=job_name, include_cls_metadata=include_cls_metadata)
self.latest_tuning_job = _TuningJob.start_new(self, inputs)

@classmethod
Expand Down
91 changes: 88 additions & 3 deletions tests/integ/test_tuner.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,19 +13,24 @@
from __future__ import absolute_import

import gzip
import io
import json
import os
import pickle
import sys
import time

import boto3
import numpy as np
import pytest

from sagemaker import LDA, RandomCutForest
from sagemaker.amazon.common import read_records
from sagemaker.amazon.kmeans import KMeans
from sagemaker import KMeans, LDA, RandomCutForest
from sagemaker.amazon.amazon_estimator import registry
from sagemaker.amazon.common import read_records, write_numpy_to_dense_tensor
from sagemaker.chainer import Chainer
from sagemaker.estimator import Estimator
from sagemaker.mxnet.estimator import MXNet
from sagemaker.predictor import json_deserializer
from sagemaker.tensorflow import TensorFlow
from sagemaker.tuner import IntegerParameter, ContinuousParameter, CategoricalParameter, HyperparameterTuner
from tests.integ import DATA_DIR
Expand DownExpand Up@@ -307,3 +312,83 @@ def test_tuning_chainer(sagemaker_session):
data = np.zeros((batch_size, 28, 28), dtype='float32')
output = predictor.predict(data)
assert len(output) == batch_size


@pytest.mark.continuous_testing
def test_tuning_byo_estimator(sagemaker_session):
"""Use Factorization Machines algorithm as an example here.

First we need to prepare data for training. We take standard data set, convert it to the
format that the algorithm can process and upload it to S3.
Then we create the Estimator and set hyperparamets as required by the algorithm.
Next, we can call fit() with path to the S3.
Later the trained model is deployed and prediction is called against the endpoint.
Default predictor is updated with json serializer and deserializer.
"""
image_name = registry(sagemaker_session.boto_session.region_name) + '/factorization-machines:1'

with timeout(minutes=15):
data_path = os.path.join(DATA_DIR, 'one_p_mnist', 'mnist.pkl.gz')
pickle_args = {} if sys.version_info.major == 2 else {'encoding': 'latin1'}

with gzip.open(data_path, 'rb') as f:
train_set, _, _ = pickle.load(f, **pickle_args)

# take 100 examples for faster execution
vectors = np.array([t.tolist() for t in train_set[0][:100]]).astype('float32')
labels = np.where(np.array([t.tolist() for t in train_set[1][:100]]) == 0, 1.0, 0.0).astype('float32')

buf = io.BytesIO()
write_numpy_to_dense_tensor(buf, vectors, labels)
buf.seek(0)

bucket = sagemaker_session.default_bucket()
prefix = 'test_byo_estimator'
key = 'recordio-pb-data'
boto3.resource('s3').Bucket(bucket).Object(os.path.join(prefix, 'train', key)).upload_fileobj(buf)
s3_train_data = 's3://{}/{}/train/{}'.format(bucket, prefix, key)

estimator = Estimator(image_name=image_name,
role='SageMakerRole', train_instance_count=1,
train_instance_type='ml.c4.xlarge',
sagemaker_session=sagemaker_session, base_job_name='test-byo')

estimator.set_hyperparameters(num_factors=10,
feature_dim=784,
mini_batch_size=100,
predictor_type='binary_classifier')

hyperparameter_ranges = {'mini_batch_size': IntegerParameter(100, 200)}

tuner = HyperparameterTuner(estimator=estimator, base_tuning_job_name='byo',
objective_metric_name='test:binary_classification_accuracy',
hyperparameter_ranges=hyperparameter_ranges,
max_jobs=2, max_parallel_jobs=2)

tuner.fit({'train': s3_train_data, 'test': s3_train_data}, include_cls_metadata=False)

print('Started hyperparameter tuning job with name:' + tuner.latest_tuning_job.name)

time.sleep(15)
tuner.wait()

best_training_job = tuner.best_training_job()
with timeout_and_delete_endpoint_by_name(best_training_job, sagemaker_session):
predictor = tuner.deploy(1, 'ml.m4.xlarge', endpoint_name=best_training_job)
predictor.serializer = _fm_serializer
predictor.content_type = 'application/json'
predictor.deserializer = json_deserializer

result = predictor.predict(train_set[0][:10])

assert len(result['predictions']) == 10
for prediction in result['predictions']:
assert prediction['score'] is not None


# Serializer for the Factorization Machines predictor (for BYO example)
def _fm_serializer(data):
js = {'instances': []}
for row in data:
js['instances'].append({'features': row.tolist()})
return json.dumps(js)
15 changes: 15 additions & 0 deletions tests/unit/test_tuner.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -159,6 +159,21 @@ def test_prepare_for_training(tuner):
assert tuner.static_hyperparameters['sagemaker_estimator_module'] == module


def test_prepare_for_training_with_amazon_estimator(tuner, sagemaker_session):
tuner.estimator = PCA(ROLE, TRAIN_INSTANCE_COUNT, TRAIN_INSTANCE_TYPE, NUM_COMPONENTS,
sagemaker_session=sagemaker_session)

tuner._prepare_for_training()
assert 'sagemaker_estimator_class_name' not in tuner.static_hyperparameters
assert 'sagemaker_estimator_module' not in tuner.static_hyperparameters


def test_prepare_for_training_dont_include_estimator_cls(tuner):
tuner._prepare_for_training(include_cls_metadata=False)
assert 'sagemaker_estimator_class_name' not in tuner.static_hyperparameters
assert 'sagemaker_estimator_module' not in tuner.static_hyperparameters


def test_prepare_for_training_with_job_name(tuner):
static_hyperparameters = {'validated': 1, 'another_one': 0}
tuner.estimator.set_hyperparameters(**static_hyperparameters)
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,6 +5,7 @@ CHANGELOG
1.4.3dev
========
* feature: Allow Local Serving of Models in S3
* enhancement: Allow option for ``HyperparameterTuner`` to not include estimator metadata in job


1.4.2
Expand Down
8 changes: 8 additions & 0 deletions README.rst
Original file line numberDiff line numberDiff line change
Expand Up@@ -321,6 +321,14 @@ In addition, the ``fit()`` call uses a list of ``RecordSet`` objects instead of
# Start hyperparameter tuning job
my_tuner.fit([train_records, test_records])

To aid with attaching a previously-started hyperparameter tuning job with a ``HyperparameterTuner`` instance, ``fit()`` injects metadata in the hyperparameters by default.
If the algorithm you are using cannot handle unknown hyperparameters (e.g. an Amazon ML algorithm that does not have a custom estimator in the Python SDK), then you can set ``include_cls_metadata`` to ``False`` when calling fit:

.. code:: python

my_tuner.fit({'train': 's3://my_bucket/my_training_data', 'test': 's3://my_bucket_my_testing_data'},
include_cls_metadata=False)

There is also an analytics object associated with each ``HyperparameterTuner`` instance that presents useful information about the hyperparameter tuning job.
For example, the ``dataframe`` method gets a pandas dataframe summarizing the associated training jobs:

Expand Down
8 changes: 4 additions & 4 deletions src/sagemaker/tuner.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -204,7 +204,7 @@ def __init__(self, estimator, objective_metric_name, hyperparameter_ranges, metr
self._current_job_name = None
self.latest_tuning_job = None

def _prepare_for_training(self, job_name=None):
def _prepare_for_training(self, job_name=None, include_cls_metadata=True):
if job_name is not None:
self._current_job_name = job_name
else:
Expand All@@ -217,12 +217,12 @@ def _prepare_for_training(self, job_name=None):

# For attach() to know what estimator to use for non-1P algorithms
# (1P algorithms don't accept extra hyperparameters)
if not isinstance(self.estimator, AmazonAlgorithmEstimatorBase):
if include_cls_metadata and not isinstance(self.estimator, AmazonAlgorithmEstimatorBase):
self.static_hyperparameters[self.SAGEMAKER_ESTIMATOR_CLASS_NAME] = json.dumps(
self.estimator.__class__.__name__)
self.static_hyperparameters[self.SAGEMAKER_ESTIMATOR_MODULE] = json.dumps(self.estimator.__module__)

def fit(self, inputs, job_name=None, **kwargs):
def fit(self, inputs, job_name=None, include_cls_metadata=True, **kwargs):
"""Start a hyperparameter tuning job.

Args:
Expand DownExpand Up@@ -253,7 +253,7 @@ def fit(self, inputs, job_name=None, **kwargs):
else:
self.estimator._prepare_for_training(job_name)

self._prepare_for_training(job_name=job_name)
self._prepare_for_training(job_name=job_name, include_cls_metadata=include_cls_metadata)
self.latest_tuning_job = _TuningJob.start_new(self, inputs)

@classmethod
Expand Down
91 changes: 88 additions & 3 deletions tests/integ/test_tuner.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,19 +13,24 @@
from __future__ import absolute_import

import gzip
import io
import json
import os
import pickle
import sys
import time

import boto3
import numpy as np
import pytest

from sagemaker import LDA, RandomCutForest
from sagemaker.amazon.common import read_records
from sagemaker.amazon.kmeans import KMeans
from sagemaker import KMeans, LDA, RandomCutForest
from sagemaker.amazon.amazon_estimator import registry
from sagemaker.amazon.common import read_records, write_numpy_to_dense_tensor
from sagemaker.chainer import Chainer
from sagemaker.estimator import Estimator
from sagemaker.mxnet.estimator import MXNet
from sagemaker.predictor import json_deserializer
from sagemaker.tensorflow import TensorFlow
from sagemaker.tuner import IntegerParameter, ContinuousParameter, CategoricalParameter, HyperparameterTuner
from tests.integ import DATA_DIR
Expand DownExpand Up@@ -307,3 +312,83 @@ def test_tuning_chainer(sagemaker_session):
data = np.zeros((batch_size, 28, 28), dtype='float32')
output = predictor.predict(data)
assert len(output) == batch_size


@pytest.mark.continuous_testing
def test_tuning_byo_estimator(sagemaker_session):
"""Use Factorization Machines algorithm as an example here.

First we need to prepare data for training. We take standard data set, convert it to the
format that the algorithm can process and upload it to S3.
Then we create the Estimator and set hyperparamets as required by the algorithm.
Next, we can call fit() with path to the S3.
Later the trained model is deployed and prediction is called against the endpoint.
Default predictor is updated with json serializer and deserializer.
"""
image_name = registry(sagemaker_session.boto_session.region_name) + '/factorization-machines:1'

with timeout(minutes=15):
data_path = os.path.join(DATA_DIR, 'one_p_mnist', 'mnist.pkl.gz')
pickle_args = {} if sys.version_info.major == 2 else {'encoding': 'latin1'}

with gzip.open(data_path, 'rb') as f:
train_set, _, _ = pickle.load(f, **pickle_args)

# take 100 examples for faster execution
vectors = np.array([t.tolist() for t in train_set[0][:100]]).astype('float32')
labels = np.where(np.array([t.tolist() for t in train_set[1][:100]]) == 0, 1.0, 0.0).astype('float32')

buf = io.BytesIO()
write_numpy_to_dense_tensor(buf, vectors, labels)
buf.seek(0)

bucket = sagemaker_session.default_bucket()
prefix = 'test_byo_estimator'
key = 'recordio-pb-data'
boto3.resource('s3').Bucket(bucket).Object(os.path.join(prefix, 'train', key)).upload_fileobj(buf)
s3_train_data = 's3://{}/{}/train/{}'.format(bucket, prefix, key)

estimator = Estimator(image_name=image_name,
role='SageMakerRole', train_instance_count=1,
train_instance_type='ml.c4.xlarge',
sagemaker_session=sagemaker_session, base_job_name='test-byo')

estimator.set_hyperparameters(num_factors=10,
feature_dim=784,
mini_batch_size=100,
predictor_type='binary_classifier')

hyperparameter_ranges = {'mini_batch_size': IntegerParameter(100, 200)}

tuner = HyperparameterTuner(estimator=estimator, base_tuning_job_name='byo',
objective_metric_name='test:binary_classification_accuracy',
hyperparameter_ranges=hyperparameter_ranges,
max_jobs=2, max_parallel_jobs=2)

tuner.fit({'train': s3_train_data, 'test': s3_train_data}, include_cls_metadata=False)

print('Started hyperparameter tuning job with name:' + tuner.latest_tuning_job.name)

time.sleep(15)
tuner.wait()

best_training_job = tuner.best_training_job()
with timeout_and_delete_endpoint_by_name(best_training_job, sagemaker_session):
predictor = tuner.deploy(1, 'ml.m4.xlarge', endpoint_name=best_training_job)
predictor.serializer = _fm_serializer
predictor.content_type = 'application/json'
predictor.deserializer = json_deserializer

result = predictor.predict(train_set[0][:10])

assert len(result['predictions']) == 10
for prediction in result['predictions']:
assert prediction['score'] is not None


# Serializer for the Factorization Machines predictor (for BYO example)
def _fm_serializer(data):
js = {'instances': []}
for row in data:
js['instances'].append({'features': row.tolist()})
return json.dumps(js)
15 changes: 15 additions & 0 deletions tests/unit/test_tuner.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -159,6 +159,21 @@ def test_prepare_for_training(tuner):
assert tuner.static_hyperparameters['sagemaker_estimator_module'] == module


def test_prepare_for_training_with_amazon_estimator(tuner, sagemaker_session):
tuner.estimator = PCA(ROLE, TRAIN_INSTANCE_COUNT, TRAIN_INSTANCE_TYPE, NUM_COMPONENTS,
sagemaker_session=sagemaker_session)

tuner._prepare_for_training()
assert 'sagemaker_estimator_class_name' not in tuner.static_hyperparameters
assert 'sagemaker_estimator_module' not in tuner.static_hyperparameters


def test_prepare_for_training_dont_include_estimator_cls(tuner):
tuner._prepare_for_training(include_cls_metadata=False)
assert 'sagemaker_estimator_class_name' not in tuner.static_hyperparameters
assert 'sagemaker_estimator_module' not in tuner.static_hyperparameters


def test_prepare_for_training_with_job_name(tuner):
static_hyperparameters = {'validated': 1, 'another_one': 0}
tuner.estimator.set_hyperparameters(**static_hyperparameters)
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,6 +5,7 @@ CHANGELOG
1.4.3dev
========
* feature: Allow Local Serving of Models in S3
* enhancement: Allow option for ``HyperparameterTuner`` to not include estimator metadata in job


1.4.2
Expand Down
8 changes: 8 additions & 0 deletions README.rst
Original file line numberDiff line numberDiff line change
Expand Up@@ -321,6 +321,14 @@ In addition, the ``fit()`` call uses a list of ``RecordSet`` objects instead of
# Start hyperparameter tuning job
my_tuner.fit([train_records, test_records])

To aid with attaching a previously-started hyperparameter tuning job with a ``HyperparameterTuner`` instance, ``fit()`` injects metadata in the hyperparameters by default.
If the algorithm you are using cannot handle unknown hyperparameters (e.g. an Amazon ML algorithm that does not have a custom estimator in the Python SDK), then you can set ``include_cls_metadata`` to ``False`` when calling fit:

.. code:: python

my_tuner.fit({'train': 's3://my_bucket/my_training_data', 'test': 's3://my_bucket_my_testing_data'},
include_cls_metadata=False)

There is also an analytics object associated with each ``HyperparameterTuner`` instance that presents useful information about the hyperparameter tuning job.
For example, the ``dataframe`` method gets a pandas dataframe summarizing the associated training jobs:

Expand Down
8 changes: 4 additions & 4 deletions src/sagemaker/tuner.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -204,7 +204,7 @@ def __init__(self, estimator, objective_metric_name, hyperparameter_ranges, metr
self._current_job_name = None
self.latest_tuning_job = None

def _prepare_for_training(self, job_name=None):
def _prepare_for_training(self, job_name=None, include_cls_metadata=True):
if job_name is not None:
self._current_job_name = job_name
else:
Expand All@@ -217,12 +217,12 @@ def _prepare_for_training(self, job_name=None):

# For attach() to know what estimator to use for non-1P algorithms
# (1P algorithms don't accept extra hyperparameters)
if not isinstance(self.estimator, AmazonAlgorithmEstimatorBase):
if include_cls_metadata and not isinstance(self.estimator, AmazonAlgorithmEstimatorBase):
self.static_hyperparameters[self.SAGEMAKER_ESTIMATOR_CLASS_NAME] = json.dumps(
self.estimator.__class__.__name__)
self.static_hyperparameters[self.SAGEMAKER_ESTIMATOR_MODULE] = json.dumps(self.estimator.__module__)

def fit(self, inputs, job_name=None, **kwargs):
def fit(self, inputs, job_name=None, include_cls_metadata=True, **kwargs):
"""Start a hyperparameter tuning job.

Args:
Expand DownExpand Up@@ -253,7 +253,7 @@ def fit(self, inputs, job_name=None, **kwargs):
else:
self.estimator._prepare_for_training(job_name)

self._prepare_for_training(job_name=job_name)
self._prepare_for_training(job_name=job_name, include_cls_metadata=include_cls_metadata)
self.latest_tuning_job = _TuningJob.start_new(self, inputs)

@classmethod
Expand Down
91 changes: 88 additions & 3 deletions tests/integ/test_tuner.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,19 +13,24 @@
from __future__ import absolute_import

import gzip
import io
import json
import os
import pickle
import sys
import time

import boto3
import numpy as np
import pytest

from sagemaker import LDA, RandomCutForest
from sagemaker.amazon.common import read_records
from sagemaker.amazon.kmeans import KMeans
from sagemaker import KMeans, LDA, RandomCutForest
from sagemaker.amazon.amazon_estimator import registry
from sagemaker.amazon.common import read_records, write_numpy_to_dense_tensor
from sagemaker.chainer import Chainer
from sagemaker.estimator import Estimator
from sagemaker.mxnet.estimator import MXNet
from sagemaker.predictor import json_deserializer
from sagemaker.tensorflow import TensorFlow
from sagemaker.tuner import IntegerParameter, ContinuousParameter, CategoricalParameter, HyperparameterTuner
from tests.integ import DATA_DIR
Expand DownExpand Up@@ -307,3 +312,83 @@ def test_tuning_chainer(sagemaker_session):
data = np.zeros((batch_size, 28, 28), dtype='float32')
output = predictor.predict(data)
assert len(output) == batch_size


@pytest.mark.continuous_testing
def test_tuning_byo_estimator(sagemaker_session):
"""Use Factorization Machines algorithm as an example here.

First we need to prepare data for training. We take standard data set, convert it to the
format that the algorithm can process and upload it to S3.
Then we create the Estimator and set hyperparamets as required by the algorithm.
Next, we can call fit() with path to the S3.
Later the trained model is deployed and prediction is called against the endpoint.
Default predictor is updated with json serializer and deserializer.
"""
image_name = registry(sagemaker_session.boto_session.region_name) + '/factorization-machines:1'

with timeout(minutes=15):
data_path = os.path.join(DATA_DIR, 'one_p_mnist', 'mnist.pkl.gz')
pickle_args = {} if sys.version_info.major == 2 else {'encoding': 'latin1'}

with gzip.open(data_path, 'rb') as f:
train_set, _, _ = pickle.load(f, **pickle_args)

# take 100 examples for faster execution
vectors = np.array([t.tolist() for t in train_set[0][:100]]).astype('float32')
labels = np.where(np.array([t.tolist() for t in train_set[1][:100]]) == 0, 1.0, 0.0).astype('float32')

buf = io.BytesIO()
write_numpy_to_dense_tensor(buf, vectors, labels)
buf.seek(0)

bucket = sagemaker_session.default_bucket()
prefix = 'test_byo_estimator'
key = 'recordio-pb-data'
boto3.resource('s3').Bucket(bucket).Object(os.path.join(prefix, 'train', key)).upload_fileobj(buf)
s3_train_data = 's3://{}/{}/train/{}'.format(bucket, prefix, key)

estimator = Estimator(image_name=image_name,
role='SageMakerRole', train_instance_count=1,
train_instance_type='ml.c4.xlarge',
sagemaker_session=sagemaker_session, base_job_name='test-byo')

estimator.set_hyperparameters(num_factors=10,
feature_dim=784,
mini_batch_size=100,
predictor_type='binary_classifier')

hyperparameter_ranges = {'mini_batch_size': IntegerParameter(100, 200)}

tuner = HyperparameterTuner(estimator=estimator, base_tuning_job_name='byo',
objective_metric_name='test:binary_classification_accuracy',
hyperparameter_ranges=hyperparameter_ranges,
max_jobs=2, max_parallel_jobs=2)

tuner.fit({'train': s3_train_data, 'test': s3_train_data}, include_cls_metadata=False)

print('Started hyperparameter tuning job with name:' + tuner.latest_tuning_job.name)

time.sleep(15)
tuner.wait()

best_training_job = tuner.best_training_job()
with timeout_and_delete_endpoint_by_name(best_training_job, sagemaker_session):
predictor = tuner.deploy(1, 'ml.m4.xlarge', endpoint_name=best_training_job)
predictor.serializer = _fm_serializer
predictor.content_type = 'application/json'
predictor.deserializer = json_deserializer

result = predictor.predict(train_set[0][:10])

assert len(result['predictions']) == 10
for prediction in result['predictions']:
assert prediction['score'] is not None


# Serializer for the Factorization Machines predictor (for BYO example)
def _fm_serializer(data):
js = {'instances': []}
for row in data:
js['instances'].append({'features': row.tolist()})
return json.dumps(js)
15 changes: 15 additions & 0 deletions tests/unit/test_tuner.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -159,6 +159,21 @@ def test_prepare_for_training(tuner):
assert tuner.static_hyperparameters['sagemaker_estimator_module'] == module


def test_prepare_for_training_with_amazon_estimator(tuner, sagemaker_session):
tuner.estimator = PCA(ROLE, TRAIN_INSTANCE_COUNT, TRAIN_INSTANCE_TYPE, NUM_COMPONENTS,
sagemaker_session=sagemaker_session)

tuner._prepare_for_training()
assert 'sagemaker_estimator_class_name' not in tuner.static_hyperparameters
assert 'sagemaker_estimator_module' not in tuner.static_hyperparameters


def test_prepare_for_training_dont_include_estimator_cls(tuner):
tuner._prepare_for_training(include_cls_metadata=False)
assert 'sagemaker_estimator_class_name' not in tuner.static_hyperparameters
assert 'sagemaker_estimator_module' not in tuner.static_hyperparameters


def test_prepare_for_training_with_job_name(tuner):
static_hyperparameters = {'validated': 1, 'another_one': 0}
tuner.estimator.set_hyperparameters(**static_hyperparameters)
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,6 +5,7 @@ CHANGELOG
1.4.3dev
========
* feature: Allow Local Serving of Models in S3
* enhancement: Allow option for ``HyperparameterTuner`` to not include estimator metadata in job


1.4.2
Expand Down
8 changes: 8 additions & 0 deletions README.rst
Original file line numberDiff line numberDiff line change
Expand Up@@ -321,6 +321,14 @@ In addition, the ``fit()`` call uses a list of ``RecordSet`` objects instead of
# Start hyperparameter tuning job
my_tuner.fit([train_records, test_records])

To aid with attaching a previously-started hyperparameter tuning job with a ``HyperparameterTuner`` instance, ``fit()`` injects metadata in the hyperparameters by default.
If the algorithm you are using cannot handle unknown hyperparameters (e.g. an Amazon ML algorithm that does not have a custom estimator in the Python SDK), then you can set ``include_cls_metadata`` to ``False`` when calling fit:

.. code:: python

my_tuner.fit({'train': 's3://my_bucket/my_training_data', 'test': 's3://my_bucket_my_testing_data'},
include_cls_metadata=False)

There is also an analytics object associated with each ``HyperparameterTuner`` instance that presents useful information about the hyperparameter tuning job.
For example, the ``dataframe`` method gets a pandas dataframe summarizing the associated training jobs:

Expand Down
8 changes: 4 additions & 4 deletions src/sagemaker/tuner.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -204,7 +204,7 @@ def __init__(self, estimator, objective_metric_name, hyperparameter_ranges, metr
self._current_job_name = None
self.latest_tuning_job = None

def _prepare_for_training(self, job_name=None):
def _prepare_for_training(self, job_name=None, include_cls_metadata=True):
if job_name is not None:
self._current_job_name = job_name
else:
Expand All@@ -217,12 +217,12 @@ def _prepare_for_training(self, job_name=None):

# For attach() to know what estimator to use for non-1P algorithms
# (1P algorithms don't accept extra hyperparameters)
if not isinstance(self.estimator, AmazonAlgorithmEstimatorBase):
if include_cls_metadata and not isinstance(self.estimator, AmazonAlgorithmEstimatorBase):
self.static_hyperparameters[self.SAGEMAKER_ESTIMATOR_CLASS_NAME] = json.dumps(
self.estimator.__class__.__name__)
self.static_hyperparameters[self.SAGEMAKER_ESTIMATOR_MODULE] = json.dumps(self.estimator.__module__)

def fit(self, inputs, job_name=None, **kwargs):
def fit(self, inputs, job_name=None, include_cls_metadata=True, **kwargs):
"""Start a hyperparameter tuning job.

Args:
Expand DownExpand Up@@ -253,7 +253,7 @@ def fit(self, inputs, job_name=None, **kwargs):
else:
self.estimator._prepare_for_training(job_name)

self._prepare_for_training(job_name=job_name)
self._prepare_for_training(job_name=job_name, include_cls_metadata=include_cls_metadata)
self.latest_tuning_job = _TuningJob.start_new(self, inputs)

@classmethod
Expand Down
91 changes: 88 additions & 3 deletions tests/integ/test_tuner.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,19 +13,24 @@
from __future__ import absolute_import

import gzip
import io
import json
import os
import pickle
import sys
import time

import boto3
import numpy as np
import pytest

from sagemaker import LDA, RandomCutForest
from sagemaker.amazon.common import read_records
from sagemaker.amazon.kmeans import KMeans
from sagemaker import KMeans, LDA, RandomCutForest
from sagemaker.amazon.amazon_estimator import registry
from sagemaker.amazon.common import read_records, write_numpy_to_dense_tensor
from sagemaker.chainer import Chainer
from sagemaker.estimator import Estimator
from sagemaker.mxnet.estimator import MXNet
from sagemaker.predictor import json_deserializer
from sagemaker.tensorflow import TensorFlow
from sagemaker.tuner import IntegerParameter, ContinuousParameter, CategoricalParameter, HyperparameterTuner
from tests.integ import DATA_DIR
Expand DownExpand Up@@ -307,3 +312,83 @@ def test_tuning_chainer(sagemaker_session):
data = np.zeros((batch_size, 28, 28), dtype='float32')
output = predictor.predict(data)
assert len(output) == batch_size


@pytest.mark.continuous_testing
def test_tuning_byo_estimator(sagemaker_session):
"""Use Factorization Machines algorithm as an example here.

First we need to prepare data for training. We take standard data set, convert it to the
format that the algorithm can process and upload it to S3.
Then we create the Estimator and set hyperparamets as required by the algorithm.
Next, we can call fit() with path to the S3.
Later the trained model is deployed and prediction is called against the endpoint.
Default predictor is updated with json serializer and deserializer.
"""
image_name = registry(sagemaker_session.boto_session.region_name) + '/factorization-machines:1'

with timeout(minutes=15):
data_path = os.path.join(DATA_DIR, 'one_p_mnist', 'mnist.pkl.gz')
pickle_args = {} if sys.version_info.major == 2 else {'encoding': 'latin1'}

with gzip.open(data_path, 'rb') as f:
train_set, _, _ = pickle.load(f, **pickle_args)

# take 100 examples for faster execution
vectors = np.array([t.tolist() for t in train_set[0][:100]]).astype('float32')
labels = np.where(np.array([t.tolist() for t in train_set[1][:100]]) == 0, 1.0, 0.0).astype('float32')

buf = io.BytesIO()
write_numpy_to_dense_tensor(buf, vectors, labels)
buf.seek(0)

bucket = sagemaker_session.default_bucket()
prefix = 'test_byo_estimator'
key = 'recordio-pb-data'
boto3.resource('s3').Bucket(bucket).Object(os.path.join(prefix, 'train', key)).upload_fileobj(buf)
s3_train_data = 's3://{}/{}/train/{}'.format(bucket, prefix, key)

estimator = Estimator(image_name=image_name,
role='SageMakerRole', train_instance_count=1,
train_instance_type='ml.c4.xlarge',
sagemaker_session=sagemaker_session, base_job_name='test-byo')

estimator.set_hyperparameters(num_factors=10,
feature_dim=784,
mini_batch_size=100,
predictor_type='binary_classifier')

hyperparameter_ranges = {'mini_batch_size': IntegerParameter(100, 200)}

tuner = HyperparameterTuner(estimator=estimator, base_tuning_job_name='byo',
objective_metric_name='test:binary_classification_accuracy',
hyperparameter_ranges=hyperparameter_ranges,
max_jobs=2, max_parallel_jobs=2)

tuner.fit({'train': s3_train_data, 'test': s3_train_data}, include_cls_metadata=False)

print('Started hyperparameter tuning job with name:' + tuner.latest_tuning_job.name)

time.sleep(15)
tuner.wait()

best_training_job = tuner.best_training_job()
with timeout_and_delete_endpoint_by_name(best_training_job, sagemaker_session):
predictor = tuner.deploy(1, 'ml.m4.xlarge', endpoint_name=best_training_job)
predictor.serializer = _fm_serializer
predictor.content_type = 'application/json'
predictor.deserializer = json_deserializer

result = predictor.predict(train_set[0][:10])

assert len(result['predictions']) == 10
for prediction in result['predictions']:
assert prediction['score'] is not None


# Serializer for the Factorization Machines predictor (for BYO example)
def _fm_serializer(data):
js = {'instances': []}
for row in data:
js['instances'].append({'features': row.tolist()})
return json.dumps(js)
15 changes: 15 additions & 0 deletions tests/unit/test_tuner.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -159,6 +159,21 @@ def test_prepare_for_training(tuner):
assert tuner.static_hyperparameters['sagemaker_estimator_module'] == module


def test_prepare_for_training_with_amazon_estimator(tuner, sagemaker_session):
tuner.estimator = PCA(ROLE, TRAIN_INSTANCE_COUNT, TRAIN_INSTANCE_TYPE, NUM_COMPONENTS,
sagemaker_session=sagemaker_session)

tuner._prepare_for_training()
assert 'sagemaker_estimator_class_name' not in tuner.static_hyperparameters
assert 'sagemaker_estimator_module' not in tuner.static_hyperparameters


def test_prepare_for_training_dont_include_estimator_cls(tuner):
tuner._prepare_for_training(include_cls_metadata=False)
assert 'sagemaker_estimator_class_name' not in tuner.static_hyperparameters
assert 'sagemaker_estimator_module' not in tuner.static_hyperparameters


def test_prepare_for_training_with_job_name(tuner):
static_hyperparameters = {'validated': 1, 'another_one': 0}
tuner.estimator.set_hyperparameters(**static_hyperparameters)
Expand Down