Skip to content

Commit 3dd7a14

Browse files
authored
Merge branch 'master' into master
2 parents 770c61a + f43b9c3 commit 3dd7a14

File tree

11 files changed

+333
-4
lines changed

11 files changed

+333
-4
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,4 @@ examples/tensorflow/distributed_mnist/data
1919
*.iml
2020
doc/_build
2121
**/.DS_Store
22+
venv/

README.rst

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1506,3 +1506,24 @@ Once you have the aws cli installed, you can upload a directory of files to S3 w
15061506
aws s3 cp /tmp/foo/ s3://bucket/path
15071507
15081508
You can read more about using the aws cli for manipulating S3 resources in the `AWS cli command reference <http://docs.aws.amazon.com/cli/latest/reference/s3/index.html>`__.
1509+
1510+
1511+
How do I make predictions against an existing endpoint?
1512+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1513+
Create a Predictor object and provide it your endpoint name. Then, simply call its predict() method with your input.
1514+
1515+
You can either use the generic RealTimePredictor class, which by default does not perform any serialization/deserialization transformations on your input, but can be configured to do so through constructor arguments:
1516+
http://sagemaker.readthedocs.io/en/latest/predictors.html
1517+
1518+
Or you can use the TensorFlow / MXNet specific predictor classes, which have default serialization/deserialization logic:
1519+
http://sagemaker.readthedocs.io/en/latest/sagemaker.tensorflow.html#tensorflow-predictor
1520+
http://sagemaker.readthedocs.io/en/latest/sagemaker.mxnet.html#mxnet-predictor
1521+
1522+
Example code using the TensorFlow predictor:
1523+
1524+
::
1525+
1526+
from sagemaker.tensorflow import TensorFlowPredictor
1527+
1528+
predictor = TensorFlowPredictor('myexistingendpoint')
1529+
result = predictor.predict(['my request body'])

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def read(fname):
3232
],
3333

3434
# Declare minimal set for installation
35-
install_requires=['boto3>=1.4.8', 'numpy>=1.9.0', 'protobuf>=3.1'],
35+
install_requires=['boto3>=1.4.8', 'numpy>=1.9.0', 'protobuf>=3.1', 'scipy>=1.0.0'],
3636

