Skip to content

Commit 72ec7a7

Browse files
authored
Merge branch 'master' into fix-integ-test-region
2 parents a55bdb4 + 447197e commit 72ec7a7

14 files changed

+987
-715
lines changed

CHANGELOG.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ CHANGELOG
66
========
77

88
* feature: Tests: create configurable ``sagemaker_session`` pytest fixture for all integration tests
9+
* bug-fix: AmazonEstimators: fix inaccurate hyper-parameters in kmeans, pca and linear learner
910

1011
1.1.2
1112
=====

src/sagemaker/amazon/kmeans.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from sagemaker.amazon.amazon_estimator import AmazonAlgorithmEstimatorBase, registry
1414
from sagemaker.amazon.common import numpy_to_record_serializer, record_deserializer
1515
from sagemaker.amazon.hyperparameter import Hyperparameter as hp # noqa
16-
from sagemaker.amazon.validation import gt, isin, ge
16+
from sagemaker.amazon.validation import gt, isin, ge, le
1717
from sagemaker.predictor import RealTimePredictor
1818
from sagemaker.model import Model
1919
from sagemaker.session import Session
@@ -27,16 +27,18 @@ class KMeans(AmazonAlgorithmEstimatorBase):
2727
k = hp('k', gt(1), 'An integer greater-than 1', int)
2828
init_method = hp('init_method', isin('random', 'kmeans++'), 'One of "random", "kmeans++"', str)
2929
max_iterations = hp('local_lloyd_max_iterations', gt(0), 'An integer greater-than 0', int)
30-
tol = hp('local_lloyd_tol', gt(0), 'An integer greater-than 0', int)
30+
tol = hp('local_lloyd_tol', (ge(0), le(1)), 'An float in [0, 1]', float)
3131
num_trials = hp('local_lloyd_num_trials', gt(0), 'An integer greater-than 0', int)
3232
local_init_method = hp('local_lloyd_init_method', isin('random', 'kmeans++'), 'One of "random", "kmeans++"', str)
3333
half_life_time_size = hp('half_life_time_size', ge(0), 'An integer greater-than-or-equal-to 0', int)
3434
epochs = hp('epochs', gt(0), 'An integer greater-than 0', int)
3535
center_factor = hp('extra_center_factor', gt(0), 'An integer greater-than 0', int)
36+
eval_metrics = hp(name='eval_metrics', validation_message='A comma separated list of "msd" or "ssd"',
37+
data_type=list)
3638

