diff --git a/LICENSE.txt b/LICENSE.txt
index d645695673..6ff2c6fd00 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -200,3 +200,21 @@
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.
+
+ ======================================================================================
+ Amazon SageMaker Examples Subcomponents:
+
+ The Amazon SageMaker Examples project contains subcomponents with separate
+ copyright notices and license terms. Your use of the source code for the
+ these subcomponents is subject to the terms and conditions of the following
+ licenses. See licenses/ for text of these licenses.
+
+ If a folder hierarchy is listed as subcomponent, separate listings of
+ further subcomponents (files or folder hierarchies) part of the hierarchy
+ take precedence.
+
+ =======================================================================================
+ 2-clause BSD license
+ =======================================================================================
+ _static/kendrasearchtools.js
+ _templates/search.html
diff --git a/_static/kendrasearchtools.js b/_static/kendrasearchtools.js
new file mode 100644
index 0000000000..f2d47ef889
--- /dev/null
+++ b/_static/kendrasearchtools.js
@@ -0,0 +1,692 @@
+/*
+ * kendrasearchtools.js
+ * ~~~~~~~~~~~~~~~~
+ *
+ * A modification of searchtools.js (https://github.com/sphinx-doc/sphinx/blob/275d9/sphinx/themes/basic/static/searchtools.js)
+ * where the default full-text search implemented in searchtools.js is replaced with AWS Kendra searching over multiple
+ * websites. The default full-text search is still kept and implemented as a fallback in the case that the Kendra search doesn't work.
+ *
+ * :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS.
+ * :license: BSD, see LICENSE for details.
+ *
+ */
+
+if (!Scorer) {
+ /**
+ * Simple result scoring code.
+ */
+ var Scorer = {
+ // Implement the following function to further tweak the score for each result
+ // The function takes a result array [filename, title, anchor, descr, score]
+ // and returns the new score.
+ /*
+ score: function(result) {
+ return result[4];
+ },
+ */
+
+ // query matches the full name of an object
+ objNameMatch: 11,
+ // or matches in the last dotted part of the object name
+ objPartialMatch: 6,
+ // Additive scores depending on the priority of the object
+ objPrio: {0: 15, // used to be importantResults
+ 1: 5, // used to be objectResults
+ 2: -5}, // used to be unimportantResults
+ // Used when the priority is not in the mapping.
+ objPrioDefault: 0,
+
+ // query found in title
+ title: 15,
+ partialTitle: 7,
+ // query found in terms
+ term: 5,
+ partialTerm: 2
+ };
+}
+
+if (!splitQuery) {
+ function splitQuery(query) {
+ return query.split(/\s+/);
+ }
+}
+
+/**
+ * default rtd search (used as fallback)
+ */
+var Search = {
+
+ _index : null,
+ _queued_query : null,
+ _pulse_status : -1,
+
+ htmlToText : function(htmlString) {
+ var virtualDocument = document.implementation.createHTMLDocument('virtual');
+ var htmlElement = $(htmlString, virtualDocument);
+ htmlElement.find('.headerlink').remove();
+ docContent = htmlElement.find('[role=main]')[0];
+ if(docContent === undefined) {
+ console.warn("Content block not found. Sphinx search tries to obtain it " +
+ "via '[role=main]'. Could you check your theme or template.");
+ return "";
+ }
+ return docContent.textContent || docContent.innerText;
+ },
+
+ init : function() {
+ var params = $.getQueryParameters();
+ if (params.q) {
+ var query = params.q[0];
+ $('input[name="q"]')[0].value = query;
+ // this.performSearch(query);
+ }
+ },
+
+ loadIndex : function(url) {
+ $.ajax({type: "GET", url: url, data: null,
+ dataType: "script", cache: true,
+ complete: function(jqxhr, textstatus) {
+ if (textstatus != "success") {
+ document.getElementById("searchindexloader").src = url;
+ }
+ }});
+ },
+
+ setIndex : function(index) {
+ var q;
+ this._index = index;
+ if ((q = this._queued_query) !== null) {
+ this._queued_query = null;
+ Search.query(q);
+ }
+ },
+
+ hasIndex : function() {
+ return this._index !== null;
+ },
+
+ deferQuery : function(query) {
+ this._queued_query = query;
+ },
+
+ stopPulse : function() {
+ this._pulse_status = 0;
+ },
+
+ startPulse : function() {
+ if (this._pulse_status >= 0)
+ return;
+ function pulse() {
+ var i;
+ Search._pulse_status = (Search._pulse_status + 1) % 4;
+ var dotString = '';
+ for (i = 0; i < Search._pulse_status; i++)
+ dotString += '.';
+ Search.dots.text(dotString);
+ if (Search._pulse_status > -1)
+ window.setTimeout(pulse, 500);
+ }
+ pulse();
+ },
+
+ /**
+ * perform a search for something (or wait until index is loaded)
+ */
+ performSearch : function(query) {
+ // create the required interface elements
+ this.out = $('#search-results');
+ this.title = $('#search-results h2:first'); // $('
').appendTo(this.out);
+ this.out.css("margin", "auto");
+
+ $('#search-progress').text(_('Preparing search...'));
+ this.startPulse();
+
+ this.query(query, 1)
+ },
+
+};
+
+$(document).ready(function() {
+ KendraSearch.init();
+});
diff --git a/_static/pagination.css b/_static/pagination.css
new file mode 100644
index 0000000000..7584510574
--- /dev/null
+++ b/_static/pagination.css
@@ -0,0 +1,17 @@
+.pagination {
+ display: inline-block;
+}
+
+.pagination a {
+ color: black;
+ float: left;
+ padding: 8px 16px;
+ text-decoration: none;
+}
+
+.pagination a.active {
+ background-color: #2a80b9;
+ color: white;
+}
+
+.pagination a:hover:not(.active) {background-color: #ddd;}
\ No newline at end of file
diff --git a/_static/search_accessories.css b/_static/search_accessories.css
new file mode 100644
index 0000000000..c7e09e1f06
--- /dev/null
+++ b/_static/search_accessories.css
@@ -0,0 +1,29 @@
+.example-badge {
+ background-color: #c63340;
+ color: white;
+ padding: 0.25rem 0.5rem;
+ text-align: center;
+ border-radius: 5px;
+ font-size: 0.8rem;
+ display: inline-block;
+}
+
+.aws-doc-badge {
+ background-color: #e18b50;
+ color: white;
+ padding: 0.25rem 0.5rem;
+ text-align: center;
+ border-radius: 5px;
+ font-size: 0.8rem;
+ display: inline-block;
+}
+
+.sdk-doc-badge {
+ background-color: #4c968f;
+ color: white;
+ padding: 0.25rem 0.5rem;
+ text-align: center;
+ border-radius: 5px;
+ font-size: 0.8rem;
+ display: inline-block;
+}
\ No newline at end of file
diff --git a/_templates/search.html b/_templates/search.html
new file mode 100644
index 0000000000..93c01c7799
--- /dev/null
+++ b/_templates/search.html
@@ -0,0 +1,56 @@
+{#
+ basic/search.html
+ ~~~~~~~~~~~~~~~~~
+
+ Template for the search page.
+
+ :copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS.
+ :license: BSD, see https://github.com/sphinx-doc/sphinx/blob/master/LICENSE for details.
+#}
+{%- extends "layout.html" %}
+{% set title = _('Search') %}
+{% set display_vcs_links = False %}
+{%- block scripts %}
+ {{ super() }}
+
+
+{%- endblock %}
+{% block footer %}
+{# this is used when loading the search index using $.ajax fails,
+ such as on Chrome for documents on localhost #}
+
+
+{{ super() }}
+{% endblock %}
+{% block body %}
+
+
+{% if search_performed %}
+ {# Translators: Search is a noun, not a verb #}
+
{{ _('Search Results') }}
+ {% if not search_results %}
+
{{ _('Your search did not match any documents. Please make sure that all words are spelled correctly and that you\'ve used the correct terminology.') }}
+ {% endif %}
+{% endif %}
+
+{% if search_results %}
+
+ {% for href, caption, context in search_results %}
+
+{% endblock %}
diff --git a/advanced_functionality/multi_model_catboost/container/Dockerfile b/advanced_functionality/multi_model_catboost/container/Dockerfile
index 089390df06..4e05fca116 100644
--- a/advanced_functionality/multi_model_catboost/container/Dockerfile
+++ b/advanced_functionality/multi_model_catboost/container/Dockerfile
@@ -16,7 +16,7 @@ RUN apt-get update && \
python3 \
vim \
&& rm -rf /var/lib/apt/lists/* \
- && curl -O https://bootstrap.pypa.io/pip/3.7/get-pip.py \
+ && curl -O https://bootstrap.pypa.io/pip/3.6/get-pip.py \
&& python3 get-pip.py
RUN update-alternatives --install /usr/bin/python python /usr/bin/python3 1
diff --git a/advanced_functionality/multi_model_catboost/multi_model_catboost.ipynb b/advanced_functionality/multi_model_catboost/multi_model_catboost.ipynb
index cc9c7f91a5..233f0966cf 100644
--- a/advanced_functionality/multi_model_catboost/multi_model_catboost.ipynb
+++ b/advanced_functionality/multi_model_catboost/multi_model_catboost.ipynb
@@ -469,7 +469,7 @@
"metadata": {},
"source": [
"### Invoke just one of models 1000 times \n",
- "Since the models will be in memory and loaded, these invocations will not have any latency \n"
+ "Since the models are in memory and loaded, these invocations should not have any latency \n"
]
},
{
diff --git a/advanced_functionality/pipe_bring_your_own/pipe_bring_your_own.ipynb b/advanced_functionality/pipe_bring_your_own/pipe_bring_your_own.ipynb
index 32e05c3714..746a55d1ef 100644
--- a/advanced_functionality/pipe_bring_your_own/pipe_bring_your_own.ipynb
+++ b/advanced_functionality/pipe_bring_your_own/pipe_bring_your_own.ipynb
@@ -203,6 +203,7 @@
"%%sh\n",
"REGION=$(aws configure get region)\n",
"account=$(aws sts get-caller-identity --query Account --output text)\n",
+ "docker login --username AWS --password $(aws ecr get-login-password --region us-west-2) 763104351884.dkr.ecr.us-west-2.amazonaws.com\n",
"aws ecr get-login-password --region ${REGION} | docker login --username AWS --password-stdin ${account}.dkr.ecr.${REGION}.amazonaws.com"
]
},
diff --git a/advanced_functionality/scikit_bring_your_own/container/decision_trees/train b/advanced_functionality/scikit_bring_your_own/container/decision_trees/train
index 8654139ed8..1be2e1eea6 100755
--- a/advanced_functionality/scikit_bring_your_own/container/decision_trees/train
+++ b/advanced_functionality/scikit_bring_your_own/container/decision_trees/train
@@ -44,7 +44,7 @@ def train():
'This usually indicates that the channel ({}) was incorrectly specified,\n' +
'the data specification in S3 was incorrectly specified or the role specified\n' +
'does not have permission to access the data.').format(training_path, channel_name))
- raw_data = [ pd.read_csv(file, header=None) for file in input_files ]
+ raw_data = [ pd.read_csv(file, header=None) for file in input_files if file.endswith(".csv")]
train_data = pd.concat(raw_data)
# labels are in the first column
diff --git a/advanced_functionality/scikit_bring_your_own/scikit_bring_your_own.ipynb b/advanced_functionality/scikit_bring_your_own/scikit_bring_your_own.ipynb
index 9fab0f8d5b..d6c046ce83 100644
--- a/advanced_functionality/scikit_bring_your_own/scikit_bring_your_own.ipynb
+++ b/advanced_functionality/scikit_bring_your_own/scikit_bring_your_own.ipynb
@@ -276,7 +276,7 @@
"# Build the docker image locally with the image name and then push it to ECR\n",
"# with the full name.\n",
"\n",
- "docker build -t ${algorithm_name} .\n",
+ "docker build -t ${algorithm_name} .\n",
"docker tag ${algorithm_name} ${fullname}\n",
"\n",
"docker push ${fullname}"
@@ -315,9 +315,7 @@
{
"cell_type": "code",
"execution_count": null,
- "metadata": {
- "collapsed": true
- },
+ "metadata": {},
"outputs": [],
"source": [
"# S3 prefix\n",
@@ -347,9 +345,7 @@
{
"cell_type": "code",
"execution_count": null,
- "metadata": {
- "collapsed": true
- },
+ "metadata": {},
"outputs": [],
"source": [
"import sagemaker as sage\n",
diff --git a/conf.py b/conf.py
index bdef605979..05259a9c13 100644
--- a/conf.py
+++ b/conf.py
@@ -63,3 +63,8 @@
html_js_files = [
"https://a0.awsstatic.com/s_code/js/3.0/awshome_s_code.js",
]
+
+html_css_files = [
+ 'pagination.css',
+ 'search_accessories.css',
+]
diff --git a/hyperparameter_tuning/r_bring_your_own/Dockerfile b/hyperparameter_tuning/r_bring_your_own/Dockerfile
index 8f441f5113..5ca1d8798a 100644
--- a/hyperparameter_tuning/r_bring_your_own/Dockerfile
+++ b/hyperparameter_tuning/r_bring_your_own/Dockerfile
@@ -8,7 +8,31 @@ RUN apt-get -y update && apt-get install -y --no-install-recommends \
r-base-dev \
ca-certificates
-RUN R -e "install.packages(c('mda', 'plumber'), repos='https://cloud.r-project.org')"
+RUN R -e "install.packages(c('Rcpp', 'BH', 'R6', 'jsonlite', 'crayon'), repos='https://cloud.r-project.org')"
+
+RUN wget http://cran.r-project.org/src/contrib/Archive/stringi/stringi_1.2.4.tar.gz
+RUN R CMD INSTALL stringi_1.2.4.tar.gz
+
+RUN wget http://cran.r-project.org/src/contrib/Archive/rlang/rlang_0.2.2.tar.gz
+RUN R CMD INSTALL rlang_0.2.2.tar.gz
+
+RUN wget http://cran.r-project.org/src/contrib/Archive/magrittr/magrittr_1.5.tar.gz
+RUN R CMD INSTALL magrittr_1.5.tar.gz
+
+RUN wget http://cran.r-project.org/src/contrib/Archive/later/later_0.7.5.tar.gz
+RUN R CMD INSTALL later_0.7.5.tar.gz
+
+RUN wget http://cran.r-project.org/src/contrib/Archive/promises/promises_1.0.1.tar.gz
+RUN R CMD INSTALL promises_1.0.1.tar.gz
+
+RUN wget http://cran.r-project.org/src/contrib/Archive/httpuv/httpuv_1.4.4.2.tar.gz
+RUN R CMD INSTALL httpuv_1.4.4.2.tar.gz
+
+RUN wget http://cran.r-project.org/src/contrib/Archive/mda/mda_0.4-10.tar.gz
+RUN R CMD INSTALL mda_0.4-10.tar.gz
+
+RUN wget http://cran.r-project.org/src/contrib/Archive/plumber/plumber_0.4.6.tar.gz
+RUN R CMD INSTALL plumber_0.4.6.tar.gz
COPY mars.R /opt/ml/mars.R
COPY plumber.R /opt/ml/plumber.R
diff --git a/hyperparameter_tuning/r_bring_your_own/tune_r_bring_your_own.ipynb b/hyperparameter_tuning/r_bring_your_own/tune_r_bring_your_own.ipynb
index 7a8c873773..072a57b9a7 100644
--- a/hyperparameter_tuning/r_bring_your_own/tune_r_bring_your_own.ipynb
+++ b/hyperparameter_tuning/r_bring_your_own/tune_r_bring_your_own.ipynb
@@ -225,7 +225,7 @@
")\n",
"\n",
"estimator = sagemaker.estimator.Estimator(\n",
- " image_name=\"{}.dkr.ecr.{}.{}/rmars:latest\".format(account, region, domain),\n",
+ " image_uri=\"{}.dkr.ecr.{}.{}/rmars:latest\".format(account, region, domain),\n",
" role=role,\n",
" train_instance_count=1,\n",
" train_instance_type=\"ml.m4.xlarge\",\n",
diff --git a/licenses/2-CLAUSE-BSD b/licenses/2-CLAUSE-BSD
new file mode 100644
index 0000000000..b79ea4a15c
--- /dev/null
+++ b/licenses/2-CLAUSE-BSD
@@ -0,0 +1,28 @@
+2-Clause BSD License
+=====================
+
+Copyright (c) 2007-2020 by the Sphinx team (see AUTHORS file).
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+* Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
diff --git a/r_examples/r_api_serving_examples/API Serving Examples.ipynb b/r_examples/r_api_serving_examples/API Serving Examples.ipynb
deleted file mode 100644
index cb85c5bac6..0000000000
--- a/r_examples/r_api_serving_examples/API Serving Examples.ipynb
+++ /dev/null
@@ -1,610 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "# R API Serving Examples\n",
- "\n",
- "In this example, we demonstrate how to quickly compare the runtimes of three methods for serving a model from an R hosted REST API. The following SageMaker examples discuss each method in detail:\n",
- "\n",
- "* **Plumber**\n",
- " * Website: [https://www.rplumber.io/](https://www.rplumber.io)\n",
- " * SageMaker Example: [r_serving_with_plumber](../r_serving_with_plumber)\n",
- "* **RestRServe**\n",
- " * Website: [https://restrserve.org](https://restrserve.org)\n",
- " * SageMaker Example: [r_serving_with_restrserve](../r_serving_with_restrserve)\n",
- "* **FastAPI** (reticulated from Python)\n",
- " * Website: [https://fastapi.tiangolo.com](https://fastapi.tiangolo.com)\n",
- " * SageMaker Example: [r_serving_with_fastapi](../r_serving_with_fastapi)\n",
- " \n",
- "We will reuse the docker images from each of these examples. Each one is configured to serve a small XGBoost model which has already been trained on the classical Iris dataset."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Building Docker Images for Serving\n",
- "\n",
- "First, we will build each docker image from the provided SageMaker Examples."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Plumber Serving Image"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "scrolled": true
- },
- "outputs": [],
- "source": [
- "!cd .. && docker build -t r-plumber -f r_serving_with_plumber/Dockerfile r_serving_with_plumber"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### RestRServe Serving Image"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "scrolled": true
- },
- "outputs": [],
- "source": [
- "!cd .. && docker build -t r-restrserve -f r_serving_with_restrserve/Dockerfile r_serving_with_restrserve"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### FastAPI Serving Image"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "scrolled": true
- },
- "outputs": [],
- "source": [
- "!cd .. && docker build -t r-fastapi -f r_serving_with_fastapi/Dockerfile r_serving_with_fastapi"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Launch Serving Containers"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Next, we will launch each search container. The containers will be launch on the following ports to avoid port collisions on your local machine or SageMaker Notebook instance:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "ports = {\n",
- " \"plumber\": 5000,\n",
- " \"restrserve\": 5001,\n",
- " \"fastapi\": 5002,\n",
- "}"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "!bash launch.sh"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "!docker container list"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Define Simple Client"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "import requests\n",
- "from tqdm import tqdm\n",
- "import pandas as pd"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "def get_predictions(examples, instance=requests, port=5000):\n",
- " payload = {\"features\": examples}\n",
- " return instance.post(f\"http://127.0.0.1:{port}/invocations\", json=payload)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "def get_health(instance=requests, port=5000):\n",
- " instance.get(f\"http://127.0.0.1:{port}/ping\")"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Define Example Inputs"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Next, we define a example inputs from the classical [Iris](https://archive.ics.uci.edu/ml/datasets/iris) dataset.\n",
- "* Dua, D. and Graff, C. (2019). UCI Machine Learning Repository [http://archive.ics.uci.edu/ml]. Irvine, CA: University of California, School of Information and Computer Science."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "column_names = [\"Sepal.Length\", \"Sepal.Width\", \"Petal.Length\", \"Petal.Width\", \"Label\"]\n",
- "iris = pd.read_csv(\n",
- " \"s3://sagemaker-sample-files/datasets/tabular/iris/iris.data\", names=column_names\n",
- ")"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "iris_features = iris[[\"Sepal.Length\", \"Sepal.Width\", \"Petal.Length\", \"Petal.Width\"]]"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "example = iris_features.values[:1].tolist()"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "many_examples = iris_features.values[:100].tolist()"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Testing\n",
- "\n",
- "Now it's time to test how each API server performs under stress."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "We will test two use cases:\n",
- "* **New Requests**: In this scenario, we test how quickly the server can respond with predictions when each client request establishes a new connection with the server. This simulates the server's ability to handle real-time requests. We could make this more realistic by creating an asynchronous environment that tests the server's ability to fulfill concurrent rather than sequential requests.\n",
- "* **Keep Alive / Reuse Session**: In this scenario, we test how quickly the server can respond with predictions when each client request uses a session to keep its connection to the server alive between requests. This simulates the server's ability to handle sequential batch requests from the same client."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "For each of the two use cases, we will test the performance on following situations:\n",
- "\n",
- "* 1000 requests of a single example\n",
- "* 1000 requests of 100 examples\n",
- "* 1000 pings for health status"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## New Requests"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Plumber"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "# verify the prediction output\n",
- "get_predictions(example, port=ports[\"plumber\"]).json()"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "for i in tqdm(range(1000)):\n",
- " _ = get_predictions(example, port=ports[\"plumber\"])"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "for i in tqdm(range(1000)):\n",
- " _ = get_predictions(many_examples, port=ports[\"plumber\"])"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "for i in tqdm(range(1000)):\n",
- " get_health(port=ports[\"plumber\"])"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### RestRserve"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "# verify the prediction output\n",
- "get_predictions(example, port=ports[\"restrserve\"]).json()"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "for i in tqdm(range(1000)):\n",
- " _ = get_predictions(example, port=ports[\"restrserve\"])"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "for i in tqdm(range(1000)):\n",
- " _ = get_predictions(many_examples, port=ports[\"restrserve\"])"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "for i in tqdm(range(1000)):\n",
- " get_health(port=ports[\"restrserve\"])"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### FastAPI"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "# verify the prediction output\n",
- "get_predictions(example, port=ports[\"fastapi\"]).json()"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "for i in tqdm(range(1000)):\n",
- " _ = get_predictions(example, port=ports[\"fastapi\"])"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "for i in tqdm(range(1000)):\n",
- " _ = get_predictions(many_examples, port=ports[\"fastapi\"])"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "for i in tqdm(range(1000)):\n",
- " get_health(port=ports[\"fastapi\"])"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Keep Alive (Reuse Session)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Now, let's test how each one performs when each request reuses a session connection. "
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "# reuse the session for each post and get request\n",
- "instance = requests.Session()"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Plumber"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "for i in tqdm(range(1000)):\n",
- " _ = get_predictions(example, instance=instance, port=ports[\"plumber\"])"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "for i in tqdm(range(1000)):\n",
- " _ = get_predictions(many_examples, instance=instance, port=ports[\"plumber\"])"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "for i in tqdm(range(1000)):\n",
- " get_health(instance=instance, port=ports[\"plumber\"])"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### RestRserve"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "for i in tqdm(range(1000)):\n",
- " _ = get_predictions(example, instance=instance, port=ports[\"restrserve\"])"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "for i in tqdm(range(1000)):\n",
- " _ = get_predictions(many_examples, instance=instance, port=ports[\"restrserve\"])"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "for i in tqdm(range(1000)):\n",
- " get_health(instance=instance, port=ports[\"restrserve\"])"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### FastAPI"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "for i in tqdm(range(1000)):\n",
- " _ = get_predictions(example, instance=instance, port=ports[\"fastapi\"])"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "for i in tqdm(range(1000)):\n",
- " _ = get_predictions(many_examples, instance=instance, port=ports[\"fastapi\"])"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "for i in tqdm(range(1000)):\n",
- " get_health(instance=instance, port=ports[\"fastapi\"])"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Stop All Serving Containers"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Finally, we will shut down the serving containers we launched for the tests."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "!docker kill $(docker ps -q)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Conclusion"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "In this example, we demonstrated how to conduct a simple performance benchmark across three R model serving solutions. We leave the choice of serving solution up to the reader since in some cases it might be appropriate to customize the benchmark in the following ways:\n",
- "\n",
- "* Update the serving example to serve a specific model\n",
- "* Perform the tests across multiple instances types\n",
- "* Modify the serving example and client to test asynchronous requests.\n",
- "* Deploy the serving examples to SageMaker Endpoints to test within an autoscaling environment.\n",
- "\n",
- "For more information on serving your models in custom containers on SageMaker, please see our [support documentation](https://docs.aws.amazon.com/sagemaker/latest/dg/your-algorithms-inference-main.html) for the latest updates and best practices."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": []
- }
- ],
- "metadata": {
- "kernelspec": {
- "display_name": "conda_python3",
- "language": "python",
- "name": "conda_python3"
- },
- "language_info": {
- "codemirror_mode": {
- "name": "ipython",
- "version": 3
- },
- "file_extension": ".py",
- "mimetype": "text/x-python",
- "name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
- "version": "3.6.13"
- }
- },
- "nbformat": 4,
- "nbformat_minor": 4
-}
diff --git a/r_examples/r_api_serving_examples/iris.csv b/r_examples/r_api_serving_examples/iris.csv
deleted file mode 100644
index 8b6393099a..0000000000
--- a/r_examples/r_api_serving_examples/iris.csv
+++ /dev/null
@@ -1,151 +0,0 @@
-Sepal.Length,Sepal.Width,Petal.Length,Petal.Width,Species
-5.1,3.5,1.4,0.2,setosa
-4.9,3,1.4,0.2,setosa
-4.7,3.2,1.3,0.2,setosa
-4.6,3.1,1.5,0.2,setosa
-5,3.6,1.4,0.2,setosa
-5.4,3.9,1.7,0.4,setosa
-4.6,3.4,1.4,0.3,setosa
-5,3.4,1.5,0.2,setosa
-4.4,2.9,1.4,0.2,setosa
-4.9,3.1,1.5,0.1,setosa
-5.4,3.7,1.5,0.2,setosa
-4.8,3.4,1.6,0.2,setosa
-4.8,3,1.4,0.1,setosa
-4.3,3,1.1,0.1,setosa
-5.8,4,1.2,0.2,setosa
-5.7,4.4,1.5,0.4,setosa
-5.4,3.9,1.3,0.4,setosa
-5.1,3.5,1.4,0.3,setosa
-5.7,3.8,1.7,0.3,setosa
-5.1,3.8,1.5,0.3,setosa
-5.4,3.4,1.7,0.2,setosa
-5.1,3.7,1.5,0.4,setosa
-4.6,3.6,1,0.2,setosa
-5.1,3.3,1.7,0.5,setosa
-4.8,3.4,1.9,0.2,setosa
-5,3,1.6,0.2,setosa
-5,3.4,1.6,0.4,setosa
-5.2,3.5,1.5,0.2,setosa
-5.2,3.4,1.4,0.2,setosa
-4.7,3.2,1.6,0.2,setosa
-4.8,3.1,1.6,0.2,setosa
-5.4,3.4,1.5,0.4,setosa
-5.2,4.1,1.5,0.1,setosa
-5.5,4.2,1.4,0.2,setosa
-4.9,3.1,1.5,0.2,setosa
-5,3.2,1.2,0.2,setosa
-5.5,3.5,1.3,0.2,setosa
-4.9,3.6,1.4,0.1,setosa
-4.4,3,1.3,0.2,setosa
-5.1,3.4,1.5,0.2,setosa
-5,3.5,1.3,0.3,setosa
-4.5,2.3,1.3,0.3,setosa
-4.4,3.2,1.3,0.2,setosa
-5,3.5,1.6,0.6,setosa
-5.1,3.8,1.9,0.4,setosa
-4.8,3,1.4,0.3,setosa
-5.1,3.8,1.6,0.2,setosa
-4.6,3.2,1.4,0.2,setosa
-5.3,3.7,1.5,0.2,setosa
-5,3.3,1.4,0.2,setosa
-7,3.2,4.7,1.4,versicolor
-6.4,3.2,4.5,1.5,versicolor
-6.9,3.1,4.9,1.5,versicolor
-5.5,2.3,4,1.3,versicolor
-6.5,2.8,4.6,1.5,versicolor
-5.7,2.8,4.5,1.3,versicolor
-6.3,3.3,4.7,1.6,versicolor
-4.9,2.4,3.3,1,versicolor
-6.6,2.9,4.6,1.3,versicolor
-5.2,2.7,3.9,1.4,versicolor
-5,2,3.5,1,versicolor
-5.9,3,4.2,1.5,versicolor
-6,2.2,4,1,versicolor
-6.1,2.9,4.7,1.4,versicolor
-5.6,2.9,3.6,1.3,versicolor
-6.7,3.1,4.4,1.4,versicolor
-5.6,3,4.5,1.5,versicolor
-5.8,2.7,4.1,1,versicolor
-6.2,2.2,4.5,1.5,versicolor
-5.6,2.5,3.9,1.1,versicolor
-5.9,3.2,4.8,1.8,versicolor
-6.1,2.8,4,1.3,versicolor
-6.3,2.5,4.9,1.5,versicolor
-6.1,2.8,4.7,1.2,versicolor
-6.4,2.9,4.3,1.3,versicolor
-6.6,3,4.4,1.4,versicolor
-6.8,2.8,4.8,1.4,versicolor
-6.7,3,5,1.7,versicolor
-6,2.9,4.5,1.5,versicolor
-5.7,2.6,3.5,1,versicolor
-5.5,2.4,3.8,1.1,versicolor
-5.5,2.4,3.7,1,versicolor
-5.8,2.7,3.9,1.2,versicolor
-6,2.7,5.1,1.6,versicolor
-5.4,3,4.5,1.5,versicolor
-6,3.4,4.5,1.6,versicolor
-6.7,3.1,4.7,1.5,versicolor
-6.3,2.3,4.4,1.3,versicolor
-5.6,3,4.1,1.3,versicolor
-5.5,2.5,4,1.3,versicolor
-5.5,2.6,4.4,1.2,versicolor
-6.1,3,4.6,1.4,versicolor
-5.8,2.6,4,1.2,versicolor
-5,2.3,3.3,1,versicolor
-5.6,2.7,4.2,1.3,versicolor
-5.7,3,4.2,1.2,versicolor
-5.7,2.9,4.2,1.3,versicolor
-6.2,2.9,4.3,1.3,versicolor
-5.1,2.5,3,1.1,versicolor
-5.7,2.8,4.1,1.3,versicolor
-6.3,3.3,6,2.5,virginica
-5.8,2.7,5.1,1.9,virginica
-7.1,3,5.9,2.1,virginica
-6.3,2.9,5.6,1.8,virginica
-6.5,3,5.8,2.2,virginica
-7.6,3,6.6,2.1,virginica
-4.9,2.5,4.5,1.7,virginica
-7.3,2.9,6.3,1.8,virginica
-6.7,2.5,5.8,1.8,virginica
-7.2,3.6,6.1,2.5,virginica
-6.5,3.2,5.1,2,virginica
-6.4,2.7,5.3,1.9,virginica
-6.8,3,5.5,2.1,virginica
-5.7,2.5,5,2,virginica
-5.8,2.8,5.1,2.4,virginica
-6.4,3.2,5.3,2.3,virginica
-6.5,3,5.5,1.8,virginica
-7.7,3.8,6.7,2.2,virginica
-7.7,2.6,6.9,2.3,virginica
-6,2.2,5,1.5,virginica
-6.9,3.2,5.7,2.3,virginica
-5.6,2.8,4.9,2,virginica
-7.7,2.8,6.7,2,virginica
-6.3,2.7,4.9,1.8,virginica
-6.7,3.3,5.7,2.1,virginica
-7.2,3.2,6,1.8,virginica
-6.2,2.8,4.8,1.8,virginica
-6.1,3,4.9,1.8,virginica
-6.4,2.8,5.6,2.1,virginica
-7.2,3,5.8,1.6,virginica
-7.4,2.8,6.1,1.9,virginica
-7.9,3.8,6.4,2,virginica
-6.4,2.8,5.6,2.2,virginica
-6.3,2.8,5.1,1.5,virginica
-6.1,2.6,5.6,1.4,virginica
-7.7,3,6.1,2.3,virginica
-6.3,3.4,5.6,2.4,virginica
-6.4,3.1,5.5,1.8,virginica
-6,3,4.8,1.8,virginica
-6.9,3.1,5.4,2.1,virginica
-6.7,3.1,5.6,2.4,virginica
-6.9,3.1,5.1,2.3,virginica
-5.8,2.7,5.1,1.9,virginica
-6.8,3.2,5.9,2.3,virginica
-6.7,3.3,5.7,2.5,virginica
-6.7,3,5.2,2.3,virginica
-6.3,2.5,5,1.9,virginica
-6.5,3,5.2,2,virginica
-6.2,3.4,5.4,2.3,virginica
-5.9,3,5.1,1.8,virginica
diff --git a/r_examples/r_api_serving_examples/launch.sh b/r_examples/r_api_serving_examples/launch.sh
deleted file mode 100644
index e456602d35..0000000000
--- a/r_examples/r_api_serving_examples/launch.sh
+++ /dev/null
@@ -1,11 +0,0 @@
-#!/bin/bash
-
-echo "Launching Plumber"
-docker run -d --rm -p 5000:8080 r-plumber
-
-echo "Launching RestRServer"
-docker run -d --rm -p 5001:8080 r-restrserve
-
-echo "Launching FastAPI"
-docker run -d --rm -p 5002:8080 r-fastapi
-
diff --git a/r_examples/r_serving_with_fastapi/FastAPI Example.ipynb b/r_examples/r_serving_with_fastapi/FastAPI_Example.ipynb
similarity index 100%
rename from r_examples/r_serving_with_fastapi/FastAPI Example.ipynb
rename to r_examples/r_serving_with_fastapi/FastAPI_Example.ipynb
diff --git a/r_examples/r_serving_with_plumber/Plumber Example.ipynb b/r_examples/r_serving_with_plumber/Plumber_Example.ipynb
similarity index 100%
rename from r_examples/r_serving_with_plumber/Plumber Example.ipynb
rename to r_examples/r_serving_with_plumber/Plumber_Example.ipynb
diff --git a/r_examples/r_serving_with_restrserve/Dockerfile b/r_examples/r_serving_with_restrserve/Dockerfile
index 69dc88b8c2..5aaaf57689 100644
--- a/r_examples/r_serving_with_restrserve/Dockerfile
+++ b/r_examples/r_serving_with_restrserve/Dockerfile
@@ -2,7 +2,9 @@ FROM r-base:3.6.3
MAINTAINER Amazon SageMaker Examples
-RUN R -e "install.packages(c('RestRserve','xgboost','dplyr'), repos='https://cloud.r-project.org')"
+RUN R -e "install.packages(c('RestRserve','data.table', 'stringi', 'dplyr'), repos='https://cloud.r-project.org')"
+RUN wget http://cran.r-project.org/src/contrib/Archive/xgboost/xgboost_1.4.1.1.tar.gz
+RUN R CMD INSTALL xgboost_1.4.1.1.tar.gz
COPY xgb.model /opt/ml/xgb.model
COPY restrserve.R /opt/ml/restrserve.R
diff --git a/r_examples/r_serving_with_restrserve/RestRServe Example.ipynb b/r_examples/r_serving_with_restrserve/RestRServe_Example.ipynb
similarity index 100%
rename from r_examples/r_serving_with_restrserve/RestRServe Example.ipynb
rename to r_examples/r_serving_with_restrserve/RestRServe_Example.ipynb
diff --git a/sagemaker-inference-recommender/inference-recommender.ipynb b/sagemaker-inference-recommender/inference-recommender.ipynb
index bbbbf958fd..95eb54c7e8 100644
--- a/sagemaker-inference-recommender/inference-recommender.ipynb
+++ b/sagemaker-inference-recommender/inference-recommender.ipynb
@@ -24,7 +24,7 @@
"source": [
"## 2. Setup \n",
"\n",
- "Note that we are using the `conda_tensorflow2_p36` kernel in SageMaker Notebook Instances. This is running Python 3.6 and TensorFlow 2.1.3. If you'd like to use the same setup, in the AWS Management Console, go to the Amazon SageMaker console. Choose Notebook Instances, and click create a new notebook instance. Upload the current notebook and set the kernel. You can also run this in SageMaker Studio Notebooks with the `TensorFlow 2.1 Python 3.6 CPU Optimized` kernel.\n",
+ "Note that we are using the `conda_tensorflow2_p36` kernel in SageMaker Notebook Instances. This is running Python 3.6 and TensorFlow 2.1.3. If you'd like to use the same setup, in the AWS Management Console, go to the Amazon SageMaker console. Choose Notebook Instances, and click create a new notebook instance. Upload the current notebook and set the kernel. You can also run this in SageMaker Studio Notebooks with the `TensorFlow 2.6 Python 3.8 CPU Optimized` kernel.\n",
"\n",
"In the next steps, you'll import standard methods and libraries as well as set variables that will be used in this notebook. The `get_execution_role` function retrieves the AWS Identity and Access Management (IAM) role you created at the time of creating your notebook instance."
]
diff --git a/sagemaker-pipelines/tabular/abalone_build_train_deploy/sagemaker-pipelines-preprocess-train-evaluate-batch-transform_outputs.ipynb b/sagemaker-pipelines/tabular/abalone_build_train_deploy/sagemaker-pipelines-preprocess-train-evaluate-batch-transform_outputs.ipynb
index d20c0f6d83..21102fda26 100644
--- a/sagemaker-pipelines/tabular/abalone_build_train_deploy/sagemaker-pipelines-preprocess-train-evaluate-batch-transform_outputs.ipynb
+++ b/sagemaker-pipelines/tabular/abalone_build_train_deploy/sagemaker-pipelines-preprocess-train-evaluate-batch-transform_outputs.ipynb
@@ -272,9 +272,9 @@
"name": "stdout",
"output_type": "stream",
"text": [
- "\u001b[33mWARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv\u001b[0m\r\n",
- "\u001b[33mWARNING: You are using pip version 21.1.3; however, version 22.1.2 is available.\r\n",
- "You should consider upgrading via the '/opt/conda/bin/python -m pip install --upgrade pip' command.\u001b[0m\r\n"
+ "\u001B[33mWARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv\u001B[0m\r\n",
+ "\u001B[33mWARNING: You are using pip version 21.1.3; however, version 22.1.2 is available.\r\n",
+ "You should consider upgrading via the '/opt/conda/bin/python -m pip install --upgrade pip' command.\u001B[0m\r\n"
]
}
],
@@ -3441,4 +3441,4 @@
},
"nbformat": 4,
"nbformat_minor": 5
-}
+}
\ No newline at end of file
diff --git a/sagemaker-pipelines/tabular/custom_callback_pipelines_step/sagemaker-pipelines-callback-step.ipynb b/sagemaker-pipelines/tabular/custom_callback_pipelines_step/sagemaker-pipelines-callback-step.ipynb
index a36145affd..263936108a 100644
--- a/sagemaker-pipelines/tabular/custom_callback_pipelines_step/sagemaker-pipelines-callback-step.ipynb
+++ b/sagemaker-pipelines/tabular/custom_callback_pipelines_step/sagemaker-pipelines-callback-step.ipynb
@@ -899,7 +899,7 @@
"metadata": {},
"outputs": [],
"source": [
- "!pip install \"sagemaker==2.91.1\""
+ "!pip install \"sagemaker>=2.99.0\""
]
},
{
@@ -977,6 +977,7 @@
"outputs": [],
"source": [
"from sagemaker.workflow.callback_step import CallbackStep, CallbackOutput, CallbackOutputTypeEnum\n",
+ "from sagemaker.workflow.functions import Join\n",
"\n",
"callback1_output = CallbackOutput(\n",
" output_name=\"s3_data_out\", output_type=CallbackOutputTypeEnum.String\n",
@@ -987,7 +988,9 @@
" sqs_queue_url=queue_url,\n",
" inputs={\n",
" \"input_location\": f\"s3://{default_bucket}/{taxi_prefix}/\",\n",
- " \"output_location\": f\"s3://{default_bucket}/{taxi_prefix}_{id_out}/\",\n",
+ " \"output_location\": Join(\n",
+ " on=\"/\", values=[\"s3:/\", default_bucket, f\"{taxi_prefix}_output\", id_out]\n",
+ " ),\n",
" },\n",
" outputs=[callback1_output],\n",
")"
@@ -1000,9 +1003,9 @@
"source": [
"#### 2 - Training Step \n",
"\n",
- "Next, we'll configure the training step by first configuring the estimator for random cut forest. Then, we'll configure the training step. \n",
+ "Next, we'll configure the training step by first configuring the estimator for random cut forest. Then, we use the output of the estimator's .fit() method as arguments to the TrainingStep. By passing the pipeline_session to the sagemaker_session, calling .fit() does not launch the training job. Instead, it returns the arguments needed to run the job as a step in the pipeline.\n",
"\n",
- "The training step will accept the following **inputs**: \n",
+ "To generate the step arguments for the training step, it will accept the following **inputs**: \n",
" * S3 location of processed data to be used for model training\n",
" * ECR containing the training image for rcf\n",
" * Estimator configuration\n",
@@ -1018,6 +1021,8 @@
"metadata": {},
"outputs": [],
"source": [
+ "from sagemaker.workflow.pipeline_context import PipelineSession\n",
+ "\n",
"containers = {\n",
" \"us-west-2\": \"174872318107.dkr.ecr.us-west-2.amazonaws.com/randomcutforest:latest\",\n",
" \"us-east-1\": \"382416733822.dkr.ecr.us-east-1.amazonaws.com/randomcutforest:latest\",\n",
@@ -1028,7 +1033,7 @@
"container = containers[region_name]\n",
"model_prefix = \"model\"\n",
"\n",
- "session = sagemaker.Session()\n",
+ "pipeline_session = PipelineSession()\n",
"\n",
"rcf = sagemaker.estimator.Estimator(\n",
" container,\n",
@@ -1036,7 +1041,7 @@
" output_path=\"s3://{}/{}/output\".format(default_bucket, model_prefix),\n",
" instance_count=training_instance_count,\n",
" instance_type=\"ml.c5.xlarge\",\n",
- " sagemaker_session=session,\n",
+ " sagemaker_session=pipeline_session,\n",
")\n",
"\n",
"rcf.set_hyperparameters(num_samples_per_tree=200, num_trees=50, feature_dim=1)"
@@ -1052,9 +1057,7 @@
"from sagemaker.inputs import TrainingInput\n",
"from sagemaker.workflow.steps import TrainingStep\n",
"\n",
- "step_train = TrainingStep(\n",
- " name=\"TrainModel\",\n",
- " estimator=rcf,\n",
+ "train_step_args = rcf.fit(\n",
" inputs={\n",
" \"train\": TrainingInput(\n",
" # s3_data = Output of the previous call back step\n",
@@ -1063,6 +1066,10 @@
" distribution=\"ShardedByS3Key\",\n",
" ),\n",
" },\n",
+ ")\n",
+ "step_train = TrainingStep(\n",
+ " name=\"TrainModel\",\n",
+ " step_args=train_step_args,\n",
")"
]
},
@@ -1073,9 +1080,9 @@
"source": [
"#### 3 - Create Model\n",
"\n",
- "Next, we'll package the trained model for deployment. \n",
+ "Next, we'll package the trained model for deployment. To achieve this, we define the ModelStep by providing the return values from `model.create()` as the step arguments. Similarly, the `pipeline_session` is required when defining the model, which puts off the model creation to the pipeline execution time.\n",
"\n",
- "The create model step will accept the following **inputs**: \n",
+ "To generate the step arguments for the model step, it will accept the following **inputs**: \n",
" * S3 location of the trained model artifact\n",
" * ECR containing the inference image for rcf\n",
" \n",
@@ -1100,7 +1107,7 @@
"model = Model(\n",
" image_uri=image_uri,\n",
" model_data=step_train.properties.ModelArtifacts.S3ModelArtifacts,\n",
- " sagemaker_session=sagemaker_session,\n",
+ " sagemaker_session=pipeline_session,\n",
" role=role,\n",
")"
]
@@ -1112,19 +1119,14 @@
"metadata": {},
"outputs": [],
"source": [
- "from sagemaker.inputs import CreateModelInput\n",
- "from sagemaker.workflow.steps import CreateModelStep\n",
+ "from sagemaker.workflow.model_step import ModelStep\n",
"\n",
"\n",
- "inputs = CreateModelInput(\n",
+ "model_step_args = model.create(\n",
" instance_type=\"ml.m5.large\",\n",
")\n",
"\n",
- "create_model = CreateModelStep(\n",
- " name=\"TaxiModel\",\n",
- " model=model,\n",
- " inputs=inputs,\n",
- ")"
+ "create_model = ModelStep(name=\"TaxiModel\", step_args=model_step_args)"
]
},
{
@@ -1134,9 +1136,9 @@
"source": [
"#### 4 - Batch Transform\n",
"\n",
- "Next, we'll deploy the model using batch transform then do a quick evaluation with our data to compute anomaly scores for each of our data points on input. \n",
+ "Next, we'll deploy the model using batch transform then do a quick evaluation with our data to compute anomaly scores for each of our data points on input.\n",
"\n",
- "The batch transform step will accept the following **inputs**: \n",
+ "To generate the step arguments for the batch transform step, it will accept the following **inputs**: \n",
" * SageMaker packaged model\n",
" * S3 location of the input data\n",
" * ECR containing the inference image for rcf\n",
@@ -1164,6 +1166,7 @@
" accept=\"text/csv\",\n",
" instance_count=1,\n",
" output_path=f\"s3://{default_bucket}/{output_prefix}/\",\n",
+ " sagemaker_session=pipeline_session,\n",
")"
]
},
@@ -1179,17 +1182,18 @@
"\n",
"batch_data = step_callback_data.properties.Outputs[\"s3_data_out\"]\n",
"\n",
+ "transform_step_args = transformer.transform(\n",
+ " data=batch_data,\n",
+ " content_type=\"text/csv\",\n",
+ " split_type=\"Line\",\n",
+ " input_filter=\"$[0]\",\n",
+ " join_source=\"Input\",\n",
+ " output_filter=\"$[0,-1]\",\n",
+ ")\n",
+ "\n",
"step_transform = TransformStep(\n",
" name=\"TaxiTransform\",\n",
- " transformer=transformer,\n",
- " inputs=TransformInput(\n",
- " data=batch_data,\n",
- " content_type=\"text/csv\",\n",
- " split_type=\"Line\",\n",
- " input_filter=\"$[0]\",\n",
- " join_source=\"Input\",\n",
- " output_filter=\"$[0,-1]\",\n",
- " ),\n",
+ " step_args=transform_step_args,\n",
")"
]
},
@@ -1201,19 +1205,6 @@
"### Configure Pipeline Using Created Steps"
]
},
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "e646229c",
- "metadata": {},
- "outputs": [],
- "source": [
- "import uuid\n",
- "\n",
- "id_out = uuid.uuid4().hex\n",
- "print(\"Unique ID:\", id_out)"
- ]
- },
{
"cell_type": "code",
"execution_count": null,
@@ -1222,8 +1213,9 @@
"outputs": [],
"source": [
"from sagemaker.workflow.pipeline import Pipeline\n",
+ "from sagemaker.utils import unique_name_from_base\n",
"\n",
- "pipeline_name = f\"GluePipeline-{id_out}\"\n",
+ "pipeline_name = unique_name_from_base(\"GluePipeline\")\n",
"pipeline = Pipeline(\n",
" name=pipeline_name,\n",
" parameters=[\n",
@@ -1318,9 +1310,9 @@
"metadata": {
"instance_type": "ml.t3.medium",
"kernelspec": {
- "display_name": "Python 3 (Data Science)",
+ "display_name": "Python 3",
"language": "python",
- "name": "python3__SAGEMAKER_INTERNAL__arn:aws:sagemaker:us-east-2:429704687514:image/datascience-1.0"
+ "name": "python3"
},
"language_info": {
"codemirror_mode": {
@@ -1332,9 +1324,9 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.7.10"
+ "version": "3.6.14"
}
},
"nbformat": 4,
"nbformat_minor": 5
-}
\ No newline at end of file
+}
diff --git a/sagemaker-pipelines/tabular/custom_callback_pipelines_step/setup_iam_roles.py b/sagemaker-pipelines/tabular/custom_callback_pipelines_step/setup_iam_roles.py
index ef5ae80d1b..2da54d19d5 100644
--- a/sagemaker-pipelines/tabular/custom_callback_pipelines_step/setup_iam_roles.py
+++ b/sagemaker-pipelines/tabular/custom_callback_pipelines_step/setup_iam_roles.py
@@ -1,241 +1,212 @@
import json
import boto3
-iam = boto3.client('iam')
+iam = boto3.client("iam")
def create_ecs_task_role(role_name):
try:
response = iam.create_role(
- RoleName = role_name,
- AssumeRolePolicyDocument = json.dumps({
- "Version": "2012-10-17",
- "Statement": [
- {
- "Effect": "Allow",
- "Principal": {
- "Service": "ecs-tasks.amazonaws.com"
- },
- "Action": "sts:AssumeRole"
- }
- ]
- }),
- Description='Role for ECS task execution'
+ RoleName=role_name,
+ AssumeRolePolicyDocument=json.dumps(
+ {
+ "Version": "2012-10-17",
+ "Statement": [
+ {
+ "Effect": "Allow",
+ "Principal": {"Service": "ecs-tasks.amazonaws.com"},
+ "Action": "sts:AssumeRole",
+ }
+ ],
+ }
+ ),
+ Description="Role for ECS task execution",
)
- role_arn = response['Role']['Arn']
+ role_arn = response["Role"]["Arn"]
response = iam.attach_role_policy(
RoleName=role_name,
- PolicyArn='arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy'
+ PolicyArn="arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy",
)
-
+
response = iam.put_role_policy(
RoleName=role_name,
- PolicyName='create_log_group',
- PolicyDocument='{"Version":"2012-10-17","Statement":{"Effect":"Allow","Action":"logs:CreateLogGroup","Resource":"*"}}'
+ PolicyName="create_log_group",
+ PolicyDocument='{"Version":"2012-10-17","Statement":{"Effect":"Allow","Action":"logs:CreateLogGroup","Resource":"*"}}',
)
-
+
return role_arn
-
+
except iam.exceptions.EntityAlreadyExistsException:
- print(f'Using ARN from existing role: {role_name}')
+ print(f"Using ARN from existing role: {role_name}")
response = iam.get_role(RoleName=role_name)
- return response['Role']['Arn']
+ return response["Role"]["Arn"]
def create_task_runner_role(role_name):
try:
response = iam.create_role(
- RoleName = role_name,
- AssumeRolePolicyDocument = json.dumps({
+ RoleName=role_name,
+ AssumeRolePolicyDocument=json.dumps(
+ {
+ "Version": "2012-10-17",
+ "Statement": [
+ {
+ "Effect": "Allow",
+ "Principal": {"Service": "ecs-tasks.amazonaws.com"},
+ "Action": "sts:AssumeRole",
+ }
+ ],
+ }
+ ),
+ Description="Role for ECS tasks",
+ )
+
+ role_arn = response["Role"]["Arn"]
+
+ role_policy_document = json.dumps(
+ {
"Version": "2012-10-17",
"Statement": [
+ {"Effect": "Allow", "Action": "sagemaker:*", "Resource": "*"},
{
"Effect": "Allow",
- "Principal": {
- "Service": "ecs-tasks.amazonaws.com"
- },
- "Action": "sts:AssumeRole"
- }
- ]
- }),
- Description='Role for ECS tasks'
+ "Action": ["glue:StartJobRun", "glue:GetJobRun"],
+ "Resource": "*",
+ },
+ {"Effect": "Allow", "Action": "logs:CreateLogGroup", "Resource": "*"},
+ ],
+ }
)
- role_arn = response['Role']['Arn']
-
- role_policy_document = json.dumps({
- "Version": "2012-10-17",
- "Statement": [
- {
- "Effect": "Allow",
- "Action": "sagemaker:*",
- "Resource": "*"
- },
- {
- "Effect": "Allow",
- "Action": [
- "glue:StartJobRun",
- "glue:GetJobRun"
- ],
- "Resource": "*"
- },
- {
- "Effect": "Allow",
- "Action": "logs:CreateLogGroup",
- "Resource": "*"
- }
- ]
- })
-
response = iam.put_role_policy(
RoleName=role_name,
- PolicyName='glue_logs_sagemaker',
- PolicyDocument=role_policy_document
+ PolicyName="glue_logs_sagemaker",
+ PolicyDocument=role_policy_document,
)
-
+
response = iam.put_role_policy(
RoleName=role_name,
- PolicyName='create_log_group',
- PolicyDocument='{"Version":"2012-10-17","Statement":{"Effect":"Allow","Action":"logs:CreateLogGroup","Resource":"*"}}'
+ PolicyName="create_log_group",
+ PolicyDocument='{"Version":"2012-10-17","Statement":{"Effect":"Allow","Action":"logs:CreateLogGroup","Resource":"*"}}',
)
-
+
return role_arn
except iam.exceptions.EntityAlreadyExistsException:
- print(f'Using ARN from existing role: {role_name}')
+ print(f"Using ARN from existing role: {role_name}")
response = iam.get_role(RoleName=role_name)
- return response['Role']['Arn']
+ return response["Role"]["Arn"]
def create_glue_pipeline_role(role_name, bucket):
try:
response = iam.create_role(
- RoleName = role_name,
- AssumeRolePolicyDocument = json.dumps({
- "Version": "2012-10-17",
- "Statement": [
- {
- "Effect": "Allow",
- "Principal": {
- "Service": "glue.amazonaws.com"
- },
- "Action": "sts:AssumeRole"
- }
- ]
- }),
- Description='Role for Glue ETL job'
+ RoleName=role_name,
+ AssumeRolePolicyDocument=json.dumps(
+ {
+ "Version": "2012-10-17",
+ "Statement": [
+ {
+ "Effect": "Allow",
+ "Principal": {"Service": "glue.amazonaws.com"},
+ "Action": "sts:AssumeRole",
+ }
+ ],
+ }
+ ),
+ Description="Role for Glue ETL job",
)
- role_arn = response['Role']['Arn']
+ role_arn = response["Role"]["Arn"]
response = iam.attach_role_policy(
- RoleName=role_name,
- PolicyArn='arn:aws:iam::aws:policy/service-role/AWSGlueServiceRole'
+ RoleName=role_name, PolicyArn="arn:aws:iam::aws:policy/service-role/AWSGlueServiceRole"
)
-
- role_policy_document = json.dumps({
- "Version": "2012-10-17",
- "Statement": [
- {
- "Effect": "Allow",
- "Action": "s3:*",
- "Resource": f"arn:aws:s3:::{bucket}"
- }
- ]
- })
-
+
+ role_policy_document = json.dumps(
+ {
+ "Version": "2012-10-17",
+ "Statement": [
+ {"Effect": "Allow", "Action": "s3:*", "Resource": f"arn:aws:s3:::{bucket}"}
+ ],
+ }
+ )
+
response = iam.put_role_policy(
- RoleName=role_name,
- PolicyName='glue_s3_bucket',
- PolicyDocument=role_policy_document
+ RoleName=role_name, PolicyName="glue_s3_bucket", PolicyDocument=role_policy_document
)
-
- role_policy_document = json.dumps({
- "Version": "2012-10-17",
- "Statement": [
- {
- "Effect": "Allow",
- "Action": "s3:*",
- "Resource": f"arn:aws:s3:::{bucket}/*"
- }
- ]
- })
-
+
+ role_policy_document = json.dumps(
+ {
+ "Version": "2012-10-17",
+ "Statement": [
+ {"Effect": "Allow", "Action": "s3:*", "Resource": f"arn:aws:s3:::{bucket}/*"}
+ ],
+ }
+ )
+
response = iam.put_role_policy(
- RoleName=role_name,
- PolicyName='glue_s3_objects',
- PolicyDocument=role_policy_document
+ RoleName=role_name, PolicyName="glue_s3_objects", PolicyDocument=role_policy_document
)
-
+
return role_arn
except iam.exceptions.EntityAlreadyExistsException:
- print(f'Using ARN from existing role: {role_name}')
+ print(f"Using ARN from existing role: {role_name}")
response = iam.get_role(RoleName=role_name)
- return response['Role']['Arn']
-
+ return response["Role"]["Arn"]
+
+
def create_lambda_sm_pipeline_role(role_name, ecs_role_arn, task_role_arn):
try:
response = iam.create_role(
- RoleName = role_name,
- AssumeRolePolicyDocument = json.dumps({
- "Version": "2012-10-17",
- "Statement": [
- {
- "Effect": "Allow",
- "Principal": {
- "Service": "lambda.amazonaws.com"
- },
- "Action": "sts:AssumeRole"
- }
- ]
- }),
- Description='Role for Lambda to call ECS Fargate task'
+ RoleName=role_name,
+ AssumeRolePolicyDocument=json.dumps(
+ {
+ "Version": "2012-10-17",
+ "Statement": [
+ {
+ "Effect": "Allow",
+ "Principal": {"Service": "lambda.amazonaws.com"},
+ "Action": "sts:AssumeRole",
+ }
+ ],
+ }
+ ),
+ Description="Role for Lambda to call ECS Fargate task",
)
- role_arn = response['Role']['Arn']
+ role_arn = response["Role"]["Arn"]
response = iam.attach_role_policy(
RoleName=role_name,
- PolicyArn='arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'
+ PolicyArn="arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole",
)
- role_policy_document = json.dumps({
- "Version": "2012-10-17",
- "Statement": [
- {
- "Effect": "Allow",
- "Action": "ecs:RunTask",
- "Resource": ["*"]
- },
- {
- "Effect": "Allow",
- "Action": "sqs:*",
- "Resource": ["*"]
- },
- {
- "Effect": "Allow",
- "Action": "sagemaker:*",
- "Resource": ["*"]
- },
- {
- "Effect": "Allow",
- "Action": "iam:PassRole",
- "Resource": [ecs_role_arn, task_role_arn]
- },
- ]
- })
+ role_policy_document = json.dumps(
+ {
+ "Version": "2012-10-17",
+ "Statement": [
+ {"Effect": "Allow", "Action": "ecs:RunTask", "Resource": ["*"]},
+ {"Effect": "Allow", "Action": "sqs:*", "Resource": ["*"]},
+ {"Effect": "Allow", "Action": "sagemaker:*", "Resource": ["*"]},
+ {
+ "Effect": "Allow",
+ "Action": "iam:PassRole",
+ "Resource": [ecs_role_arn, task_role_arn],
+ },
+ ],
+ }
+ )
response = iam.put_role_policy(
- RoleName=role_name,
- PolicyName='ecs_sqs_sagemaker',
- PolicyDocument=role_policy_document
+ RoleName=role_name, PolicyName="ecs_sqs_sagemaker", PolicyDocument=role_policy_document
)
return role_arn
except iam.exceptions.EntityAlreadyExistsException:
- print(f'Using ARN from existing role: {role_name}')
+ print(f"Using ARN from existing role: {role_name}")
response = iam.get_role(RoleName=role_name)
- return response['Role']['Arn']
\ No newline at end of file
+ return response["Role"]["Arn"]
diff --git a/sagemaker-pipelines/tabular/lambda-step/sagemaker-pipelines-lambda-step.ipynb b/sagemaker-pipelines/tabular/lambda-step/sagemaker-pipelines-lambda-step.ipynb
index 695995a096..1036bce5cb 100644
--- a/sagemaker-pipelines/tabular/lambda-step/sagemaker-pipelines-lambda-step.ipynb
+++ b/sagemaker-pipelines/tabular/lambda-step/sagemaker-pipelines-lambda-step.ipynb
@@ -1051,4 +1051,4 @@
},
"nbformat": 4,
"nbformat_minor": 4
-}
+}
\ No newline at end of file
diff --git a/sagemaker-pipelines/tabular/lambda-step/sagemaker-pipelines-lambda-step_outputs.ipynb b/sagemaker-pipelines/tabular/lambda-step/sagemaker-pipelines-lambda-step_outputs.ipynb
index fa431f8a44..1e10714587 100644
--- a/sagemaker-pipelines/tabular/lambda-step/sagemaker-pipelines-lambda-step_outputs.ipynb
+++ b/sagemaker-pipelines/tabular/lambda-step/sagemaker-pipelines-lambda-step_outputs.ipynb
@@ -329,9 +329,9 @@
"name": "stdout",
"output_type": "stream",
"text": [
- "\u001b[33mWARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv\u001b[0m\r\n",
- "\u001b[33mWARNING: You are using pip version 21.1.3; however, version 22.1.2 is available.\r\n",
- "You should consider upgrading via the '/opt/conda/bin/python -m pip install --upgrade pip' command.\u001b[0m\r\n"
+ "\u001B[33mWARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv\u001B[0m\r\n",
+ "\u001B[33mWARNING: You are using pip version 21.1.3; however, version 22.1.2 is available.\r\n",
+ "You should consider upgrading via the '/opt/conda/bin/python -m pip install --upgrade pip' command.\u001B[0m\r\n"
]
}
],
@@ -1917,4 +1917,4 @@
},
"nbformat": 4,
"nbformat_minor": 5
-}
+}
\ No newline at end of file
diff --git a/sagemaker-pipelines/tabular/local-mode/sagemaker-pipelines-local-mode.ipynb b/sagemaker-pipelines/tabular/local-mode/sagemaker-pipelines-local-mode.ipynb
new file mode 100644
index 0000000000..55fa5c418f
--- /dev/null
+++ b/sagemaker-pipelines/tabular/local-mode/sagemaker-pipelines-local-mode.ipynb
@@ -0,0 +1,1134 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Use SageMaker Pipelines to Run Your Jobs Locally\n",
+ "\n",
+ "This notebook demonstrates how to orchestrate SageMaker jobs locally using SageMaker Pipelines. \n",
+ "\n",
+ "Using a `LocalPipelineSession` object, you can now run your pipelines on your local machine before running them in the cloud. \n",
+ "\n",
+ "The `LocalPipelineSession` object is used while defining each pipeline step and when defining the complete Pipeline object. To run this pipeline in the cloud, each step along with the Pipeline object must be redefined using `PipelineSession`.\n",
+ "\n",
+ "**Note**: This notebook will not run in SageMaker Studio. You can run this on SageMaker Classic Notebook instances OR your local IDE."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## SageMaker Pipelines Local Mode\n",
+ "\n",
+ "SageMaker Pipelines Local Mode supports the following activities, which are demonstrated in this notebook:\n",
+ "\n",
+ "* ProcessingStep\n",
+ "* TrainingStep\n",
+ "* ConditionStep\n",
+ "* ModelStep\n",
+ "* TransformStep\n",
+ "* FailStep"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "tags": []
+ },
+ "source": [
+ "## Dataset\n",
+ "\n",
+ "The dataset you use is the [UCI Machine Learning Abalone Dataset](https://archive.ics.uci.edu/ml/datasets/abalone) [1]. The aim for this task is to determine the age of an abalone snail from its physical measurements. At the core, this is a regression problem.\n",
+ "\n",
+ "The dataset contains several features: length (the longest shell measurement), diameter (the diameter perpendicular to length), height (the height with meat in the shell), whole_weight (the weight of whole abalone), shucked_weight (the weight of meat), viscera_weight (the gut weight after bleeding), shell_weight (the weight after being dried), sex ('M', 'F', 'I' where 'I' is Infant), and rings (integer).\n",
+ "\n",
+ "The number of rings turns out to be a good approximation for age (age is rings + 1.5). However, to obtain this number requires cutting the shell through the cone, staining the section, and counting the number of rings through a microscope, which is a time-consuming task. However, the other physical measurements are easier to determine. You use the dataset to build a predictive model of the variable rings through these other physical measurements.\n",
+ "\n",
+ "Before you upload the data to an S3 bucket, install the SageMaker Python SDK and gather some constants you can use later in this notebook.\n",
+ "\n",
+ "> [1] Dua, D. and Graff, C. (2019). [UCI Machine Learning Repository](http://archive.ics.uci.edu/ml). Irvine, CA: University of California, School of Information and Computer Science."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "#### Install the latest version of the SageMaker Python SDK. "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "!pip install 'sagemaker' --upgrade"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import sys\n",
+ "\n",
+ "import boto3\n",
+ "import sagemaker\n",
+ "from sagemaker.workflow.pipeline_context import LocalPipelineSession, PipelineSession\n",
+ "\n",
+ "# Create a `LocalPipelineSession` object so that each pipeline step will run locally\n",
+ "# To run this pipeline in the cloud, you must change `LocalPipelineSession()` to `PipelineSession()`\n",
+ "local_pipeline_session = LocalPipelineSession()\n",
+ "\n",
+ "region = local_pipeline_session.boto_region_name\n",
+ "role = sagemaker.get_execution_role()\n",
+ "\n",
+ "default_bucket = local_pipeline_session.default_bucket()\n",
+ "prefix = \"sagemaker-pipelines-local-mode-example\""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Now, upload the data into the default bucket. You can select our own data set for the `input_data_uri` as is appropriate."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "!mkdir -p data"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Pull the dataset from SageMaker's public S3 bucket and upload it to your own S3 bucket\n",
+ "\n",
+ "local_path = \"data/abalone-dataset.csv\"\n",
+ "\n",
+ "s3 = boto3.resource(\"s3\")\n",
+ "s3.Bucket(f\"sagemaker-sample-files\").download_file(\n",
+ " \"datasets/tabular/uci_abalone/abalone.csv\", local_path\n",
+ ")\n",
+ "\n",
+ "base_uri = f\"s3://{default_bucket}/{prefix}/abalone-data-set\"\n",
+ "input_data_uri = sagemaker.s3.S3Uploader.upload(\n",
+ " local_path=local_path,\n",
+ " desired_s3_uri=base_uri,\n",
+ ")\n",
+ "print(input_data_uri)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from sagemaker.workflow.parameters import ParameterString, ParameterFloat\n",
+ "\n",
+ "processing_instance_count = 1\n",
+ "training_instance_count = 1\n",
+ "transform_instance_count = 1\n",
+ "instance_type = \"ml.m5.xlarge\"\n",
+ "\n",
+ "input_data = ParameterString(\n",
+ " name=\"InputData\",\n",
+ " default_value=input_data_uri,\n",
+ ")\n",
+ "\n",
+ "mse_threshold = ParameterFloat(name=\"MseThreshold\", default_value=7.0)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Define a Processing Step for Feature Engineering\n",
+ "\n",
+ "First, develop a preprocessing script that is specified in the Processing step.\n",
+ "\n",
+ "This notebook cell writes a file `preprocessing_abalone.py`, which contains the preprocessing script. You can update the script, and rerun this cell to overwrite. The preprocessing script uses `scikit-learn` to do the following:\n",
+ "\n",
+ "* Fill in missing sex category data and encode it so that it is suitable for training.\n",
+ "* Scale and normalize all numerical fields, aside from sex and rings numerical data.\n",
+ "* Split the data into training, validation, and test datasets.\n",
+ "\n",
+ "The Processing step executes the script on the input data. The Training step uses the preprocessed training features and labels to train a model. The Evaluation step uses the trained model and preprocessed test features and labels to evaluate the model."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "!mkdir -p code"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "%%writefile code/preprocessing.py\n",
+ "import argparse\n",
+ "import os\n",
+ "import requests\n",
+ "import tempfile\n",
+ "\n",
+ "import numpy as np\n",
+ "import pandas as pd\n",
+ "\n",
+ "from sklearn.compose import ColumnTransformer\n",
+ "from sklearn.impute import SimpleImputer\n",
+ "from sklearn.pipeline import Pipeline\n",
+ "from sklearn.preprocessing import StandardScaler, OneHotEncoder\n",
+ "\n",
+ "\n",
+ "# Since we get a headerless CSV file, we specify the column names here.\n",
+ "feature_columns_names = [\n",
+ " \"sex\",\n",
+ " \"length\",\n",
+ " \"diameter\",\n",
+ " \"height\",\n",
+ " \"whole_weight\",\n",
+ " \"shucked_weight\",\n",
+ " \"viscera_weight\",\n",
+ " \"shell_weight\",\n",
+ "]\n",
+ "label_column = \"rings\"\n",
+ "\n",
+ "feature_columns_dtype = {\n",
+ " \"sex\": str,\n",
+ " \"length\": np.float64,\n",
+ " \"diameter\": np.float64,\n",
+ " \"height\": np.float64,\n",
+ " \"whole_weight\": np.float64,\n",
+ " \"shucked_weight\": np.float64,\n",
+ " \"viscera_weight\": np.float64,\n",
+ " \"shell_weight\": np.float64,\n",
+ "}\n",
+ "label_column_dtype = {\"rings\": np.float64}\n",
+ "\n",
+ "\n",
+ "def merge_two_dicts(x, y):\n",
+ " z = x.copy()\n",
+ " z.update(y)\n",
+ " return z\n",
+ "\n",
+ "\n",
+ "if __name__ == \"__main__\":\n",
+ " base_dir = \"/opt/ml/processing\"\n",
+ "\n",
+ " df = pd.read_csv(\n",
+ " f\"{base_dir}/input/abalone-dataset.csv\",\n",
+ " header=None,\n",
+ " names=feature_columns_names + [label_column],\n",
+ " dtype=merge_two_dicts(feature_columns_dtype, label_column_dtype),\n",
+ " )\n",
+ " numeric_features = list(feature_columns_names)\n",
+ " numeric_features.remove(\"sex\")\n",
+ " numeric_transformer = Pipeline(\n",
+ " steps=[(\"imputer\", SimpleImputer(strategy=\"median\")), (\"scaler\", StandardScaler())]\n",
+ " )\n",
+ "\n",
+ " categorical_features = [\"sex\"]\n",
+ " categorical_transformer = Pipeline(\n",
+ " steps=[\n",
+ " (\"imputer\", SimpleImputer(strategy=\"constant\", fill_value=\"missing\")),\n",
+ " (\"onehot\", OneHotEncoder(handle_unknown=\"ignore\")),\n",
+ " ]\n",
+ " )\n",
+ "\n",
+ " preprocess = ColumnTransformer(\n",
+ " transformers=[\n",
+ " (\"num\", numeric_transformer, numeric_features),\n",
+ " (\"cat\", categorical_transformer, categorical_features),\n",
+ " ]\n",
+ " )\n",
+ "\n",
+ " y = df.pop(\"rings\")\n",
+ " X_pre = preprocess.fit_transform(df)\n",
+ " y_pre = y.to_numpy().reshape(len(y), 1)\n",
+ "\n",
+ " X = np.concatenate((y_pre, X_pre), axis=1)\n",
+ "\n",
+ " np.random.shuffle(X)\n",
+ " train, validation, test = np.split(X, [int(0.7 * len(X)), int(0.85 * len(X))])\n",
+ "\n",
+ " pd.DataFrame(train).to_csv(f\"{base_dir}/train/train.csv\", header=False, index=False)\n",
+ " pd.DataFrame(validation).to_csv(\n",
+ " f\"{base_dir}/validation/validation.csv\", header=False, index=False\n",
+ " )\n",
+ " pd.DataFrame(test).to_csv(f\"{base_dir}/test/test.csv\", header=False, index=False)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Next, create an instance of a `SKLearnProcessor` processor and use that in our `ProcessingStep`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from sagemaker.sklearn.processing import SKLearnProcessor\n",
+ "\n",
+ "framework_version = \"1.0-1\"\n",
+ "\n",
+ "sklearn_processor = SKLearnProcessor(\n",
+ " framework_version=framework_version,\n",
+ " instance_type=instance_type,\n",
+ " instance_count=processing_instance_count,\n",
+ " base_job_name=\"sklearn-abalone-process\",\n",
+ " role=role,\n",
+ " sagemaker_session=local_pipeline_session,\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Finally, we take the output of the processor's `run` method and pass that as arguments to the `ProcessingStep`. By passing `local_pipeline_session` to the `sagemaker_session`, calling `.run()` does not launch the processing job, it returns the arguments needed to run the job as a step in the pipeline.\n",
+ "\n",
+ "Note the `\"train_data\"` and `\"test_data\"` named channels specified in the output configuration for the processing job. Step `Properties` can be used in subsequent steps and resolve to their runtime values at execution. Specifically, this usage is called out when you define the training step."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from sagemaker.processing import ProcessingInput, ProcessingOutput\n",
+ "from sagemaker.workflow.steps import ProcessingStep\n",
+ "\n",
+ "processor_args = sklearn_processor.run(\n",
+ " inputs=[\n",
+ " ProcessingInput(source=input_data, destination=\"/opt/ml/processing/input\"),\n",
+ " ],\n",
+ " outputs=[\n",
+ " ProcessingOutput(output_name=\"train\", source=\"/opt/ml/processing/train\"),\n",
+ " ProcessingOutput(output_name=\"validation\", source=\"/opt/ml/processing/validation\"),\n",
+ " ProcessingOutput(output_name=\"test\", source=\"/opt/ml/processing/test\"),\n",
+ " ],\n",
+ " code=\"code/preprocessing.py\",\n",
+ ")\n",
+ "\n",
+ "step_process = ProcessingStep(name=\"AbaloneProcess\", step_args=processor_args)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "%%writefile code/abalone.py\n",
+ "\n",
+ "import argparse\n",
+ "import json\n",
+ "import logging\n",
+ "import os\n",
+ "import pathlib\n",
+ "import pickle as pkl\n",
+ "import tarfile\n",
+ "\n",
+ "\n",
+ "import numpy as np\n",
+ "import pandas as pd\n",
+ "import xgboost as xgb\n",
+ "\n",
+ "logging.basicConfig(level=logging.INFO)\n",
+ "\n",
+ "TRAIN_VALIDATION_FRACTION = 0.2\n",
+ "RANDOM_STATE_SAMPLING = 200\n",
+ "\n",
+ "logging.basicConfig(level=logging.INFO)\n",
+ "\n",
+ "\n",
+ "def prepare_data(train_dir, validation_dir):\n",
+ " \"\"\"Read data from train and validation channel, and return predicting features and target variables.\n",
+ "\n",
+ " Args:\n",
+ " data_dir (str): directory which saves the training data.\n",
+ "\n",
+ " Returns:\n",
+ " Tuple of training features, training target, validation features, validation target.\n",
+ " \"\"\"\n",
+ " df_train = pd.read_csv(\n",
+ " os.path.join(train_dir, \"train.csv\"),\n",
+ " header=None,\n",
+ " )\n",
+ " df_train = df_train.iloc[np.random.permutation(len(df_train))]\n",
+ " df_train.columns = [\"target\"] + [f\"feature_{x}\" for x in range(df_train.shape[1] - 1)]\n",
+ "\n",
+ " try:\n",
+ " df_validation = pd.read_csv(\n",
+ " os.path.join(validation_dir, \"validation.csv\"),\n",
+ " header=None,\n",
+ " )\n",
+ " df_validation.columns = [\"target\"] + [\n",
+ " f\"feature_{x}\" for x in range(df_validation.shape[1] - 1)\n",
+ " ]\n",
+ "\n",
+ " except FileNotFoundError: # when validation data is not available in the directory\n",
+ " logging.info(\n",
+ " f\"Validation data is not found. {TRAIN_VALIDATION_FRACTION * 100}% of training data is \"\n",
+ " f\"randomly selected as validation data. The seed for random sampling is {RANDOM_STATE_SAMPLING}.\"\n",
+ " )\n",
+ " df_validation = df_train.sample(\n",
+ " frac=TRAIN_VALIDATION_FRACTION,\n",
+ " random_state=RANDOM_STATE_SAMPLING,\n",
+ " )\n",
+ " df_train.drop(df_validation.index, inplace=True)\n",
+ " df_validation.reset_index(drop=True, inplace=True)\n",
+ " df_train.reset_index(drop=True, inplace=True)\n",
+ "\n",
+ " X_train, y_train = df_train.iloc[:, 1:], df_train.iloc[:, :1]\n",
+ " X_val, y_val = df_validation.iloc[:, 1:], df_validation.iloc[:, :1]\n",
+ "\n",
+ " return X_train.values, y_train.values, X_val.values, y_val.values\n",
+ "\n",
+ "\n",
+ "def main():\n",
+ " \"\"\"Run training.\"\"\"\n",
+ " parser = argparse.ArgumentParser()\n",
+ "\n",
+ " parser.add_argument(\n",
+ " \"--max_depth\",\n",
+ " type=int,\n",
+ " )\n",
+ " parser.add_argument(\"--eta\", type=float)\n",
+ " parser.add_argument(\"--gamma\", type=int)\n",
+ " parser.add_argument(\"--min_child_weight\", type=int)\n",
+ " parser.add_argument(\"--subsample\", type=float)\n",
+ " parser.add_argument(\"--verbosity\", type=int)\n",
+ " parser.add_argument(\"--objective\", type=str)\n",
+ " parser.add_argument(\"--num_round\", type=int)\n",
+ " parser.add_argument(\"--tree_method\", type=str, default=\"auto\")\n",
+ " parser.add_argument(\"--predictor\", type=str, default=\"auto\")\n",
+ " parser.add_argument(\"--learning_rate\", type=str, default=\"auto\")\n",
+ " parser.add_argument(\"--output_data_dir\", type=str, default=os.environ.get(\"SM_OUTPUT_DATA_DIR\"))\n",
+ " parser.add_argument(\"--model_dir\", type=str, default=os.environ.get(\"SM_MODEL_DIR\"))\n",
+ " parser.add_argument(\"--train\", type=str, default=os.environ.get(\"SM_CHANNEL_TRAIN\"))\n",
+ " parser.add_argument(\"--validation\", type=str, default=os.environ.get(\"SM_CHANNEL_VALIDATION\"))\n",
+ " parser.add_argument(\"--sm_hosts\", type=str, default=os.environ.get(\"SM_HOSTS\"))\n",
+ " parser.add_argument(\"--sm_current_host\", type=str, default=os.environ.get(\"SM_CURRENT_HOST\"))\n",
+ "\n",
+ " args, _ = parser.parse_known_args()\n",
+ "\n",
+ " X_train, y_train, X_val, y_val = prepare_data(args.train, args.validation)\n",
+ "\n",
+ " # create dataset for lightgbm\n",
+ " dtrain = xgb.DMatrix(data=X_train, label=y_train)\n",
+ " dval = xgb.DMatrix(data=X_val, label=y_val)\n",
+ " watchlist = [(dtrain, \"train\"), (dval, \"validation\")]\n",
+ "\n",
+ " # specify your configurations as a dict\n",
+ " params = {\n",
+ " \"booster\": \"gbtree\",\n",
+ " \"objective\": args.objective,\n",
+ " \"learning_rate\": args.learning_rate,\n",
+ " \"gamma\": args.gamma,\n",
+ " \"min_child_weight\": args.min_child_weight,\n",
+ " \"max_depth\": args.max_depth,\n",
+ " \"subsample\": args.subsample,\n",
+ " \"colsample_bytree\": 1,\n",
+ " \"reg_lambda\": 1,\n",
+ " \"reg_alpha\": 0,\n",
+ " \"eval_metric\": \"rmse\",\n",
+ " }\n",
+ "\n",
+ " bst = xgb.train(\n",
+ " params=params,\n",
+ " dtrain=dtrain,\n",
+ " num_boost_round=args.num_round,\n",
+ " evals=watchlist,\n",
+ " xgb_model=None,\n",
+ " )\n",
+ "\n",
+ " model_location = args.model_dir + \"/xgboost-model\"\n",
+ " pkl.dump(bst, open(model_location, \"wb\"))\n",
+ " logging.info(\"Stored trained model at {}\".format(model_location))\n",
+ "\n",
+ "\n",
+ "if __name__ == \"__main__\":\n",
+ " main()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from sagemaker.estimator import Estimator\n",
+ "from sagemaker.inputs import TrainingInput\n",
+ "\n",
+ "model_path = f\"s3://{default_bucket}/{prefix}/model\"\n",
+ "image_uri = sagemaker.image_uris.retrieve(\n",
+ " framework=\"xgboost\",\n",
+ " region=region,\n",
+ " version=\"1.5-1\",\n",
+ " py_version=\"py3\",\n",
+ " instance_type=instance_type,\n",
+ ")\n",
+ "\n",
+ "xgb_train = Estimator(\n",
+ " image_uri=image_uri,\n",
+ " entry_point=\"code/abalone.py\",\n",
+ " instance_type=instance_type,\n",
+ " instance_count=training_instance_count,\n",
+ " output_path=model_path,\n",
+ " role=role,\n",
+ " sagemaker_session=local_pipeline_session,\n",
+ ")\n",
+ "\n",
+ "xgb_train.set_hyperparameters(\n",
+ " objective=\"reg:squarederror\",\n",
+ " learning_rate=0.01,\n",
+ " num_round=50,\n",
+ " max_depth=5,\n",
+ " eta=0.2,\n",
+ " gamma=4,\n",
+ " min_child_weight=6,\n",
+ " subsample=0.7,\n",
+ ")\n",
+ "\n",
+ "train_args = xgb_train.fit(\n",
+ " inputs={\n",
+ " \"train\": TrainingInput(\n",
+ " s3_data=step_process.properties.ProcessingOutputConfig.Outputs[\"train\"].S3Output.S3Uri,\n",
+ " content_type=\"text/csv\",\n",
+ " ),\n",
+ " \"validation\": TrainingInput(\n",
+ " s3_data=step_process.properties.ProcessingOutputConfig.Outputs[\n",
+ " \"validation\"\n",
+ " ].S3Output.S3Uri,\n",
+ " content_type=\"text/csv\",\n",
+ " ),\n",
+ " }\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Finally, we use the output of the estimator's `.fit()` method as arguments to the `TrainingStep`. By passing `local_pipeline_session` to the `sagemaker_session`, calling `.fit()` does not launch the training job, it returns the arguments needed to run the job as a step in the pipeline.\n",
+ "\n",
+ "Pass in the `S3Uri` of the `\"train_data\"` output channel to the `.fit()` method. Also, use the other `\"test_data\"` output channel for model evaluation in the pipeline. The `properties` attribute of a Pipeline step matches the object model of the corresponding response of a describe call. These properties can be referenced as placeholder values and are resolved at runtime. For example, the `ProcessingStep` `properties` attribute matches the object model of the [DescribeProcessingJob](https://docs.aws.amazon.com/sagemaker/latest/APIReference/API_DescribeProcessingJob.html) response object."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from sagemaker.inputs import TrainingInput\n",
+ "from sagemaker.workflow.steps import TrainingStep\n",
+ "\n",
+ "step_train = TrainingStep(\n",
+ " name=\"AbaloneTrain\",\n",
+ " step_args=train_args,\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Define a Model Evaluation Step to Evaluate the Trained Model\n",
+ "\n",
+ "First, develop an evaluation script that is specified in a Processing step that performs the model evaluation.\n",
+ "\n",
+ "After pipeline execution, you can examine the resulting `evaluation.json` for analysis.\n",
+ "\n",
+ "The evaluation script uses `xgboost` to do the following:\n",
+ "\n",
+ "* Load the model.\n",
+ "* Read the test data.\n",
+ "* Issue predictions against the test data.\n",
+ "* Build a classification report, including accuracy and ROC curve.\n",
+ "* Save the evaluation report to the evaluation directory."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "%%writefile code/evaluation.py\n",
+ "import json\n",
+ "import pathlib\n",
+ "import pickle\n",
+ "import tarfile\n",
+ "\n",
+ "import joblib\n",
+ "import numpy as np\n",
+ "import pandas as pd\n",
+ "import xgboost\n",
+ "import math\n",
+ "\n",
+ "from sklearn.metrics import mean_squared_error\n",
+ "\n",
+ "if __name__ == \"__main__\":\n",
+ " model_path = f\"/opt/ml/processing/model/model.tar.gz\"\n",
+ " with tarfile.open(model_path) as tar:\n",
+ " tar.extractall(path=\".\")\n",
+ "\n",
+ " model = pickle.load(open(\"xgboost-model\", \"rb\"))\n",
+ "\n",
+ " test_path = \"/opt/ml/processing/test/test.csv\"\n",
+ " df = pd.read_csv(test_path, header=None)\n",
+ " df.columns = [\"target\"] + [f\"feature_{x}\" for x in range(df.shape[1] - 1)]\n",
+ "\n",
+ " y_test = df.iloc[:, 0].to_numpy()\n",
+ " df.drop(df.columns[0], axis=1, inplace=True)\n",
+ "\n",
+ " X_test = xgboost.DMatrix(df.values)\n",
+ "\n",
+ " predictions = model.predict(X_test)\n",
+ "\n",
+ " mse = mean_squared_error(y_test, predictions)\n",
+ " std = np.std(y_test - predictions)\n",
+ " report_dict = {\n",
+ " \"regression_metrics\": {\n",
+ " \"mse\": {\"value\": math.sqrt(mse), \"standard_deviation\": std},\n",
+ " },\n",
+ " }\n",
+ "\n",
+ " output_dir = \"/opt/ml/processing/evaluation\"\n",
+ " pathlib.Path(output_dir).mkdir(parents=True, exist_ok=True)\n",
+ "\n",
+ " evaluation_path = f\"{output_dir}/evaluation.json\"\n",
+ " with open(evaluation_path, \"w\") as f:\n",
+ " f.write(json.dumps(report_dict))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Next, create an instance of a `ScriptProcessor` processor and use it in the `ProcessingStep`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from sagemaker.processing import ScriptProcessor\n",
+ "\n",
+ "script_eval = ScriptProcessor(\n",
+ " image_uri=image_uri,\n",
+ " command=[\"python3\"],\n",
+ " instance_type=instance_type,\n",
+ " instance_count=processing_instance_count,\n",
+ " base_job_name=\"script-abalone-eval\",\n",
+ " role=role,\n",
+ " sagemaker_session=local_pipeline_session,\n",
+ ")\n",
+ "\n",
+ "eval_args = script_eval.run(\n",
+ " inputs=[\n",
+ " ProcessingInput(\n",
+ " source=step_train.properties.ModelArtifacts.S3ModelArtifacts,\n",
+ " destination=\"/opt/ml/processing/model\",\n",
+ " ),\n",
+ " ProcessingInput(\n",
+ " source=step_process.properties.ProcessingOutputConfig.Outputs[\"test\"].S3Output.S3Uri,\n",
+ " destination=\"/opt/ml/processing/test\",\n",
+ " ),\n",
+ " ],\n",
+ " outputs=[\n",
+ " ProcessingOutput(output_name=\"evaluation\", source=\"/opt/ml/processing/evaluation\"),\n",
+ " ],\n",
+ " code=\"code/evaluation.py\",\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Use the processor's arguments returned by `.run()` to construct a `ProcessingStep`, along with the input and output channels and the code that will be executed when the pipeline invokes pipeline execution. \n",
+ "\n",
+ "Specifically, the `S3ModelArtifacts` from the `step_train` `properties` and the `S3Uri` of the `\"test_data\"` output channel of the `step_process` `properties` are passed as inputs. The `TrainingStep` and `ProcessingStep` `properties` attribute matches the object model of the [DescribeTrainingJob](https://docs.aws.amazon.com/sagemaker/latest/APIReference/API_DescribeTrainingJob.html) and [DescribeProcessingJob](https://docs.aws.amazon.com/sagemaker/latest/APIReference/API_DescribeProcessingJob.html) response objects, respectively."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from sagemaker.workflow.properties import PropertyFile\n",
+ "\n",
+ "evaluation_report = PropertyFile(\n",
+ " name=\"EvaluationReport\", output_name=\"evaluation\", path=\"evaluation.json\"\n",
+ ")\n",
+ "step_eval = ProcessingStep(\n",
+ " name=\"AbaloneEval\",\n",
+ " step_args=eval_args,\n",
+ " property_files=[evaluation_report],\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Define a Create Model Step to Create a Model\n",
+ "\n",
+ "In order to perform batch transformation using the example model, create a SageMaker model. \n",
+ "\n",
+ "Specifically, pass in the `S3ModelArtifacts` from the `TrainingStep`, `step_train` properties. The `TrainingStep` `properties` attribute matches the object model of the [DescribeTrainingJob](https://docs.aws.amazon.com/sagemaker/latest/APIReference/API_DescribeTrainingJob.html) response object.\n",
+ "\n",
+ "We provide a custom inference script that defines the logic for the batch transform job"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "%%writefile code/inference.py\n",
+ "\n",
+ "import json\n",
+ "import os\n",
+ "import pickle as pkl\n",
+ "\n",
+ "import numpy as np\n",
+ "import pandas as pd\n",
+ "import sagemaker_xgboost_container.encoder as xgb_encoders\n",
+ "import xgboost as xgb\n",
+ "import io\n",
+ "import logging\n",
+ "\n",
+ "logging.basicConfig(level=logging.INFO)\n",
+ "\n",
+ "\n",
+ "def model_fn(model_dir):\n",
+ " \"\"\"\n",
+ " Deserialize and return fitted model.\n",
+ " \"\"\"\n",
+ " model_file = \"xgboost-model\"\n",
+ " booster = pkl.load(open(os.path.join(model_dir, model_file), \"rb\"))\n",
+ " return booster\n",
+ "\n",
+ "\n",
+ "def transform_fn(model, request_body, request_content_type, accept):\n",
+ " \"\"\" \"\"\"\n",
+ " if request_content_type == \"text/libsvm\":\n",
+ " input_data = xgb_encoders.libsvm_to_dmatrix(request_body)\n",
+ " if request_content_type == \"text/csv\":\n",
+ " df = pd.read_csv(io.StringIO(request_body.strip(\"\\n\")), header=None)\n",
+ " df.drop(0, axis=1, inplace=True)\n",
+ " input_data = xgb.DMatrix(data=df)\n",
+ "\n",
+ " else:\n",
+ " raise ValueError(\"Content type {} is not supported.\".format(request_content_type))\n",
+ "\n",
+ " prediction = model.predict(input_data)\n",
+ " feature_contribs = model.predict(input_data, pred_contribs=True, validate_features=False)\n",
+ " output = np.hstack((prediction[:, np.newaxis], feature_contribs))\n",
+ "\n",
+ " logging.info(\"Successfully completed transform job!\")\n",
+ "\n",
+ " return \",\".join(str(x) for x in output[0])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from sagemaker.model import Model\n",
+ "\n",
+ "model = Model(\n",
+ " image_uri=image_uri,\n",
+ " model_data=step_train.properties.ModelArtifacts.S3ModelArtifacts,\n",
+ " source_dir=\"code\",\n",
+ " entry_point=\"inference.py\",\n",
+ " role=role,\n",
+ " sagemaker_session=local_pipeline_session,\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Define the `ModelStep` by providing the return values from `model.create()` as the step arguments. "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from sagemaker.workflow.model_step import ModelStep\n",
+ "\n",
+ "step_create_model = ModelStep(\n",
+ " name=\"AbaloneCreateModel\", step_args=model.create(instance_type=instance_type)\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Define a Transform Step to Perform Batch Transformation\n",
+ "\n",
+ "Now that a model instance is defined, create a `Transformer` instance with the appropriate model type, compute instance type, and desired output S3 URI.\n",
+ "\n",
+ "Specifically, pass in the `ModelName` from the `CreateModelStep`, `step_create_model` properties. The `CreateModelStep` `properties` attribute matches the object model of the [DescribeModel](https://docs.aws.amazon.com/sagemaker/latest/APIReference/API_DescribeModel.html) response object."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from sagemaker.transformer import Transformer\n",
+ "\n",
+ "\n",
+ "transformer = Transformer(\n",
+ " model_name=step_create_model.properties.ModelName,\n",
+ " instance_type=instance_type,\n",
+ " instance_count=transform_instance_count,\n",
+ " output_path=f\"s3://{default_bucket}/{prefix}/transform\",\n",
+ " sagemaker_session=local_pipeline_session,\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Pass in the transformer instance and the `TransformInput` with the `batch_data` pipeline parameter defined earlier."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from sagemaker.inputs import TransformInput\n",
+ "from sagemaker.workflow.steps import TransformStep\n",
+ "from sagemaker.workflow.functions import Join\n",
+ "\n",
+ "transform_data = Join(\n",
+ " on=\"/\",\n",
+ " values=[\n",
+ " step_process.properties.ProcessingOutputConfig.Outputs[\"test\"].S3Output.S3Uri,\n",
+ " \"test.csv\",\n",
+ " ],\n",
+ ")\n",
+ "\n",
+ "transform_args = transformer.transform(transform_data, content_type=\"text/csv\")\n",
+ "\n",
+ "step_transform = TransformStep(name=\"AbaloneTransform\", step_args=transform_args)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from sagemaker.workflow.fail_step import FailStep\n",
+ "\n",
+ "step_fail = FailStep(\n",
+ " name=\"AbaloneMSEFail\",\n",
+ " error_message=Join(on=\" \", values=[\"Execution failed due to MSE >\", mse_threshold]),\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Define a Condition Step to Check Accuracy and Conditionally Create a Model and Run a Batch Transformation Or Terminate the Execution in Failed State\n",
+ "\n",
+ "In this step, the model is registered only if the accuracy of the model, as determined by the evaluation step `step_eval`, exceeded a specified value. Otherwise, the pipeline execution fails and terminates. A `ConditionStep` enables pipelines to support conditional execution in the pipeline DAG based on the conditions of the step properties.\n",
+ "\n",
+ "In the following section, you:\n",
+ "\n",
+ "* Define a `ConditionLessThanOrEqualTo` on the accuracy value found in the output of the evaluation step, `step_eval`.\n",
+ "* Use the condition in the list of conditions in a `ConditionStep`.\n",
+ "* Pass the `CreateModelStep` and `TransformStep` steps into the `if_steps` of the `ConditionStep`, which are only executed if the condition evaluates to `True`.\n",
+ "* Pass the `FailStep` step into the `else_steps`of the `ConditionStep`, which is only executed if the condition evaluates to `False`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from sagemaker.workflow.conditions import ConditionLessThanOrEqualTo\n",
+ "from sagemaker.workflow.condition_step import ConditionStep\n",
+ "from sagemaker.workflow.functions import JsonGet\n",
+ "\n",
+ "cond_lte = ConditionLessThanOrEqualTo(\n",
+ " left=JsonGet(\n",
+ " step_name=step_eval.name,\n",
+ " property_file=evaluation_report,\n",
+ " json_path=\"regression_metrics.mse.value\",\n",
+ " ),\n",
+ " right=mse_threshold,\n",
+ ")\n",
+ "\n",
+ "step_cond = ConditionStep(\n",
+ " name=\"AbaloneMSECond\",\n",
+ " conditions=[cond_lte],\n",
+ " if_steps=[step_create_model, step_transform],\n",
+ " else_steps=[step_fail],\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Define a Pipeline using `LocalPipelineSession`\n",
+ "\n",
+ "In this section, combine the steps into a Pipeline so it can be executed. We provide a `LocalPipelineSession` object to the `Pipeline` so that when executed, all the steps in the pipeline will run locally on the machine."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from sagemaker.workflow.pipeline import Pipeline\n",
+ "\n",
+ "pipeline_name = f\"LocalModelPipeline\"\n",
+ "pipeline = Pipeline(\n",
+ " name=pipeline_name,\n",
+ " parameters=[\n",
+ " input_data,\n",
+ " mse_threshold,\n",
+ " ],\n",
+ " steps=[step_process, step_train, step_eval, step_cond],\n",
+ " sagemaker_session=local_pipeline_session,\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### (Optional) Examining the pipeline definition\n",
+ "\n",
+ "The JSON of the pipeline definition can be examined to confirm the pipeline is well-defined and the parameters and step properties resolve correctly."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import json\n",
+ "\n",
+ "definition = json.loads(pipeline.definition())\n",
+ "definition"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Submit the pipeline to SageMaker and start execution\n",
+ "\n",
+ "Submit the pipeline definition to the Pipeline service. The Pipeline service uses the role that is passed in to create all the jobs defined in the steps."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "pipeline.upsert(role_arn=role)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Start the pipeline and accept all the default parameters."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "execution = pipeline.start()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "steps = execution.list_steps()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "steps"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Get the step outputs"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Get output files from processing job\n",
+ "\n",
+ "processing_job_name = steps[\"PipelineExecutionSteps\"][0][\"Metadata\"][\"ProcessingJob\"][\"Arn\"]\n",
+ "outputs = local_pipeline_session.sagemaker_client.describe_processing_job(\n",
+ " ProcessingJobName=processing_job_name\n",
+ ")[\"ProcessingOutputConfig\"][\"Outputs\"]\n",
+ "for key in outputs:\n",
+ " print(outputs[key][\"S3Output\"][\"S3Uri\"])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Get output from training job\n",
+ "\n",
+ "training_job_name = steps[\"PipelineExecutionSteps\"][1][\"Metadata\"][\"TrainingJob\"][\"Arn\"]\n",
+ "outputs = local_pipeline_session.sagemaker_client.describe_training_job(\n",
+ " TrainingJobName=training_job_name\n",
+ ")\n",
+ "print(\"Model location : \", outputs[\"ModelArtifacts\"][\"S3ModelArtifacts\"])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Get output from model evaluation step (processing job)\n",
+ "\n",
+ "processing_job_name = steps[\"PipelineExecutionSteps\"][2][\"Metadata\"][\"ProcessingJob\"][\"Arn\"]\n",
+ "outputs = local_pipeline_session.sagemaker_client.describe_processing_job(\n",
+ " ProcessingJobName=processing_job_name\n",
+ ")[\"ProcessingOutputConfig\"][\"Outputs\"]\n",
+ "for key in outputs:\n",
+ " print(outputs[key][\"S3Output\"][\"S3Uri\"])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Get output of ModelStep\n",
+ "import json\n",
+ "\n",
+ "model_name = steps[\"PipelineExecutionSteps\"][-1][\"Metadata\"][\"Model\"][\"Arn\"]\n",
+ "outputs = local_pipeline_session.sagemaker_client.describe_model(ModelName=model_name)\n",
+ "print(outputs)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Get output from the TransformStep\n",
+ "\n",
+ "transform_job_name = steps[\"PipelineExecutionSteps\"][4][\"Metadata\"][\"TransformJob\"][\"Arn\"]\n",
+ "outputs = local_pipeline_session.sagemaker_client.describe_transform_job(\n",
+ " TransformJobName=transform_job_name\n",
+ ")\n",
+ "print(outputs)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Conclusion\n",
+ "\n",
+ "In this notebook we define a pipeline that will run on your local machine and tested that all the steps are returning the desired output. Once this is done, by switching the `LocalPipelineSession` to a `PipelineSession` object, you can switch the execution to run in the cloud on SageMaker instances."
+ ]
+ }
+ ],
+ "metadata": {
+ "instance_type": "ml.t3.medium",
+ "kernelspec": {
+ "display_name": "conda_python3",
+ "language": "python",
+ "name": "conda_python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.8.12"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/sagemaker-pipelines/time_series_forecasting/amazon_forecast_pipeline/sm_pipeline_with_amazon_forecast.ipynb b/sagemaker-pipelines/time_series_forecasting/amazon_forecast_pipeline/sm_pipeline_with_amazon_forecast.ipynb
index 4904fbbfe7..c49389ab19 100644
--- a/sagemaker-pipelines/time_series_forecasting/amazon_forecast_pipeline/sm_pipeline_with_amazon_forecast.ipynb
+++ b/sagemaker-pipelines/time_series_forecasting/amazon_forecast_pipeline/sm_pipeline_with_amazon_forecast.ipynb
@@ -2,7 +2,7 @@
"cells": [
{
"cell_type": "markdown",
- "id": "0c9ee48f",
+ "id": "91ee6b6d",
"metadata": {},
"source": [
"# Creating an Amazon Forecast Predictor with SageMaker Pipelines\n",
@@ -27,7 +27,7 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "932dc4d0",
+ "id": "86a4678e",
"metadata": {},
"outputs": [],
"source": [
@@ -52,7 +52,7 @@
},
{
"cell_type": "markdown",
- "id": "de12d5a8",
+ "id": "b0125efc",
"metadata": {},
"source": [
"Finally, you will need the following trust policies."
@@ -61,7 +61,7 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "957d4d1f",
+ "id": "8050bbca",
"metadata": {},
"outputs": [],
"source": [
@@ -81,7 +81,7 @@
},
{
"cell_type": "markdown",
- "id": "45298f90",
+ "id": "9ed30cce",
"metadata": {},
"source": [
"## Prerequisites\n",
@@ -95,7 +95,17 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "9ab8df52",
+ "id": "1d137518",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "! pip install sagemaker==2.93.0"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "b763c7b0",
"metadata": {},
"outputs": [],
"source": [
@@ -135,7 +145,7 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "51f2beea",
+ "id": "ad1e02a1",
"metadata": {},
"outputs": [],
"source": [
@@ -189,7 +199,7 @@
},
{
"cell_type": "markdown",
- "id": "fff1a8b5",
+ "id": "23cb4c19",
"metadata": {},
"source": [
"## Dataset\n",
@@ -200,7 +210,7 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "e4367c4a",
+ "id": "d71ab7f2",
"metadata": {},
"outputs": [],
"source": [
@@ -226,7 +236,7 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "5a4a1d4a",
+ "id": "8ce30a2a",
"metadata": {},
"outputs": [],
"source": [
@@ -243,7 +253,7 @@
},
{
"cell_type": "markdown",
- "id": "db24153a",
+ "id": "f58aea79",
"metadata": {},
"source": [
"The dataset happens to span January 01, 2011, to January 01, 2015. We are only going to use about two and a half week's of hourly data to train Amazon Forecast. \n",
@@ -253,7 +263,7 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "ac099d3c",
+ "id": "724eee5f",
"metadata": {},
"outputs": [],
"source": [
@@ -262,7 +272,7 @@
},
{
"cell_type": "markdown",
- "id": "d114bd69",
+ "id": "18b87844",
"metadata": {},
"source": [
"Next, we define parameters that can be set for the execution of the pipeline. They serve as variables. We define the following:\n",
@@ -286,7 +296,7 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "f1d86c83",
+ "id": "52ba0c45",
"metadata": {},
"outputs": [],
"source": [
@@ -294,7 +304,6 @@
"processing_instance_type = ParameterString(\n",
" name=\"ProcessingInstanceType\", default_value=\"ml.m5.large\"\n",
")\n",
- "training_instance_count = ParameterInteger(name=\"TrainingInstanceCount\", default_value=1)\n",
"training_instance_type = ParameterString(name=\"TrainingInstanceType\", default_value=\"ml.m5.large\")\n",
"\n",
"input_train = ParameterString(\n",
@@ -312,7 +321,7 @@
},
{
"cell_type": "markdown",
- "id": "eff2dad9",
+ "id": "3a2ee68c",
"metadata": {},
"source": [
"We use an updated [SKLearnProcessor](https://sagemaker.readthedocs.io/en/stable/frameworks/sklearn/sagemaker.sklearn.html#sagemaker.sklearn.processing.SKLearnProcessor) to run Python scripts to build a dataset group and train an Amazon Forecast predictor using `boto3`. In the next chunk, we instantiate an instance of `ScriptProcessor`, which is essentially an SKLearnProcessor with updated `boto3` and `botocore` (as built above) that we use in the next steps. "
@@ -321,7 +330,7 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "11e82c55",
+ "id": "130c2059",
"metadata": {},
"outputs": [],
"source": [
@@ -336,7 +345,7 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "88fb293a",
+ "id": "2abf7b80",
"metadata": {},
"outputs": [],
"source": [
@@ -353,7 +362,7 @@
},
{
"cell_type": "markdown",
- "id": "5d40d2b1",
+ "id": "26bd50c0",
"metadata": {},
"source": [
"First we preprocess the data using an Amazon SageMaker [ProcessingStep](https://sagemaker.readthedocs.io/en/stable/workflows/pipelines/sagemaker.workflow.pipelines.html?highlight=ProcessingStep#sagemaker.workflow.steps.ProcessingStep) that provides a containerized execution environment to run the `preprocess.py` script."
@@ -362,7 +371,7 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "b5d84ca3",
+ "id": "aa0259f4",
"metadata": {},
"outputs": [],
"source": [
@@ -383,7 +392,7 @@
},
{
"cell_type": "markdown",
- "id": "6d4b1540",
+ "id": "6e05150d",
"metadata": {},
"source": [
"The next step is to train and evaluate the forecasting model calling Amazon Forecast using `boto3`. We instantiate an instance of `SKLearn` estimator that we use in the next `TrainingStep` to run the script `train.py`. \n",
@@ -394,7 +403,7 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "b80ada3f",
+ "id": "95177b2f",
"metadata": {},
"outputs": [],
"source": [
@@ -425,7 +434,7 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "b52d154a",
+ "id": "cf10e258",
"metadata": {},
"outputs": [],
"source": [
@@ -433,7 +442,6 @@
" entry_point=\"train.py\",\n",
" role=role_arn,\n",
" image_uri=container_image_uri,\n",
- " instance_count=training_instance_count,\n",
" instance_type=training_instance_type,\n",
" sagemaker_session=sagemaker_session,\n",
" base_job_name=\"forecast-train\",\n",
@@ -446,7 +454,7 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "f5ddecce",
+ "id": "82f5536a",
"metadata": {},
"outputs": [],
"source": [
@@ -455,7 +463,7 @@
},
{
"cell_type": "markdown",
- "id": "29f0d4d4",
+ "id": "867f2daf",
"metadata": {},
"source": [
"The third step is an Amazon SageMaker ProcessingStep that deletes or keeps the Amazon Forecast model running using the script `conditional_delete.py`. If the error reported after training is higher than a threshold you specify for the metric you specify, this step deletes all the resources created by Amazon Forecast that are related to the pipeline's execution.\n",
@@ -465,7 +473,7 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "43c79816",
+ "id": "f6122249",
"metadata": {},
"outputs": [],
"source": [
@@ -492,7 +500,7 @@
},
{
"cell_type": "markdown",
- "id": "41ef4915",
+ "id": "991697b7",
"metadata": {},
"source": [
"Finally, we combine all the steps and define our pipeline."
@@ -501,7 +509,7 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "7cf7b196",
+ "id": "fdc925a3",
"metadata": {},
"outputs": [],
"source": [
@@ -513,7 +521,6 @@
" parameters=[\n",
" processing_instance_type,\n",
" processing_instance_count,\n",
- " training_instance_count,\n",
" training_instance_type,\n",
" input_train,\n",
" forecast_horizon,\n",
@@ -532,7 +539,7 @@
},
{
"cell_type": "markdown",
- "id": "c838b490",
+ "id": "681b8721",
"metadata": {},
"source": [
"Once the pipeline is successfully defined, we can start the execution."
@@ -541,7 +548,7 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "1cbe62f1",
+ "id": "5b375f45",
"metadata": {},
"outputs": [],
"source": [
@@ -551,7 +558,7 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "35e9c22d",
+ "id": "b2fec897",
"metadata": {},
"outputs": [],
"source": [
@@ -561,7 +568,7 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "ccb70b34",
+ "id": "72464cc3",
"metadata": {},
"outputs": [],
"source": [
@@ -571,7 +578,7 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "a20d8f39",
+ "id": "e66f34a3",
"metadata": {},
"outputs": [],
"source": [
@@ -580,7 +587,7 @@
},
{
"cell_type": "markdown",
- "id": "1e285dfd",
+ "id": "c5c56dff",
"metadata": {},
"source": [
"## Experiments Tracking\n",
@@ -602,7 +609,7 @@
},
{
"cell_type": "markdown",
- "id": "067b7888",
+ "id": "a0030897",
"metadata": {},
"source": [
"## Conclusion"
@@ -610,7 +617,7 @@
},
{
"cell_type": "markdown",
- "id": "40a6ba7e",
+ "id": "132ad067",
"metadata": {},
"source": [
"In this notebook we have seen how to create a SageMaker Pipeline to train an Amazon Forecast predictor on your own dataset with a target and related time series."
@@ -618,7 +625,7 @@
},
{
"cell_type": "markdown",
- "id": "93c99720",
+ "id": "d10d8baf",
"metadata": {},
"source": [
"## Clean up\n",
@@ -629,7 +636,7 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "bc956081",
+ "id": "c8665320",
"metadata": {},
"outputs": [],
"source": [
@@ -654,7 +661,7 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "4c2e6e8a",
+ "id": "6a234cbf",
"metadata": {},
"outputs": [],
"source": [
@@ -670,7 +677,7 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "e50ca583",
+ "id": "b269f192",
"metadata": {},
"outputs": [],
"source": [
@@ -680,7 +687,7 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "82e5928a",
+ "id": "a8828ef6",
"metadata": {},
"outputs": [],
"source": [
@@ -690,7 +697,7 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "44212447",
+ "id": "abeda944",
"metadata": {},
"outputs": [],
"source": [
@@ -708,7 +715,7 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "41649cfd",
+ "id": "d64d4ae5",
"metadata": {},
"outputs": [],
"source": [
@@ -720,7 +727,7 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "a9fd48dc",
+ "id": "33d2861f",
"metadata": {},
"outputs": [],
"source": [
@@ -733,7 +740,7 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "cc00b557",
+ "id": "d0e4bd9d",
"metadata": {},
"outputs": [],
"source": [
@@ -744,7 +751,7 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "336300e0",
+ "id": "d9968b15",
"metadata": {},
"outputs": [],
"source": [
@@ -756,7 +763,7 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "8eed39f2",
+ "id": "1440c84e",
"metadata": {},
"outputs": [],
"source": [
@@ -767,9 +774,9 @@
"metadata": {
"instance_type": "ml.t3.medium",
"kernelspec": {
- "display_name": "Python 3 (ipykernel)",
+ "display_name": "conda_python3",
"language": "python",
- "name": "python3"
+ "name": "conda_python3"
},
"language_info": {
"codemirror_mode": {
@@ -781,7 +788,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.7.11"
+ "version": "3.8.12"
}
},
"nbformat": 4,
diff --git a/sagemaker-python-sdk/paddlepaddle_sentiment_analysis_byo_mms/Bring Your Own DL Framework to Amazon Sagemaker with Model Server for Apache MXNet's (MMS) BYO container.ipynb b/sagemaker-python-sdk/paddlepaddle_sentiment_analysis_byo_mms/Bring Your Own DL Framework to Amazon Sagemaker with Model Server for Apache MXNet's (MMS) BYO container.ipynb
deleted file mode 100644
index ab9f3d330b..0000000000
--- a/sagemaker-python-sdk/paddlepaddle_sentiment_analysis_byo_mms/Bring Your Own DL Framework to Amazon Sagemaker with Model Server for Apache MXNet's (MMS) BYO container.ipynb
+++ /dev/null
@@ -1,539 +0,0 @@
-{
- "cells": [
- {
- "attachments": {
- "image.png": {
- "image/png": "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"
- }
- },
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Introduction\n",
- "\n",
- "Deep Learning frameworks enable Machine Learning (ML) practitioners to build and train ML models. However, the process of deploying ML models in production to serve predictions (also known as inferences) in real time is more complex. It requires that ML practitioners build a scalable and performant model server, which can host these models and handle inference requests at scale. Model Server for Apache MXNet (MMS), was developed to address this hurdle. MMS is a highly scalable, production ready inference server. MMS was designed in a ML/DL framework agnostic way to host models trained in any ML/DL framework.\n",
- "\n",
- "In this blog post, we will showcase how anyone could use Model Server for Apache MXNet (MMS) to host their model trained using any Machine Learning/Deep Learning (ML/DL) framework or tool kit in production. We chose Amazon Sagemaker service for production hosting - this PaaS solution does a lot of heavy lifting to provide infrastructure and allows users to focus on their use cases. We will be using 'Bring your own Inference code with Amazon Sagemaker hosting' approach, where users could bring their models together with all necessary dependencies, libraries, frameworks and other components compiled inside of a single custom-built docker container and host it on Sagemaker. \n",
- "\n",
- "To showcase the true 'ML/DL framework agnostic architecture' of MMS, we chose to launch a model trained with 'PaddlePaddle' framework into production.\n",
- "\n",
- "The overall picture of steps involved to take a model trained on any ML/DL framework to Amazon Sagemaker using MMS BYO container looks as follows:\n",
- "\n",
- "![image.png](attachment:image.png)\n",
- "\n",
- "As shown in the picture above, in order to bring your own ML/DL framework to Amazon Sagemaker using MMS Bring Your Own (BYO) container, we need two main components\n",
- "\n",
- "1. **Model artifacts/Model Archive**: These are all the artifacts required to run your model on a given host. This contains the following:\n",
- " 1. **Model files**, which are usually symbols and weights. These are the artifacts of training a model.\n",
- " 2. **Custom Service File**: This file contains the entry-point which gets called every time when inference request is received and served by MMS. This file generally contains the logic to initialize the model in a particular ML/DL framework, preprocess the incoming request, run inference in a particular ML/DL framework and post-process logic which takes the data coming out of framework's inference method and converts it to end-user consumable data.\n",
- " 3. **MANIFEST File**: This is the interface between custom service file and the MMS. This file is generated by running a tool that comes as part of MMS distribution, called 'model-archiver'.\n",
- "2. **Container artifact**: To load and run a model written in a custom DL framework on Sagemaker, you need to bring a container that will be run on Sagemaker service. In this document we will show how to use MMS base container and extend it to support custom DL frameworks and other model dependencies. The MMS base container is a docker container that comes with a highly scalable and performant model-server which is readily launchable onto Sagemaker service.\n",
- "In the following sections, we will see each of the above components in detail.\n",
- "\n",
- "## Preparing a Model\n",
- "MMS container is completely ML/DL framework agnostic. Users can write models in a ML/DL framework of their choice and bring it to Sagemaker with MMS BYO container to get the features of scalability and performance. In this blogpost, we chose to showcase this by bringing in a model written for PaddlePaddle framework. Lets look at how to prepare a PaddlePaddle model in the following sections. The model artifact is readily available at <*TODO: Update this with the S3 link with model.tar.gz*>.\n",
- "\n",
- "### Preparing Model Artifacts\n",
- "We are going to use [Understand Sentiment](https://github.com/PaddlePaddle/book/tree/develop/06.understand_sentiment) example that is available and published in examples section of PaddlePaddle repository. First of all we need to create a model. In order to do that we followed instructions provided in [PaddlePaddle/book](https://github.com/PaddlePaddle/book) repository: downloaded container and ran training by the notebook that is provided as part of the example. We used 'Stacked Bidirectional LSTM' network for our training and trained the model for 100 epochs. At the end of this training exercise, we get the following list of trained model artifacts.\n",
- "\n",
- "```bash\n",
- "!ls\n",
- "embedding_0.w_0 fc_2.w_0 fc_5.w_0 learning_rate_0 lstm_3.b_0 moment_10 moment_18 moment_25 moment_32 moment_8\n",
- "embedding_1.w_0 fc_2.w_1 fc_5.w_1 learning_rate_1 lstm_3.w_0 moment_11 moment_19 moment_26 moment_33 moment_9\n",
- "fc_0.b_0 fc_3.b_0 fc_6.b_0 lstm_0.b_0 lstm_4.b_0 moment_12 moment_2 moment_27 moment_34\n",
- "fc_0.w_0 fc_3.w_0 fc_6.w_0 lstm_0.w_0 lstm_4.w_0 moment_13 moment_20 moment_28 moment_35\n",
- "fc_1.b_0 fc_3.w_1 fc_6.w_1 lstm_1.b_0 lstm_5.b_0 moment_14 moment_21 moment_29 moment_4\n",
- "fc_1.w_0 fc_4.b_0 fc_7.b_0 lstm_1.w_0 lstm_5.w_0 moment_15 moment_22 moment_3 moment_5\n",
- "fc_1.w_1 fc_4.w_0 fc_7.w_0 lstm_2.b_0 moment_0 moment_16 moment_23 moment_30 moment_6\n",
- "fc_2.b_0 fc_5.b_0 fc_7.w_1 lstm_2.w_0 moment_1 moment_17 moment_24 moment_31 moment_7\n",
- "```\n",
- "\n",
- "These artifacts constitute a PaddlePaddle model. We copy these artifacts from within training container to localhost so that it will be easier to begin preparation of the model for production hosting. To learn more on how to copy files from inside a docker container to location outside of it please refer to [Docker CLI](https://docs.docker.com/engine/reference/commandline/cp/).\n",
- "\n",
- "### Writing Custom Service Code\n",
- "We now have model files required to host the model in production. We can now define a custom service file which knows how to use these files and also knows how to 'preprocess' the raw request coming into the server and how to 'postprocess' the responses coming out of the PaddlePaddle framework's 'infer' method. For this, we modified the notebook example written to test the trained model **. Let's look at some code. \n",
- "\n",
- "We created a custom service file called 'paddle_sentiment_analysis.py'. Here, we first define a class called 'PaddleSentimentAnalysis' which contains methods to initialize the model and also defines pre-processing, post-processing and inference methods. Refer [Custom Service Code](https://github.com/awslabs/mxnet-model-server/blob/master/docs/custom_service.md) document to learn how to write your custom-service code. The skeleton of this file is as follows:\n",
- "\n",
- "```bash\n",
- "$ cat paddle_sentiment_analysis.py\n",
- "```\n",
- "```python\n",
- "\n",
- "from __future__ import print_function\n",
- "import paddle\n",
- "import paddle.fluid as fluid\n",
- "import paddle.dataset as dataset\n",
- "from functools import partial\n",
- "\n",
- " \n",
- "class PaddleSentimentAnalysis(object):\n",
- " def __init__(self):\n",
- " ...\n",
- "\n",
- " def initialize(self, context):\n",
- " \"\"\"\n",
- " This method is used to initialize the network and read other artifacts.\n",
- " \"\"\"\n",
- " ...\n",
- " \n",
- " def preprocess(self, data):\n",
- " \"\"\"\n",
- " This method is used to convert the string requests coming from client \n",
- " into tensors. \n",
- " \"\"\"\n",
- " ...\n",
- "\n",
- " def inference(self, input):\n",
- " \"\"\"\n",
- " This method runs the tensors created in preprocess method through the \n",
- " DL framework's infer method.\n",
- " \"\"\"\n",
- " ...\n",
- "\n",
- " def postprocess(self, output, data):\n",
- " \"\"\"\n",
- " Here the values returned from the inference method is converted to a \n",
- " human understandable response.\n",
- " \"\"\"\n",
- " ...\n",
- " \n",
- "\n",
- "_service = PaddleSentimentAnalysis()\n",
- "\n",
- "\n",
- "def handle(data, context):\n",
- "\"\"\"\n",
- "This method is the entrypoint \\\"handler\\\" method that is used by MMS.\n",
- "Any request coming in for this model will be sent to this method.\n",
- "\"\"\"\n",
- " if not _service.initialized:\n",
- " _service.initialize(context)\n",
- "\n",
- " if data is None:\n",
- " return None\n",
- "\n",
- " pre = _service.preprocess(data)\n",
- " inf = _service.inference(pre)\n",
- " ret = _service.postprocess(inf, data)\n",
- " return ret\n",
- "```"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Note about Permissions\n",
- "Running this notebook requires permissions in addition to the normal **SageMakerFullAccess** permissions. This is because we'll creating new repositories in Amazon ECR. The easiest way to add these permissions is simply to add the managed policy **AmazonEC2ContainerRegistryFullAccess** to the role that you used to start your notebook instance. There's no need to restart your notebook instance when you do this, the new permissions will be available immediately."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Creating Model artifact file to be hosted on sagemaker\n",
- "In order to load this model onto Sagemaker platform with MMS BYO container, we need to do the following:\n",
- "\n",
- "1. Create a MANIFEST file, which is used by MMS as a model's metadata to load and run the model.\n",
- "2. Add the above custom-service file and the trained model-artifacts, along with the MANIFEST file, to a .tar.gz file.\n",
- "\n",
- "Let's use 'model-archiver' tool, to accomplish the above points. Before we use the tool to create a ''.tar.gz' artifact, we need to collect all the model artifacts, including the custom-service-file mentioned above, into a separate folder. For ease of getting started, we have uploaded all the model artifacts onto an [S3 bucket](https://s3.amazonaws.com/model-server/blog_artifacts/PaddlePaddle_blog/sentiment.tar.gz). Lets run the following commands to get this artifact onto your host:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "!(curl https://s3.amazonaws.com/model-server/blog_artifacts/PaddlePaddle_blog/artifacts.tgz | tar zxvf -) 2>/dev/null"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "!ls -R artifacts/sentiment"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Now that we have the model artifacts, let's convert this to a model artifact that can be hosted on Sagemaker. \n",
- "\n",
- "### Prerequisites\n",
- "Before we proceed with preparing a Sagemaker model-artifact and endpoint, we need the following:\n",
- "#### Software packages and tools\n",
- "1. pip\n",
- "1. Docker\n",
- "1. Model-archiver tool\n",
- "1. Sagemaker SDK\n",
- "1. Boto3 \n",
- "\n",
- "#### AWS user account with following permissions\n",
- "We will need AWS account user with permissions to \n",
- "1. Create roles (or access to an already existing Sagemaker role)\n",
- "2. Create Sagemaker Endpoint\n",
- "3. Create an ECR repository and upload a container to the repository\n",
- "4. Create an S3 bucket and upload an artifact to S3 bucket"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "We are now ready to create a sagemaker model artifact. For this, we use the \"model-archiver\" tool to create a Sagemaker model artifact. "
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "!pip install -U mxnet-model-server"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "!model-archiver -f --model-name paddle_sentiment \\\n",
- "--handler paddle_sentiment_analysis:handle \\\n",
- "--model-path artifacts/sentiment --export-path . --archive-format tgz"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "The above command would create an model artifact called `paddle_sentiment.tar.gz`, which we will use to host our endpoint. Let's verify if this model artifact is created."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "!ls"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Next let's take a look at how to build a container with it and bring it into Sagemaker.\n",
- "\n",
- "### Building your own BYO container with MMS\n",
- "\n",
- "In this section, we build our own MMS based container which can be brought onto Sagemaker (also known as BYO Container).\n",
- "\n",
- "To help with this process, every released version of MMS comes with a corresponding MMS base container, hosted on [DockerHub](https://hub.docker.com/r/awsdeeplearningteam/mxnet-model-server/tags) which can be hosted on the Sagemaker platform.\n",
- "\n",
- "For this example, we will use container tagged *awsdeeplearningteam/mxnet-model-server:base_cpu_py3.6*. To host the model created in the above section, we need to install 'PaddlePaddle' and 'numpy' packages in the container. This can be done by creating a Dockerfile which extends from the base MMS image and installs the above python packages. Here is how its content should look like:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "!cat artifacts/Dockerfile.paddle.mms"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Now that we have Dockerfile that describes our BYO container let's build it:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "!cd artifacts && docker build -t paddle-mms -f Dockerfile.paddle.mms ."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Creating Sagemaker endpoint with PaddlePaddle model\n",
- "Before we go on and create a Sagemaker endpoint for our model, we need to do some preparations:\n",
- "\n",
- "### Upload the Sagemaker model artifact to a S3 bucket\n",
- "Upload the model archive **sentiment.tar.gz** created above to a S3 bucket. Here we uploaded it to the S3 bucket called paddle_paddle. "
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "import boto3, os, uuid\n",
- "\n",
- "s3 = boto3.resource(\"s3\")\n",
- "s3_bucket_name = \"paddle-sentiment-model-\" + str(uuid.uuid1())\n",
- "local_model_artifact = s3_model_artifact = \"paddle_sentiment.tar.gz\""
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Now lets create a bucket called **paddle-sentiment-model**. Here is where we will copy the model, **paddle_sentiment.tar.gz**, that we had created above."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "import sagemaker\n",
- "from sagemaker import get_execution_role\n",
- "import boto3\n",
- "from botocore.exceptions import ClientError\n",
- "import json\n",
- "\n",
- "sess = sagemaker.Session()\n",
- "account = sess.boto_session.client(\"sts\").get_caller_identity()[\"Account\"]\n",
- "region = sess.boto_session.region_name\n",
- "\n",
- "s3.create_bucket(Bucket=s3_bucket_name, CreateBucketConfiguration={\"LocationConstraint\": region})"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "s3.meta.client.upload_file(local_model_artifact, s3_bucket_name, s3_model_artifact)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "We now have **paddle_sentiment.tar.gz** on S3 in our account. Now let's look at having the container that we built on ECR, so that we can go ahead and set up our Sagemaker Endpoint."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Upload the container image to ECR\n",
- "We had built an image called **paddle-mms** above. We need to upload this to a Amazon ECR in our account."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "%%sh\n",
- "\n",
- "# The name of our algorithm\n",
- "algorithm_name=paddle-mms\n",
- "\n",
- "account=$(aws sts get-caller-identity --query Account --output text)\n",
- "\n",
- "# Get the region defined in the current configuration (default to us-west-2 if none defined)\n",
- "region=$(aws configure get region)\n",
- "# specifically setting to us-east-1 since during the pre-release period, we support only that region.\n",
- "region=${region:-us-east-1}\n",
- "\n",
- "echo \"region is \" $region\n",
- "\n",
- "fullname=\"${account}.dkr.ecr.${region}.amazonaws.com/${algorithm_name}:latest\"\n",
- "\n",
- "echo $fullname\n",
- "# If the repository doesn't exist in ECR, create it.\n",
- "\n",
- "aws ecr describe-repositories --repository-names \"${algorithm_name}\" > /dev/null 2>&1\n",
- "\n",
- "if [ $? -ne 0 ]\n",
- "then\n",
- " aws ecr create-repository --repository-name \"${algorithm_name}\" > /dev/null\n",
- "fi\n",
- "\n",
- "# Get the login command from ECR and execute it directly\n",
- "$(aws ecr get-login --region ${region} --no-include-email)\n",
- "\n",
- "# Build the docker image locally with the image name and then push it to ECR\n",
- "# with the full name.\n",
- "\n",
- "docker tag ${algorithm_name}:latest ${fullname}\n",
- "\n",
- "docker push ${fullname}"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "This pushes the \"paddle-mms\" container to Amazon ECR in your account."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Creating Sagemaker Endpoint\n",
- "Now that the model and container artifacts are uploaded onto S3 and ECR respectively, we can go ahead and create Sagemaker endpoint. To do that we need to complete following steps\n",
- "\n",
- "\n",
- "#### Sagemaker role\n",
- "\n",
- "Before we go onto create an Sagemaker endpoint, we need to setup an IAM role which has **AmazonSageMakerFullAccess** and **AmazonS3FullAccess** and **AmazonEC2ContainerRegistryFullAccess** policy attached to it. "
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "import sagemaker\n",
- "from sagemaker import get_execution_role\n",
- "import boto3\n",
- "from botocore.exceptions import ClientError\n",
- "import json\n",
- "\n",
- "sess = sagemaker.Session()\n",
- "account = sess.boto_session.client(\"sts\").get_caller_identity()[\"Account\"]\n",
- "region = sess.boto_session.region_name\n",
- "# NOTE: If you already have a sagemaker execution role created with above attached policies, use it instead of calling get_execution_role()\n",
- "sm_role = get_execution_role()\n",
- "inference_image = \"{}.dkr.ecr.{}.amazonaws.com/paddle-mms:latest\".format(account, region)\n",
- "s3_url = \"s3://{}/{}\".format(s3_bucket_name, s3_model_artifact)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "We created the role required to launch our Sagemaker endpoint above. Now let's use the Sagemaker SDK to launch an endpoint."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "inf_handler = None"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "from sagemaker.model import Model\n",
- "\n",
- "endpoint = \"PaddleSentiment\"\n",
- "paddle_model = Model(model_data=s3_url, image=inference_image, role=sm_role)\n",
- "try:\n",
- " inf_handler = paddle_model.deploy(1, \"ml.m4.xlarge\", endpoint_name=endpoint)\n",
- "except ClientError as e:\n",
- " if \"ValidationException\" == e.response[\"Error\"][\"Code\"]:\n",
- " print('The endpoint \"{}\"already exists'.format(endpoint))\n",
- " pass\n",
- " else:\n",
- " raise"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "This creats an sagemaker endpoint using the model artifact \"paddle_sentiment.tar.gz\".\n",
- "\n",
- "### Testing the endpoint\n",
- "Let's test the endpoint. To do this, we will send a movie review to the endpoint \"paddle-sentiment\"."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "from sagemaker.predictor import (\n",
- " json_serializer,\n",
- " csv_serializer,\n",
- " json_deserializer,\n",
- " RealTimePredictor,\n",
- ")\n",
- "\n",
- "predictor = RealTimePredictor(endpoint=endpoint, sagemaker_session=sess)\n",
- "\n",
- "message = \"This is an amazing movie.\"\n",
- "print(predictor.predict(message).decode(\"utf-8\"))"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "You would get a response showing that the review was positive.\n",
- "### Delete Endpoint\n",
- "After testing your endpoint, you could delete the endpoint you created as follows."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "sess.delete_endpoint(endpoint)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Conclusion\n",
- "We have just shown how to build and host PaddlePaddle model on Sagemaker using MMS BYO container. This flow can be reused with minor modifications in order to build BYO containers serving inference traffic on Sagemaker endpoints with MMS for models built using many ML/DL frameworks, not just PaddlePaddle."
- ]
- }
- ],
- "metadata": {
- "kernelspec": {
- "display_name": "conda_python3",
- "language": "python",
- "name": "conda_python3"
- },
- "language_info": {
- "codemirror_mode": {
- "name": "ipython",
- "version": 3
- },
- "file_extension": ".py",
- "mimetype": "text/x-python",
- "name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
- "version": "3.6.5"
- }
- },
- "nbformat": 4,
- "nbformat_minor": 2
-}
diff --git a/sagemaker_batch_transform/tensorflow_open-images_tfrecord/tensorflow-serving-tfrecord.cli.ipynb b/sagemaker_batch_transform/tensorflow_open-images_tfrecord/tensorflow-serving-tfrecord.cli.ipynb
index 48f7427362..678a38c58c 100644
--- a/sagemaker_batch_transform/tensorflow_open-images_tfrecord/tensorflow-serving-tfrecord.cli.ipynb
+++ b/sagemaker_batch_transform/tensorflow_open-images_tfrecord/tensorflow-serving-tfrecord.cli.ipynb
@@ -270,7 +270,7 @@
"SPLIT_TYPE=\"TFRecord\"\n",
"BATCH_STRATEGY=\"SingleRecord\"\n",
"\n",
- "# Join outputs by newline characters. This will make JSONLines output, since each output is JSON.\n",
+ "# Join outputs by newline characters. This will make JSON Lines output, since each output is JSON.\n",
"ASSEMBLE_WITH=\"Line\"\n",
"\n",
"# The Data Source tells Batch to get all objects under the S3 prefix.\n",
diff --git a/sagemaker_processing/fairness_and_explainability/fairness_and_explainability.ipynb b/sagemaker_processing/fairness_and_explainability/fairness_and_explainability.ipynb
index 7831728e60..99fa1fe0bc 100644
--- a/sagemaker_processing/fairness_and_explainability/fairness_and_explainability.ipynb
+++ b/sagemaker_processing/fairness_and_explainability/fairness_and_explainability.ipynb
@@ -50,7 +50,7 @@
"1. Explaining the importance of the various input features on the model's decision\n",
"1. Accessing the reports through SageMaker Studio if you have an instance set up.\n",
"\n",
- "In doing so, the notebook first trains a [SageMaker XGBoost](https://docs.aws.amazon.com/sagemaker/latest/dg/xgboost.html) model using training dataset, then use SageMaker Clarify to analyze a testing dataset in CSV format. SageMaker Clarify also supports analyzing dataset in [SageMaker JSONLines dense format](https://docs.aws.amazon.com/sagemaker/latest/dg/cdf-inference.html#common-in-formats), which is illustrated in [another notebook](https://github.com/aws/amazon-sagemaker-examples/blob/master/sagemaker_processing/fairness_and_explainability/fairness_and_explainability_jsonlines_format.ipynb)."
+ "In doing so, the notebook first trains a [SageMaker XGBoost](https://docs.aws.amazon.com/sagemaker/latest/dg/xgboost.html) model using training dataset, then use SageMaker Clarify to analyze a testing dataset in CSV format. SageMaker Clarify also supports analyzing dataset in [SageMaker JSON Lines dense format](https://docs.aws.amazon.com/sagemaker/latest/dg/cdf-inference.html#common-in-formats), which is illustrated in [another notebook](https://github.com/aws/amazon-sagemaker-examples/blob/master/sagemaker_processing/fairness_and_explainability/fairness_and_explainability_jsonlines_format.ipynb)."
]
},
{
diff --git a/sagemaker_processing/fairness_and_explainability/fairness_and_explainability_byoc.ipynb b/sagemaker_processing/fairness_and_explainability/fairness_and_explainability_byoc.ipynb
index cfa55ef88d..df31b3060e 100644
--- a/sagemaker_processing/fairness_and_explainability/fairness_and_explainability_byoc.ipynb
+++ b/sagemaker_processing/fairness_and_explainability/fairness_and_explainability_byoc.ipynb
@@ -422,9 +422,9 @@
"* At container startup, the script initializes an estimator using the model file provided by the client side deploy() method. The model directory and model file name are the same as in the `train` script.\n",
"* Once started, the server is ready to serve inference requests. The logic resides in the `predict` method,\n",
" * Input validation. The example container supports the same MIME types as Clarify job does, i.e., `text/csv` and `application/jsonlines`.\n",
- " * Parse payload. Clarify job may send **batch requests** to the container for better efficiency, i.e., the payload can have multiple lines and each is a sample. So, the method decodes request payload and then split lines, then loads the lines according to the content type. For JSONLines content, the method uses a key \"features\" to extract the list of features from a JSON line. The key shall be the same as the one defined in your Clarify job analysis configuration `predictor.content_template`. It is a **contract** between the Clarify job and the container, here you can change it to something else, like \"attributes\", but remember to update the `predictor.content_template` configuration accordingly.\n",
+ " * Parse payload. Clarify job may send **batch requests** to the container for better efficiency, i.e., the payload can have multiple lines and each is a sample. So, the method decodes request payload and then split lines, then loads the lines according to the content type. For JSON Lines content, the method uses a key \"features\" to extract the list of features from a JSON line. The key shall be the same as the one defined in your Clarify job analysis configuration `predictor.content_template`. It is a **contract** between the Clarify job and the container, here you can change it to something else, like \"attributes\", but remember to update the `predictor.content_template` configuration accordingly.\n",
" * Do prediction. The method gets the probability scores instead of binary labels, because scores are better for feature explainability.\n",
- " * Format output. For a **batch request**, Clarify job expects the same number of result lines as the number of samples in the request. So, the method encodes each prediction and then join them by line-break. For JSONLines accept type, the method uses two keys \"predicted_label\" and \"score\" to indicate the prediction. The keys shall be the same as your Clarify job analysis configuration `predictor.label` and `predictor.probability`, and they are used by the Clarify job to extract predictions from container response payload. The keys are **contracts** between the Clarify job and the container, here you can change them to something else, but remember to update the analysis configuration accordingly.\n",
+ " * Format output. For a **batch request**, Clarify job expects the same number of result lines as the number of samples in the request. So, the method encodes each prediction and then join them by line-break. For JSON Lines accept type, the method uses two keys \"predicted_label\" and \"score\" to indicate the prediction. The keys shall be the same as your Clarify job analysis configuration `predictor.label` and `predictor.probability`, and they are used by the Clarify job to extract predictions from container response payload. The keys are **contracts** between the Clarify job and the container, here you can change them to something else, but remember to update the analysis configuration accordingly.\n",
"\n",
"Similarly, the script is built from scratch for demonstration purpose. In a real project, you can utilize [SageMaker Inference Toolkit](https://github.com/aws/sagemaker-inference-toolkit) which implements a model serving stack built on [Multi Model Server](https://github.com/awslabs/multi-model-server), and it can serve your own models or those you trained on SageMaker using Machine Learning frameworks with native SageMaker support."
]
@@ -473,7 +473,7 @@
"python serve --model_dir \n",
"```\n",
"\n",
- "Upon successful execution, the script should be listening on local host port `8080` for inference requests. The following cell generates a few CURL commands to send inference requests (both CSV and JSONLines) to the port. You can copy&paste them to your local terminal for execution, to hit the port and trigger the inference code. For a single sample request, the command should output only one result, and for a batch request, the command should output the same number of results (lines) as the number of samples in the request."
+ "Upon successful execution, the script should be listening on local host port `8080` for inference requests. The following cell generates a few CURL commands to send inference requests (both CSV and JSON Lines) to the port. You can copy&paste them to your local terminal for execution, to hit the port and trigger the inference code. For a single sample request, the command should output only one result, and for a batch request, the command should output the same number of results (lines) as the number of samples in the request."
]
},
{
@@ -923,13 +923,13 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "There are three scenarios where Clarify handles data types, and they all support both CSV (`text/csv`) and JSONLines (`application/jsonlines`).\n",
+ "There are three scenarios where Clarify handles data types, and they all support both CSV (`text/csv`) and JSON Lines (`application/jsonlines`).\n",
"\n",
"* dataset type: the MIME type of the dataset and SHAP baseline.\n",
"* content type: the MIME type of the shadow endpoint request payload\n",
"* accept type: the MIME type of the shadow endpoint response payload\n",
"\n",
- "The Clarify jobs in this notebook always uses CSV for dataset type, but you can choose for the other two. The following code chose JSONLines for both, but it is fine if you change one of them or both of them to CSV, because CSV and JSONLines are supported by the customer container as well."
+ "The Clarify jobs in this notebook always uses CSV for dataset type, but you can choose for the other two. The following code chose JSON Lines for both, but it is fine if you change one of them or both of them to CSV, because CSV and JSON Lines are supported by the customer container as well."
]
},
{
@@ -991,7 +991,7 @@
"A [ModelConfig](https://sagemaker.readthedocs.io/en/stable/api/training/processing.html#sagemaker.clarify.ModelConfig) object communicates information about your trained model. To avoid additional traffic to your production models, SageMaker Clarify sets up and tears down a dedicated endpoint when processing.\n",
"* `instance_type` and `instance_count` specify your preferred instance type and instance count used to run your model on during SageMaker Clarify's processing. The testing dataset is small so a single standard instance is good enough to run this example. If you have a large complex dataset, you may want to use a better instance type to speed up, or add more instances to enable Spark parallelization.\n",
"* `accept_type` denotes the endpoint response payload format, and `content_type` denotes the payload format of request to the endpoint.\n",
- "* `content_template` is used by SageMaker Clarify to compose the request payload if the content type is JSONLines. To be more specific, the placeholder `$features` will be replaced by the features list from samples. For example, the first sample of the test dataset is `25,2,226802,1,7,4,6,3,2,1,0,0,40,37`, so the corresponding request payload is `'{\"features\":[25,2,226802,1,7,4,6,3,2,1,0,0,40,37]}'`, which conforms to [SageMaker JSONLines dense format](https://docs.aws.amazon.com/sagemaker/latest/dg/cdf-inference.html#common-in-formats)."
+ "* `content_template` is used by SageMaker Clarify to compose the request payload if the content type is JSON Lines. To be more specific, the placeholder `$features` will be replaced by the features list from samples. For example, the first sample of the test dataset is `25,2,226802,1,7,4,6,3,2,1,0,0,40,37`, so the corresponding request payload is `'{\"features\":[25,2,226802,1,7,4,6,3,2,1,0,0,40,37]}'`, which conforms to [SageMaker JSON Lines dense format](https://docs.aws.amazon.com/sagemaker/latest/dg/cdf-inference.html#common-in-formats)."
]
},
{
@@ -1017,7 +1017,7 @@
"#### Writing ModelPredictedLabelConfig\n",
"\n",
"A [ModelPredictedLabelConfig](https://sagemaker.readthedocs.io/en/stable/api/training/processing.html#sagemaker.clarify.ModelPredictedLabelConfig) provides information on the format of your predictions.\n",
- "* `probability` is used by SageMaker Clarify to locate the probability score in endpoint response if the accept type is JSONLines. In this case, the response payload for a single sample request looks like `'{\"predicted_label\": 0, \"score\": 0.026494730307781475}'`, so SageMaker Clarify can find the score `0.026494730307781475` by JSONPath `'score'`.\n",
+ "* `probability` is used by SageMaker Clarify to locate the probability score in endpoint response if the accept type is JSON Lines. In this case, the response payload for a single sample request looks like `'{\"predicted_label\": 0, \"score\": 0.026494730307781475}'`, so SageMaker Clarify can find the score `0.026494730307781475` by JSONPath `'score'`.\n",
"* `probability_threshold` is used by SageMaker Clarify to convert the probability to binary labels for bias analysis. Prediction above the threshold is interpreted as label value 1 and below or equal as label value 0."
]
},
diff --git a/sagemaker_processing/fairness_and_explainability/fairness_and_explainability_jsonlines_format.ipynb b/sagemaker_processing/fairness_and_explainability/fairness_and_explainability_jsonlines_format.ipynb
index 4a7412b295..90b08ec30d 100644
--- a/sagemaker_processing/fairness_and_explainability/fairness_and_explainability_jsonlines_format.ipynb
+++ b/sagemaker_processing/fairness_and_explainability/fairness_and_explainability_jsonlines_format.ipynb
@@ -4,7 +4,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "# Fairness and Explainability with SageMaker Clarify - JSONLines Format"
+ "# Fairness and Explainability with SageMaker Clarify - JSON Lines Format"
]
},
{
@@ -44,7 +44,7 @@
"1. Explaining the importance of the various input features on the model's decision\n",
"1. Accessing the reports through SageMaker Studio if you have an instance set up.\n",
"\n",
- "In doing so, the notebook will first train a [SageMaker Linear Learner](https://docs.aws.amazon.com/sagemaker/latest/dg/linear-learner.html) model using training dataset, then use SageMaker Clarify to analyze a testing dataset in [SageMaker JSONLines dense format](https://docs.aws.amazon.com/sagemaker/latest/dg/cdf-inference.html#common-in-formats). SageMaker Clarify also supports analyzing CSV dataset, which is illustrated in [another notebook](https://github.com/aws/amazon-sagemaker-examples/blob/master/sagemaker_processing/fairness_and_explainability/fairness_and_explainability.ipynb)."
+ "In doing so, the notebook will first train a [SageMaker Linear Learner](https://docs.aws.amazon.com/sagemaker/latest/dg/linear-learner.html) model using training dataset, then use SageMaker Clarify to analyze a testing dataset in [SageMaker JSON Lines dense format](https://docs.aws.amazon.com/sagemaker/latest/dg/cdf-inference.html#common-in-formats). SageMaker Clarify also supports analyzing CSV dataset, which is illustrated in [another notebook](https://github.com/aws/amazon-sagemaker-examples/blob/master/sagemaker_processing/fairness_and_explainability/fairness_and_explainability.ipynb)."
]
},
{
@@ -247,7 +247,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "Then save the testing dataset to a JSONLines file. The file conforms to [SageMaker JSONLines dense format](https://docs.aws.amazon.com/sagemaker/latest/dg/cdf-inference.html#common-in-formats), with an additional field to hold the ground truth label."
+ "Then save the testing dataset to a JSON Lines file. The file conforms to [SageMaker JSON Lines dense format](https://docs.aws.amazon.com/sagemaker/latest/dg/cdf-inference.html#common-in-formats), with an additional field to hold the ground truth label."
]
},
{
@@ -392,14 +392,14 @@
"#### Writing DataConfig and ModelConfig\n",
"A `DataConfig` object communicates some basic information about data I/O to SageMaker Clarify. We specify where to find the input dataset, where to store the output, the target column (`label`), the header names, and the dataset type.\n",
"\n",
- "Some special things to note about this configuration for the JSONLines dataset,\n",
+ "Some special things to note about this configuration for the JSON Lines dataset,\n",
"* Argument `features` or `label` is **NOT** header string. Instead, it is a [JSONPath string](https://jmespath.org/specification.html) to locate the features list or label in the dataset. For example, for a sample like below, `features` should be 'data.features.values', and `label` should be 'data.label'. \n",
"\n",
"```\n",
"{\"data\": {\"features\": {\"values\": [25, 2, 226802, 1, 7, 4, 6, 3, 2, 1, 0, 0, 40, 37]}, \"label\": 0}}\n",
"```\n",
"\n",
- "* SageMaker Clarify will load the JSONLines dataset into tabular representation for further analysis, and argument `headers` is the list of column names. The label header shall be the last one in the headers list, and the order of feature headers shall be the same as the order of features in a sample."
+ "* SageMaker Clarify will load the JSON Lines dataset into tabular representation for further analysis, and argument `headers` is the list of column names. The label header shall be the last one in the headers list, and the order of feature headers shall be the same as the order of features in a sample."
]
},
{
@@ -426,7 +426,7 @@
"A `ModelConfig` object communicates information about your trained model. To avoid additional traffic to your production models, SageMaker Clarify sets up and tears down a dedicated endpoint when processing.\n",
"* `instance_type` and `instance_count` specify your preferred instance type and instance count used to run your model on during SageMaker Clarify's processing. The testing dataset is small so a single standard instance is good enough to run this example. If your have a large complex dataset, you may want to use a better instance type to speed up, or add more instances to enable Spark parallelization.\n",
"* `accept_type` denotes the endpoint response payload format, and `content_type` denotes the payload format of request to the endpoint.\n",
- "* `content_template` is used by SageMaker Clarify to compose the request payload if the content type is JSONLines. To be more specific, the placeholder `$features` will be replaced by the features list from samples. The request payload of a sample from the testing dataset happens to be similar to the sample itself, like `'{\"features\": [25, 2, 226802, 1, 7, 4, 6, 3, 2, 1, 0, 0, 40, 37]}'`, because both the dataset and the model input conform to [SageMaker JSONLines dense format](https://docs.aws.amazon.com/sagemaker/latest/dg/cdf-inference.html#common-in-formats)."
+ "* `content_template` is used by SageMaker Clarify to compose the request payload if the content type is JSON Lines. To be more specific, the placeholder `$features` will be replaced by the features list from samples. The request payload of a sample from the testing dataset happens to be similar to the sample itself, like `'{\"features\": [25, 2, 226802, 1, 7, 4, 6, 3, 2, 1, 0, 0, 40, 37]}'`, because both the dataset and the model input conform to [SageMaker JSON Lines dense format](https://docs.aws.amazon.com/sagemaker/latest/dg/cdf-inference.html#common-in-formats)."
]
},
{
@@ -465,7 +465,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "If you are building your own model, then you may choose a different JSONLines format, as long as it has the key elements like label and features list, and request payload built using `content_template` is supported by the model (you can customize the template but the placeholder of features list must be `$features`). Also, `dataset_type`, `accept_type` and `content_type` don't have to be the same, for example, a use case may use CSV dataset and content type, but JSONLines accept type."
+ "If you are building your own model, then you may choose a different JSON Lines format, as long as it has the key elements like label and features list, and request payload built using `content_template` is supported by the model (you can customize the template but the placeholder of features list must be `$features`). Also, `dataset_type`, `accept_type` and `content_type` don't have to be the same, for example, a use case may use CSV dataset and content type, but JSON Lines accept type."
]
},
{
diff --git a/sagemaker_processing/fairness_and_explainability/fairness_and_explainability_outputs.ipynb b/sagemaker_processing/fairness_and_explainability/fairness_and_explainability_outputs.ipynb
index 289d3b8e5b..ca4cd45863 100644
--- a/sagemaker_processing/fairness_and_explainability/fairness_and_explainability_outputs.ipynb
+++ b/sagemaker_processing/fairness_and_explainability/fairness_and_explainability_outputs.ipynb
@@ -70,7 +70,7 @@
"1. Explaining the importance of the various input features on the model's decision\n",
"1. Accessing the reports through SageMaker Studio if you have an instance set up.\n",
"\n",
- "In doing so, the notebook first trains a [SageMaker XGBoost](https://docs.aws.amazon.com/sagemaker/latest/dg/xgboost.html) model using training dataset, then use SageMaker Clarify to analyze a testing dataset in CSV format. SageMaker Clarify also supports analyzing dataset in [SageMaker JSONLines dense format](https://docs.aws.amazon.com/sagemaker/latest/dg/cdf-inference.html#common-in-formats), which is illustrated in [another notebook](https://github.com/aws/amazon-sagemaker-examples/blob/master/sagemaker_processing/fairness_and_explainability/fairness_and_explainability_jsonlines_format.ipynb)."
+ "In doing so, the notebook first trains a [SageMaker XGBoost](https://docs.aws.amazon.com/sagemaker/latest/dg/xgboost.html) model using training dataset, then use SageMaker Clarify to analyze a testing dataset in CSV format. SageMaker Clarify also supports analyzing dataset in [SageMaker JSON Lines dense format](https://docs.aws.amazon.com/sagemaker/latest/dg/cdf-inference.html#common-in-formats), which is illustrated in [another notebook](https://github.com/aws/amazon-sagemaker-examples/blob/master/sagemaker_processing/fairness_and_explainability/fairness_and_explainability_jsonlines_format.ipynb)."
]
},
{