<jupyter_start><jupyter_text>MNIST Tensorflow training optionsThe **SageMaker Python SDK** helps you deploy your models for training and hosting in optimized, productions ready containers in SageMaker. The SageMaker Python SDK is easy to use, modular, extensible and compatible with TensorFlow and MXNet. This tutorial focuses on how to create a convolutional neural network model to train the [MNIST dataset](http://yann.lecun.com/exdb/mnist/) using **TensorFlow training**.  Set up the environment<jupyter_code>import os
import sagemaker
from sagemaker import get_execution_role
import project_path
from lib import utils
import boto3

sagemaker_session = sagemaker.Session()

role = get_execution_role()

# use the region-specific sample data bucket
region = boto3.Session().region_name
bucket = '{{s3_workshop_bucket}}'<jupyter_output><empty_output><jupyter_text>Download the MNIST dataset<jupyter_code>from tensorflow.contrib.learn.python.learn.datasets import mnist
import tensorflow as tf

data_sets = mnist.read_data_sets('data', dtype=tf.uint8, reshape=False, validation_size=5000)

utils.convert_to(data_sets.train, 'train', 'data')
utils.convert_to(data_sets.validation, 'validation', 'data')
utils.convert_to(data_sets.test, 'test', 'data')<jupyter_output><empty_output><jupyter_text>Upload the dataWe use the ```sagemaker.Session.upload_data``` function to upload our datasets to an S3 location. The return value inputs identifies the location -- we will use this later when we start the training job.<jupyter_code>inputs = sagemaker_session.upload_data(bucket=bucket, path='data', key_prefix='data/DEMO-mnist')<jupyter_output><empty_output><jupyter_text>Construct a script for distributed training Here is the full code for the network model:<jupyter_code>!pygmentize '../scripts/mnist.py'<jupyter_output><empty_output><jupyter_text>Create a training job using the sagemaker.TensorFlow estimator The script here is and adaptation of the [TensorFlow MNIST example](https://github.com/tensorflow/models/tree/master/official/mnist). It provides a ```model_fn(features, labels, mode)```, which is used for training, evaluation and inference.  A regular ```model_fn```A regular **```model_fn```** follows the pattern:1. [defines a neural network](https://github.com/tensorflow/models/blob/master/official/mnist/mnist.pyL96)- [applies the ```features``` in the neural network](https://github.com/tensorflow/models/blob/master/official/mnist/mnist.pyL178)- [if the ```mode``` is ```PREDICT```, returns the output from the neural network](https://github.com/tensorflow/models/blob/master/official/mnist/mnist.pyL186)- [calculates the loss function comparing the output with the ```labels```](https://github.com/tensorflow/models/blob/master/official/mnist/mnist.pyL188)- [creates an optimizer and minimizes the loss function to improve the neural network](https://github.com/tensorflow/models/blob/master/official/mnist/mnist.pyL193)- [returns the output, optimizer and loss function](https://github.com/tensorflow/models/blob/master/official/mnist/mnist.pyL205) Writing a ```model_fn``` for distributed trainingWhen distributed training happens, the same neural network will be sent to the multiple training instances. Each instance will predict a batch of the dataset, calculate loss and minimize the optimizer. One entire loop of this process is called **training step**. Syncronizing training stepsA [global step](https://www.tensorflow.org/api_docs/python/tf/train/global_step) is a global variable shared between the instances. It's necessary for distributed training, so the optimizer will keep track of the number of **training steps** between runs: ```pythontrain_op = optimizer.minimize(loss, tf.train.get_or_create_global_step())```That is the only required change for distributed training!<jupyter_code>from sagemaker.session import Session

# Location to save your custom code in tar.gz format.
custom_code_upload_location = 's3://{}/customcode/tensorflow_pipemode'.format(bucket)

# Location where results of model training are saved.
model_artifacts_location = 's3://{}/sagemaker/artifacts'.format(bucket)
%%time
from sagemaker.tensorflow import TensorFlow

mnist_estimator = TensorFlow(entry_point='../scripts/mnist.py',
                             role=role,
                             framework_version='1.11.0',
                             training_steps=1000, 
                             evaluation_steps=100,
                             output_path=model_artifacts_location,
                             train_instance_count=2,
                             train_instance_type='ml.c4.xlarge',
                             tags=[{"Key":"Project", "Value":"Tensorflow_Demo"}])

mnist_estimator.fit(inputs)<jupyter_output><empty_output><jupyter_text>The **```fit```** method will create a training job in two **ml.c4.xlarge** instances. The logs above will show the instances doing training, evaluation, and incrementing the number of **training steps**. In the end of the training, the training job will generate a saved model for TF serving.  Deploy the trained model to prepare for predictionsThe deploy() method creates an endpoint which serves prediction requests in real-time.<jupyter_code>mnist_predictor = mnist_estimator.deploy(initial_instance_count=1,
                                             instance_type='ml.m4.xlarge')<jupyter_output><empty_output><jupyter_text>Perform inferenceNow that we've trained a model, we're going to use it to perform inference with a SageMaker endpoint as well as a batch transform job. The request handling behavior of the Endpoint deployed during the transform job is determined by the `mnist.py` script we looked at earlier.  Invoking the endpoint<jupyter_code>%matplotlib inline
import matplotlib.pyplot as plt
plt.rcParams["figure.figsize"] = (2,10)

def show_digit(arr):
    two_d = (np.reshape(arr, (28, 28)) * 255).astype(np.uint8)
    plt.imshow(two_d, interpolation='nearest')
    return plt
import numpy as np
from tensorflow.examples.tutorials.mnist import input_data

mnist = input_data.read_data_sets("/tmp/data/", one_hot=True)

for i in range(5):
    data = mnist.test.images[i].tolist()
    tensor_proto = tf.make_tensor_proto(values=np.asarray(data), shape=[1, len(data)], dtype=tf.float32)
    predict_response = mnist_predictor.predict(tensor_proto)
    
    print("========================================")
    label = np.argmax(mnist.test.labels[i])
    print("label is {}".format(label))
    show_digit(mnist.test.images[i]).show()
    prediction = predict_response['outputs']['classes']['int64_val'][0]
    print("prediction is {}".format(prediction))<jupyter_output><empty_output><jupyter_text>SageMaker's transformer classAfter training, we use our TensorFlow estimator object to create a `Transformer` by invoking the `transformer()` method. This method takes arguments for configuring our options with the batch transform job; these do not need to be the same values as the one we used for the training job. The method also creates a SageMaker Model to be used for the batch transform jobs.The `Transformer` class is responsible for running batch transform jobs, which will deploy the trained model to an endpoint and send requests for performing inference.<jupyter_code>output_path = 's3://{}/{}'.format(bucket, 'sagemaker/batch-transform')
transformer = mnist_estimator.transformer(instance_count=1, instance_type='ml.m4.xlarge', output_path=output_path)<jupyter_output><empty_output><jupyter_text>Run a batch transform jobFor our batch transform job, we're going to use input data that contains 1000 MNIST images, located in the public SageMaker sample data S3 bucket. To create the batch transform job, we simply call `transform()` on our transformer with information about the input data.<jupyter_code>input_bucket_name = 'sagemaker-sample-data-{}'.format(region)
input_file_path = 'batch-transform/mnist-1000-samples'

transformer.transform('s3://{}/{}'.format(input_bucket_name, input_file_path), content_type='text/csv')<jupyter_output><empty_output><jupyter_text>Now we wait for the batch transform job to complete. We have a convenience method, `wait()`, that will block until the batch transform job has completed. We can call that here to see if the batch transform job is still running; the cell will finish running when the batch transform job has completed.<jupyter_code>transformer.wait()<jupyter_output><empty_output><jupyter_text>Download the resultsThe batch transform job uploads its predictions to S3. Since we did not specify `output_path` when creating the Transformer, one was generated based on the batch transform job name:<jupyter_code>print(transformer.output_path)<jupyter_output><empty_output><jupyter_text>Now let's download the first ten results from S3:<jupyter_code>import json
from six.moves.urllib import parse

import boto3

parsed_url = parse.urlparse(transformer.output_path)
bucket_name = parsed_url.netloc
prefix = parsed_url.path[1:]

s3 = boto3.resource('s3')

predictions = []
for i in range(10):
    file_key = '{}/data-{}.csv.out'.format(prefix, i)

    output_obj = s3.Object(bucket_name, file_key)
    output = output_obj.get()["Body"].read().decode('utf-8')

    predictions.extend(json.loads(output)['outputs']['classes']['int64Val'])<jupyter_output><empty_output><jupyter_text>For demonstration purposes, we're also going to download the corresponding original input data so that we can see how the model did with its predictions.<jupyter_code>import os

import matplotlib.pyplot as plt
from numpy import genfromtxt

plt.rcParams['figure.figsize'] = (2,10)

def show_digit(img, caption='', subplot=None):
    if subplot == None:
        _,(subplot) = plt.subplots(1,1)
    imgr = img.reshape((28,28))
    subplot.axis('off')
    subplot.imshow(imgr, cmap='gray')
    plt.title(caption)

tmp_dir = '/tmp/data'
if not os.path.exists(tmp_dir):
    os.makedirs(tmp_dir)

for i in range(10):
    input_file_name = 'data-{}.csv'.format(i)
    input_file_key = '{}/{}'.format(input_file_path, input_file_name)
    
    s3.Bucket(input_bucket_name).download_file(input_file_key, os.path.join(tmp_dir, input_file_name))
    input_data = genfromtxt(os.path.join(tmp_dir, input_file_name), delimiter=',')

    show_digit(input_data)<jupyter_output><empty_output><jupyter_text>Here, we can see the original labels are:```7, 2, 1, 0, 4, 1, 4, 9, 5, 9```Now let's print out the predictions to compare:<jupyter_code>print(', '.join(predictions))<jupyter_output><empty_output><jupyter_text>Deleting the endpoint<jupyter_code>sagemaker.Session().delete_endpoint(mnist_predictor.endpoint)<jupyter_output><empty_output><jupyter_text>Deploy the trained model using NeoNow the model is ready to be compiled by Neo to be optimized for our hardware of choice. We are using the  ``TensorFlowEstimator.compile_model`` method to do this. For this example, our target hardware is ``'ml_c5'``. You can changed these to other supported target hardware if you prefer. [Amazon SageMaker Neo Blog](https://aws.amazon.com/blogs/aws/amazon-sagemaker-neo-train-your-machine-learning-models-once-run-them-anywhere/) for more information. Compiling the modelThe ``input_shape`` is the definition for the model's input tensor and ``output_path`` is where the compiled model will be stored in S3. **Important. If the following command result in a permission error, scroll up and locate the value of execution role returned by `get_execution_role()`. The role must have access to the S3 bucket specified in ``output_path``.**<jupyter_code>!pygmentize ../scripts/mnist.py<jupyter_output><empty_output><jupyter_text>The script here is and adaptation of the [TensorFlow MNIST example](https://github.com/tensorflow/models/tree/master/official/mnist). It provides a ```model_fn(features, labels, mode)```, which is used for training, evaluation and inference. See [TensorFlow MNIST distributed training notebook](https://github.com/awslabs/amazon-sagemaker-examples/blob/master/sagemaker-python-sdk/tensorflow_distributed_mnist/tensorflow_distributed_mnist.ipynb) for more details about the training script.At the end of the training script, there are two additional functions, to be used with Neo Deep Learning Runtime:* `neo_preprocess(payload, content_type)`: Function that takes in the payload and Content-Type of each incoming request and returns a NumPy array* `neo_postprocess(result)`: Function that takes the prediction results produced by Deep Learining Runtime and returns the response body<jupyter_code>output_path = '/'.join(mnist_estimator.output_path.split('/')[:-1])
optimized_estimator = mnist_estimator.compile_model(target_instance_family='ml_c5', 
                              input_shape={'data':[1, 784]},  # Batch size 1, 3 channels, 224x224 Images.
                              output_path=output_path,
                              framework='tensorflow', framework_version='1.11.0')<jupyter_output><empty_output><jupyter_text>Deploying the compiled model<jupyter_code>optimized_predictor = optimized_estimator.deploy(initial_instance_count = 1,
                                                 instance_type = 'ml.c5.4xlarge')
# The neo_preprocess() function expects an image in the request body
# But the MNIST example data is saved as NumPy array.
# So we convert it to PNG before invoking the endpoint
def png_serializer(data):
    im = PIL.Image.fromarray(data.reshape((28,28))*255).convert('L')
    f = io.BytesIO()
    im.save(f, format='png')
    f.seek(0)
    return f.read()

optimized_predictor.content_type = 'application/x-image'
optimized_predictor.serializer = png_serializer<jupyter_output><empty_output><jupyter_text>Invoking the endpoint<jupyter_code>from tensorflow.examples.tutorials.mnist import input_data
from IPython import display
import PIL.Image
import io

mnist = input_data.read_data_sets("/tmp/data/", one_hot=True)

for i in range(10):
    data = mnist.test.images[i]
    print(data.shape)
    # Display image
    im = PIL.Image.fromarray(data.reshape((28,28))*255).convert('L')
    display.display(im)
    # Invoke endpoint with image
    predict_response = optimized_predictor.predict(data)
    
    print("========================================")
    label = np.argmax(mnist.test.labels[i])
    print("label is {}".format(label))
    prediction = predict_response
    print("prediction is {}".format(prediction))<jupyter_output><empty_output><jupyter_text>AWS Serverless Application Model (SAM) Deploy using SAM The `service.py` file in the zip file contains the code required to invoke the SageMaker endpoint.```pythonfrom __future__ import print_functionimport boto3from PIL import Imagefrom io import BytesIOimport base64import os import jsonimport os.pathimport sysimport urllibimport astimport numpy as np def handler(event, context):    print(event)    print(context)    body = json.loads(event['body'])    endpoint = os.environ['ENDPOINT_NAME']     print("%s" % (endpoint))    image_str = base64.b64decode(body['data'])    image = Image.open(BytesIO(image_str))    with BytesIO() as output:        with image as img:            img.save(output, 'PNG')        data = output.getvalue()    runtime = boto3.Session().client(service_name='sagemaker-runtime')    response = runtime.invoke_endpoint(EndpointName=endpoint, ContentType='application/x-image', Body=data)        result = response['Body'].read()    predictions = ast.literal_eval(result)    prediction = np.argmax(predictions)        return {        'prediction' : prediction    }``` In order to get Tensorflow installed in Lambda there is a compressed pack available in [GitHub Lambda Packs](https://github.com/ryfeus/lambda-packs). We will use the OpenCV and PIL zip to upload to our Lambda function. We modified the `service.py` file for our needs to call the endpoint.<jupyter_code>key = '<KEY>'
data = open(key, 'rb')
s3 = boto3.resource('s3')
prefix = os.path.join('sagemaker', 'lambda')
print(prefix)
s3.Bucket(bucket).put_object(Key=os.path.join(prefix, key), Body=data)

s3_lambda_code = 's3://{}/{}/{}'.format(bucket, prefix, key)
print('uploaded lambda function to location: {}'.format(s3_lambda_code))<jupyter_output><empty_output><jupyter_text>Get the Neo optimized endpoint from the estimator<jupyter_code>print(optimized_predictor.endpoint)<jupyter_output><empty_output><jupyter_text>Create the SAM yaml file to deploy our serviceThe yaml file below is the CloudFormation template needed to deploy our service. We will point to the CodeUri we uploaded above and created an environment variable `ENDPOINT_NAME` to pass into the Lambda function. Find the `{{optimized_predictor.endpoint}}` below and replace with the cell result above.<jupyter_code>%%writefile tensorflow-lambda.yaml

AWSTemplateFormatVersion: '2010-09-09'
Transform: 'AWS::Serverless-2016-10-31'
Description: Tensorflow MNIST Inference REST API
Resources:
  InferenceApi:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: s3://{{s3_workshop_bucket}}/sagemaker/lambda/opencv_pil.zip
      Handler: service.handler
      Runtime: python2.7
      MemorySize: 1024
      Timeout: 30
      Events:
        Endpoint:
          Type: Api
          Properties:
            Path: /predict
            Method: post
      Policies:
          # Give SageMaker Full Access to your Lambda Function
          - AmazonSageMakerFullAccess
      Environment:
        Variables:
            ENDPOINT_NAME: {{optimized_predictor.endpoint}} #Replace with result from cell above

Outputs:
  endpoint:
    Description: The API Gateway endpoint for invoking the API
    Value: !Sub 'https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com'<jupyter_output><empty_output><jupyter_text>With the yaml filed created we can now `package` out CloudFormation template to prepare it for deployment and finally call the `deploy` function on the CloudFormation service to build our API service for out Inference endpoint.<jupyter_code>!aws cloudformation package \
 --template-file tensorflow-lambda.yaml \
 --output-template-file tensorflow-lambda-out.yaml \
 --s3-bucket $s3_lambda_code
stack_name = "Tensorflow-Lambda-MNIST"

!aws cloudformation deploy \
--template-file tensorflow-lambda-out.yaml \
--stack-name $stack_name \
--capabilities CAPABILITY_IAM \
--region $region<jupyter_output><empty_output><jupyter_text>Get API Gateway Endpoint<jupyter_code>api_endpoint = !aws cloudformation describe-stacks --stack-name $stack_name --region $region | python -c 'import json,sys;obj=json.load(sys.stdin);print obj["Stacks"][0]["Outputs"][0]["OutputValue"];'
print(api_endpoint[0])<jupyter_output><empty_output><jupyter_text>EvaluateWe can now use this predictor to classify hand-written digits. Drawing into the image box loads the pixel data into a `data` variable in this notebook, which we can then pass to the `predictor`.<jupyter_code>from IPython.display import HTML
HTML(open("input.html").read())<jupyter_output><empty_output><jupyter_text>Save the output of the html reader to PNG<jupyter_code>%matplotlib inline
import numpy as np
import imageio
from PIL import Image

image = np.array([data], dtype=np.float32)
two_d = (np.reshape(image, (28, 28)) * 255).astype(np.uint8)
imageio.imwrite("../images/output.png", two_d)<jupyter_output><empty_output><jupyter_text>Using the requests library to make a request to the new API Gateway endpointIn this section we will take the PNG we saved locally and base64 encode it and send it to the API gateway endpoint. The gateway endpoint is deployed to the Prod stage and has a POST medthod at the /predict path. The Lambda function calls the Neo optimized hosted model and will use the `neo_preprocess` method of the model to convert the image into the appropriate input format.<jupyter_code>import requests
import json
import base64
import numpy as np
from io import BytesIO
import ast

url = api_endpoint[0] + '/Prod/predict'
print(url)

# prepare headers for http request
content_type = 'application/json'
headers = {'content-type': content_type}

file_name = "../images/output.png"
encoded_string = ""
with open(file_name, "rb") as image_file:
    encoded_string = base64.b64encode(image_file.read())

body = {
    "data": encoded_string
}
response = requests.post(url, data=json.dumps(body), headers=headers)
prediction = response.json()
print("prediction is {}".format(prediction['prediction']))<jupyter_output><empty_output><jupyter_text>Delete the CloudFormation template<jupyter_code>!aws cloudformation delete-stack --stack-name $stack_name<jupyter_output><empty_output><jupyter_text>Deleting endpoint<jupyter_code>sagemaker.Session().delete_endpoint(optimized_predictor.endpoint)<jupyter_output><empty_output><jupyter_text>Set up hyperparameter tuning job*Note, with the default setting below, the hyperparameter tuning job can take about 30 minutes to complete.*Now we will set up the hyperparameter tuning job using SageMaker Python SDK, following below steps:* Create an estimator to set up the TensorFlow training job* Define the ranges of hyperparameters we plan to tune, in this example, we are tuning "learning_rate"* Define the objective metric for the tuning job to optimize* Create a hyperparameter tuner with above setting, as well as tuning resource configurations  Similar to training a single TensorFlow job in SageMaker, we define our TensorFlow estimator passing in the TensorFlow script, IAM role, and (per job) hardware configuration.<jupyter_code>import boto3
from time import gmtime, strftime
from sagemaker.tensorflow import TensorFlow
from sagemaker.tuner import IntegerParameter, CategoricalParameter, ContinuousParameter, HyperparameterTuner

estimator = TensorFlow(entry_point='../scripts/mnist.py',
                  role=role,
                  framework_version='1.11.0',
                  training_steps=1000, 
                  evaluation_steps=100,
                  train_instance_count=1,
                  train_instance_type='ml.m4.xlarge',
                  base_job_name='DEMO-hpo-tensorflow')<jupyter_output><empty_output><jupyter_text>Once we've defined our estimator we can specify the hyperparameters we'd like to tune and their possible values.  We have three different types of hyperparameters.- Categorical parameters need to take one value from a discrete set.  We define this by passing the list of possible values to `CategoricalParameter(list)`- Continuous parameters can take any real number value between the minimum and maximum value, defined by `ContinuousParameter(min, max)`- Integer parameters can take any integer value between the minimum and maximum value, defined by `IntegerParameter(min, max)`*Note, if possible, it's almost always best to specify a value as the least restrictive type.  For example, tuning learning rate as a continuous value between 0.01 and 0.2 is likely to yield a better result than tuning as a categorical parameter with values 0.01, 0.1, 0.15, or 0.2.*<jupyter_code>hyperparameter_ranges = {'learning_rate': ContinuousParameter(0.01, 0.2)}<jupyter_output><empty_output><jupyter_text>Next we'll specify the objective metric that we'd like to tune and its definition, which includes the regular expression (Regex) needed to extract that metric from the CloudWatch logs of the training job. In this particular case, our script emits loss value and we will use it as the objective metric, we also set the objective_type to be 'minimize', so that hyperparameter tuning seeks to minize the objective metric when searching for the best hyperparameter setting. By default, objective_type is set to 'maximize'.<jupyter_code>objective_metric_name = 'loss'
objective_type = 'Minimize'
metric_definitions = [{'Name': 'loss',
                       'Regex': 'loss = ([0-9\\.]+)'}]<jupyter_output><empty_output><jupyter_text>Now, we'll create a `HyperparameterTuner` object, to which we pass:- The TensorFlow estimator we created above- Our hyperparameter ranges- Objective metric name and definition- Tuning resource configurations such as Number of training jobs to run in total and how many training jobs can be run in parallel.<jupyter_code>tuner = HyperparameterTuner(estimator,
                            objective_metric_name,
                            hyperparameter_ranges,
                            metric_definitions,
                            max_jobs=9,
                            max_parallel_jobs=3,
                            objective_type=objective_type)<jupyter_output><empty_output><jupyter_text>Launch hyperparameter tuning jobAnd finally, we can start our hyperprameter tuning job by calling `.fit()` and passing in the S3 path to our train and test dataset.After the hyperprameter tuning job is created, you should be able to describe the tuning job to see its progress in the next step, and you can go to SageMaker console->Jobs to check out the progress of the progress of the hyperparameter tuning job.<jupyter_code>tuner.fit(inputs)<jupyter_output><empty_output><jupyter_text>Let's just run a quick check of the hyperparameter tuning jobs status to make sure it started successfully.<jupyter_code>boto3.client('sagemaker').describe_hyper_parameter_tuning_job(
    HyperParameterTuningJobName=tuner.latest_tuning_job.job_name)['HyperParameterTuningJobStatus']<jupyter_output><empty_output><jupyter_text>Analyze tuning job results - after tuning job is completedPlease refer to "HPO_Analyze_TuningJob_Results.ipynb" to see example code to analyze the tuning job results.  Deploy the best modelNow that we have got the best model, we can deploy it to an endpoint. Please refer to other SageMaker sample notebooks or SageMaker documentation to see how to deploy a model.  Extra Credit: Training with SageMaker Pipe Mode and TensorFlow using the SageMaker Python SDKSageMaker Pipe Mode is an input mechanism for SageMaker training containers based on Linux named pipes. SageMaker makes the data available to the training container using named pipes, which allows data to be downloaded from S3 to the container while training is running. For larger datasets, this dramatically improves the time to start training, as the data does not need to be first downloaded to the container. To learn more about pipe mode, please consult the AWS documentation at: https://docs.aws.amazon.com/sagemaker/latest/dg/your-algorithms-training-algo.htmlyour-algorithms-training-algo-running-container-trainingdata.In this tutorial, we show you how to train a tf.estimator using data read with SageMaker Pipe Mode. We'll use the SageMaker `PipeModeDataset` class - a special TensorFlow `Dataset` built specifically to read from SageMaker Pipe Mode data. This `Dataset` is available in our TensorFlow containers for TensorFlow versions 1.7.0 and up. It's also open-sourced at https://github.com/aws/sagemaker-tensorflow-extensions and can be built into custom TensorFlow images for use in SageMaker. Although you can also build the PipeModeDataset into your own containers, in this tutorial we'll show how you can use the PipeModeDataset by launching training from the SageMaker Python SDK. The SageMaker Python SDK helps you deploy your models for training and hosting in optimized, production ready containers in SageMaker. The SageMaker Python SDK is easy to use, modular, extensible and compatible with TensorFlow and MXNet. Different collections of S3 files can be made available to the training container while it's running. These are referred to as "channels" in SageMaker. In this example, we use two channels - one for training data and one for evaluation data. Each channel is mapped to S3 files from different directories. The SageMaker PipeModeDataset knows how to read from the named pipes for each channel given just the channel name. When we launch SageMaker training we tell SageMaker what channels we have and where in S3 to read the data for each channel.  Complete training source code In this section of the tutorial we train a TensorFlow LinearClassifier using pipe mode data. The TensorFlow training script is contained in following file:<jupyter_code>!pygmentize "../scripts/pipemode.py"<jupyter_output><empty_output><jupyter_text>The above script implements all the functions required for a sagemaker tensorflow training script (See: [Preparing TensorFlow Training Script](https://github.com/aws/sagemaker-python-sdk/blob/master/src/sagemaker/tensorflow/README.rstpreparing-the-tensorflow-training-script)).   Using a PipeModeDataset in an input_fnTo train an estimator using a Pipe Mode channel, we must construct an input_fn that reads from the channel. To do this, we use the SageMaker PipeModeDataset. This is a TensorFlow Dataset specifically created to read from a SageMaker Pipe Mode channel. A PipeModeDataset is a fully-featured TensorFlow Dataset and can be used in exactly the same ways as a regular TensorFlow Dataset can be used.The training and evaluation data used in this tutorial is synthetic. It contains a series of records stored in a TensorFlow Example protobuf object. Each record contains a numeric class label and an array of 1024 floating point numbers. Each array is sampled from a multi-dimensional Gaussian distribution with a class-specific mean. This means it is possible to learn a model using a TensorFlow Linear classifier which can classify examples well. Each record is separated using RecordIO encoding (though the PipeModeDataset class also supports the TFRecord format as well). The training and evaluation data were produced using the benchmarking source code in the sagemaker-tensorflow-extensions benchmarking sub-package. If you want to investigate this further, please visit the GitHub repository for sagemaker-tensorflow-extensions at https://github.com/aws/sagemaker-tensorflow-extensions. The following example code shows how to use a PipeModeDataset in an input_fn.```pythonfrom sagemaker_tensorflow import PipeModeDatasetdef input_fn():     Simple example data - a labeled vector.    features = {        'data': tf.FixedLenFeature([], tf.string),        'labels': tf.FixedLenFeature([], tf.int64),    }         A function to parse record bytes to a labeled vector record    def parse(record):        parsed = tf.parse_single_example(record, features)        return ({            'data': tf.decode_raw(parsed['data'], tf.float64)        }, parsed['labels'])     Construct a PipeModeDataset reading from a 'training' channel, using     the TF Record encoding.    ds = PipeModeDataset(channel='training', record_format='TFRecord')     The PipeModeDataset is a TensorFlow Dataset and provides standard Dataset methods    ds = ds.repeat(20)    ds = ds.prefetch(10)    ds = ds.map(parse, num_parallel_calls=10)    ds = ds.batch(64)        return ds```  Running training using the Python SDKWe can use the SDK to run our local training script on SageMaker infrastructure.1. Pass the path to the pipemode.py file, which contains the functions for defining your estimator, to the sagemaker.TensorFlow init method.2. Pass the S3 location that we uploaded our data to previously to the fit() method.<jupyter_code>from sagemaker.tensorflow import TensorFlow

tensorflow = TensorFlow(entry_point='../scripts/pipemode.py',
                        role=role,
                        framework_version='1.11.0',
                        input_mode='Pipe',
                        output_path=model_artifacts_location,
                        code_location=custom_code_upload_location,
                        train_instance_count=1,
                        training_steps=1000,
                        evaluation_steps=100,
                        train_instance_type='ml.c4.xlarge')<jupyter_output><empty_output><jupyter_text>After we've created the SageMaker Python SDK TensorFlow object, we can call fit to launch TensorFlow training:<jupyter_code>%%time
import boto3

# use the region-specific sample data bucket
region = boto3.Session().region_name

# training data that is already partitioned
train_data = 's3://sagemaker-sample-data-{}/tensorflow/pipe-mode/train'.format(region)
eval_data = 's3://sagemaker-sample-data-{}/tensorflow/pipe-mode/eval'.format(region)

tensorflow.fit({'train':train_data, 'eval':eval_data})<jupyter_output><empty_output><jupyter_text>After ``fit`` returns, you've successfully trained a TensorFlow LinearClassifier using SageMaker pipe mode! The TensorFlow model data will be stored in '``s3:///artifacts``' - where '````' is the name of the bucket you supplied earlier.  Final cleanup<jupyter_code>!aws s3 rm s3://$bucket<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.08876649949232332, 'fraction_numerical': 0.009384326636103505, 'mean_word_length': 4.755622454400567, 'pattern_counts': {'":': 3, '<': 158, '<?xml version=': 0, '>': 159, 'https://': 17, 'lorem ipsum': 0, 'www.': 1, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 11, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '39', 'dir': 'jupyter-structured-clean-dedup', 'id': '302157', 'license': 'MIT', 'n_tokens_mistral': 9383, 'n_tokens_neox': 8997, 'n_words': 3147, 'path': 'notebooks/tensorflow_mnist.ipynb', 'repo_name': 'randyridgley/sagemaker-tensorflow', 'size': '32510'}