From f43b9c3c54b152829e7b683c5a46e4cd2b3d242e Mon Sep 17 00:00:00 2001 From: Joan Alabort Date: Fri, 12 Jan 2018 20:56:43 +0000 Subject: [PATCH 1/2] Correctly handle TooManyBuckets error_code in default_bucket method (#40) * Update session.py Correctly handles the `TooManyBuckets` error_code when the bucket already exists. --- src/sagemaker/session.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/sagemaker/session.py b/src/sagemaker/session.py index cd6f222d42..7b6639c935 100644 --- a/src/sagemaker/session.py +++ b/src/sagemaker/session.py @@ -170,6 +170,13 @@ def default_bucket(self): elif error_code == 'OperationAborted' and 'conflicting conditional operation' in message: # If this bucket is already being concurrently created, we don't need to create it again. pass + elif error_code == 'TooManyBuckets': + # Succeed if the default bucket exists + try: + s3.meta.client.head_bucket(Bucket=default_bucket) + pass + except ClientError: + raise else: raise From 2e0ed8ff225d4d8a0e674f62b698d3552fb39a61 Mon Sep 17 00:00:00 2001 From: lukmis <32072210+lukmis@users.noreply.github.com> Date: Mon, 15 Jan 2018 10:34:55 -0800 Subject: [PATCH 2/2] Add wrapper for FactorizationMachiones algorithm. (#38) * Add FactorizationMachines class with unit tests. * Add integration test for FactorizationMachines. * Add CHANGELOG file. --- CHANGELOG.rst | 30 +++ setup.py | 2 +- src/sagemaker/__init__.py | 6 +- src/sagemaker/amazon/amazon_estimator.py | 7 +- .../amazon/factorization_machines.py | 202 ++++++++++++++++++ src/sagemaker/amazon/kmeans.py | 3 + src/sagemaker/amazon/linear_learner.py | 9 + src/sagemaker/amazon/pca.py | 9 + tests/integ/test_factorization_machines.py | 55 +++++ tests/unit/test_fm.py | 96 +++++++++ 10 files changed, 412 insertions(+), 7 deletions(-) create mode 100644 CHANGELOG.rst create mode 100644 src/sagemaker/amazon/factorization_machines.py create mode 100644 tests/integ/test_factorization_machines.py create mode 100644 tests/unit/test_fm.py diff --git a/CHANGELOG.rst b/CHANGELOG.rst new file mode 100644 index 0000000000..86b55eb7fa --- /dev/null +++ b/CHANGELOG.rst @@ -0,0 +1,30 @@ +========= +CHANGELOG +========= + +1.0.2 +===== + +* feature: Estimators: add support for Amazon FactorizationMachines algorithm +* feature: Session: Correctly handle TooManyBuckets error_code in default_bucket method +* feature: Tests: add training failure tests for TF and MXNet +* feature: Documentation: show how to make predictions against existing endpoint +* feature: Estimators: implement write_spmatrix_to_sparse_tensor to support any scipy.sparse matrix + + +1.0.1 +===== + +* api-change: Model: Remove support for 'supplemental_containers' when creating Model +* feature: Documentation: multiple updates +* feature: Tests: ignore tests data in tox.ini, increase timeout for endpoint creation, capture exceptions during endpoint deletion, tests for input-output functions +* feature: Logging: change to describe job every 30s when showing logs +* feature: Session: use custom user agent at all times +* feature: Setup: add travis file + + +1.0.0 +===== + +* Initial commit + diff --git a/setup.py b/setup.py index f29cab11f3..fa81d4aaeb 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,7 @@ def read(fname): setup(name="sagemaker", - version="1.0.1", + version="1.0.2", description="Open source library for training and deploying models on Amazon SageMaker.", packages=find_packages('src'), package_dir={'': 'src'}, diff --git a/src/sagemaker/__init__.py b/src/sagemaker/__init__.py index d5901c086d..098c067f96 100644 --- a/src/sagemaker/__init__.py +++ b/src/sagemaker/__init__.py @@ -16,6 +16,8 @@ from sagemaker.amazon.kmeans import KMeans, KMeansModel, KMeansPredictor from sagemaker.amazon.pca import PCA, PCAModel, PCAPredictor from sagemaker.amazon.linear_learner import LinearLearner, LinearLearnerModel, LinearLearnerPredictor +from sagemaker.amazon.factorization_machines import FactorizationMachines, FactorizationMachinesModel +from sagemaker.amazon.factorization_machines import FactorizationMachinesPredictor from sagemaker.model import Model from sagemaker.predictor import RealTimePredictor @@ -27,5 +29,7 @@ __all__ = [estimator, KMeans, KMeansModel, KMeansPredictor, PCA, PCAModel, PCAPredictor, LinearLearner, - LinearLearnerModel, LinearLearnerPredictor, Model, RealTimePredictor, Session, + LinearLearnerModel, LinearLearnerPredictor, + FactorizationMachines, FactorizationMachinesModel, FactorizationMachinesPredictor, + Model, RealTimePredictor, Session, container_def, s3_input, production_variant, get_execution_role] diff --git a/src/sagemaker/amazon/amazon_estimator.py b/src/sagemaker/amazon/amazon_estimator.py index 6da58aa165..9ed28c1894 100644 --- a/src/sagemaker/amazon/amazon_estimator.py +++ b/src/sagemaker/amazon/amazon_estimator.py @@ -28,8 +28,6 @@ class AmazonAlgorithmEstimatorBase(EstimatorBase): """Base class for Amazon first-party Estimator implementations. This class isn't intended to be instantiated directly.""" - MAX_DEFAULT_BATCH_SIZE = 500 - feature_dim = hp('feature_dim', (validation.isint, validation.gt(0))) mini_batch_size = hp('mini_batch_size', (validation.isint, validation.gt(0))) @@ -87,10 +85,9 @@ def fit(self, records, mini_batch_size=None, **kwargs): mini_batch_size (int or None): The size of each mini-batch to use when training. If None, a default value will be used. """ - default_mini_batch_size = min(self.MAX_DEFAULT_BATCH_SIZE, - max(1, int(records.num_records / self.train_instance_count))) - self.mini_batch_size = mini_batch_size or default_mini_batch_size self.feature_dim = records.feature_dim + self.mini_batch_size = mini_batch_size + data = {records.channel: s3_input(records.s3_data, distribution='ShardedByS3Key', s3_data_type=records.s3_data_type)} super(AmazonAlgorithmEstimatorBase, self).fit(data, **kwargs) diff --git a/src/sagemaker/amazon/factorization_machines.py b/src/sagemaker/amazon/factorization_machines.py new file mode 100644 index 0000000000..5340fa11f0 --- /dev/null +++ b/src/sagemaker/amazon/factorization_machines.py @@ -0,0 +1,202 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +from sagemaker.amazon.amazon_estimator import AmazonAlgorithmEstimatorBase, registry +from sagemaker.amazon.common import numpy_to_record_serializer, record_deserializer +from sagemaker.amazon.hyperparameter import Hyperparameter as hp # noqa +from sagemaker.amazon.validation import gt, isin, isint, ge, isnumber +from sagemaker.predictor import RealTimePredictor +from sagemaker.model import Model +from sagemaker.session import Session + + +class FactorizationMachines(AmazonAlgorithmEstimatorBase): + + repo = 'factorization-machines:1' + + num_factors = hp('num_factors', (gt(0), isint), 'An integer greater than zero') + predictor_type = hp('predictor_type', isin('binary_classifier', 'regressor'), + 'Value "binary_classifier" or "regressor"') + epochs = hp('epochs', (gt(0), isint), "An integer greater than 0") + clip_gradient = hp('clip_gradient', isnumber, "A float value") + eps = hp('eps', isnumber, "A float value") + rescale_grad = hp('rescale_grad', isnumber, "A float value") + bias_lr = hp('bias_lr', (ge(0), isnumber), "A non-negative float") + linear_lr = hp('linear_lr', (ge(0), isnumber), "A non-negative float") + factors_lr = hp('factors_lr', (ge(0), isnumber), "A non-negative float") + bias_wd = hp('bias_wd', (ge(0), isnumber), "A non-negative float") + linear_wd = hp('linear_wd', (ge(0), isnumber), "A non-negative float") + factors_wd = hp('factors_wd', (ge(0), isnumber), "A non-negative float") + bias_init_method = hp('bias_init_method', isin('normal', 'uniform', 'constant'), + 'Value "normal", "uniform" or "constant"') + bias_init_scale = hp('bias_init_scale', (ge(0), isnumber), "A non-negative float") + bias_init_sigma = hp('bias_init_sigma', (ge(0), isnumber), "A non-negative float") + bias_init_value = hp('bias_init_value', isnumber, "A float value") + linear_init_method = hp('linear_init_method', isin('normal', 'uniform', 'constant'), + 'Value "normal", "uniform" or "constant"') + linear_init_scale = hp('linear_init_scale', (ge(0), isnumber), "A non-negative float") + linear_init_sigma = hp('linear_init_sigma', (ge(0), isnumber), "A non-negative float") + linear_init_value = hp('linear_init_value', isnumber, "A float value") + factors_init_method = hp('factors_init_method', isin('normal', 'uniform', 'constant'), + 'Value "normal", "uniform" or "constant"') + factors_init_scale = hp('factors_init_scale', (ge(0), isnumber), "A non-negative float") + factors_init_sigma = hp('factors_init_sigma', (ge(0), isnumber), "A non-negative float") + factors_init_value = hp('factors_init_value', isnumber, "A float value") + + def __init__(self, role, train_instance_count, train_instance_type, + num_factors, predictor_type, + epochs=None, clip_gradient=None, eps=None, rescale_grad=None, + bias_lr=None, linear_lr=None, factors_lr=None, + bias_wd=None, linear_wd=None, factors_wd=None, + bias_init_method=None, bias_init_scale=None, bias_init_sigma=None, bias_init_value=None, + linear_init_method=None, linear_init_scale=None, linear_init_sigma=None, linear_init_value=None, + factors_init_method=None, factors_init_scale=None, factors_init_sigma=None, factors_init_value=None, + **kwargs): + """Factorization Machines is :class:`Estimator` for general-purpose supervised learning. + + Amazon SageMaker Factorization Machines is a general-purpose supervised learning algorithm that you can use + for both classification and regression tasks. It is an extension of a linear model that is designed + to parsimoniously capture interactions between features within high dimensional sparse datasets. + + This Estimator may be fit via calls to + :meth:`~sagemaker.amazon.amazon_estimator.AmazonAlgorithmEstimatorBase.fit`. It requires Amazon + :class:`~sagemaker.amazon.record_pb2.Record` protobuf serialized data to be stored in S3. + There is an utility :meth:`~sagemaker.amazon.amazon_estimator.AmazonAlgorithmEstimatorBase.record_set` that + can be used to upload data to S3 and creates :class:`~sagemaker.amazon.amazon_estimator.RecordSet` to be passed + to the `fit` call. + + To learn more about the Amazon protobuf Record class and how to prepare bulk data in this format, please + consult AWS technical documentation: https://docs.aws.amazon.com/sagemaker/latest/dg/cdf-training.html + + After this Estimator is fit, model data is stored in S3. The model may be deployed to an Amazon SageMaker + Endpoint by invoking :meth:`~sagemaker.amazon.estimator.EstimatorBase.deploy`. As well as deploying an Endpoint, + deploy returns a :class:`~sagemaker.amazon.pca.FactorizationMachinesPredictor` object that can be used + for inference calls using the trained model hosted in the SageMaker Endpoint. + + FactorizationMachines Estimators can be configured by setting hyperparameters. The available hyperparameters for + FactorizationMachines are documented below. + + For further information on the AWS FactorizationMachines algorithm, + please consult AWS technical documentation: https://docs.aws.amazon.com/sagemaker/latest/dg/fact-machines.html + + Args: + role (str): An AWS IAM role (either name or full ARN). The Amazon SageMaker training jobs and + APIs that create Amazon SageMaker endpoints use this role to access + training data and model artifacts. After the endpoint is created, + the inference code might use the IAM role, if accessing AWS resource. + train_instance_count (int): Number of Amazon EC2 instances to use for training. + train_instance_type (str): Type of EC2 instance to use for training, for example, 'ml.c4.xlarge'. + num_factors (int): Dimensionality of factorization. + predictor_type (str): Type of predictor 'binary_classifier' or 'regressor'. + epochs (int): Number of training epochs to run. + clip_gradient (float): Optimizer parameter. Clip the gradient by projecting onto + the box [-clip_gradient, +clip_gradient] + eps (float): Optimizer parameter. Small value to avoid division by 0. + rescale_grad (float): Optimizer parameter. If set, multiplies the gradient with rescale_grad + before updating. Often choose to be 1.0/batch_size. + bias_lr (float): Non-negative learning rate for the bias term. + linear_lr (float): Non-negative learning rate for linear terms. + factors_lr (float): Noon-negative learning rate for factorization terms. + bias_wd (float): Non-negative weight decay for the bias term. + linear_wd (float): Non-negative weight decay for linear terms. + factors_wd (float): Non-negative weight decay for factorization terms. + bias_init_method (string): Initialization method for the bias term: 'normal', 'uniform' or 'constant'. + bias_init_scale (float): Non-negative range for initialization of the bias term that takes + effect when bias_init_method parameter is 'uniform' + bias_init_sigma (float): Non-negative standard deviation for initialization of the bias term that takes + effect when bias_init_method parameter is 'normal'. + bias_init_value (float): Initial value of the bias term that takes effect + when bias_init_method parameter is 'constant'. + linear_init_method (string): Initialization method for linear term: 'normal', 'uniform' or 'constant'. + linear_init_scale (float): Non-negative range for initialization of linear terms that takes + effect when linear_init_method parameter is 'uniform'. + linear_init_sigma (float): Non-negative standard deviation for initialization of linear terms that takes + effect when linear_init_method parameter is 'normal'. + linear_init_value (float): Initial value of linear terms that takes effect + when linear_init_method parameter is 'constant'. + factors_init_method (string): Initialization method for factorization term: 'normal', + 'uniform' or 'constant'. + factors_init_scale (float): Non-negative range for initialization of factorization terms that takes + effect when factors_init_method parameter is 'uniform'. + factors_init_sigma (float): Non-negative standard deviation for initialization of factorization terms that + takes effect when factors_init_method parameter is 'normal'. + factors_init_value (float): Initial value of factorization terms that takes + effect when factors_init_method parameter is 'constant'. + **kwargs: base class keyword argument values. + """ + super(FactorizationMachines, self).__init__(role, train_instance_count, train_instance_type, **kwargs) + + self.num_factors = num_factors + self.predictor_type = predictor_type + self.epochs = epochs + self.clip_gradient = clip_gradient + self.eps = eps + self.rescale_grad = rescale_grad + self.bias_lr = bias_lr + self.linear_lr = linear_lr + self.factors_lr = factors_lr + self.bias_wd = bias_wd + self.linear_wd = linear_wd + self.factors_wd = factors_wd + self.bias_init_method = bias_init_method + self.bias_init_scale = bias_init_scale + self.bias_init_sigma = bias_init_sigma + self.bias_init_value = bias_init_value + self.linear_init_method = linear_init_method + self.linear_init_scale = linear_init_scale + self.linear_init_sigma = linear_init_sigma + self.linear_init_value = linear_init_value + self.factors_init_method = factors_init_method + self.factors_init_scale = factors_init_scale + self.factors_init_sigma = factors_init_sigma + self.factors_init_value = factors_init_value + + def create_model(self): + """Return a :class:`~sagemaker.amazon.FactorizationMachinesModel` referencing the latest + s3 model data produced by this Estimator.""" + + return FactorizationMachinesModel(self.model_data, self.role, sagemaker_session=self.sagemaker_session) + + +class FactorizationMachinesPredictor(RealTimePredictor): + """Performs binary-classification or regression prediction from input vectors. + + The implementation of :meth:`~sagemaker.predictor.RealTimePredictor.predict` in this + `RealTimePredictor` requires a numpy ``ndarray`` as input. The array should contain the + same number of columns as the feature-dimension of the data used to fit the model this + Predictor performs inference on. + + :meth:`predict()` returns a list of :class:`~sagemaker.amazon.record_pb2.Record` objects, one + for each row in the input ``ndarray``. The prediction is stored in the ``"score"`` + key of the ``Record.label`` field. + Please refer to the formats details described: https://docs.aws.amazon.com/sagemaker/latest/dg/fm-in-formats.html + """ + + def __init__(self, endpoint, sagemaker_session=None): + super(FactorizationMachinesPredictor, self).__init__(endpoint, + sagemaker_session, + serializer=numpy_to_record_serializer(), + deserializer=record_deserializer()) + + +class FactorizationMachinesModel(Model): + """Reference S3 model data created by FactorizationMachines estimator. Calling :meth:`~sagemaker.model.Model.deploy` + creates an Endpoint and returns :class:`FactorizationMachinesPredictor`.""" + + def __init__(self, model_data, role, sagemaker_session=None): + sagemaker_session = sagemaker_session or Session() + image = registry(sagemaker_session.boto_session.region_name) + "/" + FactorizationMachines.repo + super(FactorizationMachinesModel, self).__init__(model_data, + image, + role, + predictor_cls=FactorizationMachinesPredictor, + sagemaker_session=sagemaker_session) diff --git a/src/sagemaker/amazon/kmeans.py b/src/sagemaker/amazon/kmeans.py index d2abc0870c..d3fb5d670e 100644 --- a/src/sagemaker/amazon/kmeans.py +++ b/src/sagemaker/amazon/kmeans.py @@ -99,6 +99,9 @@ def create_model(self): s3 model data produced by this Estimator.""" return KMeansModel(self.model_data, self.role, self.sagemaker_session) + def fit(self, records, mini_batch_size=5000, **kwargs): + super(KMeans, self).fit(records, mini_batch_size, **kwargs) + def hyperparameters(self): """Return the SageMaker hyperparameters for training this KMeans Estimator""" hp = dict(force_dense='True') # KMeans requires this hp to fit on Record objects diff --git a/src/sagemaker/amazon/linear_learner.py b/src/sagemaker/amazon/linear_learner.py index ae7f5e52eb..af336a8e6b 100644 --- a/src/sagemaker/amazon/linear_learner.py +++ b/src/sagemaker/amazon/linear_learner.py @@ -23,6 +23,8 @@ class LinearLearner(AmazonAlgorithmEstimatorBase): repo = 'linear-learner:1' + DEFAULT_MINI_BATCH_SIZE = 1000 + binary_classifier_model_selection_criteria = hp('binary_classifier_model_selection_criteria', isin('accuracy', 'f1', 'precision_at_target_recall', 'recall_at_target_precision', 'cross_entropy_loss')) @@ -191,6 +193,13 @@ def create_model(self): return LinearLearnerModel(self, self.model_data, self.role, self.sagemaker_session) + def fit(self, records, mini_batch_size=None, **kwargs): + # mini_batch_size can't be greater than number of records or training job fails + default_mini_batch_size = min(self.DEFAULT_MINI_BATCH_SIZE, + max(1, int(records.num_records / self.train_instance_count))) + use_mini_batch_size = mini_batch_size or default_mini_batch_size + super(LinearLearner, self).fit(records, use_mini_batch_size, **kwargs) + class LinearLearnerPredictor(RealTimePredictor): """Performs binary-classification or regression prediction from input vectors. diff --git a/src/sagemaker/amazon/pca.py b/src/sagemaker/amazon/pca.py index 6e09a1abf5..7a23f60c7c 100644 --- a/src/sagemaker/amazon/pca.py +++ b/src/sagemaker/amazon/pca.py @@ -22,6 +22,8 @@ class PCA(AmazonAlgorithmEstimatorBase): repo = 'pca:1' + DEFAULT_MINI_BATCH_SIZE = 500 + num_components = hp(name='num_components', validate=lambda x: x > 0 and isinstance(x, int), validation_message='Value must be an integer greater than zero') algorithm_mode = hp(name='algorithm_mode', validate=lambda x: x in ['regular', 'stable', 'randomized'], @@ -86,6 +88,13 @@ def create_model(self): return PCAModel(self.model_data, self.role, sagemaker_session=self.sagemaker_session) + def fit(self, records, mini_batch_size=None, **kwargs): + # mini_batch_size is a required parameter + default_mini_batch_size = min(self.DEFAULT_MINI_BATCH_SIZE, + max(1, int(records.num_records / self.train_instance_count))) + use_mini_batch_size = mini_batch_size or default_mini_batch_size + super(PCA, self).fit(records, use_mini_batch_size, **kwargs) + class PCAPredictor(RealTimePredictor): """Transforms input vectors to lower-dimesional representations. diff --git a/tests/integ/test_factorization_machines.py b/tests/integ/test_factorization_machines.py new file mode 100644 index 0000000000..76fbb93ac7 --- /dev/null +++ b/tests/integ/test_factorization_machines.py @@ -0,0 +1,55 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +import gzip +import pickle +import sys + +import boto3 +import os + +import sagemaker +from sagemaker import FactorizationMachines, FactorizationMachinesModel +from sagemaker.utils import name_from_base +from tests.integ import DATA_DIR, REGION +from tests.integ.timeout import timeout, timeout_and_delete_endpoint_by_name + + +def test_factorization_machines(): + + with timeout(minutes=15): + sagemaker_session = sagemaker.Session(boto_session=boto3.Session(region_name=REGION)) + data_path = os.path.join(DATA_DIR, 'one_p_mnist', 'mnist.pkl.gz') + pickle_args = {} if sys.version_info.major == 2 else {'encoding': 'latin1'} + + # Load the data into memory as numpy arrays + with gzip.open(data_path, 'rb') as f: + train_set, _, _ = pickle.load(f, **pickle_args) + + fm = FactorizationMachines(role='SageMakerRole', train_instance_count=1, + train_instance_type='ml.c4.xlarge', + num_factors=10, predictor_type='regressor', + epochs=2, clip_gradient=1e2, eps=0.001, rescale_grad=1.0/100, + sagemaker_session=sagemaker_session, base_job_name='test-fm') + + # training labels must be 'float32' + fm.fit(fm.record_set(train_set[0][:200], train_set[1][:200].astype('float32'))) + + endpoint_name = name_from_base('fm') + with timeout_and_delete_endpoint_by_name(endpoint_name, sagemaker_session, minutes=20): + model = FactorizationMachinesModel(fm.model_data, role='SageMakerRole', sagemaker_session=sagemaker_session) + predictor = model.deploy(1, 'ml.c4.xlarge', endpoint_name=endpoint_name) + result = predictor.predict(train_set[0][:10]) + + assert len(result) == 10 + for record in result: + assert record.label["score"] is not None diff --git a/tests/unit/test_fm.py b/tests/unit/test_fm.py new file mode 100644 index 0000000000..2854c09164 --- /dev/null +++ b/tests/unit/test_fm.py @@ -0,0 +1,96 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +import pytest +from mock import Mock + +from sagemaker.amazon.factorization_machines import FactorizationMachines +from sagemaker.amazon.amazon_estimator import registry + + +COMMON_TRAIN_ARGS = {'role': 'myrole', 'train_instance_count': 1, 'train_instance_type': 'ml.c4.xlarge'} +ALL_REQ_ARGS = dict({'num_factors': 3, 'predictor_type': 'regressor'}, **COMMON_TRAIN_ARGS) + +REGION = "us-west-2" +BUCKET_NAME = "Some-Bucket" + + +@pytest.fixture() +def sagemaker_session(): + boto_mock = Mock(name='boto_session', region_name=REGION) + sms = Mock(name='sagemaker_session', boto_session=boto_mock) + sms.boto_region_name = REGION + sms.default_bucket = Mock(name='default_bucket', return_value=BUCKET_NAME) + return sms + + +def test_init_required_positional(sagemaker_session): + fm = FactorizationMachines('myrole', 1, 'ml.c4.xlarge', 3, 'regressor', + sagemaker_session=sagemaker_session) + assert fm.role == 'myrole' + assert fm.train_instance_count == 1 + assert fm.train_instance_type == 'ml.c4.xlarge' + assert fm.num_factors == 3 + assert fm.predictor_type == 'regressor' + + +def test_init_required_named(sagemaker_session): + fm = FactorizationMachines(sagemaker_session=sagemaker_session, **ALL_REQ_ARGS) + + assert fm.role == COMMON_TRAIN_ARGS['role'] + assert fm.train_instance_count == COMMON_TRAIN_ARGS['train_instance_count'] + assert fm.train_instance_type == COMMON_TRAIN_ARGS['train_instance_type'] + assert fm.num_factors == ALL_REQ_ARGS['num_factors'] + assert fm.predictor_type == ALL_REQ_ARGS['predictor_type'] + + +def test_all_hyperparameters(sagemaker_session): + fm = FactorizationMachines(sagemaker_session=sagemaker_session, + epochs=2, clip_gradient=1e2, eps=0.001, rescale_grad=2.2, + bias_lr=0.01, linear_lr=0.002, factors_lr=0.0003, + bias_wd=0.0004, linear_wd=1.01, factors_wd=1.002, + bias_init_method='uniform', bias_init_scale=0.1, bias_init_sigma=0.05, + bias_init_value=2.002, linear_init_method='constant', linear_init_scale=0.02, + linear_init_sigma=0.003, linear_init_value=1.0, factors_init_method='normal', + factors_init_scale=1.101, factors_init_sigma=1.202, factors_init_value=1.303, + **ALL_REQ_ARGS) + assert fm.hyperparameters() == dict( + num_factors=str(ALL_REQ_ARGS['num_factors']), + predictor_type=ALL_REQ_ARGS['predictor_type'], + epochs='2', + clip_gradient='100.0', + eps='0.001', + rescale_grad='2.2', + bias_lr='0.01', + linear_lr='0.002', + factors_lr='0.0003', + bias_wd='0.0004', + linear_wd='1.01', + factors_wd='1.002', + bias_init_method='uniform', + bias_init_scale='0.1', + bias_init_sigma='0.05', + bias_init_value='2.002', + linear_init_method='constant', + linear_init_scale='0.02', + linear_init_sigma='0.003', + linear_init_value='1.0', + factors_init_method='normal', + factors_init_scale='1.101', + factors_init_sigma='1.202', + factors_init_value='1.303', + ) + + +def test_image(sagemaker_session): + fm = FactorizationMachines(sagemaker_session=sagemaker_session, **ALL_REQ_ARGS) + assert fm.train_image() == registry(REGION) + '/factorization-machines:1'