3739
def __init__(self, role, train_instance_count, train_instance_type, k, init_method=None,
3840
max_iterations=None, tol=None, num_trials=None, local_init_method=None,
39-
half_life_time_size=None, epochs=None, center_factor=None, **kwargs):
41+
half_life_time_size=None, epochs=None, center_factor=None, eval_metrics=None, **kwargs):
4042
"""
4143
A k-means clustering :class:`~sagemaker.amazon.AmazonAlgorithmEstimatorBase`. Finds k clusters of data in an
4244
unlabeled dataset.
@@ -70,7 +72,7 @@ def __init__(self, role, train_instance_count, train_instance_type, k, init_meth
7072
k (int): The number of clusters to produce.
7173
init_method (str): How to initialize cluster locations. One of 'random' or 'kmeans++'.
7274
max_iterations (int): Maximum iterations for Lloyds EM procedure in the local kmeans used in finalize stage.
73-
tol (int): Tolerance for change in ssd for early stopping in local kmeans.
75+
tol (float): Tolerance for change in ssd for early stopping in local kmeans.
7476
num_trials (int): Local version is run multiple times and the one with the best loss is chosen. This
7577
determines how many times.
7678
local_init_method (str): Initialization method for local version. One of 'random', 'kmeans++'
@@ -82,6 +84,9 @@ def __init__(self, role, train_instance_count, train_instance_type, k, init_meth
8284
epochs (int): Number of passes done over the training data.
8385
center_factor(int): The algorithm will create ``num_clusters * extra_center_factor`` as it runs and
8486
reduce the number of centers to ``k`` when finalizing
87+
eval_metrics(list): JSON list of metrics types to be used for reporting the score for the model.
88+
Allowed values are "msd" Means Square Error, "ssd": Sum of square distance. If test data is provided,
89+
the score shall be reported in terms of all requested metrics.
8590
**kwargs: base class keyword argument values.
8691
"""
8792
super(KMeans, self).__init__(role, train_instance_count, train_instance_type, **kwargs)
@@ -94,6 +99,7 @@ def __init__(self, role, train_instance_count, train_instance_type, k, init_meth
9499
self.half_life_time_size = half_life_time_size
95100
self.epochs = epochs
96101
self.center_factor = center_factor
102+
self.eval_metrics = eval_metrics
97103

98104
def create_model(self):
99105
"""Return a :class:`~sagemaker.amazon.kmeans.KMeansModel` referencing the latest

src/sagemaker/amazon/linear_learner.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ class LinearLearner(AmazonAlgorithmEstimatorBase):
3232
data_type=str)
3333
target_recall = hp('target_recall', (gt(0), lt(1)), "A float in (0,1)", float)
3434
target_precision = hp('target_precision', (gt(0), lt(1)), "A float in (0,1)", float)
35-
positive_example_weight_mult = hp('positive_example_weight_mult', gt(0), "A float greater than 0", float)
3635
epochs = hp('epochs', gt(0), "An integer greater-than 0", int)
3736
predictor_type = hp('predictor_type', isin('binary_classifier', 'regressor'),
3837
'One of "binary_classifier" or "regressor"', str)
@@ -64,9 +63,9 @@ class LinearLearner(AmazonAlgorithmEstimatorBase):
6463
unbias_label = hp('unbias_label', (), 'A boolean', bool)
6564
num_point_for_scaler = hp('num_point_for_scaler', gt(0), 'An integer greater-than 0', int)
6665

67-
def __init__(self, role, train_instance_count, train_instance_type, predictor_type='binary_classifier',
66+
def __init__(self, role, train_instance_count, train_instance_type, predictor_type,
6867
binary_classifier_model_selection_criteria=None, target_recall=None, target_precision=None,
69-
positive_example_weight_mult=None, epochs=None, use_bias=None, num_models=None,
68+
epochs=None, use_bias=None, num_models=None,
7069
num_calibration_samples=None, init_method=None, init_scale=None, init_sigma=None, init_bias=None,
7170
optimizer=None, loss=None, wd=None, l1=None, momentum=None, learning_rate=None, beta_1=None,
7271
beta_2=None, bias_lr_mult=None, bias_wd_mult=None, use_lr_scheduler=None, lr_scheduler_step=None,
@@ -114,8 +113,6 @@ def __init__(self, role, train_instance_count, train_instance_type, predictor_ty
114113
precision_at_target_recall.
115114
target_precision (float): Target precision. Only applicable if binary_classifier_model_selection_criteria
116115
is recall_at_target_precision.
117-
positive_example_weight_mult (float): The importance weight of positive examples is multiplied by this
118-
constant. Useful for skewed datasets. Only applies for classification tasks.
119116
epochs (int): The maximum number of passes to make over the training data.
120117
use_bias (bool): Whether to include a bias field
121118
num_models (int): Number of models to train in parallel. If not set, the number of parallel models to
@@ -160,7 +157,6 @@ def __init__(self, role, train_instance_count, train_instance_type, predictor_ty
160157
self.binary_classifier_model_selection_criteria = binary_classifier_model_selection_criteria
161158
self.target_recall = target_recall
162159
self.target_precision = target_precision
163-
self.positive_example_weight_mult = positive_example_weight_mult
164160
self.epochs = epochs
165161
self.use_bias = use_bias
166162
self.num_models = num_models

src/sagemaker/amazon/pca.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from sagemaker.amazon.amazon_estimator import AmazonAlgorithmEstimatorBase, registry
1414
from sagemaker.amazon.common import numpy_to_record_serializer, record_deserializer
1515
from sagemaker.amazon.hyperparameter import Hyperparameter as hp # noqa
16+
from sagemaker.amazon.validation import gt, isin
1617
from sagemaker.predictor import RealTimePredictor
1718
from sagemaker.model import Model
1819
from sagemaker.session import Session
@@ -25,13 +26,13 @@ class PCA(AmazonAlgorithmEstimatorBase):
2526

2627
DEFAULT_MINI_BATCH_SIZE = 500
2728

28-
num_components = hp(name='num_components', validate=lambda x: x > 0,
29-
validation_message='Value must be an integer greater than zero', data_type=int)
30-
algorithm_mode = hp(name='algorithm_mode', validate=lambda x: x in ['regular', 'stable', 'randomized'],
31-
validation_message='Value must be one of "regular", "stable", "randomized"', data_type=str)
29+
num_components = hp('num_components', gt(0), 'Value must be an integer greater than zero', int)
30+
algorithm_mode = hp('algorithm_mode', isin('regular', 'randomized'),
31+
'Value must be one of "regular" and "randomized"', str)
3232
subtract_mean = hp(name='subtract_mean', validation_message='Value must be a boolean', data_type=bool)
33-
extra_components = hp(name='extra_components', validate=lambda x: x >= 0,
34-
validation_message="Value must be an integer greater than or equal to 0", data_type=int)
33+
extra_components = hp(name='extra_components',
34+
validation_message="Value must be an integer greater than or equal to 0, or -1.",
35+
data_type=int)
3536

3637
def __init__(self, role, train_instance_count, train_instance_type, num_components,
3738
algorithm_mode=None, subtract_mean=None, extra_components=None, **kwargs):
@@ -68,12 +69,13 @@ def __init__(self, role, train_instance_count, train_instance_type, num_componen
6869
train_instance_count (int): Number of Amazon EC2 instances to use for training.
6970
train_instance_type (str): Type of EC2 instance to use for training, for example, 'ml.c4.xlarge'.
7071
num_components(int): The number of principal components. Must be greater than zero.
71-
algorithm_mode (str): Mode for computing the principal components. One of 'regular', 'stable' or
72+
algorithm_mode (str): Mode for computing the principal components. One of 'regular' or
7273
'randomized'.
7374
subtract_mean (bool): Whether the data should be unbiased both during train and at inference.
7475
extra_components (int): As the value grows larger, the solution becomes more accurate but the
75-
runtime and memory consumption increase linearly. If this value is unset, then a default value equal
76-
to the maximum of 10 and num_components will be used. Valid for randomized mode only.
76+
runtime and memory consumption increase linearly. If this value is unset or set to -1,
77+
then a default value equal to the maximum of 10 and num_components will be used.
78+
Valid for randomized mode only.
7779
**kwargs: base class keyword argument values.
7880
"""
7981
super(PCA, self).__init__(role, train_instance_count, train_instance_type, **kwargs)

src/sagemaker/tensorflow/estimator.py

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@
1010
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
1111
# ANY KIND, either express or implied. See the License for the specific
1212
# language governing permissions and limitations under the License.
13+
import contextlib
1314
import logging
1415
import os
16+
import shutil
1517
import subprocess
1618
import tempfile
1719
import threading
@@ -46,6 +48,47 @@ def _cmd_exists(cmd):
4648
for path in os.environ["PATH"].split(os.pathsep)
4749
)
4850