3737
extras_require={
3838
'test': ['tox', 'flake8', 'pytest', 'pytest-cov', 'pytest-xdist',

src/sagemaker/amazon/common.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import sys
1616

1717
import numpy as np
18+
from scipy.sparse import issparse
1819

1920
from sagemaker.amazon.record_pb2 import Record
2021

@@ -64,6 +65,24 @@ def _write_label_tensor(resolved_type, record, scalar):
6465
record.label["values"].float32_tensor.values.extend([scalar])
6566

6667

68+
def _write_keys_tensor(resolved_type, record, vector):
69+
if resolved_type == "Int32":
70+
record.features["values"].int32_tensor.keys.extend(vector)
71+
elif resolved_type == "Float64":
72+
record.features["values"].float64_tensor.keys.extend(vector)
73+
elif resolved_type == "Float32":
74+
record.features["values"].float32_tensor.keys.extend(vector)
75+
76+
77+
def _write_shape(resolved_type, record, scalar):
78+
if resolved_type == "Int32":
79+
record.features["values"].int32_tensor.shape.extend([scalar])
80+
elif resolved_type == "Float64":
81+
record.features["values"].float64_tensor.shape.extend([scalar])
82+
elif resolved_type == "Float32":
83+
record.features["values"].float32_tensor.shape.extend([scalar])
84+
85+
6786
def write_numpy_to_dense_tensor(file, array, labels=None):
6887
"""Writes a numpy array to a dense tensor"""
6988

@@ -89,6 +108,46 @@ def write_numpy_to_dense_tensor(file, array, labels=None):
89108
_write_recordio(file, record.SerializeToString())
90109

91110

111+
def write_spmatrix_to_sparse_tensor(file, array, labels=None):
112+
"""Writes a scipy sparse matrix to a sparse tensor"""
113+
114+
if not issparse(array):
115+
raise TypeError("Array must be sparse")
116+
117+
# Validate shape of array and labels, resolve array and label types
118+
if not len(array.shape) == 2:
119+
raise ValueError("Array must be a Matrix")
120+
if labels is not None:
121+
if not len(labels.shape) == 1:
122+
raise ValueError("Labels must be a Vector")
123+
if labels.shape[0] not in array.shape:
124+
raise ValueError("Label shape {} not compatible with array shape {}".format(
125+
labels.shape, array.shape))
126+
resolved_label_type = _resolve_type(labels.dtype)
127+
resolved_type = _resolve_type(array.dtype)
128+
129+
csr_array = array.tocsr()
130+
n_rows, n_cols = csr_array.shape
131+
132+
record = Record()
133+
for row_idx in range(n_rows):
134+
record.Clear()
135+
row = csr_array.getrow(row_idx)
136+
# Write values
137+
_write_feature_tensor(resolved_type, record, row.data)
138+
# Write keys
139+
_write_keys_tensor(resolved_type, record, row.indices.astype(np.uint64))
140+
141+
# Write labels
142+
if labels is not None:
143+
_write_label_tensor(resolved_label_type, record, labels[row_idx])
144+
145+
# Write shape
146+
_write_shape(resolved_type, record, n_cols)
147+
148+
_write_recordio(file, record.SerializeToString())
149+
150+
92151
def read_records(file):
93152
"""Eagerly read a collection of amazon Record protobuf objects from file."""
94153
records = []

src/sagemaker/session.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,13 @@ def default_bucket(self):
170170
elif error_code == 'OperationAborted' and 'conflicting conditional operation' in message:
171171
# If this bucket is already being concurrently created, we don't need to create it again.
172172
pass
173+
elif error_code == 'TooManyBuckets':
174+
# Succeed if the default bucket exists
175+
try:
176+
s3.meta.client.head_bucket(Bucket=default_bucket)
177+
pass
178+
except ClientError:
179+
raise
173180
else:
174181
raise
175182

tests/data/iris/failure_script.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
def estimator_fn(run_config, params):
2+
"""For use with integration tests expecting failures."""
3+
raise Exception('This failure is expected.')
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
def train(**kwargs):
2+
"""For use with integration tests expecting failures."""
3+
raise Exception('This failure is expected.')

tests/integ/test_mxnet_train.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,20 @@ def test_deploy_model(mxnet_training_job, sagemaker_session):
7070

7171
data = numpy.zeros(shape=(1, 1, 28, 28))
7272
predictor.predict(data)
73+
74+
75+
def test_failed_training_job(sagemaker_session):
76+
with timeout(minutes=15):
77+
script_path = os.path.join(DATA_DIR, 'mxnet_mnist', 'failure_script.py')
78+
data_path = os.path.join(DATA_DIR, 'mxnet_mnist')
79+
80+
mx = MXNet(entry_point=script_path, role='SageMakerRole',
81+
train_instance_count=1, train_instance_type='ml.c4.xlarge',
82+
sagemaker_session=sagemaker_session)
83+
84+
train_input = mx.sagemaker_session.upload_data(path=os.path.join(data_path, 'train'),
85+
key_prefix='integ-test-data/mxnet_mnist/train-failure')
86+
87+
with pytest.raises(ValueError) as e:
88+
mx.fit(train_input)
89+
assert 'This failure is expected' in str(e.value)

tests/integ/test_tf.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
from tests.integ import DATA_DIR, REGION
2020
from tests.integ.timeout import timeout_and_delete_endpoint, timeout
2121

22+
DATA_PATH = os.path.join(DATA_DIR, 'iris', 'data')
23+
2224

2325
@pytest.fixture(scope='module')
2426
def sagemaker_session():
@@ -28,7 +30,6 @@ def sagemaker_session():
2830
def test_tf(sagemaker_session):
2931
with timeout(minutes=15):
3032
script_path = os.path.join(DATA_DIR, 'iris', 'iris-dnn-classifier.py')
31-
data_path = os.path.join(DATA_DIR, 'iris', 'data')
3233

3334
estimator = TensorFlow(entry_point=script_path,
3435
role='SageMakerRole',
@@ -40,7 +41,7 @@ def test_tf(sagemaker_session):
4041
sagemaker_session=sagemaker_session,
4142
base_job_name='test-tf')
4243

43-
inputs = estimator.sagemaker_session.upload_data(path=data_path, key_prefix='integ-test-data/tf_iris')
44+
inputs = estimator.sagemaker_session.upload_data(path=DATA_PATH, key_prefix='integ-test-data/tf_iris')
4445
estimator.fit(inputs)
4546
print('job succeeded: {}'.format(estimator.latest_training_job.name))
4647

@@ -49,3 +50,22 @@ def test_tf(sagemaker_session):
4950

5051
result = json_predictor.predict([6.4, 3.2, 4.5, 1.5])
5152
print('predict result: {}'.format(result))
53+
54+
55+
def test_failed_tf_training(sagemaker_session):
56+
with timeout(minutes=15):
57+
script_path = os.path.join(DATA_DIR, 'iris', 'failure_script.py')
58+
estimator = TensorFlow(entry_point=script_path,
59+
role='SageMakerRole',
60+
training_steps=1,
61+
evaluation_steps=1,
62+
hyperparameters={'input_tensor_name': 'inputs'},
63+
train_instance_count=1,
64+
train_instance_type='ml.c4.xlarge',
65+
sagemaker_session=sagemaker_session)
66+
67+
inputs = estimator.sagemaker_session.upload_data(path=DATA_PATH, key_prefix='integ-test-data/tf-failure')
68+
69+
with pytest.raises(ValueError) as e:
70+
estimator.fit(inputs)
71+
assert 'This failure is expected' in str(e.value)

0 commit comments

Comments
 (0)