51+
@staticmethod
52+
def _sync_directories(from_directory, to_directory):
53+
"""Sync to_directory with from_directory by copying each file in
54+
to_directory with new contents. Files in to_directory will be
55+
overwritten by files of the same name in from_directory. We need to
56+
keep two copies of the log directory because otherwise TensorBoard
57+
picks up temp files from `aws s3 sync` and then stops reading the
58+
correct tfevent files. We walk the directory and copy each file
59+
individually because the directory that TensorBoard watches needs to
60+
always exist.
61+
62+
Args:
63+
from_directory (str): The directory with updated files.
64+
to_directory (str): The directory to be synced.
65+
"""
66+
if not os.path.exists(to_directory):
67+
os.mkdir(to_directory)
68+
for root, dirs, files in os.walk(from_directory):
69+
to_root = root.replace(from_directory, to_directory)
70+
for directory in dirs:
71+
to_child_dir = os.path.join(to_root, directory)
72+
if not os.path.exists(to_child_dir):
73+
os.mkdir(to_child_dir)
74+
for fname in files:
75+
from_file = os.path.join(root, fname)
76+
to_file = os.path.join(to_root, fname)
77+
with open(from_file, 'rb') as a, open(to_file, 'wb') as b:
78+
b.write(a.read())
79+
80+
@staticmethod
81+
@contextlib.contextmanager
82+
def _temporary_directory():
83+
"""Context manager for a temporary directory. This is similar to
84+
tempfile.TemporaryDirectory in python>=3.2.
85+
"""
86+
name = tempfile.mkdtemp()
87+
try:
88+
yield name
89+
finally:
90+
shutil.rmtree(name)
91+
4992
def validate_requirements(self):
5093
"""Ensure that TensorBoard and the AWS CLI are installed.
5194
@@ -96,10 +139,12 @@ def run(self):
96139
LOGGER.info('TensorBoard 0.1.7 at http://localhost:{}'.format(port))
97140
while not self.estimator.checkpoint_path:
98141
self.event.wait(1)
99-
while not self.event.is_set():
100-
args = ['aws', 's3', 'sync', self.estimator.checkpoint_path, self.logdir]
101-
subprocess.call(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
102-
self.event.wait(10)
142+
with self._temporary_directory() as aws_sync_dir:
143+
while not self.event.is_set():
144+
args = ['aws', 's3', 'sync', self.estimator.checkpoint_path, aws_sync_dir]
145+
subprocess.call(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
146+
self._sync_directories(aws_sync_dir, self.logdir)
147+
self.event.wait(10)
103148
tensorboard_process.terminate()
104149

105150

tests/integ/test_linear_learner.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,11 @@ def test_linear_learner(sagemaker_session):
3838
train_set = train_set[0], train_set[1].astype(np.dtype('float32'))
3939

4040
ll = LinearLearner('SageMakerRole', 1, 'ml.c4.2xlarge', base_job_name='test-linear-learner',
41-
sagemaker_session=sagemaker_session)
41+
predictor_type='binary_classifier', sagemaker_session=sagemaker_session)
4242
ll.binary_classifier_model_selection_criteria = 'accuracy'
4343
ll.target_recall = 0.5
4444
ll.target_precision = 0.5
45-
ll.positive_example_weight_mult = 0.1
4645
ll.epochs = 1
47-
ll.predictor_type = 'binary_classifier'
4846
ll.use_bias = True
4947
ll.num_models = 1
5048
ll.num_calibration_samples = 1
@@ -100,13 +98,11 @@ def test_async_linear_learner(sagemaker_session):
10098
train_set = train_set[0], train_set[1].astype(np.dtype('float32'))
10199

102100
ll = LinearLearner('SageMakerRole', 1, 'ml.c4.2xlarge', base_job_name='test-linear-learner',
103-
sagemaker_session=sagemaker_session)
101+
predictor_type='binary_classifier', sagemaker_session=sagemaker_session)
104102
ll.binary_classifier_model_selection_criteria = 'accuracy'
105103
ll.target_recall = 0.5
106104
ll.target_precision = 0.5
107-
ll.positive_example_weight_mult = 0.1
108105
ll.epochs = 1
109-
ll.predictor_type = 'binary_classifier'
110106
ll.use_bias = True
111107
ll.num_models = 1
112108
ll.num_calibration_samples = 1

tests/unit/test_amazon_estimator.py

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -95,22 +95,6 @@ def test_data_location_does_not_call_default_bucket(sagemaker_session):
9595
assert not sagemaker_session.default_bucket.called
9696

9797

98-
def test_pca_hyperparameters(sagemaker_session):
99-
pca = PCA(num_components=55, algorithm_mode='randomized',
100-
subtract_mean=True, extra_components=33, sagemaker_session=sagemaker_session,
101-
**COMMON_ARGS)
102-
assert pca.hyperparameters() == dict(
103-
num_components='55',
104-
extra_components='33',
105-
subtract_mean='True',
106-
algorithm_mode='randomized')
107-
108-
109-
def test_image(sagemaker_session):
110-
pca = PCA(num_components=55, sagemaker_session=sagemaker_session, **COMMON_ARGS)
111-
assert pca.train_image() == registry('us-west-2') + '/pca:1'
112-
113-
11498
@patch('time.strftime', return_value=TIMESTAMP)
11599
def test_fit_ndarray(time, sagemaker_session):
116100
mock_s3 = Mock()

0 commit comments

Comments
 (0)