From fe77875262a6599101a01d0fdc208aa25de855b5 Mon Sep 17 00:00:00 2001 From: EC2 Default User Date: Thu, 28 Apr 2022 19:58:12 +0000 Subject: [PATCH 01/27] refactor sequential notebooks into independent notebooks --- .../1_retail_recommend_dataprep.ipynb | 715 ------------- ...rain_tune.ipynb => retail_recommend.ipynb} | 995 ++++++++++-------- ....ipynb => retail_recommend_pipeline.ipynb} | 113 +- 3 files changed, 610 insertions(+), 1213 deletions(-) delete mode 100644 use-cases/retail_recommend/1_retail_recommend_dataprep.ipynb rename use-cases/retail_recommend/{2_retail_recommend_train_tune.ipynb => retail_recommend.ipynb} (68%) rename use-cases/retail_recommend/{3_retail_recommend_pipeline.ipynb => retail_recommend_pipeline.ipynb} (96%) diff --git a/use-cases/retail_recommend/1_retail_recommend_dataprep.ipynb b/use-cases/retail_recommend/1_retail_recommend_dataprep.ipynb deleted file mode 100644 index 6b4c30b5e4..0000000000 --- a/use-cases/retail_recommend/1_retail_recommend_dataprep.ipynb +++ /dev/null @@ -1,715 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Recommendation Engine for E-Commerce Sales: Part 1. Data Preparation\n", - "\n", - "This notebook gives an overview of techniques and services offer by SageMaker to build and deploy a personalized recommendation engine.\n", - "\n", - "## Dataset\n", - "\n", - "The dataset for this demo comes from the [UCI Machine Learning Repository](https://archive.ics.uci.edu/ml/datasets/Online+Retail). It contains all the transactions occurring between 01/12/2010 and 09/12/2011 for a UK-based and registered non-store online retail. The company mainly sells unique all-occasion gifts. The following attributes are included in our dataset:\n", - "+ InvoiceNo: Invoice number. Nominal, a 6-digit integral number uniquely assigned to each transaction. If this code starts with letter 'c', it indicates a cancellation.\n", - "+ StockCode: Product (item) code. Nominal, a 5-digit integral number uniquely assigned to each distinct product.\n", - "+ Description: Product (item) name. Nominal.\n", - "+ Quantity: The quantities of each product (item) per transaction. Numeric.\n", - "+ InvoiceDate: Invice Date and time. Numeric, the day and time when each transaction was generated.\n", - "+ UnitPrice: Unit price. Numeric, Product price per unit in sterling.\n", - "+ CustomerID: Customer number. Nominal, a 5-digit integral number uniquely assigned to each customer.\n", - "+ Country: Country name. Nominal, the name of the country where each customer resides. \n", - "\n", - "Citation: Daqing Chen, Sai Liang Sain, and Kun Guo, Data mining for the online retail industry: A case study of RFM model-based customer segmentation using data mining, Journal of Database Marketing and Customer Strategy Management, Vol. 19, No. 3, pp. 197–208, 2012 (Published online before print: 27 August 2012. doi: 10.1057/dbm.2012.17)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Solution Architecture\n", - "----\n", - "![Architecture](./images/retail_rec_dataprep.png)" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "!pip install -Uq sagemaker boto3" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Stored variables and their in-db values:\n" - ] - } - ], - "source": [ - "%store -r\n", - "%store" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "import sagemaker\n", - "import sagemaker.amazon.common as smac\n", - "import boto3\n", - "\n", - "import io\n", - "import json\n", - "import numpy as np\n", - "import pandas as pd\n", - "import matplotlib.pyplot as plt\n", - "import seaborn as sns\n", - "\n", - "from scipy.sparse import csr_matrix, hstack, save_npz\n", - "from sklearn.preprocessing import OneHotEncoder\n", - "from sklearn.feature_extraction.text import TfidfVectorizer\n", - "from sklearn.model_selection import train_test_split" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [], - "source": [ - "assert sagemaker.__version__ >= \"2.21.0\"" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [], - "source": [ - "region = boto3.Session().region_name\n", - "boto3.setup_default_session(region_name=region)\n", - "boto_session = boto3.Session(region_name=region)\n", - "\n", - "s3_client = boto3.client(\"s3\", region_name=region)\n", - "\n", - "sagemaker_boto_client = boto_session.client(\"sagemaker\")\n", - "sagemaker_session = sagemaker.session.Session(\n", - " boto_session=boto_session, sagemaker_client=sagemaker_boto_client\n", - ")\n", - "sagemaker_role = sagemaker.get_execution_role()\n", - "\n", - "bucket = sagemaker_session.default_bucket()\n", - "print(f\"using bucket{bucket} in region {region} \\n\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Read the data" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "(541909, 8)\n" - ] - }, - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
InvoiceNoStockCodeDescriptionQuantityInvoiceDateUnitPriceCustomerIDCountry
053636585123AWHITE HANGING HEART T-LIGHT HOLDER62010-12-01 08:26:002.5517850.0United Kingdom
153636571053WHITE METAL LANTERN62010-12-01 08:26:003.3917850.0United Kingdom
253636584406BCREAM CUPID HEARTS COAT HANGER82010-12-01 08:26:002.7517850.0United Kingdom
353636584029GKNITTED UNION FLAG HOT WATER BOTTLE62010-12-01 08:26:003.3917850.0United Kingdom
453636584029ERED WOOLLY HOTTIE WHITE HEART.62010-12-01 08:26:003.3917850.0United Kingdom
\n", - "
" - ], - "text/plain": [ - " InvoiceNo StockCode Description Quantity \\\n", - "0 536365 85123A WHITE HANGING HEART T-LIGHT HOLDER 6 \n", - "1 536365 71053 WHITE METAL LANTERN 6 \n", - "2 536365 84406B CREAM CUPID HEARTS COAT HANGER 8 \n", - "3 536365 84029G KNITTED UNION FLAG HOT WATER BOTTLE 6 \n", - "4 536365 84029E RED WOOLLY HOTTIE WHITE HEART. 6 \n", - "\n", - " InvoiceDate UnitPrice CustomerID Country \n", - "0 2010-12-01 08:26:00 2.55 17850.0 United Kingdom \n", - "1 2010-12-01 08:26:00 3.39 17850.0 United Kingdom \n", - "2 2010-12-01 08:26:00 2.75 17850.0 United Kingdom \n", - "3 2010-12-01 08:26:00 3.39 17850.0 United Kingdom \n", - "4 2010-12-01 08:26:00 3.39 17850.0 United Kingdom " - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df = pd.read_csv(\"data/Online Retail.csv\")\n", - "print(df.shape)\n", - "df.head()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Data Preprocessing\n", - "\n", - "First, we check for any null (i.e. missing) values." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "InvoiceNo 0\n", - "StockCode 0\n", - "Description 1454\n", - "Quantity 0\n", - "InvoiceDate 0\n", - "UnitPrice 0\n", - "CustomerID 135080\n", - "Country 0\n", - "dtype: int64" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df.isna().sum()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Drop any records with a missing CustomerID. If we do not know who the customer is, then it is not helpful to us when we make recommendations." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "(406829, 8)\n" - ] - } - ], - "source": [ - "df.dropna(subset=[\"CustomerID\"], inplace=True)\n", - "df[\"Description\"] = df[\"Description\"].apply(lambda x: x.strip())\n", - "print(df.shape)" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "plt.figure(figsize=(10, 5))\n", - "sns.distplot(df[\"Quantity\"], kde=True)\n", - "plt.title(\"Distribution of Quantity\")\n", - "plt.xlabel(\"Quantity\");" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Most of our quantities are realteively small (positive) numbers, but there are also some negative quantities as well as extreme outliers (both postiive and negative outliers). " - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "plt.figure(figsize=(10, 5))\n", - "sns.distplot(df[\"UnitPrice\"], kde=True)\n", - "plt.title(\"Distribution of Unit Prices\")\n", - "plt.xlabel(\"Price\");" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "There are no negative prices, which is good, but we can see some extreme outliers." - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
QuantityUnitPriceCustomerID
count406829.000000406829.000000406829.000000
mean12.0613033.46047115287.690570
std248.69337069.3151621713.600303
min-80995.0000000.00000012346.000000
25%2.0000001.25000013953.000000
50%5.0000001.95000015152.000000
75%12.0000003.75000016791.000000
max80995.00000038970.00000018287.000000
\n", - "
" - ], - "text/plain": [ - " Quantity UnitPrice CustomerID\n", - "count 406829.000000 406829.000000 406829.000000\n", - "mean 12.061303 3.460471 15287.690570\n", - "std 248.693370 69.315162 1713.600303\n", - "min -80995.000000 0.000000 12346.000000\n", - "25% 2.000000 1.250000 13953.000000\n", - "50% 5.000000 1.950000 15152.000000\n", - "75% 12.000000 3.750000 16791.000000\n", - "max 80995.000000 38970.000000 18287.000000" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df.describe()" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(274399, 6)" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df = df.groupby([\"StockCode\", \"Description\", \"CustomerID\", \"Country\", \"UnitPrice\"])[\n", - " \"Quantity\"\n", - "].sum()\n", - "df = df.loc[df > 0].reset_index()\n", - "df.shape" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [], - "source": [ - "def loadDataset(dataframe):\n", - " enc = OneHotEncoder(handle_unknown=\"ignore\")\n", - " onehot_cols = [\"StockCode\", \"CustomerID\", \"Country\"]\n", - " ohe_output = enc.fit_transform(dataframe[onehot_cols])\n", - "\n", - " vectorizer = TfidfVectorizer(min_df=2)\n", - " unique_descriptions = dataframe[\"Description\"].unique()\n", - " vectorizer.fit(unique_descriptions)\n", - " tfidf_output = vectorizer.transform(dataframe[\"Description\"])\n", - "\n", - " row = range(len(dataframe))\n", - " col = [0] * len(dataframe)\n", - " unit_price = csr_matrix((dataframe[\"UnitPrice\"].values, (row, col)), dtype=\"float32\")\n", - "\n", - " X = hstack([ohe_output, tfidf_output, unit_price], format=\"csr\", dtype=\"float32\")\n", - "\n", - " y = dataframe[\"Quantity\"].values.astype(\"float32\")\n", - "\n", - " return X, y" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [], - "source": [ - "X, y = loadDataset(df)" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0.9991284988048746" - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# display sparsity\n", - "total_cells = X.shape[0] * X.shape[1]\n", - "(total_cells - X.nnz) / total_cells" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Our data is over 99.9% sparse. Because of this high sparsity, the sparse matrix data type allows us to represent our data using only a small fraction of the memory that a dense matrix would require." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Prepare Data For Modeling\n", - "\n", - "+ Split the data into training and testing sets\n", - "+ Write the data to protobuf recordIO format for Pipe mode. [Read more](https://docs.aws.amazon.com/sagemaker/latest/dg/cdf-training.html) about protobuf recordIO format." - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "((219519, 9284), (54880, 9284), (219519,), (54880,))" - ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)\n", - "\n", - "X_train.shape, X_test.shape, y_train.shape, y_test.shape" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Save numpy arrays to local storage in /data folder\n" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": {}, - "outputs": [], - "source": [ - "df.to_csv(\"data/online_retail_preprocessed.csv\", index=False)\n", - "save_npz(\"data/X_train.npz\", X_train)\n", - "save_npz(\"data/X_test.npz\", X_test)\n", - "np.savez(\"data/y_train.npz\", y_train)\n", - "np.savez(\"data/y_test.npz\", y_test)" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": {}, - "outputs": [], - "source": [ - "prefix = \"personalization\"\n", - "\n", - "train_key = \"train.protobuf\"\n", - "train_prefix = f\"{prefix}/train\"\n", - "\n", - "test_key = \"test.protobuf\"\n", - "test_prefix = f\"{prefix}/test\"\n", - "\n", - "output_prefix = f\"s3://{bucket}/{prefix}/output\"" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def writeDatasetToProtobuf(X, y, bucket, prefix, key):\n", - " buf = io.BytesIO()\n", - " smac.write_spmatrix_to_sparse_tensor(buf, X, y)\n", - " buf.seek(0)\n", - " obj = \"{}/{}\".format(prefix, key)\n", - " boto3.resource(\"s3\").Bucket(bucket).Object(obj).upload_fileobj(buf)\n", - " return \"s3://{}/{}\".format(bucket, obj)\n", - "\n", - "\n", - "train_data_location = writeDatasetToProtobuf(X_train, y_train, bucket, train_prefix, train_key)\n", - "test_data_location = writeDatasetToProtobuf(X_test, y_test, bucket, test_prefix, test_key)\n", - "\n", - "print(train_data_location)\n", - "print(test_data_location)\n", - "print(\"Output: {}\".format(output_prefix))" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Stored 'train_data_location' (str)\n", - "Stored 'test_data_location' (str)\n" - ] - } - ], - "source": [ - "%store train_data_location\n", - "%store test_data_location" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In the next notebook we will explore training and tuning." - ] - } - ], - "metadata": { - "instance_type": "ml.t3.medium", - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "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.5" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/use-cases/retail_recommend/2_retail_recommend_train_tune.ipynb b/use-cases/retail_recommend/retail_recommend.ipynb similarity index 68% rename from use-cases/retail_recommend/2_retail_recommend_train_tune.ipynb rename to use-cases/retail_recommend/retail_recommend.ipynb index 3bb6535cf2..04b5d6df93 100644 --- a/use-cases/retail_recommend/2_retail_recommend_train_tune.ipynb +++ b/use-cases/retail_recommend/retail_recommend.ipynb @@ -4,14 +4,13 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Recommendation Engine for E-Commerce Sales: Part 2. Train and Make Predictions\n", + "# Recommendation Engine for E-Commerce Sales\n", "\n", "This notebook gives an overview of techniques and services offer by SageMaker to build and deploy a personalized recommendation engine.\n", "\n", "## Dataset\n", "\n", "The dataset for this demo comes from the [UCI Machine Learning Repository](https://archive.ics.uci.edu/ml/datasets/Online+Retail). It contains all the transactions occurring between 01/12/2010 and 09/12/2011 for a UK-based and registered non-store online retail. The company mainly sells unique all-occasion gifts. The following attributes are included in our dataset:\n", - "\n", "+ InvoiceNo: Invoice number. Nominal, a 6-digit integral number uniquely assigned to each transaction. If this code starts with letter 'c', it indicates a cancellation.\n", "+ StockCode: Product (item) code. Nominal, a 5-digit integral number uniquely assigned to each distinct product.\n", "+ Description: Product (item) name. Nominal.\n", @@ -28,9 +27,129 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Solution Architecture\n", + "## Part 1: Data Preparation\n", "----\n", - "![Architecture](./images/retail_rec_train_reg_deploy.png)" + "The first of the notebook will focus on preparing the data for training.\n", + "\n", + "### Solution Architecture\n", + "![Architecture](./images/retail_rec_dataprep.png)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!pip install --upgrade sagemaker" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import sagemaker\n", + "import sagemaker.amazon.common as smac\n", + "import boto3\n", + "\n", + "import io\n", + "import json\n", + "import numpy as np\n", + "import pandas as pd\n", + "import matplotlib.pyplot as plt\n", + "import seaborn as sns\n", + "\n", + "from scipy.sparse import csr_matrix, hstack, save_npz\n", + "from sklearn.preprocessing import OneHotEncoder\n", + "from sklearn.feature_extraction.text import TfidfVectorizer\n", + "from sklearn.model_selection import train_test_split" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "assert sagemaker.__version__ >= \"2.21.0\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "region = boto3.Session().region_name\n", + "boto3.setup_default_session(region_name=region)\n", + "boto_session = boto3.Session(region_name=region)\n", + "\n", + "s3_client = boto3.client(\"s3\", region_name=region)\n", + "\n", + "sagemaker_boto_client = boto_session.client(\"sagemaker\")\n", + "sagemaker_session = sagemaker.session.Session(\n", + " boto_session=boto_session, sagemaker_client=sagemaker_boto_client\n", + ")\n", + "sagemaker_role = sagemaker.get_execution_role()\n", + "\n", + "bucket = sagemaker_session.default_bucket()\n", + "print(f\"using bucket{bucket} in region {region} \\n\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Read the data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "df = pd.read_csv(\"data/Online Retail.csv\")\n", + "print(df.shape)\n", + "df.head()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Data Preprocessing\n", + "\n", + "First, we check for any null (i.e. missing) values." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "df.isna().sum()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Drop any records with a missing CustomerID. If we do not know who the customer is, then it is not helpful to us when we make recommendations." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "df.dropna(subset=[\"CustomerID\"], inplace=True)\n", + "df[\"Description\"] = df[\"Description\"].apply(lambda x: x.strip())\n", + "print(df.shape)" ] }, { @@ -38,22 +157,171 @@ "execution_count": null, "metadata": {}, "outputs": [], - "source": [] + "source": [ + "plt.figure(figsize=(10, 5))\n", + "sns.distplot(df[\"Quantity\"], kde=True)\n", + "plt.title(\"Distribution of Quantity\")\n", + "plt.xlabel(\"Quantity\");" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Most of our quantities are realteively small (positive) numbers, but there are also some negative quantities as well as extreme outliers (both postiive and negative outliers). " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "plt.figure(figsize=(10, 5))\n", + "sns.distplot(df[\"UnitPrice\"], kde=True)\n", + "plt.title(\"Distribution of Unit Prices\")\n", + "plt.xlabel(\"Price\");" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There are no negative prices, which is good, but we can see some extreme outliers." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "df.describe()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "df = df.groupby([\"StockCode\", \"Description\", \"CustomerID\", \"Country\", \"UnitPrice\"])[\n", + " \"Quantity\"\n", + "].sum()\n", + "df = df.loc[df > 0].reset_index()\n", + "df.shape" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def loadDataset(dataframe):\n", + " enc = OneHotEncoder(handle_unknown=\"ignore\")\n", + " onehot_cols = [\"StockCode\", \"CustomerID\", \"Country\"]\n", + " ohe_output = enc.fit_transform(dataframe[onehot_cols])\n", + "\n", + " vectorizer = TfidfVectorizer(min_df=2)\n", + " unique_descriptions = dataframe[\"Description\"].unique()\n", + " vectorizer.fit(unique_descriptions)\n", + " tfidf_output = vectorizer.transform(dataframe[\"Description\"])\n", + "\n", + " row = range(len(dataframe))\n", + " col = [0] * len(dataframe)\n", + " unit_price = csr_matrix((dataframe[\"UnitPrice\"].values, (row, col)), dtype=\"float32\")\n", + "\n", + " X = hstack([ohe_output, tfidf_output, unit_price], format=\"csr\", dtype=\"float32\")\n", + "\n", + " y = dataframe[\"Quantity\"].values.astype(\"float32\")\n", + "\n", + " return X, y" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "X, y = loadDataset(df)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# display sparsity\n", + "total_cells = X.shape[0] * X.shape[1]\n", + "(total_cells - X.nnz) / total_cells" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Our data is over 99.9% sparse. Because of this high sparsity, the sparse matrix data type allows us to represent our data using only a small fraction of the memory that a dense matrix would require." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Prepare Data For Modeling\n", + "\n", + "+ Split the data into training and testing sets\n", + "+ Write the data to protobuf recordIO format for Pipe mode. [Read more](https://docs.aws.amazon.com/sagemaker/latest/dg/cdf-training.html) about protobuf recordIO format." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)\n", + "\n", + "X_train.shape, X_test.shape, y_train.shape, y_test.shape" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Save numpy arrays to local storage in /data folder\n" + ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], - "source": [] + "source": [ + "df.to_csv(\"data/online_retail_preprocessed.csv\", index=False)\n", + "save_npz(\"data/X_train.npz\", X_train)\n", + "save_npz(\"data/X_test.npz\", X_test)\n", + "np.savez(\"data/y_train.npz\", y_train)\n", + "np.savez(\"data/y_test.npz\", y_test)" + ] }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "!pip install -Uq sagemaker boto3" + "prefix = \"personalization\"\n", + "\n", + "train_key = \"train.protobuf\"\n", + "train_prefix = f\"{prefix}/train\"\n", + "\n", + "test_key = \"test.protobuf\"\n", + "test_prefix = f\"{prefix}/test\"\n", + "\n", + "output_prefix = f\"s3://{bucket}/{prefix}/output\"" ] }, { @@ -62,13 +330,38 @@ "metadata": {}, "outputs": [], "source": [ - "%store -r\n", - "%store" + "def writeDatasetToProtobuf(X, y, bucket, prefix, key):\n", + " buf = io.BytesIO()\n", + " smac.write_spmatrix_to_sparse_tensor(buf, X, y)\n", + " buf.seek(0)\n", + " obj = \"{}/{}\".format(prefix, key)\n", + " boto3.resource(\"s3\").Bucket(bucket).Object(obj).upload_fileobj(buf)\n", + " return \"s3://{}/{}\".format(bucket, obj)\n", + "\n", + "\n", + "train_data_location = writeDatasetToProtobuf(X_train, y_train, bucket, train_prefix, train_key)\n", + "test_data_location = writeDatasetToProtobuf(X_test, y_test, bucket, test_prefix, test_key)\n", + "\n", + "print(train_data_location)\n", + "print(test_data_location)\n", + "print(\"Output: {}\".format(output_prefix))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Part 2: Train, Tune, and Deploy Model\n", + "----\n", + "This second part will focus on training, tuning, and deploying a model trained on the data prepared in part 1.\n", + "\n", + "### Solution Architecture\n", + "![Architecture](./images/retail_rec_train_reg_deploy.png)" ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -90,194 +383,283 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "assert sagemaker.__version__ >= \"2.21.0\"" + "region = boto3.Session().region_name\n", + "boto3.setup_default_session(region_name=region)\n", + "boto_session = boto3.Session(region_name=region)\n", + "\n", + "s3_client = boto3.client(\"s3\", region_name=region)\n", + "\n", + "sagemaker_boto_client = boto_session.client(\"sagemaker\")\n", + "sagemaker_session = sagemaker.session.Session(\n", + " boto_session=boto_session, sagemaker_client=sagemaker_boto_client\n", + ")\n", + "sagemaker_role = sagemaker.get_execution_role()\n", + "\n", + "bucket = sagemaker_session.default_bucket()\n", + "\n", + "prefix = \"personalization\"\n", + "\n", + "output_prefix = f\"s3://{bucket}/{prefix}/output\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Prepare Data For Modeling\n", + "\n", + "+ Split the data into training and testing sets\n", + "+ Write the data to protobuf recordIO format for Pipe mode. [Read more](https://docs.aws.amazon.com/sagemaker/latest/dg/cdf-training.html) about protobuf recordIO format." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# load array\n", + "X_train = load_npz(\"./data/X_train.npz\")\n", + "X_test = load_npz(\"./data/X_test.npz\")\n", + "y_train_npzfile = np.load(\"./data/y_train.npz\")\n", + "y_test_npzfile = np.load(\"./data/y_test.npz\")\n", + "y_train = y_train_npzfile.f.arr_0\n", + "y_test = y_test_npzfile.f.arr_0\n", + "\n", + "X_train.shape, X_test.shape, y_train.shape, y_test.shape" ] }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "region = boto3.Session().region_name\n", - "boto3.setup_default_session(region_name=region)\n", - "boto_session = boto3.Session(region_name=region)\n", + "input_dims = X_train.shape[1]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "container = sagemaker.image_uris.retrieve(\"factorization-machines\", region=boto_session.region_name)\n", + "\n", + "fm = sagemaker.estimator.Estimator(\n", + " container,\n", + " sagemaker_role,\n", + " instance_count=1,\n", + " instance_type=\"ml.c5.xlarge\",\n", + " output_path=output_prefix,\n", + " sagemaker_session=sagemaker_session,\n", + ")\n", + "\n", + "fm.set_hyperparameters(\n", + " feature_dim=input_dims,\n", + " predictor_type=\"regressor\",\n", + " mini_batch_size=1000,\n", + " num_factors=64,\n", + " epochs=20,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "if 'training_job_name' not in locals():\n", + " \n", + " fm.fit({'train': train_data_location, 'test': test_data_location})\n", + " training_job_name = fm.latest_training_job.job_name\n", + " \n", + "else:\n", + " print(f'Using previous training job: {training_job_name}')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Make Predictions\n", + "\n", + "Now that we've trained our model, we can deploy it behind an Amazon SageMaker real-time hosted endpoint. This will allow out to make predictions (or inference) from the model dyanamically.\n", + "\n", + "Note, Amazon SageMaker allows you the flexibility of importing models trained elsewhere, as well as the choice of not importing models if the target of model creation is AWS Lambda, AWS Greengrass, Amazon Redshift, Amazon Athena, or other deployment target.\n", + "\n", + "Here we will take the top customer, the customer who spent the most money, and try to find which items to recommend to them." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from sagemaker.deserializers import JSONDeserializer\n", + "from sagemaker.serializers import JSONSerializer" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "class FMSerializer(JSONSerializer):\n", + " def serialize(self, data):\n", + " js = {\"instances\": []}\n", + " for row in data:\n", + " js[\"instances\"].append({\"features\": row.tolist()})\n", + " return json.dumps(js)\n", + "\n", + "\n", + "fm_predictor = fm.deploy(\n", + " initial_instance_count=1,\n", + " instance_type=\"ml.m4.xlarge\",\n", + " serializer=FMSerializer(),\n", + " deserializer=JSONDeserializer(),\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# find customer who spent the most money\n", + "df = pd.read_csv(\"data/online_retail_preprocessed.csv\")\n", + "\n", + "df[\"invoice_amount\"] = df[\"Quantity\"] * df[\"UnitPrice\"]\n", + "top_customer = (\n", + " df.groupby(\"CustomerID\").sum()[\"invoice_amount\"].sort_values(ascending=False).index[0]\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def get_recommendations(df, customer_id, n_recommendations, n_ranks=100):\n", + " popular_items = (\n", + " df.groupby([\"StockCode\", \"UnitPrice\"])\n", + " .nunique()[\"CustomerID\"]\n", + " .sort_values(ascending=False)\n", + " .reset_index()\n", + " )\n", + " top_n_items = popular_items[\"StockCode\"].iloc[:n_ranks].values\n", + " top_n_prices = popular_items[\"UnitPrice\"].iloc[:n_ranks].values\n", + "\n", + " # stock codes can have multiple descriptions, so we will choose whichever description is most common\n", + " item_map = df.groupby(\"StockCode\").agg(lambda x: x.value_counts().index[0])[\"Description\"]\n", + "\n", + " # find customer's country\n", + " df_subset = df.loc[df[\"CustomerID\"] == customer_id]\n", + " country = df_subset[\"Country\"].value_counts().index[0]\n", + "\n", + " data = {\n", + " \"StockCode\": top_n_items,\n", + " \"Description\": [item_map[i] for i in top_n_items],\n", + " \"CustomerID\": customer_id,\n", + " \"Country\": country,\n", + " \"UnitPrice\": top_n_prices,\n", + " }\n", "\n", - "s3_client = boto3.client(\"s3\", region_name=region)\n", + " df_inference = pd.DataFrame(data)\n", "\n", - "sagemaker_boto_client = boto_session.client(\"sagemaker\")\n", - "sagemaker_session = sagemaker.session.Session(\n", - " boto_session=boto_session, sagemaker_client=sagemaker_boto_client\n", - ")\n", - "sagemaker_role = sagemaker.get_execution_role()\n", + " # we need to build the data set similar to how we built it for training\n", + " # it should have the same number of features as the training data\n", + " enc = OneHotEncoder(handle_unknown=\"ignore\")\n", + " onehot_cols = [\"StockCode\", \"CustomerID\", \"Country\"]\n", + " enc.fit(df[onehot_cols])\n", + " onehot_output = enc.transform(df_inference[onehot_cols])\n", "\n", - "bucket = sagemaker_session.default_bucket()\n", + " vectorizer = TfidfVectorizer(min_df=2)\n", + " unique_descriptions = df[\"Description\"].unique()\n", + " vectorizer.fit(unique_descriptions)\n", + " tfidf_output = vectorizer.transform(df_inference[\"Description\"])\n", "\n", - "prefix = \"personalization\"\n", + " row = range(len(df_inference))\n", + " col = [0] * len(df_inference)\n", + " unit_price = csr_matrix((df_inference[\"UnitPrice\"].values, (row, col)), dtype=\"float32\")\n", "\n", - "output_prefix = f\"s3://{bucket}/{prefix}/output\"" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Read the data" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Prepare Data For Modeling\n", + " X_inference = hstack([onehot_output, tfidf_output, unit_price], format=\"csr\")\n", "\n", - "+ Split the data into training and testing sets\n", - "+ Write the data to protobuf recordIO format for Pipe mode. [Read more](https://docs.aws.amazon.com/sagemaker/latest/dg/cdf-training.html) about protobuf recordIO format." + " result = fm_predictor.predict(X_inference.toarray())\n", + " preds = [i[\"score\"] for i in result[\"predictions\"]]\n", + " index_array = np.array(preds).argsort()\n", + " items = enc.inverse_transform(onehot_output)[:, 0]\n", + " top_recs = np.take_along_axis(items, index_array, axis=0)[: -n_recommendations - 1 : -1]\n", + " recommendations = [[i, item_map[i]] for i in top_recs]\n", + " return recommendations" ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "# load array\n", - "X_train = load_npz(\"./data/X_train.npz\")\n", - "X_test = load_npz(\"./data/X_test.npz\")\n", - "y_train_npzfile = np.load(\"./data/y_train.npz\")\n", - "y_test_npzfile = np.load(\"./data/y_test.npz\")\n", - "y_train = y_train_npzfile.f.arr_0\n", - "y_test = y_test_npzfile.f.arr_0" + "print(\"Top 5 recommended products:\")\n", + "get_recommendations(df, top_customer, n_recommendations=5, n_ranks=100)" ] }, { - "cell_type": "code", - "execution_count": 7, + "cell_type": "markdown", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "((219519, 9284), (54880, 9284), (219519,), (54880,))" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], "source": [ - "X_train.shape, X_test.shape, y_train.shape, y_test.shape" + "Once you are done with the endpoint, you should delete the endpoint to save cost and free resources." ] }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Stored 'input_dims' (int)\n" - ] - } - ], + "outputs": [], "source": [ - "input_dims = X_train.shape[1]\n", - "%store input_dims" + "fm_predictor.delete_model()\n", + "fm_predictor.delete_endpoint()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Train the factorization machine model\n", - "\n", - "Once we have the data preprocessed and available in the correct format for training, the next step is to actually train the model using the data. \n", + "## Optional Part: Registering the Model in SageMaker Model Registry\n", "\n", - "We'll use the Amazon SageMaker Python SDK to kick off training and monitor status until it is completed. In this example that takes only a few minutes. Despite the model only need 1-2 minutes to train, there is some extra time required upfront to provision hardware and load the algorithm container.\n", - "\n", - "First, let's specify our containers. To find the rigth container, we'll create a small lookup. More details on algorithm containers can be found in [AWS documentation.](https://docs-aws.amazon.com/sagemaker/latest/dg/sagemaker-algo-docker-registry-paths.html)" + "Once a useful model has been trained, you have the option to register the model for future reference and possible deployment. To do so, we must first properly associate the artifacts of the model." ] }, { - "cell_type": "code", - "execution_count": 9, + "cell_type": "markdown", "metadata": {}, - "outputs": [], "source": [ - "container = sagemaker.image_uris.retrieve(\"factorization-machines\", region=boto_session.region_name)\n", - "\n", - "fm = sagemaker.estimator.Estimator(\n", - " container,\n", - " sagemaker_role,\n", - " instance_count=1,\n", - " instance_type=\"ml.c5.xlarge\",\n", - " output_path=output_prefix,\n", - " sagemaker_session=sagemaker_session,\n", - ")\n", - "\n", - "fm.set_hyperparameters(\n", - " feature_dim=input_dims,\n", - " predictor_type=\"regressor\",\n", - " mini_batch_size=1000,\n", - " num_factors=64,\n", - " epochs=20,\n", - ")" + "### Training data artifact" ] }, { "cell_type": "code", "execution_count": null, - "metadata": { - "scrolled": true - }, - "outputs": [], - "source": [ - "if 'training_job_name' not in locals():\n", - " \n", - " fm.fit({'train': train_data_location, 'test': test_data_location})\n", - " training_job_name = fm.latest_training_job.job_name\n", - " %store training_job_name\n", - " \n", - "else:\n", - " print(f'Using previous training job: {training_job_name}')" - ] - }, - { - "cell_type": "code", - "execution_count": 11, "metadata": {}, "outputs": [], "source": [ "training_job_info = sagemaker_boto_client.describe_training_job(TrainingJobName=training_job_name)" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Training data artifact" - ] - }, { "cell_type": "code", - "execution_count": 12, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Using existing artifact: arn:aws:sagemaker:us-east-2:645431112437:artifact/cdd7fbecb4eefa22c43b2ad48140acc2\n" - ] - } - ], + "outputs": [], "source": [ "training_data_s3_uri = training_job_info[\"InputDataConfig\"][0][\"DataSource\"][\"S3DataSource\"][\n", " \"S3Uri\"\n", @@ -318,17 +700,9 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Using existing artifact: arn:aws:sagemaker:us-east-2:645431112437:artifact/3acde2fc029adeff9c767be68feac3a7\n" - ] - } - ], + "outputs": [], "source": [ "trained_model_s3_uri = training_job_info[\"ModelArtifacts\"][\"S3ModelArtifacts\"]\n", "\n", @@ -358,7 +732,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -377,18 +751,9 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Association already exists with DataSet\n", - "Association with Model: SUCCEESFUL\n" - ] - } - ], + "outputs": [], "source": [ "artifact_list = [[training_data_artifact, \"ContributedTo\"], [model_artifact, \"Produced\"]]\n", "\n", @@ -430,41 +795,27 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## SageMaker Model Registry\n", - "\n", - "Once a useful model has been trained and its artifacts properly associated, the next step is to register the model for future reference and possible deployment.\n", - "\n", "### Create Model Package Group\n", "\n", - "A Model Package Groups holds multiple versions or iterations of a model. Though it is not required to create them for every model in the registry, they help organize various models which all have the same purpose and provide autiomatic versioning." + "After associating all the relevant artifacts, the Model Package Group can now be created. A Model Package Groups holds multiple versions or iterations of a model. Though it is not required to create them for every model in the registry, they help organize various models which all have the same purpose and provide autiomatic versioning." ] }, { "cell_type": "code", - "execution_count": 17, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Stored 'mpg_name' (str)\n", - "Model Package Group name: retail-recommendation-2021-03-01-21-41\n" - ] - } - ], + "outputs": [], "source": [ "if 'mpg_name' not in locals():\n", " timestamp = datetime.datetime.now().strftime('%Y-%m-%d-%H-%M')\n", " mpg_name = f'retail-recommendation-{timestamp}'\n", - " %store mpg_name\n", "\n", "print(f'Model Package Group name: {mpg_name}')" ] }, { "cell_type": "code", - "execution_count": 18, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -493,7 +844,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -519,7 +870,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -546,7 +897,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -562,7 +913,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -586,17 +937,9 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "model package status: Completed\n" - ] - } - ], + "outputs": [], "source": [ "mp_info = sagemaker_boto_client.describe_model_package(\n", " ModelPackageName=mp_response[\"ModelPackageArn\"]\n", @@ -615,7 +958,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -630,277 +973,25 @@ "update_response = sagemaker_boto_client.update_model_package(**model_package_update)" ] }, - { - "cell_type": "code", - "execution_count": 26, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
Name/SourceDirectionTypeAssociation TypeLineage Type
0s3://...1-03-01-21-36-56-437/output/model.tar.gzInputModelProducedartifact
1s3://...12437/personalization/test/test.protobufInputDataSetContributedToartifact
2s3://...437/personalization/train/train.protobufInputDataSetContributedToartifact
340461...2.amazonaws.com/factorization-machines:1InputImageContributedToartifact
4s3://...1-03-01-21-36-56-437/output/model.tar.gzOutputModelProducedartifact
\n", - "
" - ], - "text/plain": [ - " Name/Source Direction Type \\\n", - "0 s3://...1-03-01-21-36-56-437/output/model.tar.gz Input Model \n", - "1 s3://...12437/personalization/test/test.protobuf Input DataSet \n", - "2 s3://...437/personalization/train/train.protobuf Input DataSet \n", - "3 40461...2.amazonaws.com/factorization-machines:1 Input Image \n", - "4 s3://...1-03-01-21-36-56-437/output/model.tar.gz Output Model \n", - "\n", - " Association Type Lineage Type \n", - "0 Produced artifact \n", - "1 ContributedTo artifact \n", - "2 ContributedTo artifact \n", - "3 ContributedTo artifact \n", - "4 Produced artifact " - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "from sagemaker.lineage.visualizer import LineageTableVisualizer\n", - "\n", - "viz = LineageTableVisualizer(sagemaker_session)\n", - "display(viz.show(training_job_name=training_job_name))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Make Predictions\n", - "\n", - "Now that we've trained our model, we can deploy it behind an Amazon SageMaker real-time hosted endpoint. This will allow out to make predictions (or inference) from the model dyanamically.\n", - "\n", - "Note, Amazon SageMaker allows you the flexibility of importing models trained elsewhere, as well as the choice of not importing models if the target of model creation is AWS Lambda, AWS Greengrass, Amazon Redshift, Amazon Athena, or other deployment target.\n", - "\n", - "Here we will take the top customer, the customer who spent the most money, and try to find which items to recommend to them." - ] - }, - { - "cell_type": "code", - "execution_count": 27, - "metadata": {}, - "outputs": [], - "source": [ - "from sagemaker.deserializers import JSONDeserializer\n", - "from sagemaker.serializers import JSONSerializer" - ] - }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "class FMSerializer(JSONSerializer):\n", - " def serialize(self, data):\n", - " js = {\"instances\": []}\n", - " for row in data:\n", - " js[\"instances\"].append({\"features\": row.tolist()})\n", - " return json.dumps(js)\n", - "\n", - "\n", - "fm_predictor = fm.deploy(\n", - " initial_instance_count=1,\n", - " instance_type=\"ml.m4.xlarge\",\n", - " serializer=FMSerializer(),\n", - " deserializer=JSONDeserializer(),\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 30, - "metadata": {}, - "outputs": [], - "source": [ - "# find customer who spent the most money\n", - "df = pd.read_csv(\"data/online_retail_preprocessed.csv\")\n", - "\n", - "df[\"invoice_amount\"] = df[\"Quantity\"] * df[\"UnitPrice\"]\n", - "top_customer = (\n", - " df.groupby(\"CustomerID\").sum()[\"invoice_amount\"].sort_values(ascending=False).index[0]\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 31, - "metadata": {}, - "outputs": [], - "source": [ - "def get_recommendations(df, customer_id, n_recommendations, n_ranks=100):\n", - " popular_items = (\n", - " df.groupby([\"StockCode\", \"UnitPrice\"])\n", - " .nunique()[\"CustomerID\"]\n", - " .sort_values(ascending=False)\n", - " .reset_index()\n", - " )\n", - " top_n_items = popular_items[\"StockCode\"].iloc[:n_ranks].values\n", - " top_n_prices = popular_items[\"UnitPrice\"].iloc[:n_ranks].values\n", - "\n", - " # stock codes can have multiple descriptions, so we will choose whichever description is most common\n", - " item_map = df.groupby(\"StockCode\").agg(lambda x: x.value_counts().index[0])[\"Description\"]\n", - "\n", - " # find customer's country\n", - " df_subset = df.loc[df[\"CustomerID\"] == customer_id]\n", - " country = df_subset[\"Country\"].value_counts().index[0]\n", - "\n", - " data = {\n", - " \"StockCode\": top_n_items,\n", - " \"Description\": [item_map[i] for i in top_n_items],\n", - " \"CustomerID\": customer_id,\n", - " \"Country\": country,\n", - " \"UnitPrice\": top_n_prices,\n", - " }\n", - "\n", - " df_inference = pd.DataFrame(data)\n", - "\n", - " # we need to build the data set similar to how we built it for training\n", - " # it should have the same number of features as the training data\n", - " enc = OneHotEncoder(handle_unknown=\"ignore\")\n", - " onehot_cols = [\"StockCode\", \"CustomerID\", \"Country\"]\n", - " enc.fit(df[onehot_cols])\n", - " onehot_output = enc.transform(df_inference[onehot_cols])\n", - "\n", - " vectorizer = TfidfVectorizer(min_df=2)\n", - " unique_descriptions = df[\"Description\"].unique()\n", - " vectorizer.fit(unique_descriptions)\n", - " tfidf_output = vectorizer.transform(df_inference[\"Description\"])\n", - "\n", - " row = range(len(df_inference))\n", - " col = [0] * len(df_inference)\n", - " unit_price = csr_matrix((df_inference[\"UnitPrice\"].values, (row, col)), dtype=\"float32\")\n", - "\n", - " X_inference = hstack([onehot_output, tfidf_output, unit_price], format=\"csr\")\n", + "from sagemaker.lineage.visualizer import LineageTableVisualizer\n", "\n", - " result = fm_predictor.predict(X_inference.toarray())\n", - " preds = [i[\"score\"] for i in result[\"predictions\"]]\n", - " index_array = np.array(preds).argsort()\n", - " items = enc.inverse_transform(onehot_output)[:, 0]\n", - " top_recs = np.take_along_axis(items, index_array, axis=0)[: -n_recommendations - 1 : -1]\n", - " recommendations = [[i, item_map[i]] for i in top_recs]\n", - " return recommendations" - ] - }, - { - "cell_type": "code", - "execution_count": 32, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Top 5 recommended products:\n" - ] - }, - { - "data": { - "text/plain": [ - "[['22423', 'REGENCY CAKESTAND 3 TIER'],\n", - " ['22776', 'SWEETHEART CAKESTAND 3 TIER'],\n", - " ['22624', 'IVORY KITCHEN SCALES'],\n", - " ['85123A', 'WHITE HANGING HEART T-LIGHT HOLDER'],\n", - " ['85099B', 'JUMBO BAG RED RETROSPOT']]" - ] - }, - "execution_count": 32, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "print(\"Top 5 recommended products:\")\n", - "get_recommendations(df, top_customer, n_recommendations=5, n_ranks=100)" + "viz = LineageTableVisualizer(sagemaker_session)\n", + "display(viz.show(training_job_name=training_job_name))" ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { "instance_type": "ml.t3.medium", "kernelspec": { - "display_name": "Python 3", + "display_name": "conda_python3", "language": "python", - "name": "python3" + "name": "conda_python3" }, "language_info": { "codemirror_mode": { @@ -912,7 +1003,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.5" + "version": "3.6.13" } }, "nbformat": 4, diff --git a/use-cases/retail_recommend/3_retail_recommend_pipeline.ipynb b/use-cases/retail_recommend/retail_recommend_pipeline.ipynb similarity index 96% rename from use-cases/retail_recommend/3_retail_recommend_pipeline.ipynb rename to use-cases/retail_recommend/retail_recommend_pipeline.ipynb index 5d9e3b09ae..81f1a3b945 100644 --- a/use-cases/retail_recommend/3_retail_recommend_pipeline.ipynb +++ b/use-cases/retail_recommend/retail_recommend_pipeline.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Recommendation Engine for E-Commerce Sales: Part 3. Build Pipeline\n", + "# Recommendation Engine for E-Commerce Sales - Pipeline Mode\n", "\n", "This notebook gives an overview of techniques and services offer by SageMaker to build and deploy a personalized recommendation engine.\n", "\n", @@ -32,28 +32,18 @@ "![Architecture](./images/retail_rec_pipeline.png)" ] }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "!pip install -Uq sagemaker boto3" - ] - }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "%store -r\n", - "%store" + "! pip install --upgrade sagemaker" ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -63,13 +53,16 @@ "from sagemaker.workflow.step_collections import RegisterModel\n", "from sagemaker.sklearn.processing import SKLearnProcessor\n", "from sagemaker.workflow.parameters import ParameterInteger, ParameterFloat, ParameterString\n", + "import datetime\n", "import boto3\n", - "import time" + "import time\n", + "import pandas as pd\n", + "from preprocessing import loadDataset" ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -78,7 +71,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -108,9 +101,41 @@ "## Define Estimator" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "First, the number of feature dimensions must be calculated as it is a hyperparameter of the estimator. The feature dimensions are calculated by looking at the dataset, cleaning and preprocessing it as defined in the first part of [Recommendation Engine for E-Commerce Sales](retail_recommend.ipynb), and then counting the number of feature dimensions are in the processed dataset." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "df = pd.read_csv(\"data/Online Retail.csv\")\n", + "df.dropna(subset=[\"CustomerID\"], inplace=True)\n", + "df[\"Description\"] = df[\"Description\"].apply(lambda x: x.strip())\n", + "df = df.groupby([\"StockCode\", \"Description\", \"CustomerID\", \"Country\", \"UnitPrice\"])[\n", + " \"Quantity\"\n", + "].sum()\n", + "df = df.loc[df > 0].reset_index()\n", + "X, y = loadDataset(df)\n", + "input_dims = X.shape[1]\n", + "input_dims" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "After calculating all the hyperparameters that are needed, the estimator is created." + ] + }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -145,7 +170,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -159,7 +184,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -170,7 +195,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -209,7 +234,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -233,13 +258,22 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "train_step.properties.AlgorithmSpecification.TrainingImage._path" + ] + }, + { + "cell_type": "code", + "execution_count": null, "metadata": {}, "outputs": [], "source": [ "model = sagemaker.model.Model(\n", " name=\"retail-personalization-factorization-machine\",\n", - " image_uri=train_step.properties.AlgorithmSpecification.TrainingImage,\n", + " image_uri=container,#train_step.properties.AlgorithmSpecification.TrainingImage,\n", " model_data=train_step.properties.ModelArtifacts.S3ModelArtifacts,\n", " sagemaker_session=sagemaker_session,\n", " role=sagemaker_role,\n", @@ -252,10 +286,13 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ + "timestamp = datetime.datetime.now().strftime('%Y-%m-%d-%H-%M')\n", + "mpg_name = f'retail-recommendation-{timestamp}'\n", + "\n", "register_step = RegisterModel(\n", " name=\"RegisterModel\",\n", " estimator=fm,\n", @@ -271,7 +308,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -306,20 +343,11 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Stored 'pipeline_name' (str)\n" - ] - } - ], + "outputs": [], "source": [ "pipeline_name = f\"PersonalizationDemo\"\n", - "%store pipeline_name\n", "\n", "pipeline = Pipeline(\n", " name=pipeline_name,\n", @@ -376,21 +404,14 @@ " display(viz.show(pipeline_execution_step=execution_step))\n", " time.sleep(5)" ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { "instance_type": "ml.t3.medium", "kernelspec": { - "display_name": "Python 3", + "display_name": "conda_python3", "language": "python", - "name": "python3" + "name": "conda_python3" }, "language_info": { "codemirror_mode": { @@ -402,7 +423,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.5" + "version": "3.6.13" } }, "nbformat": 4, From a3c42d585357ef1b0d8b6ef7d90ab8665c6b854f Mon Sep 17 00:00:00 2001 From: EC2 Default User Date: Thu, 28 Apr 2022 20:20:39 +0000 Subject: [PATCH 02/27] cleanup --- .../retail_recommend/retail_recommend_pipeline.ipynb | 9 --------- 1 file changed, 9 deletions(-) diff --git a/use-cases/retail_recommend/retail_recommend_pipeline.ipynb b/use-cases/retail_recommend/retail_recommend_pipeline.ipynb index 81f1a3b945..261616f913 100644 --- a/use-cases/retail_recommend/retail_recommend_pipeline.ipynb +++ b/use-cases/retail_recommend/retail_recommend_pipeline.ipynb @@ -256,15 +256,6 @@ ")" ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "train_step.properties.AlgorithmSpecification.TrainingImage._path" - ] - }, { "cell_type": "code", "execution_count": null, From 790beddd64d61f5363b25f01ad2a7e16d2b07e0e Mon Sep 17 00:00:00 2001 From: EC2 Default User Date: Thu, 28 Apr 2022 20:36:02 +0000 Subject: [PATCH 03/27] reformat --- .../retail_recommend/retail_recommend.ipynb | 18 +++++++++--------- .../retail_recommend_pipeline.ipynb | 6 +++--- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/use-cases/retail_recommend/retail_recommend.ipynb b/use-cases/retail_recommend/retail_recommend.ipynb index 04b5d6df93..abe3fa0ea5 100644 --- a/use-cases/retail_recommend/retail_recommend.ipynb +++ b/use-cases/retail_recommend/retail_recommend.ipynb @@ -474,13 +474,13 @@ "metadata": {}, "outputs": [], "source": [ - "if 'training_job_name' not in locals():\n", - " \n", - " fm.fit({'train': train_data_location, 'test': test_data_location})\n", + "if \"training_job_name\" not in locals():\n", + "\n", + " fm.fit({\"train\": train_data_location, \"test\": test_data_location})\n", " training_job_name = fm.latest_training_job.job_name\n", - " \n", + "\n", "else:\n", - " print(f'Using previous training job: {training_job_name}')" + " print(f\"Using previous training job: {training_job_name}\")" ] }, { @@ -806,11 +806,11 @@ "metadata": {}, "outputs": [], "source": [ - "if 'mpg_name' not in locals():\n", - " timestamp = datetime.datetime.now().strftime('%Y-%m-%d-%H-%M')\n", - " mpg_name = f'retail-recommendation-{timestamp}'\n", + "if \"mpg_name\" not in locals():\n", + " timestamp = datetime.datetime.now().strftime(\"%Y-%m-%d-%H-%M\")\n", + " mpg_name = f\"retail-recommendation-{timestamp}\"\n", "\n", - "print(f'Model Package Group name: {mpg_name}')" + "print(f\"Model Package Group name: {mpg_name}\")" ] }, { diff --git a/use-cases/retail_recommend/retail_recommend_pipeline.ipynb b/use-cases/retail_recommend/retail_recommend_pipeline.ipynb index 261616f913..a8a1b23605 100644 --- a/use-cases/retail_recommend/retail_recommend_pipeline.ipynb +++ b/use-cases/retail_recommend/retail_recommend_pipeline.ipynb @@ -264,7 +264,7 @@ "source": [ "model = sagemaker.model.Model(\n", " name=\"retail-personalization-factorization-machine\",\n", - " image_uri=container,#train_step.properties.AlgorithmSpecification.TrainingImage,\n", + " image_uri=container, # train_step.properties.AlgorithmSpecification.TrainingImage,\n", " model_data=train_step.properties.ModelArtifacts.S3ModelArtifacts,\n", " sagemaker_session=sagemaker_session,\n", " role=sagemaker_role,\n", @@ -281,8 +281,8 @@ "metadata": {}, "outputs": [], "source": [ - "timestamp = datetime.datetime.now().strftime('%Y-%m-%d-%H-%M')\n", - "mpg_name = f'retail-recommendation-{timestamp}'\n", + "timestamp = datetime.datetime.now().strftime(\"%Y-%m-%d-%H-%M\")\n", + "mpg_name = f\"retail-recommendation-{timestamp}\"\n", "\n", "register_step = RegisterModel(\n", " name=\"RegisterModel\",\n", From ced1ef28215bc9017c440e487dfa6037e832fec6 Mon Sep 17 00:00:00 2001 From: atqy Date: Thu, 28 Apr 2022 23:46:51 +0000 Subject: [PATCH 04/27] make pandas version compatible --- .../retail_recommend/retail_recommend.ipynb | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/use-cases/retail_recommend/retail_recommend.ipynb b/use-cases/retail_recommend/retail_recommend.ipynb index abe3fa0ea5..6bb3adbe38 100644 --- a/use-cases/retail_recommend/retail_recommend.ipynb +++ b/use-cases/retail_recommend/retail_recommend.ipynb @@ -565,14 +565,17 @@ " # find customer's country\n", " df_subset = df.loc[df[\"CustomerID\"] == customer_id]\n", " country = df_subset[\"Country\"].value_counts().index[0]\n", - "\n", - " data = {\n", - " \"StockCode\": top_n_items,\n", - " \"Description\": [item_map[i] for i in top_n_items],\n", - " \"CustomerID\": customer_id,\n", - " \"Country\": country,\n", - " \"UnitPrice\": top_n_prices,\n", - " }\n", + " \n", + " data = []\n", + " flattened_item_map = [item_map[i] for i in top_n_items]\n", + " for idx in range(len(top_n_items)):\n", + " data.append({\n", + " \"StockCode\": top_n_items[idx],\n", + " \"Description\": flattened_item_map[idx],\n", + " \"CustomerID\": customer_id,\n", + " \"Country\": country,\n", + " \"UnitPrice\": top_n_prices[idx],\n", + " })\n", "\n", " df_inference = pd.DataFrame(data)\n", "\n", @@ -987,7 +990,6 @@ } ], "metadata": { - "instance_type": "ml.t3.medium", "kernelspec": { "display_name": "conda_python3", "language": "python", From 873eefae97ef9e1b81afeaed23666ea925cce0a6 Mon Sep 17 00:00:00 2001 From: atqy Date: Thu, 28 Apr 2022 23:47:58 +0000 Subject: [PATCH 05/27] reformat --- .../retail_recommend/retail_recommend.ipynb | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/use-cases/retail_recommend/retail_recommend.ipynb b/use-cases/retail_recommend/retail_recommend.ipynb index 6bb3adbe38..ef3775439e 100644 --- a/use-cases/retail_recommend/retail_recommend.ipynb +++ b/use-cases/retail_recommend/retail_recommend.ipynb @@ -565,17 +565,19 @@ " # find customer's country\n", " df_subset = df.loc[df[\"CustomerID\"] == customer_id]\n", " country = df_subset[\"Country\"].value_counts().index[0]\n", - " \n", + "\n", " data = []\n", " flattened_item_map = [item_map[i] for i in top_n_items]\n", " for idx in range(len(top_n_items)):\n", - " data.append({\n", - " \"StockCode\": top_n_items[idx],\n", - " \"Description\": flattened_item_map[idx],\n", - " \"CustomerID\": customer_id,\n", - " \"Country\": country,\n", - " \"UnitPrice\": top_n_prices[idx],\n", - " })\n", + " data.append(\n", + " {\n", + " \"StockCode\": top_n_items[idx],\n", + " \"Description\": flattened_item_map[idx],\n", + " \"CustomerID\": customer_id,\n", + " \"Country\": country,\n", + " \"UnitPrice\": top_n_prices[idx],\n", + " }\n", + " )\n", "\n", " df_inference = pd.DataFrame(data)\n", "\n", From aff2a74e1d99860331652c4fa479d14bf8ccac15 Mon Sep 17 00:00:00 2001 From: atqy Date: Thu, 28 Apr 2022 23:49:50 +0000 Subject: [PATCH 06/27] cleanup --- use-cases/retail_recommend/retail_recommend_pipeline.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/use-cases/retail_recommend/retail_recommend_pipeline.ipynb b/use-cases/retail_recommend/retail_recommend_pipeline.ipynb index a8a1b23605..72bfcfa4e9 100644 --- a/use-cases/retail_recommend/retail_recommend_pipeline.ipynb +++ b/use-cases/retail_recommend/retail_recommend_pipeline.ipynb @@ -264,7 +264,7 @@ "source": [ "model = sagemaker.model.Model(\n", " name=\"retail-personalization-factorization-machine\",\n", - " image_uri=container, # train_step.properties.AlgorithmSpecification.TrainingImage,\n", + " image_uri=container,\n", " model_data=train_step.properties.ModelArtifacts.S3ModelArtifacts,\n", " sagemaker_session=sagemaker_session,\n", " role=sagemaker_role,\n", From 3238674b098813487a33f65375bbfbcbd53327c4 Mon Sep 17 00:00:00 2001 From: atqy Date: Thu, 28 Apr 2022 23:59:47 +0000 Subject: [PATCH 07/27] dleete instance type --- use-cases/retail_recommend/retail_recommend_pipeline.ipynb | 1 - 1 file changed, 1 deletion(-) diff --git a/use-cases/retail_recommend/retail_recommend_pipeline.ipynb b/use-cases/retail_recommend/retail_recommend_pipeline.ipynb index 72bfcfa4e9..3e5bc3b221 100644 --- a/use-cases/retail_recommend/retail_recommend_pipeline.ipynb +++ b/use-cases/retail_recommend/retail_recommend_pipeline.ipynb @@ -398,7 +398,6 @@ } ], "metadata": { - "instance_type": "ml.t3.medium", "kernelspec": { "display_name": "conda_python3", "language": "python", From c79501da119cd86f0e9769461d2bbbc2bbc08b6d Mon Sep 17 00:00:00 2001 From: atqy Date: Fri, 29 Apr 2022 00:56:44 +0000 Subject: [PATCH 08/27] edit links --- use-cases/index.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/use-cases/index.rst b/use-cases/index.rst index 9ab084bbf6..5f406a8668 100644 --- a/use-cases/index.rst +++ b/use-cases/index.rst @@ -27,9 +27,8 @@ E-Commerce Personalization .. toctree:: :maxdepth: 1 - retail_recommend/1_retail_recommend_dataprep - retail_recommend/2_retail_recommend_train_tune - retail_recommend/3_retail_recommend_pipeline + retail_recommend/retail_recommend + retail_recommend/retail_recommend_pipeline Computer Vision for Medical Imaging From 116be4d87b3cc6ac84f52ba5edd6f0a902f5e27a Mon Sep 17 00:00:00 2001 From: atqy Date: Fri, 29 Apr 2022 23:07:12 +0000 Subject: [PATCH 09/27] refactor sequential notebooks --- .../0_cust_churn_overview_dw.ipynb | 529 +++++++++- .../2_cust_churn_train_deploy_infer.ipynb | 985 ++++++++++++++++-- 2 files changed, 1394 insertions(+), 120 deletions(-) diff --git a/use-cases/customer_churn/0_cust_churn_overview_dw.ipynb b/use-cases/customer_churn/0_cust_churn_overview_dw.ipynb index 2f2a2f6e51..0d270b8a89 100644 --- a/use-cases/customer_churn/0_cust_churn_overview_dw.ipynb +++ b/use-cases/customer_churn/0_cust_churn_overview_dw.ipynb @@ -164,7 +164,8 @@ "metadata": {}, "outputs": [], "source": [ - "!pip install -q 'sagemaker==2.19.0' 'botocore == 1.19.4' 's3fs==0.4.2' 'sagemaker-experiments' 'boto3 == 1.16.4'\n", + "!pip install -q 's3fs==0.4.2' 'sagemaker-experiments'\n", + "!pip install --upgrade sagemaker boto3\n", "# s3fs is needed for pandas to read files from S3" ] }, @@ -207,26 +208,6 @@ "prefix = \"music-streaming\"" ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%store -r\n", - "%store" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%store bucket\n", - "%store prefix" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -937,8 +918,9 @@ "outputs": [], "source": [ "processing_output_filename = f\"{processing_output_path}/{final_features_filename}\"\n", - "%store processing_output_filename\n", - "%store -r" + "# %store processing_output_filename\n", + "# %store -r\n", + "processing_output_filename" ] }, { @@ -975,8 +957,365 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Citation\n", - "The data used in this notebook is simulated using the [EventSim](https://github.com/Interana/eventsim)." + "### Preprocess the Data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from sagemaker.sklearn.processing import SKLearnProcessor\n", + "\n", + "sklearn_processor = SKLearnProcessor(\n", + " # framework_version='0.20.0',\n", + " framework_version=\"0.23-1\",\n", + " role=role,\n", + " instance_type=\"ml.m5.xlarge\",\n", + " instance_count=1,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "### SAVE THE OUTPUT FILE NAME FROM PROCESSING JOB\n", + "processing_job_output_name = 'processing_job_output.csv'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%writefile preprocessing.py\n", + "\n", + "import os\n", + "import warnings\n", + "import time\n", + "import argparse\n", + "import subprocess\n", + "import sys\n", + "\n", + "subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"--upgrade\", \"pandas\"])\n", + "subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"awswrangler\"])\n", + "import pandas as pd\n", + "import awswrangler as wr\n", + "\n", + "start_time = time.time()\n", + "\n", + "if __name__ == \"__main__\":\n", + " parser = argparse.ArgumentParser()\n", + " parser.add_argument(\"--dw-output-path\")\n", + " parser.add_argument(\"--processing-output-filename\")\n", + "\n", + " args, _ = parser.parse_known_args()\n", + " print(\"Received arguments {}\".format(args))\n", + "\n", + " data_s3_uri = args.dw_output_path\n", + " output_filename = args.processing_output_filename\n", + "\n", + " # data_path = os.path.join('/opt/ml/processing/input', dw_output_name)\n", + " # df = pd.read_csv(data_path)\n", + " df = wr.s3.read_csv(path=data_s3_uri, dataset=True)\n", + " ## convert to time\n", + " df[\"date\"] = pd.to_datetime(df[\"ts\"], unit=\"ms\")\n", + " df[\"ts_dow\"] = df[\"date\"].dt.weekday\n", + " df[\"ts_date_day\"] = df[\"date\"].dt.date\n", + " df[\"ts_is_weekday\"] = [1 if x in [0, 1, 2, 3, 4] else 0 for x in df[\"ts_dow\"]]\n", + " df[\"registration_ts\"] = pd.to_datetime(df[\"registration\"], unit=\"ms\").dt.date\n", + " ## add labels\n", + " df[\"churned_event\"] = [1 if x == \"Cancellation Confirmation\" else 0 for x in df[\"page\"]]\n", + " df[\"user_churned\"] = df.groupby(\"userId\")[\"churned_event\"].transform(\"max\")\n", + "\n", + " ## convert pages categorical variables to numerical\n", + " events_list = [\n", + " \"NextSong\",\n", + " \"Thumbs Down\",\n", + " \"Thumbs Up\",\n", + " \"Add to Playlist\",\n", + " \"Roll Advert\",\n", + " \"Add Friend\",\n", + " \"Downgrade\",\n", + " \"Upgrade\",\n", + " \"Error\",\n", + " ]\n", + " usage_column_name = []\n", + " for event in events_list:\n", + " event_name = \"_\".join(event.split()).lower()\n", + " usage_column_name.append(event_name)\n", + " df[event_name] = [1 if x == event else 0 for x in df[\"page\"]]\n", + " ## feature engineering\n", + " # average_events_weekday (numerical): average number of events per day during weekday\n", + " # average_events_weekend (numerical): average number of events per day during the weekend\n", + " base_df = (\n", + " df.groupby([\"userId\", \"ts_date_day\", \"ts_is_weekday\"])\n", + " .agg({\"page\": \"count\"})\n", + " .groupby([\"userId\", \"ts_is_weekday\"])[\"page\"]\n", + " .mean()\n", + " .unstack(fill_value=0)\n", + " .reset_index()\n", + " .rename(columns={0: \"average_events_weekend\", 1: \"average_events_weekday\"})\n", + " )\n", + "\n", + " # num_ads_7d, num_songs_played_7d, num_songs_played_30d, num_songs_played_90d, num_ads_7d, num_error_7d\n", + " base_df_daily = (\n", + " df.groupby([\"userId\", \"ts_date_day\"])\n", + " .agg({\"page\": \"count\", \"nextsong\": \"sum\", \"roll_advert\": \"sum\", \"error\": \"sum\"})\n", + " .reset_index()\n", + " )\n", + " feature34 = (\n", + " base_df_daily.groupby([\"userId\", \"ts_date_day\"])\n", + " .tail(7)\n", + " .groupby([\"userId\"])\n", + " .agg({\"nextsong\": \"sum\", \"roll_advert\": \"sum\", \"error\": \"sum\"})\n", + " .reset_index()\n", + " .rename(\n", + " columns={\n", + " \"nextsong\": \"num_songs_played_7d\",\n", + " \"roll_advert\": \"num_ads_7d\",\n", + " \"error\": \"num_error_7d\",\n", + " }\n", + " )\n", + " )\n", + " feature5 = (\n", + " base_df_daily.groupby([\"userId\", \"ts_date_day\"])\n", + " .tail(30)\n", + " .groupby([\"userId\"])\n", + " .agg({\"nextsong\": \"sum\"})\n", + " .reset_index()\n", + " .rename(columns={\"nextsong\": \"num_songs_played_30d\"})\n", + " )\n", + " feature6 = (\n", + " base_df_daily.groupby([\"userId\", \"ts_date_day\"])\n", + " .tail(90)\n", + " .groupby([\"userId\"])\n", + " .agg({\"nextsong\": \"sum\"})\n", + " .reset_index()\n", + " .rename(columns={\"nextsong\": \"num_songs_played_90d\"})\n", + " )\n", + " # num_artists, num_songs, num_ads, num_thumbsup, num_thumbsdown, num_playlist, num_addfriend, num_error, user_downgrade,\n", + " # user_upgrade, percentage_ad, days_since_active\n", + " base_df_user = (\n", + " df.groupby([\"userId\"])\n", + " .agg(\n", + " {\n", + " \"page\": \"count\",\n", + " \"nextsong\": \"sum\",\n", + " \"artist\": \"nunique\",\n", + " \"song\": \"nunique\",\n", + " \"thumbs_down\": \"sum\",\n", + " \"thumbs_up\": \"sum\",\n", + " \"add_to_playlist\": \"sum\",\n", + " \"roll_advert\": \"sum\",\n", + " \"add_friend\": \"sum\",\n", + " \"downgrade\": \"max\",\n", + " \"upgrade\": \"max\",\n", + " \"error\": \"sum\",\n", + " \"ts_date_day\": \"max\",\n", + " \"registration_ts\": \"min\",\n", + " \"user_churned\": \"max\",\n", + " }\n", + " )\n", + " .reset_index()\n", + " )\n", + " base_df_user[\"percentage_ad\"] = base_df_user[\"roll_advert\"] / base_df_user[\"page\"]\n", + " base_df_user[\"days_since_active\"] = (\n", + " base_df_user[\"ts_date_day\"] - base_df_user[\"registration_ts\"]\n", + " ).dt.days\n", + " # repeats ratio\n", + " base_df_user[\"repeats_ratio\"] = 1 - base_df_user[\"song\"] / base_df_user[\"nextsong\"]\n", + "\n", + " # num_sessions, avg_time_per_session, avg_events_per_session,\n", + " base_df_session = (\n", + " df.groupby([\"userId\", \"sessionId\"])\n", + " .agg({\"length\": \"sum\", \"page\": \"count\", \"date\": \"min\"})\n", + " .reset_index()\n", + " )\n", + " base_df_session[\"prev_session_ts\"] = base_df_session.groupby([\"userId\"])[\"date\"].shift(1)\n", + " base_df_session[\"gap_session\"] = (\n", + " base_df_session[\"date\"] - base_df_session[\"prev_session_ts\"]\n", + " ).dt.days\n", + " user_sessions = (\n", + " base_df_session.groupby(\"userId\")\n", + " .agg({\"sessionId\": \"count\", \"length\": \"mean\", \"page\": \"mean\", \"gap_session\": \"mean\"})\n", + " .reset_index()\n", + " .rename(\n", + " columns={\n", + " \"sessionId\": \"num_sessions\",\n", + " \"length\": \"avg_time_per_session\",\n", + " \"page\": \"avg_events_per_session\",\n", + " \"gap_session\": \"avg_gap_between_session\",\n", + " }\n", + " )\n", + " )\n", + "\n", + " # merge features together\n", + " base_df[\"userId\"] = base_df[\"userId\"].astype(\"int\")\n", + " final_feature_df = base_df.merge(feature34, how=\"left\", on=\"userId\")\n", + " final_feature_df = final_feature_df.merge(feature5, how=\"left\", on=\"userId\")\n", + " final_feature_df = final_feature_df.merge(feature6, how=\"left\", on=\"userId\")\n", + " final_feature_df = final_feature_df.merge(user_sessions, how=\"left\", on=\"userId\")\n", + " final_feature_df = final_feature_df.merge(base_df_user, how=\"left\", on=\"userId\")\n", + "\n", + " final_feature_df = final_feature_df.fillna(0)\n", + " # renaming columns\n", + " final_feature_df.columns = [\n", + " \"userId\",\n", + " \"average_events_weekend\",\n", + " \"average_events_weekday\",\n", + " \"num_songs_played_7d\",\n", + " \"num_ads_7d\",\n", + " \"num_error_7d\",\n", + " \"num_songs_played_30d\",\n", + " \"num_songs_played_90d\",\n", + " \"num_sessions\",\n", + " \"avg_time_per_session\",\n", + " \"avg_events_per_session\",\n", + " \"avg_gap_between_session\",\n", + " \"num_events\",\n", + " \"num_songs\",\n", + " \"num_artists\",\n", + " \"num_unique_songs\",\n", + " \"num_thumbs_down\",\n", + " \"num_thumbs_up\",\n", + " \"num_add_to_playlist\",\n", + " \"num_ads\",\n", + " \"num_add_friend\",\n", + " \"num_downgrade\",\n", + " \"num_upgrade\",\n", + " \"num_error\",\n", + " \"ts_date_day\",\n", + " \"registration_ts\",\n", + " \"user_churned\",\n", + " \"percentage_ad\",\n", + " \"days_since_active\",\n", + " \"repeats_ratio\",\n", + " ]\n", + " # only keep created feature columns\n", + " final_feature_df = final_feature_df[\n", + " [\n", + " \"userId\",\n", + " \"user_churned\",\n", + " \"average_events_weekend\",\n", + " \"average_events_weekday\",\n", + " \"num_songs_played_7d\",\n", + " \"num_ads_7d\",\n", + " \"num_error_7d\",\n", + " \"num_songs_played_30d\",\n", + " \"num_songs_played_90d\",\n", + " \"num_sessions\",\n", + " \"avg_time_per_session\",\n", + " \"avg_events_per_session\",\n", + " \"avg_gap_between_session\",\n", + " \"num_events\",\n", + " \"num_songs\",\n", + " \"num_artists\",\n", + " \"num_thumbs_down\",\n", + " \"num_thumbs_up\",\n", + " \"num_add_to_playlist\",\n", + " \"num_ads\",\n", + " \"num_add_friend\",\n", + " \"num_downgrade\",\n", + " \"num_upgrade\",\n", + " \"num_error\",\n", + " \"percentage_ad\",\n", + " \"days_since_active\",\n", + " \"repeats_ratio\",\n", + " ]\n", + " ]\n", + "\n", + " print(\"shape of file to append:\\t\\t{}\".format(final_feature_df.shape))\n", + " iter_end_time = time.time()\n", + " end_time = time.time()\n", + " print(\"minutes elapsed: {}\".format(str((end_time - start_time) / 60)))\n", + "\n", + " final_features_output_path = os.path.join(\"/opt/ml/processing/output\", output_filename)\n", + " print(\"Saving processed data to {}\".format(final_features_output_path))\n", + " final_feature_df.to_csv(final_features_output_path, header=True, index=False)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "output_path = processing_output_filename" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "%%time\n", + "from sagemaker.processing import ProcessingInput, ProcessingOutput\n", + "\n", + "processing_job_output_path = f\"s3://{bucket}/{prefix}/data/processing\"\n", + "\n", + "sklearn_processor.run(\n", + " code=\"preprocessing.py\",\n", + " outputs=[\n", + " ProcessingOutput(\n", + " output_name=\"processed_data\",\n", + " source=\"/opt/ml/processing/output\",\n", + " destination=processing_job_output_path,\n", + " )\n", + " ],\n", + " arguments=[\n", + " \"--dw-output-path\",\n", + " processing_job_output_path,\n", + " \"--processing-output-filename\",\n", + " processing_job_output_name,\n", + " ],\n", + ")\n", + "\n", + "preprocessing_job_description = sklearn_processor.jobs[-1].describe()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "preprocessing_job_description" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Congratulations! You have preprocessed the data. You can proceed to modelling." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "### Data Splitting\n", + "\n", + "You formulated the use case as a classification problem on user level, so you can randomly split your data from last step into train/validation/test. If you want to predict \"will user X churn in the next Y days\" on per user per day level, you should think about spliting data in chronological order instead of random. \n", + "\n", + "You should split the data and make sure that data of both classes exist in your train, validation and test sets, to make sure both classes are represented in your data. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Find the output of Processing Job" ] }, { @@ -984,7 +1323,143 @@ "execution_count": null, "metadata": {}, "outputs": [], - "source": [] + "source": [ + "processing_job_output_uri = f\"{processing_job_output_path}/{processing_job_output_name}\"\n", + "processing_job_output_uri" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!aws s3 cp $processing_job_output_uri ./data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "processed_data = pd.read_csv(processing_job_output_uri)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Optional: you can also load the processed data from the provided feature set\n", + "# processed_data = pd.read_csv('./data/full_feature_data.csv')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "processed_data.head(4)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Split data to train/validation/test by 70/20/10" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data = processed_data.sample(frac=1, random_state=1729)\n", + "grouped_df = data.groupby(\"user_churned\")\n", + "arr_list = [np.split(g, [int(0.7 * len(g)), int(0.9 * len(g))]) for i, g in grouped_df]\n", + "\n", + "train_data = pd.concat([t[0] for t in arr_list])\n", + "validation_data = pd.concat([t[1] for t in arr_list])\n", + "test_data = pd.concat([v[2] for v in arr_list])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def process_data(data, name, header=False):\n", + " data = data.drop(columns=[\"userId\"])\n", + " data = pd.concat([data[\"user_churned\"], data.drop([\"user_churned\"], axis=1)], axis=1)\n", + " data.to_csv(name, header=header, index=False)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "process_data(train_data, \"data/train_updated.csv\")\n", + "process_data(validation_data, \"data/validation_updated.csv\")\n", + "process_data(test_data, \"data/test_updated.csv\")\n", + "\n", + "process_data(train_data, \"data/train_w_header.csv\", header=True)\n", + "process_data(validation_data, \"data/validation_w_header.csv\", header=True)\n", + "process_data(test_data, \"data/test_w_header.csv\", header=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Save splitted data to S3\n", + "The splitted data is provided in the /data folder. You can also upload the provided files (`data/train_updated.csv`,`data/validation_updated.csv`, `data/test_updated.csv`) and proceed to the next step. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "\n", + "s3_input_train = (\n", + " boto3.Session()\n", + " .resource(\"s3\")\n", + " .Bucket(bucket)\n", + " .Object(os.path.join(prefix, \"train/train.csv\"))\n", + " .upload_file(\"data/train_updated.csv\")\n", + ")\n", + "s3_input_validation = (\n", + " boto3.Session()\n", + " .resource(\"s3\")\n", + " .Bucket(bucket)\n", + " .Object(os.path.join(prefix, \"validation/validation.csv\"))\n", + " .upload_file(\"data/validation_updated.csv\")\n", + ")\n", + "s3_input_validation = (\n", + " boto3.Session()\n", + " .resource(\"s3\")\n", + " .Bucket(bucket)\n", + " .Object(os.path.join(prefix, \"test/test_labeled.csv\"))\n", + " .upload_file(\"data/test_updated.csv\")\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Citation\n", + "The data used in this notebook is simulated using the [EventSim](https://github.com/Interana/eventsim)." + ] } ], "metadata": { @@ -992,7 +1467,7 @@ "kernelspec": { "display_name": "Python 3 (Data Science)", "language": "python", - "name": "python3__SAGEMAKER_INTERNAL__arn:aws:sagemaker:us-east-1:081325390199:image/datascience-1.0" + "name": "python3__SAGEMAKER_INTERNAL__arn:aws:sagemaker:us-west-2:236514542706:image/datascience-1.0" }, "language_info": { "codemirror_mode": { diff --git a/use-cases/customer_churn/2_cust_churn_train_deploy_infer.ipynb b/use-cases/customer_churn/2_cust_churn_train_deploy_infer.ipynb index b459fd1f29..604dd393bb 100644 --- a/use-cases/customer_churn/2_cust_churn_train_deploy_infer.ipynb +++ b/use-cases/customer_churn/2_cust_churn_train_deploy_infer.ipynb @@ -123,17 +123,7 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%store -r\n", - "%store" - ] - }, - { - "cell_type": "code", - "execution_count": 2, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -142,12 +132,13 @@ "import pandas as pd\n", "import glob\n", "import s3fs\n", - "import boto3" + "import boto3\n", + "from datetime import datetime" ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 7, "metadata": {}, "outputs": [], "source": [ @@ -156,12 +147,14 @@ "\n", "region = boto3.Session().region_name\n", "role = sagemaker.get_execution_role()\n", - "smclient = boto3.Session().client(\"sagemaker\")" + "smclient = boto3.Session().client(\"sagemaker\")\n", + "bucket = sagemaker_session.default_bucket()\n", + "prefix = \"music-streaming\"" ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 8, "metadata": {}, "outputs": [], "source": [ @@ -179,7 +172,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 9, "metadata": {}, "outputs": [], "source": [ @@ -205,15 +198,15 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "CPU times: user 66 µs, sys: 0 ns, total: 66 µs\n", - "Wall time: 68.7 µs\n" + "CPU times: user 102 µs, sys: 0 ns, total: 102 µs\n", + "Wall time: 309 µs\n" ] } ], @@ -235,7 +228,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 11, "metadata": {}, "outputs": [], "source": [ @@ -252,11 +245,114 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 12, "metadata": { "scrolled": true }, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2022-04-29 21:58:04 Starting - Starting the training job...\n", + "2022-04-29 21:58:28 Starting - Preparing the instances for trainingProfilerReport-1651269483: InProgress\n", + ".........\n", + "2022-04-29 21:59:56 Downloading - Downloading input data...\n", + "2022-04-29 22:00:31 Training - Downloading the training image......\n", + "2022-04-29 22:01:27 Training - Training image download completed. Training in progress..\u001b[34mINFO:sagemaker-containers:Imported framework sagemaker_xgboost_container.training\u001b[0m\n", + "\u001b[34mINFO:sagemaker-containers:Failed to parse hyperparameter eval_metric value auc to Json.\u001b[0m\n", + "\u001b[34mReturning the value itself\u001b[0m\n", + "\u001b[34mINFO:sagemaker-containers:Failed to parse hyperparameter objective value binary:logistic to Json.\u001b[0m\n", + "\u001b[34mReturning the value itself\u001b[0m\n", + "\u001b[34mINFO:sagemaker-containers:No GPUs detected (normal if no gpus installed)\u001b[0m\n", + "\u001b[34mINFO:sagemaker_xgboost_container.training:Running XGBoost Sagemaker in algorithm mode\u001b[0m\n", + "\u001b[34mINFO:root:Determined delimiter of CSV input is ','\u001b[0m\n", + "\u001b[34mINFO:root:Determined delimiter of CSV input is ','\u001b[0m\n", + "\u001b[34mINFO:root:Determined delimiter of CSV input is ','\u001b[0m\n", + "\u001b[34m[22:01:32] 708x25 matrix with 17700 entries loaded from /opt/ml/input/data/train?format=csv&label_column=0&delimiter=,\u001b[0m\n", + "\u001b[34mINFO:root:Determined delimiter of CSV input is ','\u001b[0m\n", + "\u001b[34m[22:01:32] 204x25 matrix with 5100 entries loaded from /opt/ml/input/data/validation?format=csv&label_column=0&delimiter=,\u001b[0m\n", + "\u001b[34mINFO:root:Single node training.\u001b[0m\n", + "\u001b[34mINFO:root:Train matrix has 708 rows\u001b[0m\n", + "\u001b[34mINFO:root:Validation matrix has 204 rows\u001b[0m\n", + "\u001b[34m[0]#011train-auc:0.91768#011validation-auc:0.94514\u001b[0m\n", + "\u001b[34m[1]#011train-auc:0.92026#011validation-auc:0.95180\u001b[0m\n", + "\u001b[34m[2]#011train-auc:0.93830#011validation-auc:0.95534\u001b[0m\n", + "\u001b[34m[3]#011train-auc:0.93852#011validation-auc:0.95507\u001b[0m\n", + "\u001b[34m[4]#011train-auc:0.95391#011validation-auc:0.96667\u001b[0m\n", + "\u001b[34m[5]#011train-auc:0.95654#011validation-auc:0.96758\u001b[0m\n", + "\u001b[34m[6]#011train-auc:0.95694#011validation-auc:0.96468\u001b[0m\n", + "\u001b[34m[7]#011train-auc:0.96200#011validation-auc:0.96473\u001b[0m\n", + "\u001b[34m[8]#011train-auc:0.96468#011validation-auc:0.96720\u001b[0m\n", + "\u001b[34m[9]#011train-auc:0.96311#011validation-auc:0.96699\u001b[0m\n", + "\u001b[34m[10]#011train-auc:0.96290#011validation-auc:0.96871\u001b[0m\n", + "\u001b[34m[11]#011train-auc:0.96521#011validation-auc:0.97434\u001b[0m\n", + "\u001b[34m[12]#011train-auc:0.96481#011validation-auc:0.97182\u001b[0m\n", + "\u001b[34m[13]#011train-auc:0.96483#011validation-auc:0.97386\u001b[0m\n", + "\u001b[34m[14]#011train-auc:0.96442#011validation-auc:0.97375\u001b[0m\n", + "\u001b[34m[15]#011train-auc:0.96458#011validation-auc:0.97418\u001b[0m\n", + "\u001b[34m[16]#011train-auc:0.96498#011validation-auc:0.97563\u001b[0m\n", + "\u001b[34m[17]#011train-auc:0.96619#011validation-auc:0.97724\u001b[0m\n", + "\u001b[34m[18]#011train-auc:0.96492#011validation-auc:0.97681\u001b[0m\n", + "\u001b[34m[19]#011train-auc:0.96406#011validation-auc:0.97584\u001b[0m\n", + "\u001b[34m[20]#011train-auc:0.96365#011validation-auc:0.97584\u001b[0m\n", + "\u001b[34m[21]#011train-auc:0.96428#011validation-auc:0.97381\u001b[0m\n", + "\u001b[34m[22]#011train-auc:0.96540#011validation-auc:0.97348\u001b[0m\n", + "\u001b[34m[23]#011train-auc:0.96511#011validation-auc:0.97445\u001b[0m\n", + "\u001b[34m[24]#011train-auc:0.96481#011validation-auc:0.97450\u001b[0m\n", + "\u001b[34m[25]#011train-auc:0.96465#011validation-auc:0.97504\u001b[0m\n", + "\u001b[34m[26]#011train-auc:0.96503#011validation-auc:0.97461\u001b[0m\n", + "\u001b[34m[27]#011train-auc:0.96627#011validation-auc:0.97364\u001b[0m\n", + "\u001b[34m[28]#011train-auc:0.96733#011validation-auc:0.97289\u001b[0m\n", + "\u001b[34m[29]#011train-auc:0.96781#011validation-auc:0.97354\u001b[0m\n", + "\u001b[34m[30]#011train-auc:0.96757#011validation-auc:0.97300\u001b[0m\n", + "\u001b[34m[31]#011train-auc:0.96827#011validation-auc:0.97300\u001b[0m\n", + "\u001b[34m[32]#011train-auc:0.96887#011validation-auc:0.97332\u001b[0m\n", + "\u001b[34m[33]#011train-auc:0.96900#011validation-auc:0.97354\u001b[0m\n", + "\u001b[34m[34]#011train-auc:0.96905#011validation-auc:0.97332\u001b[0m\n", + "\u001b[34m[35]#011train-auc:0.96980#011validation-auc:0.97440\u001b[0m\n", + "\u001b[34m[36]#011train-auc:0.96945#011validation-auc:0.97354\u001b[0m\n", + "\u001b[34m[37]#011train-auc:0.96924#011validation-auc:0.97354\u001b[0m\n", + "\u001b[34m[38]#011train-auc:0.96936#011validation-auc:0.97418\u001b[0m\n", + "\u001b[34m[39]#011train-auc:0.96936#011validation-auc:0.97418\u001b[0m\n", + "\u001b[34m[40]#011train-auc:0.96933#011validation-auc:0.97407\u001b[0m\n", + "\u001b[34m[41]#011train-auc:0.96896#011validation-auc:0.97343\u001b[0m\n", + "\u001b[34m[42]#011train-auc:0.96899#011validation-auc:0.97348\u001b[0m\n", + "\u001b[34m[43]#011train-auc:0.96945#011validation-auc:0.97359\u001b[0m\n", + "\u001b[34m[44]#011train-auc:0.96924#011validation-auc:0.97391\u001b[0m\n", + "\u001b[34m[45]#011train-auc:0.96974#011validation-auc:0.97423\u001b[0m\n", + "\u001b[34m[46]#011train-auc:0.97061#011validation-auc:0.97477\u001b[0m\n", + "\u001b[34m[47]#011train-auc:0.97083#011validation-auc:0.97467\u001b[0m\n", + "\u001b[34m[48]#011train-auc:0.97080#011validation-auc:0.97467\u001b[0m\n", + "\u001b[34m[49]#011train-auc:0.97067#011validation-auc:0.97456\u001b[0m\n", + "\u001b[34m[50]#011train-auc:0.97121#011validation-auc:0.97456\u001b[0m\n", + "\u001b[34m[51]#011train-auc:0.97121#011validation-auc:0.97456\u001b[0m\n", + "\u001b[34m[52]#011train-auc:0.97181#011validation-auc:0.97461\u001b[0m\n", + "\u001b[34m[53]#011train-auc:0.97159#011validation-auc:0.97461\u001b[0m\n", + "\u001b[34m[54]#011train-auc:0.97159#011validation-auc:0.97461\u001b[0m\n", + "\u001b[34m[55]#011train-auc:0.97246#011validation-auc:0.97504\u001b[0m\n", + "\u001b[34m[56]#011train-auc:0.97246#011validation-auc:0.97504\u001b[0m\n", + "\u001b[34m[57]#011train-auc:0.97246#011validation-auc:0.97504\u001b[0m\n", + "\u001b[34m[58]#011train-auc:0.97323#011validation-auc:0.97493\u001b[0m\n", + "\u001b[34m[59]#011train-auc:0.97323#011validation-auc:0.97493\u001b[0m\n", + "\u001b[34m[60]#011train-auc:0.97323#011validation-auc:0.97493\u001b[0m\n", + "\u001b[34m[61]#011train-auc:0.97323#011validation-auc:0.97493\u001b[0m\n", + "\u001b[34m[62]#011train-auc:0.97333#011validation-auc:0.97515\u001b[0m\n", + "\u001b[34m[63]#011train-auc:0.97349#011validation-auc:0.97515\u001b[0m\n", + "\u001b[34m[64]#011train-auc:0.97359#011validation-auc:0.97515\u001b[0m\n", + "\u001b[34m[65]#011train-auc:0.97353#011validation-auc:0.97525\u001b[0m\n", + "\u001b[34m[66]#011train-auc:0.97353#011validation-auc:0.97525\u001b[0m\n", + "\u001b[34m[67]#011train-auc:0.97353#011validation-auc:0.97525\u001b[0m\n", + "\n", + "2022-04-29 22:01:57 Uploading - Uploading generated training model\n", + "2022-04-29 22:01:57 Completed - Training job completed\n", + "Training seconds: 113\n", + "Billable seconds: 113\n", + "CPU times: user 465 ms, sys: 20.3 ms, total: 485 ms\n", + "Wall time: 4min 12s\n" + ] + } + ], "source": [ "%%time\n", "xgb.fit(inputs={\"train\": train_input, \"validation\": validation_input}, wait=True)" @@ -271,20 +367,252 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 13, "metadata": {}, "outputs": [], "source": [ "# custom trial name\n", - "experiment_name = \"music-streaming-churn-exp\"\n", + "experiment_name = \"music-streaming-churn-exp-{}\".format(datetime.now().strftime(\"%Y-%m-%d-%H-%M-%S\"))\n", "trial_name_xgb = \"xgboost\"" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 14, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Experiment creation music-streaming-churn-exp: SUCCESS\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:sagemaker.image_uris:Defaulting to the only supported framework/algorithm version: latest.\n", + "INFO:sagemaker.image_uris:Ignoring unnecessary instance type: None.\n", + "INFO:sagemaker:Creating training-job with name: sagemaker-xgboost-2022-04-29-22-02-17-343\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Create trial xgboost: SUCCESSFUL\n", + "2022-04-29 22:02:17 Starting - Starting the training job...\n", + "2022-04-29 22:02:44 Starting - Preparing the instances for trainingProfilerReport-1651269737: InProgress\n", + ".........\n", + "2022-04-29 22:04:14 Downloading - Downloading input data...\n", + "2022-04-29 22:04:45 Training - Downloading the training image......\n", + "2022-04-29 22:05:46 Training - Training image download completed. Training in progress...\u001b[34mINFO:sagemaker-containers:Imported framework sagemaker_xgboost_container.training\u001b[0m\n", + "\u001b[34mINFO:sagemaker-containers:Failed to parse hyperparameter eval_metric value auc to Json.\u001b[0m\n", + "\u001b[34mReturning the value itself\u001b[0m\n", + "\u001b[34mINFO:sagemaker-containers:Failed to parse hyperparameter objective value binary:logistic to Json.\u001b[0m\n", + "\u001b[34mReturning the value itself\u001b[0m\n", + "\u001b[34mINFO:sagemaker-containers:No GPUs detected (normal if no gpus installed)\u001b[0m\n", + "\u001b[34mINFO:sagemaker_xgboost_container.training:Running XGBoost Sagemaker in algorithm mode\u001b[0m\n", + "\u001b[34mINFO:root:Determined delimiter of CSV input is ','\u001b[0m\n", + "\u001b[34mINFO:root:Determined delimiter of CSV input is ','\u001b[0m\n", + "\u001b[34mINFO:root:Determined delimiter of CSV input is ','\u001b[0m\n", + "\u001b[34m[22:05:50] 708x25 matrix with 17700 entries loaded from /opt/ml/input/data/train?format=csv&label_column=0&delimiter=,\u001b[0m\n", + "\u001b[34mINFO:root:Determined delimiter of CSV input is ','\u001b[0m\n", + "\u001b[34m[22:05:50] 204x25 matrix with 5100 entries loaded from /opt/ml/input/data/validation?format=csv&label_column=0&delimiter=,\u001b[0m\n", + "\u001b[34mINFO:root:Single node training.\u001b[0m\n", + "\u001b[34mINFO:root:Train matrix has 708 rows\u001b[0m\n", + "\u001b[34mINFO:root:Validation matrix has 204 rows\u001b[0m\n", + "\u001b[34m[0]#011train-auc:0.91768#011validation-auc:0.94514\u001b[0m\n", + "\u001b[34m[1]#011train-auc:0.92026#011validation-auc:0.95180\u001b[0m\n", + "\u001b[34m[2]#011train-auc:0.93830#011validation-auc:0.95534\u001b[0m\n", + "\u001b[34m[3]#011train-auc:0.93852#011validation-auc:0.95507\u001b[0m\n", + "\u001b[34m[4]#011train-auc:0.95391#011validation-auc:0.96667\u001b[0m\n", + "\u001b[34m[5]#011train-auc:0.95654#011validation-auc:0.96758\u001b[0m\n", + "\u001b[34m[6]#011train-auc:0.95694#011validation-auc:0.96468\u001b[0m\n", + "\u001b[34m[7]#011train-auc:0.96200#011validation-auc:0.96473\u001b[0m\n", + "\u001b[34m[8]#011train-auc:0.96468#011validation-auc:0.96720\u001b[0m\n", + "\u001b[34m[9]#011train-auc:0.96311#011validation-auc:0.96699\u001b[0m\n", + "\u001b[34m[10]#011train-auc:0.96290#011validation-auc:0.96871\u001b[0m\n", + "\u001b[34m[11]#011train-auc:0.96521#011validation-auc:0.97434\u001b[0m\n", + "\u001b[34m[12]#011train-auc:0.96481#011validation-auc:0.97182\u001b[0m\n", + "\u001b[34m[13]#011train-auc:0.96483#011validation-auc:0.97386\u001b[0m\n", + "\u001b[34m[14]#011train-auc:0.96442#011validation-auc:0.97375\u001b[0m\n", + "\u001b[34m[15]#011train-auc:0.96458#011validation-auc:0.97418\u001b[0m\n", + "\u001b[34m[16]#011train-auc:0.96498#011validation-auc:0.97563\u001b[0m\n", + "\u001b[34m[17]#011train-auc:0.96619#011validation-auc:0.97724\u001b[0m\n", + "\u001b[34m[18]#011train-auc:0.96492#011validation-auc:0.97681\u001b[0m\n", + "\u001b[34m[19]#011train-auc:0.96406#011validation-auc:0.97584\u001b[0m\n", + "\u001b[34m[20]#011train-auc:0.96365#011validation-auc:0.97584\u001b[0m\n", + "\u001b[34m[21]#011train-auc:0.96428#011validation-auc:0.97381\u001b[0m\n", + "\u001b[34m[22]#011train-auc:0.96540#011validation-auc:0.97348\u001b[0m\n", + "\u001b[34m[23]#011train-auc:0.96511#011validation-auc:0.97445\u001b[0m\n", + "\u001b[34m[24]#011train-auc:0.96481#011validation-auc:0.97450\u001b[0m\n", + "\u001b[34m[25]#011train-auc:0.96465#011validation-auc:0.97504\u001b[0m\n", + "\u001b[34m[26]#011train-auc:0.96503#011validation-auc:0.97461\u001b[0m\n", + "\u001b[34m[27]#011train-auc:0.96627#011validation-auc:0.97364\u001b[0m\n", + "\u001b[34m[28]#011train-auc:0.96733#011validation-auc:0.97289\u001b[0m\n", + "\u001b[34m[29]#011train-auc:0.96781#011validation-auc:0.97354\u001b[0m\n", + "\u001b[34m[30]#011train-auc:0.96757#011validation-auc:0.97300\u001b[0m\n", + "\u001b[34m[31]#011train-auc:0.96827#011validation-auc:0.97300\u001b[0m\n", + "\u001b[34m[32]#011train-auc:0.96887#011validation-auc:0.97332\u001b[0m\n", + "\u001b[34m[33]#011train-auc:0.96900#011validation-auc:0.97354\u001b[0m\n", + "\u001b[34m[34]#011train-auc:0.96905#011validation-auc:0.97332\u001b[0m\n", + "\u001b[34m[35]#011train-auc:0.96980#011validation-auc:0.97440\u001b[0m\n", + "\u001b[34m[36]#011train-auc:0.96945#011validation-auc:0.97354\u001b[0m\n", + "\u001b[34m[37]#011train-auc:0.96924#011validation-auc:0.97354\u001b[0m\n", + "\u001b[34m[38]#011train-auc:0.96936#011validation-auc:0.97418\u001b[0m\n", + "\u001b[34m[39]#011train-auc:0.96936#011validation-auc:0.97418\u001b[0m\n", + "\u001b[34m[40]#011train-auc:0.96933#011validation-auc:0.97407\u001b[0m\n", + "\u001b[34m[41]#011train-auc:0.96896#011validation-auc:0.97343\u001b[0m\n", + "\u001b[34m[42]#011train-auc:0.96899#011validation-auc:0.97348\u001b[0m\n", + "\u001b[34m[43]#011train-auc:0.96945#011validation-auc:0.97359\u001b[0m\n", + "\u001b[34m[44]#011train-auc:0.96924#011validation-auc:0.97391\u001b[0m\n", + "\u001b[34m[45]#011train-auc:0.96974#011validation-auc:0.97423\u001b[0m\n", + "\u001b[34m[46]#011train-auc:0.97061#011validation-auc:0.97477\u001b[0m\n", + "\u001b[34m[47]#011train-auc:0.97083#011validation-auc:0.97467\u001b[0m\n", + "\u001b[34m[48]#011train-auc:0.97080#011validation-auc:0.97467\u001b[0m\n", + "\u001b[34m[49]#011train-auc:0.97067#011validation-auc:0.97456\u001b[0m\n", + "\u001b[34m[50]#011train-auc:0.97121#011validation-auc:0.97456\u001b[0m\n", + "\u001b[34m[51]#011train-auc:0.97121#011validation-auc:0.97456\u001b[0m\n", + "\u001b[34m[52]#011train-auc:0.97181#011validation-auc:0.97461\u001b[0m\n", + "\u001b[34m[53]#011train-auc:0.97159#011validation-auc:0.97461\u001b[0m\n", + "\u001b[34m[54]#011train-auc:0.97159#011validation-auc:0.97461\u001b[0m\n", + "\u001b[34m[55]#011train-auc:0.97246#011validation-auc:0.97504\u001b[0m\n", + "\u001b[34m[56]#011train-auc:0.97246#011validation-auc:0.97504\u001b[0m\n", + "\u001b[34m[57]#011train-auc:0.97246#011validation-auc:0.97504\u001b[0m\n", + "\u001b[34m[58]#011train-auc:0.97323#011validation-auc:0.97493\u001b[0m\n", + "\u001b[34m[59]#011train-auc:0.97323#011validation-auc:0.97493\u001b[0m\n", + "\u001b[34m[60]#011train-auc:0.97323#011validation-auc:0.97493\u001b[0m\n", + "\u001b[34m[61]#011train-auc:0.97323#011validation-auc:0.97493\u001b[0m\n", + "\u001b[34m[62]#011train-auc:0.97333#011validation-auc:0.97515\u001b[0m\n", + "\u001b[34m[63]#011train-auc:0.97349#011validation-auc:0.97515\u001b[0m\n", + "\u001b[34m[64]#011train-auc:0.97359#011validation-auc:0.97515\u001b[0m\n", + "\u001b[34m[65]#011train-auc:0.97353#011validation-auc:0.97525\u001b[0m\n", + "\u001b[34m[66]#011train-auc:0.97353#011validation-auc:0.97525\u001b[0m\n", + "\u001b[34m[67]#011train-auc:0.97353#011validation-auc:0.97525\u001b[0m\n", + "\n", + "2022-04-29 22:06:07 Uploading - Uploading generated training model\n", + "2022-04-29 22:06:07 Completed - Training job completed\n", + "Training seconds: 113\n", + "Billable seconds: 113\n", + "CPU times: user 994 ms, sys: 74.8 ms, total: 1.07 s\n", + "Wall time: 4min 13s\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
TrialComponentNameDisplayNameSourceArnSageMaker.ImageUriSageMaker.InstanceCountSageMaker.InstanceTypeSageMaker.VolumeSizeInGBearly_stopping_roundsetaeval_metric...train:auc - Lasttrain:auc - Counttrain - MediaTypetrain - Valuevalidation - MediaTypevalidation - ValueSageMaker.ModelArtifact - MediaTypeSageMaker.ModelArtifact - ValueTrialsExperiments
0sagemaker-xgboost-2022-04-29-22-02-17-343-aws-...churn-xgboostarn:aws:sagemaker:us-west-2:688520471316:train...246618743249.dkr.ecr.us-west-2.amazonaws.com/s...1.0ml.m4.xlarge30.050.00.08auc...0.9690535csvs3://sagemaker-us-west-2-688520471316/music-st...csvs3://sagemaker-us-west-2-688520471316/music-st...Nones3://sagemaker-us-west-2-688520471316/music-st...[xgboost][music-streaming-churn-exp]
\n", + "

1 rows × 36 columns

\n", + "
" + ], + "text/plain": [ + " TrialComponentName DisplayName \\\n", + "0 sagemaker-xgboost-2022-04-29-22-02-17-343-aws-... churn-xgboost \n", + "\n", + " SourceArn \\\n", + "0 arn:aws:sagemaker:us-west-2:688520471316:train... \n", + "\n", + " SageMaker.ImageUri SageMaker.InstanceCount \\\n", + "0 246618743249.dkr.ecr.us-west-2.amazonaws.com/s... 1.0 \n", + "\n", + " SageMaker.InstanceType SageMaker.VolumeSizeInGB early_stopping_rounds \\\n", + "0 ml.m4.xlarge 30.0 50.0 \n", + "\n", + " eta eval_metric ... train:auc - Last train:auc - Count \\\n", + "0 0.08 auc ... 0.96905 35 \n", + "\n", + " train - MediaType train - Value \\\n", + "0 csv s3://sagemaker-us-west-2-688520471316/music-st... \n", + "\n", + " validation - MediaType validation - Value \\\n", + "0 csv s3://sagemaker-us-west-2-688520471316/music-st... \n", + "\n", + " SageMaker.ModelArtifact - MediaType \\\n", + "0 None \n", + "\n", + " SageMaker.ModelArtifact - Value Trials \\\n", + "0 s3://sagemaker-us-west-2-688520471316/music-st... [xgboost] \n", + "\n", + " Experiments \n", + "0 [music-streaming-churn-exp] \n", + "\n", + "[1 rows x 36 columns]" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "%%time\n", "from smexperiments import experiment, trial\n", @@ -350,7 +678,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 15, "metadata": {}, "outputs": [], "source": [ @@ -382,7 +710,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 16, "metadata": {}, "outputs": [], "source": [ @@ -442,7 +770,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 17, "metadata": {}, "outputs": [ { @@ -455,13 +783,12 @@ ], "source": [ "# custom a tuner job name\n", - "tuning_job_name = \"ChurnPrediction-Tuning-Job\"\n", - "%store tuning_job_name" + "tuning_job_name = \"ChurnPrediction-Tuning-Job-{}\".format(datetime.now().strftime(\"%Y-%m-%d-%H-%M-%S\"))" ] }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 18, "metadata": {}, "outputs": [], "source": [ @@ -472,16 +799,35 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 19, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "{'HyperParameterTuningJobSummaries': [],\n", + " 'NextToken': 'cIws2QhTXUIa8bi8W47LEKvF+FCR8eCxw7lm05/6M4GEnbWgUtoUJdlQBSv7kOsUKyeD3vlHXjc+jwuuBpymNWHzVbJTQRgpv3gKfmL4gypEQUDRwvEqhJzEDswtvI3HovY77Q4w795ItXG+PyA0eT/CNgcnCrkGC1ZBCjvUDG3ik8HgfI2+WPs8rSJrNtI86VXlB+tKqBzfn6e0wkIVyMjnAtA653gJLJ6HYJjCA4wq7Q5HqeZyUP62UPhU2KKXNbvdlD2x/3WC9Z37Re53/rYLhSnzqCBH0BVz1OS0vsRuL4QUzHmrVw/b6rngygpW57lbB2WQkZJqB9yyBXjOO/G3BELqDX7SKGDYEQw6j3jklpEwBM//HEqMOppRWmDr7bpGrVFs1aWy/a79jjTWTMe2916jd/I5RWvegPXL1o5E6lfkb+7ZbMelxH2Idtj8LF6B38/DNdYEDXnjeNoXRTjUTPBb5ay0ExcwPqHQs3wSax6Js7KazMxNQBDSVOcFJ7FfjGA/CTd71ya/S6l23g5PtLj8bPbn97oJn2Xej6tvFumWLATRDxWFTQIgE9mZylxrQEYM3kVymvSvzVg42WJdbtFzikOsFPjzyaO/T7lll9K2XUY7SWsybD+NQ/JNBSimd73sbOfy',\n", + " 'ResponseMetadata': {'RequestId': '86f3a2ff-4ab7-40af-9262-0c8034a1347f',\n", + " 'HTTPStatusCode': 200,\n", + " 'HTTPHeaders': {'x-amzn-requestid': '86f3a2ff-4ab7-40af-9262-0c8034a1347f',\n", + " 'content-type': 'application/x-amz-json-1.1',\n", + " 'content-length': '706',\n", + " 'date': 'Fri, 29 Apr 2022 22:06:30 GMT'},\n", + " 'RetryAttempts': 0}}" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "smclient.list_hyper_parameter_tuning_jobs(NameContains=tuning_job_name)" ] }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 20, "metadata": {}, "outputs": [ { @@ -535,9 +881,44 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 21, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "InProgress\n", + "InProgress\n", + "InProgress\n", + "InProgress\n", + "InProgress\n", + "InProgress\n", + "InProgress\n", + "InProgress\n", + "InProgress\n", + "InProgress\n", + "InProgress\n", + "InProgress\n", + "InProgress\n", + "InProgress\n", + "InProgress\n", + "InProgress\n", + "InProgress\n", + "InProgress\n", + "InProgress\n", + "InProgress\n", + "InProgress\n", + "InProgress\n", + "InProgress\n", + "InProgress\n", + "InProgress\n", + "Completed\n", + "CPU times: user 556 ms, sys: 53.8 ms, total: 610 ms\n", + "Wall time: 25min 4s\n" + ] + } + ], "source": [ "%%time\n", "# check status\n", @@ -559,9 +940,40 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 22, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ChurnPrediction-Tuning-Job-010-f4b35971\n", + "\n", + "2022-04-29 22:19:24 Starting - Preparing the instances for training\n", + "2022-04-29 22:19:24 Downloading - Downloading input data\n", + "2022-04-29 22:19:24 Training - Training image download completed. Training in progress.\n", + "2022-04-29 22:19:24 Uploading - Uploading generated training model\n", + "2022-04-29 22:19:24 Completed - Training job completed\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:sagemaker:Creating model with name: sagemaker-xgboost-2022-04-29-22-31-36-166\n", + "INFO:sagemaker:Creating endpoint-config with name sagemaker-xgboost-2022-04-29-22-31-36-813\n", + "INFO:sagemaker:Creating endpoint with name sagemaker-xgboost-2022-04-29-22-31-36-813\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "-----!CPU times: user 306 ms, sys: 12.4 ms, total: 318 ms\n", + "Wall time: 2min 32s\n" + ] + } + ], "source": [ "%%time\n", "# Attach to an existing hyperparameter tuning job.\n", @@ -600,7 +1012,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 23, "metadata": {}, "outputs": [], "source": [ @@ -614,7 +1026,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 24, "metadata": {}, "outputs": [], "source": [ @@ -639,11 +1051,213 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 25, "metadata": { "scrolled": true }, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:sagemaker:Creating model with name: sagemaker-xgboost-2022-04-29-22-34-07-950\n", + "INFO:sagemaker:Creating transform job with name: sagemaker-xgboost-2022-04-29-22-34-08-567\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + ".................................\n", + ".\u001b[34m[2022-04-29:22:39:30:INFO] No GPUs detected (normal if no gpus installed)\u001b[0m\n", + "\u001b[34m[2022-04-29:22:39:30:INFO] No GPUs detected (normal if no gpus installed)\u001b[0m\n", + "\u001b[34m[2022-04-29:22:39:30:INFO] nginx config: \u001b[0m\n", + "\u001b[34mworker_processes auto;\u001b[0m\n", + "\u001b[34mdaemon off;\u001b[0m\n", + "\u001b[34mpid /tmp/nginx.pid;\u001b[0m\n", + "\u001b[34merror_log /dev/stderr;\u001b[0m\n", + "\u001b[34mworker_rlimit_nofile 4096;\u001b[0m\n", + "\u001b[35m[2022-04-29:22:39:30:INFO] No GPUs detected (normal if no gpus installed)\u001b[0m\n", + "\u001b[35m[2022-04-29:22:39:30:INFO] No GPUs detected (normal if no gpus installed)\u001b[0m\n", + "\u001b[35m[2022-04-29:22:39:30:INFO] nginx config: \u001b[0m\n", + "\u001b[35mworker_processes auto;\u001b[0m\n", + "\u001b[35mdaemon off;\u001b[0m\n", + "\u001b[35mpid /tmp/nginx.pid;\u001b[0m\n", + "\u001b[35merror_log /dev/stderr;\u001b[0m\n", + "\u001b[35mworker_rlimit_nofile 4096;\u001b[0m\n", + "\u001b[34mevents {\n", + " worker_connections 2048;\u001b[0m\n", + "\u001b[34m}\u001b[0m\n", + "\u001b[34mhttp {\n", + " include /etc/nginx/mime.types;\n", + " default_type application/octet-stream;\n", + " access_log /dev/stdout combined;\n", + " upstream gunicorn {\n", + " server unix:/tmp/gunicorn.sock;\n", + " }\n", + " server {\n", + " listen 8080 deferred;\n", + " client_max_body_size 0;\n", + " keepalive_timeout 3;\n", + " location ~ ^/(ping|invocations|execution-parameters) {\n", + " proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n", + " proxy_set_header Host $http_host;\n", + " proxy_redirect off;\n", + " proxy_read_timeout 60s;\n", + " proxy_pass http://gunicorn;\n", + " }\n", + " location / {\n", + " return 404 \"{}\";\n", + " }\n", + " }\u001b[0m\n", + "\u001b[34m}\u001b[0m\n", + "\u001b[34m[2022-04-29 22:39:31 +0000] [18] [INFO] Starting gunicorn 19.10.0\u001b[0m\n", + "\u001b[34m[2022-04-29 22:39:31 +0000] [18] [INFO] Listening at: unix:/tmp/gunicorn.sock (18)\u001b[0m\n", + "\u001b[34m[2022-04-29 22:39:31 +0000] [18] [INFO] Using worker: gevent\u001b[0m\n", + "\u001b[34m[2022-04-29 22:39:31 +0000] [25] [INFO] Booting worker with pid: 25\u001b[0m\n", + "\u001b[35mevents {\n", + " worker_connections 2048;\u001b[0m\n", + "\u001b[35m}\u001b[0m\n", + "\u001b[35mhttp {\n", + " include /etc/nginx/mime.types;\n", + " default_type application/octet-stream;\n", + " access_log /dev/stdout combined;\n", + " upstream gunicorn {\n", + " server unix:/tmp/gunicorn.sock;\n", + " }\n", + " server {\n", + " listen 8080 deferred;\n", + " client_max_body_size 0;\n", + " keepalive_timeout 3;\n", + " location ~ ^/(ping|invocations|execution-parameters) {\n", + " proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n", + " proxy_set_header Host $http_host;\n", + " proxy_redirect off;\n", + " proxy_read_timeout 60s;\n", + " proxy_pass http://gunicorn;\n", + " }\n", + " location / {\n", + " return 404 \"{}\";\n", + " }\n", + " }\u001b[0m\n", + "\u001b[35m}\u001b[0m\n", + "\u001b[35m[2022-04-29 22:39:31 +0000] [18] [INFO] Starting gunicorn 19.10.0\u001b[0m\n", + "\u001b[35m[2022-04-29 22:39:31 +0000] [18] [INFO] Listening at: unix:/tmp/gunicorn.sock (18)\u001b[0m\n", + "\u001b[35m[2022-04-29 22:39:31 +0000] [18] [INFO] Using worker: gevent\u001b[0m\n", + "\u001b[35m[2022-04-29 22:39:31 +0000] [25] [INFO] Booting worker with pid: 25\u001b[0m\n", + "\u001b[34m[2022-04-29 22:39:31 +0000] [26] [INFO] Booting worker with pid: 26\u001b[0m\n", + "\u001b[34m[2022-04-29 22:39:31 +0000] [31] [INFO] Booting worker with pid: 31\u001b[0m\n", + "\u001b[34m[2022-04-29 22:39:31 +0000] [30] [INFO] Booting worker with pid: 30\u001b[0m\n", + "\u001b[35m[2022-04-29 22:39:31 +0000] [26] [INFO] Booting worker with pid: 26\u001b[0m\n", + "\u001b[35m[2022-04-29 22:39:31 +0000] [31] [INFO] Booting worker with pid: 31\u001b[0m\n", + "\u001b[35m[2022-04-29 22:39:31 +0000] [30] [INFO] Booting worker with pid: 30\u001b[0m\n", + "\u001b[34m[2022-04-29:22:39:37:INFO] No GPUs detected (normal if no gpus installed)\u001b[0m\n", + "\u001b[34m169.254.255.130 - - [29/Apr/2022:22:39:37 +0000] \"GET /ping HTTP/1.1\" 200 0 \"-\" \"Go-http-client/1.1\"\u001b[0m\n", + "\u001b[35m[2022-04-29:22:39:37:INFO] No GPUs detected (normal if no gpus installed)\u001b[0m\n", + "\u001b[35m169.254.255.130 - - [29/Apr/2022:22:39:37 +0000] \"GET /ping HTTP/1.1\" 200 0 \"-\" \"Go-http-client/1.1\"\u001b[0m\n", + "\u001b[34m169.254.255.130 - - [29/Apr/2022:22:39:37 +0000] \"GET /execution-parameters HTTP/1.1\" 200 84 \"-\" \"Go-http-client/1.1\"\u001b[0m\n", + "\u001b[34m[2022-04-29:22:39:37:INFO] Determined delimiter of CSV input is ','\u001b[0m\n", + "\u001b[34m169.254.255.130 - - [29/Apr/2022:22:39:37 +0000] \"POST /invocations HTTP/1.1\" 200 2006 \"-\" \"Go-http-client/1.1\"\u001b[0m\n", + "\u001b[35m169.254.255.130 - - [29/Apr/2022:22:39:37 +0000] \"GET /execution-parameters HTTP/1.1\" 200 84 \"-\" \"Go-http-client/1.1\"\u001b[0m\n", + "\u001b[35m[2022-04-29:22:39:37:INFO] Determined delimiter of CSV input is ','\u001b[0m\n", + "\u001b[35m169.254.255.130 - - [29/Apr/2022:22:39:37 +0000] \"POST /invocations HTTP/1.1\" 200 2006 \"-\" \"Go-http-client/1.1\"\u001b[0m\n", + "\u001b[32m2022-04-29T22:39:37.156:[sagemaker logs]: MaxConcurrentTransforms=4, MaxPayloadInMB=6, BatchStrategy=MULTI_RECORD\u001b[0m\n", + "\u001b[34m[2022-04-29:22:39:30:INFO] No GPUs detected (normal if no gpus installed)\u001b[0m\n", + "\u001b[34m[2022-04-29:22:39:30:INFO] No GPUs detected (normal if no gpus installed)\u001b[0m\n", + "\u001b[34m[2022-04-29:22:39:30:INFO] nginx config: \u001b[0m\n", + "\u001b[34mworker_processes auto;\u001b[0m\n", + "\u001b[34mdaemon off;\u001b[0m\n", + "\u001b[34mpid /tmp/nginx.pid;\u001b[0m\n", + "\u001b[34merror_log /dev/stderr;\u001b[0m\n", + "\u001b[34mworker_rlimit_nofile 4096;\u001b[0m\n", + "\u001b[35m[2022-04-29:22:39:30:INFO] No GPUs detected (normal if no gpus installed)\u001b[0m\n", + "\u001b[35m[2022-04-29:22:39:30:INFO] No GPUs detected (normal if no gpus installed)\u001b[0m\n", + "\u001b[35m[2022-04-29:22:39:30:INFO] nginx config: \u001b[0m\n", + "\u001b[35mworker_processes auto;\u001b[0m\n", + "\u001b[35mdaemon off;\u001b[0m\n", + "\u001b[35mpid /tmp/nginx.pid;\u001b[0m\n", + "\u001b[35merror_log /dev/stderr;\u001b[0m\n", + "\u001b[35mworker_rlimit_nofile 4096;\u001b[0m\n", + "\u001b[34mevents {\n", + " worker_connections 2048;\u001b[0m\n", + "\u001b[34m}\u001b[0m\n", + "\u001b[34mhttp {\n", + " include /etc/nginx/mime.types;\n", + " default_type application/octet-stream;\n", + " access_log /dev/stdout combined;\n", + " upstream gunicorn {\n", + " server unix:/tmp/gunicorn.sock;\n", + " }\n", + " server {\n", + " listen 8080 deferred;\n", + " client_max_body_size 0;\n", + " keepalive_timeout 3;\n", + " location ~ ^/(ping|invocations|execution-parameters) {\n", + " proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n", + " proxy_set_header Host $http_host;\n", + " proxy_redirect off;\n", + " proxy_read_timeout 60s;\n", + " proxy_pass http://gunicorn;\n", + " }\n", + " location / {\n", + " return 404 \"{}\";\n", + " }\n", + " }\u001b[0m\n", + "\u001b[34m}\u001b[0m\n", + "\u001b[34m[2022-04-29 22:39:31 +0000] [18] [INFO] Starting gunicorn 19.10.0\u001b[0m\n", + "\u001b[34m[2022-04-29 22:39:31 +0000] [18] [INFO] Listening at: unix:/tmp/gunicorn.sock (18)\u001b[0m\n", + "\u001b[34m[2022-04-29 22:39:31 +0000] [18] [INFO] Using worker: gevent\u001b[0m\n", + "\u001b[34m[2022-04-29 22:39:31 +0000] [25] [INFO] Booting worker with pid: 25\u001b[0m\n", + "\u001b[35mevents {\n", + " worker_connections 2048;\u001b[0m\n", + "\u001b[35m}\u001b[0m\n", + "\u001b[35mhttp {\n", + " include /etc/nginx/mime.types;\n", + " default_type application/octet-stream;\n", + " access_log /dev/stdout combined;\n", + " upstream gunicorn {\n", + " server unix:/tmp/gunicorn.sock;\n", + " }\n", + " server {\n", + " listen 8080 deferred;\n", + " client_max_body_size 0;\n", + " keepalive_timeout 3;\n", + " location ~ ^/(ping|invocations|execution-parameters) {\n", + " proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n", + " proxy_set_header Host $http_host;\n", + " proxy_redirect off;\n", + " proxy_read_timeout 60s;\n", + " proxy_pass http://gunicorn;\n", + " }\n", + " location / {\n", + " return 404 \"{}\";\n", + " }\n", + " }\u001b[0m\n", + "\u001b[35m}\u001b[0m\n", + "\u001b[35m[2022-04-29 22:39:31 +0000] [18] [INFO] Starting gunicorn 19.10.0\u001b[0m\n", + "\u001b[35m[2022-04-29 22:39:31 +0000] [18] [INFO] Listening at: unix:/tmp/gunicorn.sock (18)\u001b[0m\n", + "\u001b[35m[2022-04-29 22:39:31 +0000] [18] [INFO] Using worker: gevent\u001b[0m\n", + "\u001b[35m[2022-04-29 22:39:31 +0000] [25] [INFO] Booting worker with pid: 25\u001b[0m\n", + "\u001b[34m[2022-04-29 22:39:31 +0000] [26] [INFO] Booting worker with pid: 26\u001b[0m\n", + "\u001b[34m[2022-04-29 22:39:31 +0000] [31] [INFO] Booting worker with pid: 31\u001b[0m\n", + "\u001b[34m[2022-04-29 22:39:31 +0000] [30] [INFO] Booting worker with pid: 30\u001b[0m\n", + "\u001b[35m[2022-04-29 22:39:31 +0000] [26] [INFO] Booting worker with pid: 26\u001b[0m\n", + "\u001b[35m[2022-04-29 22:39:31 +0000] [31] [INFO] Booting worker with pid: 31\u001b[0m\n", + "\u001b[35m[2022-04-29 22:39:31 +0000] [30] [INFO] Booting worker with pid: 30\u001b[0m\n", + "\u001b[34m[2022-04-29:22:39:37:INFO] No GPUs detected (normal if no gpus installed)\u001b[0m\n", + "\u001b[34m169.254.255.130 - - [29/Apr/2022:22:39:37 +0000] \"GET /ping HTTP/1.1\" 200 0 \"-\" \"Go-http-client/1.1\"\u001b[0m\n", + "\u001b[35m[2022-04-29:22:39:37:INFO] No GPUs detected (normal if no gpus installed)\u001b[0m\n", + "\u001b[35m169.254.255.130 - - [29/Apr/2022:22:39:37 +0000] \"GET /ping HTTP/1.1\" 200 0 \"-\" \"Go-http-client/1.1\"\u001b[0m\n", + "\u001b[34m169.254.255.130 - - [29/Apr/2022:22:39:37 +0000] \"GET /execution-parameters HTTP/1.1\" 200 84 \"-\" \"Go-http-client/1.1\"\u001b[0m\n", + "\u001b[34m[2022-04-29:22:39:37:INFO] Determined delimiter of CSV input is ','\u001b[0m\n", + "\u001b[34m169.254.255.130 - - [29/Apr/2022:22:39:37 +0000] \"POST /invocations HTTP/1.1\" 200 2006 \"-\" \"Go-http-client/1.1\"\u001b[0m\n", + "\u001b[35m169.254.255.130 - - [29/Apr/2022:22:39:37 +0000] \"GET /execution-parameters HTTP/1.1\" 200 84 \"-\" \"Go-http-client/1.1\"\u001b[0m\n", + "\u001b[35m[2022-04-29:22:39:37:INFO] Determined delimiter of CSV input is ','\u001b[0m\n", + "\u001b[35m169.254.255.130 - - [29/Apr/2022:22:39:37 +0000] \"POST /invocations HTTP/1.1\" 200 2006 \"-\" \"Go-http-client/1.1\"\u001b[0m\n", + "\u001b[32m2022-04-29T22:39:37.156:[sagemaker logs]: MaxConcurrentTransforms=4, MaxPayloadInMB=6, BatchStrategy=MULTI_RECORD\u001b[0m\n" + ] + } + ], "source": [ "transformer = best_model.transformer(\n", " instance_count=1, instance_type=\"ml.m4.xlarge\", output_path=batch_output\n", @@ -663,7 +1277,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 26, "metadata": {}, "outputs": [], "source": [ @@ -672,7 +1286,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 27, "metadata": {}, "outputs": [], "source": [ @@ -694,7 +1308,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 28, "metadata": {}, "outputs": [ { @@ -727,31 +1341,31 @@ " \n", " 0\n", " 0.0\n", - " 0.124609\n", + " 0.069090\n", " 0\n", " \n", " \n", " 1\n", " 0.0\n", - " 0.124609\n", + " 0.026726\n", " 0\n", " \n", " \n", " 2\n", " 0.0\n", - " 0.199627\n", + " 0.027686\n", " 0\n", " \n", " \n", " 3\n", " 0.0\n", - " 0.261825\n", + " 0.080675\n", " 0\n", " \n", " \n", " 4\n", " 0.0\n", - " 0.251063\n", + " 0.017445\n", " 0\n", " \n", " \n", @@ -763,31 +1377,31 @@ " \n", " 97\n", " 1.0\n", - " 0.880904\n", + " 0.986643\n", " 1\n", " \n", " \n", " 98\n", " 1.0\n", - " 0.879375\n", + " 0.964494\n", " 1\n", " \n", " \n", " 99\n", " 1.0\n", - " 0.135027\n", + " 0.106448\n", " 0\n", " \n", " \n", " 100\n", " 1.0\n", - " 0.898226\n", + " 0.975653\n", " 1\n", " \n", " \n", " 101\n", " 1.0\n", - " 0.886231\n", + " 0.989780\n", " 1\n", " \n", " \n", @@ -797,22 +1411,22 @@ ], "text/plain": [ " user_churned predicted_results predicted_binary\n", - "0 0.0 0.124609 0\n", - "1 0.0 0.124609 0\n", - "2 0.0 0.199627 0\n", - "3 0.0 0.261825 0\n", - "4 0.0 0.251063 0\n", + "0 0.0 0.069090 0\n", + "1 0.0 0.026726 0\n", + "2 0.0 0.027686 0\n", + "3 0.0 0.080675 0\n", + "4 0.0 0.017445 0\n", ".. ... ... ...\n", - "97 1.0 0.880904 1\n", - "98 1.0 0.879375 1\n", - "99 1.0 0.135027 0\n", - "100 1.0 0.898226 1\n", - "101 1.0 0.886231 1\n", + "97 1.0 0.986643 1\n", + "98 1.0 0.964494 1\n", + "99 1.0 0.106448 0\n", + "100 1.0 0.975653 1\n", + "101 1.0 0.989780 1\n", "\n", "[102 rows x 3 columns]" ] }, - "execution_count": 24, + "execution_count": 28, "metadata": {}, "output_type": "execute_result" } @@ -833,7 +1447,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 29, "metadata": {}, "outputs": [ { @@ -841,9 +1455,9 @@ "output_type": "stream", "text": [ "Test Evaluation: \n", - "Average F1 Score: 0.8736913204998312\n", - "Precision Score: 0.9285714285714286\n", - "Recall Score: 0.7428571428571429\n" + "Average F1 Score: 0.8861607142857144\n", + "Precision Score: 0.9310344827586207\n", + "Recall Score: 0.7714285714285715\n" ] } ], @@ -878,9 +1492,18 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 30, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:sagemaker.image_uris:Defaulting to the only supported framework/algorithm version: 1.0.\n", + "INFO:sagemaker.image_uris:Ignoring unnecessary instance type: None.\n" + ] + } + ], "source": [ "from sagemaker import clarify\n", "\n", @@ -891,7 +1514,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 31, "metadata": {}, "outputs": [], "source": [ @@ -904,7 +1527,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 32, "metadata": {}, "outputs": [], "source": [ @@ -915,7 +1538,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 33, "metadata": {}, "outputs": [ { @@ -924,7 +1547,7 @@ "{'predicted_binary', 'predicted_results', 'user_churned'}" ] }, - "execution_count": 29, + "execution_count": 33, "metadata": {}, "output_type": "execute_result" } @@ -935,9 +1558,18 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 34, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "WARNING:sagemaker.deprecations:DataConfig will be deprecated on 15 Mar 2022.s3_data_distribution_type parameter will no longer be supported. Everything else will remain as is in sagemaker>=2.\n", + "See: https://sagemaker.readthedocs.io/en/stable/v2.html for details.\n" + ] + } + ], "source": [ "shap_config = clarify.SHAPConfig(\n", " baseline=[test_set.iloc[0].values.tolist()], num_samples=100, agg_method=\"mean_abs\"\n", @@ -963,11 +1595,151 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 35, "metadata": { "scrolled": true }, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:sagemaker:Creating processing-job with name Clarify-Explainability-2022-04-29-22-39-55-756\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Job Name: Clarify-Explainability-2022-04-29-22-39-55-756\n", + "Inputs: [{'InputName': 'dataset', 'AppManaged': False, 'S3Input': {'S3Uri': 's3://sagemaker-us-west-2-688520471316/music-streaming/train/train.csv', 'LocalPath': '/opt/ml/processing/input/data', 'S3DataType': 'S3Prefix', 'S3InputMode': 'File', 'S3DataDistributionType': 'FullyReplicated', 'S3CompressionType': 'None'}}, {'InputName': 'analysis_config', 'AppManaged': False, 'S3Input': {'S3Uri': 's3://sagemaker-us-west-2-688520471316/music-streaming/clarify-explainability/analysis_config.json', 'LocalPath': '/opt/ml/processing/input/config', 'S3DataType': 'S3Prefix', 'S3InputMode': 'File', 'S3DataDistributionType': 'FullyReplicated', 'S3CompressionType': 'None'}}]\n", + "Outputs: [{'OutputName': 'analysis_result', 'AppManaged': False, 'S3Output': {'S3Uri': 's3://sagemaker-us-west-2-688520471316/music-streaming/clarify-explainability', 'LocalPath': '/opt/ml/processing/output', 'S3UploadMode': 'EndOfJob'}}]\n", + ".....................................\u001b[34m2022-04-29 22:45:55,125 logging.conf not found when configuring logging, using default logging configuration.\u001b[0m\n", + "\u001b[34m2022-04-29 22:45:55,125 Starting SageMaker Clarify Processing job\u001b[0m\n", + "\u001b[34m2022-04-29 22:45:55,126 Analysis config path: /opt/ml/processing/input/config/analysis_config.json\u001b[0m\n", + "\u001b[34m2022-04-29 22:45:55,126 Analysis result path: /opt/ml/processing/output\u001b[0m\n", + "\u001b[34m2022-04-29 22:45:55,127 This host is algo-1.\u001b[0m\n", + "\u001b[34m2022-04-29 22:45:55,127 This host is the leader.\u001b[0m\n", + "\u001b[34m2022-04-29 22:45:55,127 Number of hosts in the cluster is 1.\u001b[0m\n", + "\u001b[34m2022-04-29 22:45:55,287 Running Python / Pandas based analyzer.\u001b[0m\n", + "\u001b[34m2022-04-29 22:45:55,288 Dataset type: text/csv uri: /opt/ml/processing/input/data\u001b[0m\n", + "\u001b[34m2022-04-29 22:45:55,296 Loading dataset...\u001b[0m\n", + "\u001b[34m2022-04-29 22:45:55,314 Loaded dataset. Dataset info:\u001b[0m\n", + "\u001b[34m\u001b[0m\n", + "\u001b[34mRangeIndex: 708 entries, 0 to 707\u001b[0m\n", + "\u001b[34mData columns (total 25 columns):\n", + " # Column Non-Null Count Dtype \u001b[0m\n", + "\u001b[34m--- ------ -------------- ----- \n", + " 0 average_events_weekend 708 non-null float64\n", + " 1 average_events_weekday 708 non-null float64\n", + " 2 num_songs_played_7d 708 non-null int64 \n", + " 3 num_ads_7d 708 non-null int64 \n", + " 4 num_error_7d 708 non-null int64 \n", + " 5 num_songs_played_30d 708 non-null int64 \n", + " 6 num_songs_played_90d 708 non-null int64 \n", + " 7 num_sessions 708 non-null int64 \n", + " 8 avg_time_per_session 708 non-null float64\n", + " 9 avg_events_per_session 708 non-null float64\n", + " 10 avg_gap_between_session 708 non-null float64\n", + " 11 num_events 708 non-null int64 \n", + " 12 num_songs 708 non-null int64 \n", + " 13 num_artists 708 non-null int64 \n", + " 14 num_thumbs_down 708 non-null int64 \n", + " 15 num_thumbs_up 708 non-null int64 \n", + " 16 num_add_to_playlist 708 non-null int64 \n", + " 17 num_ads 708 non-null int64 \n", + " 18 num_add_friend 708 non-null int64 \n", + " 19 num_downgrade 708 non-null int64 \n", + " 20 num_upgrade 708 non-null int64 \n", + " 21 num_error 708 non-null int64 \n", + " 22 percentage_ad 708 non-null float64\n", + " 23 days_since_active 708 non-null int64 \n", + " 24 repeats_ratio 708 non-null float64\u001b[0m\n", + "\u001b[34mdtypes: float64(7), int64(18)\u001b[0m\n", + "\u001b[34mmemory usage: 138.4 KB\u001b[0m\n", + "\u001b[34m2022-04-29 22:45:55,482 Spinning up shadow endpoint\u001b[0m\n", + "\u001b[34m2022-04-29 22:45:55,483 Creating endpoint-config with name sm-clarify-config-1651272355-5750\u001b[0m\n", + "\u001b[34m2022-04-29 22:45:55,567 Creating endpoint: 'sm-clarify-sagemaker-xgboost-2022-04-29-22-34-0-1651272355-86d1'\u001b[0m\n", + "\u001b[34m2022-04-29 22:45:55,831 Using endpoint name: sm-clarify-sagemaker-xgboost-2022-04-29-22-34-0-1651272355-86d1\u001b[0m\n", + "\u001b[34m2022-04-29 22:45:55,832 Waiting for endpoint ...\u001b[0m\n", + "\u001b[34m2022-04-29 22:45:55,832 Checking endpoint status:\u001b[0m\n", + "\u001b[34mLegend:\u001b[0m\n", + "\u001b[34m(OutOfService: x, Creating: -, Updating: -, InService: !, RollingBack: <, Deleting: o, Failed: *)\u001b[0m\n", + "\u001b[34m2022-04-29 22:49:56,323 Endpoint is in service after 240 seconds\u001b[0m\n", + "\u001b[34m2022-04-29 22:49:56,323 Endpoint ready.\u001b[0m\n", + "\u001b[34m2022-04-29 22:49:56,326 SHAP n_samples 100\u001b[0m\n", + "\u001b[34m2022-04-29 22:49:56,480 =====================================================\u001b[0m\n", + "\u001b[34m2022-04-29 22:49:56,480 Shap analyzer: explaining 708 rows, 25 columns...\u001b[0m\n", + "\u001b[34m2022-04-29 22:49:56,480 =====================================================\n", + " 0% (0 of 708) | | Elapsed Time: 0:00:00 ETA: --:--:--\u001b[0m\n", + "\u001b[34m100% (708 of 708) |######################| Elapsed Time: 0:00:16 Time: 0:00:16\u001b[0m\n", + "\u001b[34m2022-04-29 22:50:13,201 getting explanations took 16.72 seconds.\u001b[0m\n", + "\u001b[34m2022-04-29 22:50:13,201 ===================================================\u001b[0m\n", + "\u001b[34m2022-04-29 22:50:13,271 converting explanations to tabular took 0.07 seconds.\u001b[0m\n", + "\u001b[34m2022-04-29 22:50:13,271 ===================================================\u001b[0m\n", + "\u001b[34m2022-04-29 22:50:13,274 Wrote baseline used to compute explanations to: /opt/ml/processing/output/explanations_shap/baseline.csv\u001b[0m\n", + "\u001b[34m2022-04-29 22:50:13,298 Wrote 708 local explanations to: /opt/ml/processing/output/explanations_shap/out.csv\u001b[0m\n", + "\u001b[34m2022-04-29 22:50:13,299 writing local explanations took 0.03 seconds.\u001b[0m\n", + "\u001b[34m2022-04-29 22:50:13,299 ===================================================\u001b[0m\n", + "\u001b[34m2022-04-29 22:50:13,301 aggregating local explanations took 0.00 seconds.\u001b[0m\n", + "\u001b[34m2022-04-29 22:50:13,301 ===================================================\u001b[0m\n", + "\u001b[34m2022-04-29 22:50:13,301 Shap analysis finished.\u001b[0m\n", + "\u001b[34m2022-04-29 22:50:13,302 Stop using endpoint: sm-clarify-sagemaker-xgboost-2022-04-29-22-34-0-1651272355-86d1\u001b[0m\n", + "\u001b[34m2022-04-29 22:50:13,302 Deleting endpoint configuration with name: sm-clarify-config-1651272355-5750\u001b[0m\n", + "\u001b[34m2022-04-29 22:50:13,407 Deleting endpoint with name: sm-clarify-sagemaker-xgboost-2022-04-29-22-34-0-1651272355-86d1\u001b[0m\n", + "\u001b[34m2022-04-29 22:50:13,528 Model endpoint delivered 41.81825 requests per second and a total of 710 requests over 17 seconds\u001b[0m\n", + "\u001b[34m2022-04-29 22:50:17,557 Stop using endpoint: None\u001b[0m\n", + "\n", + "\u001b[34m2022-04-29 22:51:24,009 jupyter nbconvert --to html --output /opt/ml/processing/output/report.html /opt/ml/processing/output/report.ipynb --template sagemaker-xai\u001b[0m\n", + "\u001b[34m[NbConvertApp] Converting notebook /opt/ml/processing/output/report.ipynb to html\u001b[0m\n", + "\u001b[34m[NbConvertApp] Writing 534585 bytes to /opt/ml/processing/output/report.html\u001b[0m\n", + "\u001b[34m2022-04-29 22:51:25,148 HTML report '/opt/ml/processing/output/report.html' generated successfully.\u001b[0m\n", + "\u001b[34m2022-04-29 22:51:25,148 wkhtmltopdf -q /opt/ml/processing/output/report.html /opt/ml/processing/output/report.pdf\u001b[0m\n", + "\u001b[34m2022-04-29 22:51:25,809 PDF report '/opt/ml/processing/output/report.pdf' generated successfully.\u001b[0m\n", + "\u001b[34m2022-04-29 22:51:25,810 Collected analyses: \u001b[0m\n", + "\u001b[34m{\n", + " \"version\": \"1.0\",\n", + " \"explanations\": {\n", + " \"kernel_shap\": {\n", + " \"label0\": {\n", + " \"global_shap_values\": {\n", + " \"average_events_weekend\": 0.0021496041898171037,\n", + " \"average_events_weekday\": 0.016587702435219898,\n", + " \"num_songs_played_7d\": 0.0017402863783950045,\n", + " \"num_ads_7d\": 0.00868551809925556,\n", + " \"num_error_7d\": 0.0027734162552485904,\n", + " \"num_songs_played_30d\": 0.0011722262443900576,\n", + " \"num_songs_played_90d\": 0.0012286071144514484,\n", + " \"num_sessions\": 0.0011579425530513692,\n", + " \"avg_time_per_session\": 0.010029593923214982,\n", + " \"avg_events_per_session\": 0.003897350916100629,\n", + " \"avg_gap_between_session\": 0.0448815105461223,\n", + " \"num_events\": 0.0010721354650824332,\n", + " \"num_songs\": 0.0010824064811915818,\n", + " \"num_artists\": 0.0011435948826397841,\n", + " \"num_thumbs_down\": 0.0022934530809684047,\n", + " \"num_thumbs_up\": 0.0010034211293590313,\n", + " \"num_add_to_playlist\": 0.001163952744967679,\n", + " \"num_ads\": 0.0010789208860291896,\n", + " \"num_add_friend\": 0.005465492600682657,\n", + " \"num_downgrade\": 0.001048377920399092,\n", + " \"num_upgrade\": 0.002673292801161779,\n", + " \"num_error\": 0.0011517907319294842,\n", + " \"percentage_ad\": 0.02861671713801823,\n", + " \"days_since_active\": 0.29080317686448776,\n", + " \"repeats_ratio\": 0.001010326307543725\n", + " },\n", + " \"expected_value\": 0.06909029185771942\n", + " }\n", + " }\n", + " }\u001b[0m\n", + "\u001b[34m}\u001b[0m\n", + "\u001b[34m2022-04-29 22:51:25,810 exit_message: Completed: SageMaker XAI Analyzer ran successfully\u001b[0m\n", + "\u001b[34m----!\u001b[0m\n" + ] + } + ], "source": [ "clarify_processor.run_explainability(\n", " data_config=explainability_data_config,\n", @@ -997,7 +1769,7 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 39, "metadata": {}, "outputs": [], "source": [ @@ -1005,14 +1777,34 @@ "validation_data = pd.read_csv(\"data/validation_w_header.csv\")\n", "\n", "data_for_experiment = pd.concat([train_data, validation_data])\n", - "data_for_experiment.to_csv(\"full_feature_data.csv\", index=False)\n", - "s3_input_full_set = (\n", - " boto3.Session()\n", - " .resource(\"s3\")\n", - " .Bucket(bucket)\n", - " .Object(os.path.join(prefix, \"full/fullset.csv\"))\n", - " .upload_file(\"full_feature_data.csv\")\n", - ")" + "data_for_experiment.to_csv(\"full_feature_data_temp.csv\", index=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Modeling and Reference\n", + "\n", + "Now that you have created the complete feature set, you can start to explore and find a best-working model for your churn use case. By the end of part 2, you will select an algorithm, find the best sets of hyperparameter for the model, examine how well the model performs, and finally find the top influential features.\n", + "\n", + "To start with Part 2, you can either read in data from the output of your Part 1 results, or use the provided 'data/full_feature_data.csv' as the input (variable dataframe `processed_data`) for the next steps. " + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": {}, + "outputs": [], + "source": [ + "# pd.read(\"full_feature_data.csv\")\n", + "# s3_input_full_set = (\n", + "# boto3.Session()\n", + "# .resource(\"s3\")\n", + "# .Bucket(bucket)\n", + "# .Object(os.path.join(prefix, \"full/fullset.csv\"))\n", + "# .upload_file(\"full_feature_data.csv\")\n", + "# )" ] }, { @@ -1062,14 +1854,21 @@ "\n", "The data used in this notebook is simulated using the [EventSim](https://github.com/Interana/eventsim)." ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { "instance_type": "ml.t3.medium", "kernelspec": { - "display_name": "conda_python3", + "display_name": "Python 3 (Data Science)", "language": "python", - "name": "conda_python3" + "name": "python3__SAGEMAKER_INTERNAL__arn:aws:sagemaker:us-west-2:236514542706:image/datascience-1.0" }, "language_info": { "codemirror_mode": { @@ -1081,7 +1880,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.13" + "version": "3.7.10" } }, "nbformat": 4, From 787d8a1e4ce98d583e90fef693ff12305269263a Mon Sep 17 00:00:00 2001 From: atqy Date: Sat, 30 Apr 2022 00:18:19 +0000 Subject: [PATCH 10/27] refactor sequential noteboks 1 --- .../0_cust_churn_overview_dw.ipynb | 12 +- .../2_cust_churn_train_deploy_infer.ipynb | 1149 +++-------------- 2 files changed, 150 insertions(+), 1011 deletions(-) diff --git a/use-cases/customer_churn/0_cust_churn_overview_dw.ipynb b/use-cases/customer_churn/0_cust_churn_overview_dw.ipynb index 0d270b8a89..a00ba25e52 100644 --- a/use-cases/customer_churn/0_cust_churn_overview_dw.ipynb +++ b/use-cases/customer_churn/0_cust_churn_overview_dw.ipynb @@ -918,8 +918,6 @@ "outputs": [], "source": [ "processing_output_filename = f\"{processing_output_path}/{final_features_filename}\"\n", - "# %store processing_output_filename\n", - "# %store -r\n", "processing_output_filename" ] }, @@ -941,15 +939,7 @@ " ] = processing_output_filename\n", "\n", "with open(\"dw_example.flow\", \"w\") as f:\n", - " json.dump(flow, f)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ + " json.dump(flow, f)\n", "flow" ] }, diff --git a/use-cases/customer_churn/2_cust_churn_train_deploy_infer.ipynb b/use-cases/customer_churn/2_cust_churn_train_deploy_infer.ipynb index 604dd393bb..0fe4474302 100644 --- a/use-cases/customer_churn/2_cust_churn_train_deploy_infer.ipynb +++ b/use-cases/customer_churn/2_cust_churn_train_deploy_infer.ipynb @@ -123,7 +123,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -133,12 +133,13 @@ "import glob\n", "import s3fs\n", "import boto3\n", - "from datetime import datetime" + "from datetime import datetime\n", + "import os" ] }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -152,14 +153,104 @@ "prefix = \"music-streaming\"" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "### Download Data and Upload to S3\n", + "\n", + "We ingest the simulated data from the public SageMaker S3 training database. If you want to see how the train, test, and validation datasets are created in detail, look at [Build a Customer Churn Model for Music Streaming App Users: Overview and Data Preparation](0_cust_churn_overview_dw.ipynb)" + ] + }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "container = sagemaker.image_uris.retrieve(\n", - " \"xgboost\", region, version=\"1.0-1\", instance_type=\"ml.m4.xlarge\"\n", + "##### Alternative: copy data from a public S3 bucket to your own bucket\n", + "##### data file should include full_data.csv and sample.json\n", + "#### cell 5 - 7 is not needed; the processing job before data wrangler screenshots is not needed\n", + "!aws s3 cp s3://sagemaker-sample-files/datasets/tabular/customer-churn/customer-churn-data.zip ./data/raw/customer-churn-data.zip" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!unzip -o ./data/raw/customer-churn-data.zip -d ./data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# unzip the partitioned data files into the same folder\n", + "!unzip -o data/simu-1.zip -d data/raw\n", + "!unzip -o data/simu-2.zip -d data/raw\n", + "!unzip -o data/simu-3.zip -d data/raw\n", + "!unzip -o data/simu-4.zip -d data/raw" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!rm ./data/raw/*.zip" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!unzip -o data/sample.zip -d data/raw" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!aws s3 cp ./data/raw s3://$bucket/$prefix/data/json/ --recursive" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "s3_input_train = (\n", + " boto3.Session()\n", + " .resource(\"s3\")\n", + " .Bucket(bucket)\n", + " .Object(os.path.join(prefix, \"train/train.csv\"))\n", + " .upload_file(\"data/train_updated.csv\")\n", + ")\n", + "s3_input_validation = (\n", + " boto3.Session()\n", + " .resource(\"s3\")\n", + " .Bucket(bucket)\n", + " .Object(os.path.join(prefix, \"validation/validation.csv\"))\n", + " .upload_file(\"data/validation_updated.csv\")\n", + ")\n", + "s3_input_validation = (\n", + " boto3.Session()\n", + " .resource(\"s3\")\n", + " .Bucket(bucket)\n", + " .Object(os.path.join(prefix, \"test/test_labeled.csv\"))\n", + " .upload_file(\"data/test_updated.csv\")\n", ")" ] }, @@ -172,7 +263,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -198,22 +289,18 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "CPU times: user 102 µs, sys: 0 ns, total: 102 µs\n", - "Wall time: 309 µs\n" - ] - } - ], + "outputs": [], "source": [ "%%time\n", "from time import gmtime, strftime\n", "\n", + "container = sagemaker.image_uris.retrieve(\n", + " \"xgboost\", region, version=\"1.0-1\", instance_type=\"ml.m4.xlarge\"\n", + ")\n", + "\n", + "\n", "xgb = sagemaker.estimator.Estimator(\n", " container,\n", " role,\n", @@ -228,7 +315,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -245,114 +332,11 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": null, "metadata": { "scrolled": true }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "2022-04-29 21:58:04 Starting - Starting the training job...\n", - "2022-04-29 21:58:28 Starting - Preparing the instances for trainingProfilerReport-1651269483: InProgress\n", - ".........\n", - "2022-04-29 21:59:56 Downloading - Downloading input data...\n", - "2022-04-29 22:00:31 Training - Downloading the training image......\n", - "2022-04-29 22:01:27 Training - Training image download completed. Training in progress..\u001b[34mINFO:sagemaker-containers:Imported framework sagemaker_xgboost_container.training\u001b[0m\n", - "\u001b[34mINFO:sagemaker-containers:Failed to parse hyperparameter eval_metric value auc to Json.\u001b[0m\n", - "\u001b[34mReturning the value itself\u001b[0m\n", - "\u001b[34mINFO:sagemaker-containers:Failed to parse hyperparameter objective value binary:logistic to Json.\u001b[0m\n", - "\u001b[34mReturning the value itself\u001b[0m\n", - "\u001b[34mINFO:sagemaker-containers:No GPUs detected (normal if no gpus installed)\u001b[0m\n", - "\u001b[34mINFO:sagemaker_xgboost_container.training:Running XGBoost Sagemaker in algorithm mode\u001b[0m\n", - "\u001b[34mINFO:root:Determined delimiter of CSV input is ','\u001b[0m\n", - "\u001b[34mINFO:root:Determined delimiter of CSV input is ','\u001b[0m\n", - "\u001b[34mINFO:root:Determined delimiter of CSV input is ','\u001b[0m\n", - "\u001b[34m[22:01:32] 708x25 matrix with 17700 entries loaded from /opt/ml/input/data/train?format=csv&label_column=0&delimiter=,\u001b[0m\n", - "\u001b[34mINFO:root:Determined delimiter of CSV input is ','\u001b[0m\n", - "\u001b[34m[22:01:32] 204x25 matrix with 5100 entries loaded from /opt/ml/input/data/validation?format=csv&label_column=0&delimiter=,\u001b[0m\n", - "\u001b[34mINFO:root:Single node training.\u001b[0m\n", - "\u001b[34mINFO:root:Train matrix has 708 rows\u001b[0m\n", - "\u001b[34mINFO:root:Validation matrix has 204 rows\u001b[0m\n", - "\u001b[34m[0]#011train-auc:0.91768#011validation-auc:0.94514\u001b[0m\n", - "\u001b[34m[1]#011train-auc:0.92026#011validation-auc:0.95180\u001b[0m\n", - "\u001b[34m[2]#011train-auc:0.93830#011validation-auc:0.95534\u001b[0m\n", - "\u001b[34m[3]#011train-auc:0.93852#011validation-auc:0.95507\u001b[0m\n", - "\u001b[34m[4]#011train-auc:0.95391#011validation-auc:0.96667\u001b[0m\n", - "\u001b[34m[5]#011train-auc:0.95654#011validation-auc:0.96758\u001b[0m\n", - "\u001b[34m[6]#011train-auc:0.95694#011validation-auc:0.96468\u001b[0m\n", - "\u001b[34m[7]#011train-auc:0.96200#011validation-auc:0.96473\u001b[0m\n", - "\u001b[34m[8]#011train-auc:0.96468#011validation-auc:0.96720\u001b[0m\n", - "\u001b[34m[9]#011train-auc:0.96311#011validation-auc:0.96699\u001b[0m\n", - "\u001b[34m[10]#011train-auc:0.96290#011validation-auc:0.96871\u001b[0m\n", - "\u001b[34m[11]#011train-auc:0.96521#011validation-auc:0.97434\u001b[0m\n", - "\u001b[34m[12]#011train-auc:0.96481#011validation-auc:0.97182\u001b[0m\n", - "\u001b[34m[13]#011train-auc:0.96483#011validation-auc:0.97386\u001b[0m\n", - "\u001b[34m[14]#011train-auc:0.96442#011validation-auc:0.97375\u001b[0m\n", - "\u001b[34m[15]#011train-auc:0.96458#011validation-auc:0.97418\u001b[0m\n", - "\u001b[34m[16]#011train-auc:0.96498#011validation-auc:0.97563\u001b[0m\n", - "\u001b[34m[17]#011train-auc:0.96619#011validation-auc:0.97724\u001b[0m\n", - "\u001b[34m[18]#011train-auc:0.96492#011validation-auc:0.97681\u001b[0m\n", - "\u001b[34m[19]#011train-auc:0.96406#011validation-auc:0.97584\u001b[0m\n", - "\u001b[34m[20]#011train-auc:0.96365#011validation-auc:0.97584\u001b[0m\n", - "\u001b[34m[21]#011train-auc:0.96428#011validation-auc:0.97381\u001b[0m\n", - "\u001b[34m[22]#011train-auc:0.96540#011validation-auc:0.97348\u001b[0m\n", - "\u001b[34m[23]#011train-auc:0.96511#011validation-auc:0.97445\u001b[0m\n", - "\u001b[34m[24]#011train-auc:0.96481#011validation-auc:0.97450\u001b[0m\n", - "\u001b[34m[25]#011train-auc:0.96465#011validation-auc:0.97504\u001b[0m\n", - "\u001b[34m[26]#011train-auc:0.96503#011validation-auc:0.97461\u001b[0m\n", - "\u001b[34m[27]#011train-auc:0.96627#011validation-auc:0.97364\u001b[0m\n", - "\u001b[34m[28]#011train-auc:0.96733#011validation-auc:0.97289\u001b[0m\n", - "\u001b[34m[29]#011train-auc:0.96781#011validation-auc:0.97354\u001b[0m\n", - "\u001b[34m[30]#011train-auc:0.96757#011validation-auc:0.97300\u001b[0m\n", - "\u001b[34m[31]#011train-auc:0.96827#011validation-auc:0.97300\u001b[0m\n", - "\u001b[34m[32]#011train-auc:0.96887#011validation-auc:0.97332\u001b[0m\n", - "\u001b[34m[33]#011train-auc:0.96900#011validation-auc:0.97354\u001b[0m\n", - "\u001b[34m[34]#011train-auc:0.96905#011validation-auc:0.97332\u001b[0m\n", - "\u001b[34m[35]#011train-auc:0.96980#011validation-auc:0.97440\u001b[0m\n", - "\u001b[34m[36]#011train-auc:0.96945#011validation-auc:0.97354\u001b[0m\n", - "\u001b[34m[37]#011train-auc:0.96924#011validation-auc:0.97354\u001b[0m\n", - "\u001b[34m[38]#011train-auc:0.96936#011validation-auc:0.97418\u001b[0m\n", - "\u001b[34m[39]#011train-auc:0.96936#011validation-auc:0.97418\u001b[0m\n", - "\u001b[34m[40]#011train-auc:0.96933#011validation-auc:0.97407\u001b[0m\n", - "\u001b[34m[41]#011train-auc:0.96896#011validation-auc:0.97343\u001b[0m\n", - "\u001b[34m[42]#011train-auc:0.96899#011validation-auc:0.97348\u001b[0m\n", - "\u001b[34m[43]#011train-auc:0.96945#011validation-auc:0.97359\u001b[0m\n", - "\u001b[34m[44]#011train-auc:0.96924#011validation-auc:0.97391\u001b[0m\n", - "\u001b[34m[45]#011train-auc:0.96974#011validation-auc:0.97423\u001b[0m\n", - "\u001b[34m[46]#011train-auc:0.97061#011validation-auc:0.97477\u001b[0m\n", - "\u001b[34m[47]#011train-auc:0.97083#011validation-auc:0.97467\u001b[0m\n", - "\u001b[34m[48]#011train-auc:0.97080#011validation-auc:0.97467\u001b[0m\n", - "\u001b[34m[49]#011train-auc:0.97067#011validation-auc:0.97456\u001b[0m\n", - "\u001b[34m[50]#011train-auc:0.97121#011validation-auc:0.97456\u001b[0m\n", - "\u001b[34m[51]#011train-auc:0.97121#011validation-auc:0.97456\u001b[0m\n", - "\u001b[34m[52]#011train-auc:0.97181#011validation-auc:0.97461\u001b[0m\n", - "\u001b[34m[53]#011train-auc:0.97159#011validation-auc:0.97461\u001b[0m\n", - "\u001b[34m[54]#011train-auc:0.97159#011validation-auc:0.97461\u001b[0m\n", - "\u001b[34m[55]#011train-auc:0.97246#011validation-auc:0.97504\u001b[0m\n", - "\u001b[34m[56]#011train-auc:0.97246#011validation-auc:0.97504\u001b[0m\n", - "\u001b[34m[57]#011train-auc:0.97246#011validation-auc:0.97504\u001b[0m\n", - "\u001b[34m[58]#011train-auc:0.97323#011validation-auc:0.97493\u001b[0m\n", - "\u001b[34m[59]#011train-auc:0.97323#011validation-auc:0.97493\u001b[0m\n", - "\u001b[34m[60]#011train-auc:0.97323#011validation-auc:0.97493\u001b[0m\n", - "\u001b[34m[61]#011train-auc:0.97323#011validation-auc:0.97493\u001b[0m\n", - "\u001b[34m[62]#011train-auc:0.97333#011validation-auc:0.97515\u001b[0m\n", - "\u001b[34m[63]#011train-auc:0.97349#011validation-auc:0.97515\u001b[0m\n", - "\u001b[34m[64]#011train-auc:0.97359#011validation-auc:0.97515\u001b[0m\n", - "\u001b[34m[65]#011train-auc:0.97353#011validation-auc:0.97525\u001b[0m\n", - "\u001b[34m[66]#011train-auc:0.97353#011validation-auc:0.97525\u001b[0m\n", - "\u001b[34m[67]#011train-auc:0.97353#011validation-auc:0.97525\u001b[0m\n", - "\n", - "2022-04-29 22:01:57 Uploading - Uploading generated training model\n", - "2022-04-29 22:01:57 Completed - Training job completed\n", - "Training seconds: 113\n", - "Billable seconds: 113\n", - "CPU times: user 465 ms, sys: 20.3 ms, total: 485 ms\n", - "Wall time: 4min 12s\n" - ] - } - ], + "outputs": [], "source": [ "%%time\n", "xgb.fit(inputs={\"train\": train_input, \"validation\": validation_input}, wait=True)" @@ -367,252 +351,20 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# custom trial name\n", - "experiment_name = \"music-streaming-churn-exp-{}\".format(datetime.now().strftime(\"%Y-%m-%d-%H-%M-%S\"))\n", - "trial_name_xgb = \"xgboost\"" + "experiment_name = \"music-streaming-churn-exp-{}\".format(datetime.now().strftime(\"%Y%m%d-%H%M%S\"))\n", + "trial_name_xgb = \"xgboost-{}\".format(datetime.now().strftime(\"%Y%m%d-%H%M%S\"))" ] }, { "cell_type": "code", - "execution_count": 14, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Experiment creation music-streaming-churn-exp: SUCCESS\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "INFO:sagemaker.image_uris:Defaulting to the only supported framework/algorithm version: latest.\n", - "INFO:sagemaker.image_uris:Ignoring unnecessary instance type: None.\n", - "INFO:sagemaker:Creating training-job with name: sagemaker-xgboost-2022-04-29-22-02-17-343\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Create trial xgboost: SUCCESSFUL\n", - "2022-04-29 22:02:17 Starting - Starting the training job...\n", - "2022-04-29 22:02:44 Starting - Preparing the instances for trainingProfilerReport-1651269737: InProgress\n", - ".........\n", - "2022-04-29 22:04:14 Downloading - Downloading input data...\n", - "2022-04-29 22:04:45 Training - Downloading the training image......\n", - "2022-04-29 22:05:46 Training - Training image download completed. Training in progress...\u001b[34mINFO:sagemaker-containers:Imported framework sagemaker_xgboost_container.training\u001b[0m\n", - "\u001b[34mINFO:sagemaker-containers:Failed to parse hyperparameter eval_metric value auc to Json.\u001b[0m\n", - "\u001b[34mReturning the value itself\u001b[0m\n", - "\u001b[34mINFO:sagemaker-containers:Failed to parse hyperparameter objective value binary:logistic to Json.\u001b[0m\n", - "\u001b[34mReturning the value itself\u001b[0m\n", - "\u001b[34mINFO:sagemaker-containers:No GPUs detected (normal if no gpus installed)\u001b[0m\n", - "\u001b[34mINFO:sagemaker_xgboost_container.training:Running XGBoost Sagemaker in algorithm mode\u001b[0m\n", - "\u001b[34mINFO:root:Determined delimiter of CSV input is ','\u001b[0m\n", - "\u001b[34mINFO:root:Determined delimiter of CSV input is ','\u001b[0m\n", - "\u001b[34mINFO:root:Determined delimiter of CSV input is ','\u001b[0m\n", - "\u001b[34m[22:05:50] 708x25 matrix with 17700 entries loaded from /opt/ml/input/data/train?format=csv&label_column=0&delimiter=,\u001b[0m\n", - "\u001b[34mINFO:root:Determined delimiter of CSV input is ','\u001b[0m\n", - "\u001b[34m[22:05:50] 204x25 matrix with 5100 entries loaded from /opt/ml/input/data/validation?format=csv&label_column=0&delimiter=,\u001b[0m\n", - "\u001b[34mINFO:root:Single node training.\u001b[0m\n", - "\u001b[34mINFO:root:Train matrix has 708 rows\u001b[0m\n", - "\u001b[34mINFO:root:Validation matrix has 204 rows\u001b[0m\n", - "\u001b[34m[0]#011train-auc:0.91768#011validation-auc:0.94514\u001b[0m\n", - "\u001b[34m[1]#011train-auc:0.92026#011validation-auc:0.95180\u001b[0m\n", - "\u001b[34m[2]#011train-auc:0.93830#011validation-auc:0.95534\u001b[0m\n", - "\u001b[34m[3]#011train-auc:0.93852#011validation-auc:0.95507\u001b[0m\n", - "\u001b[34m[4]#011train-auc:0.95391#011validation-auc:0.96667\u001b[0m\n", - "\u001b[34m[5]#011train-auc:0.95654#011validation-auc:0.96758\u001b[0m\n", - "\u001b[34m[6]#011train-auc:0.95694#011validation-auc:0.96468\u001b[0m\n", - "\u001b[34m[7]#011train-auc:0.96200#011validation-auc:0.96473\u001b[0m\n", - "\u001b[34m[8]#011train-auc:0.96468#011validation-auc:0.96720\u001b[0m\n", - "\u001b[34m[9]#011train-auc:0.96311#011validation-auc:0.96699\u001b[0m\n", - "\u001b[34m[10]#011train-auc:0.96290#011validation-auc:0.96871\u001b[0m\n", - "\u001b[34m[11]#011train-auc:0.96521#011validation-auc:0.97434\u001b[0m\n", - "\u001b[34m[12]#011train-auc:0.96481#011validation-auc:0.97182\u001b[0m\n", - "\u001b[34m[13]#011train-auc:0.96483#011validation-auc:0.97386\u001b[0m\n", - "\u001b[34m[14]#011train-auc:0.96442#011validation-auc:0.97375\u001b[0m\n", - "\u001b[34m[15]#011train-auc:0.96458#011validation-auc:0.97418\u001b[0m\n", - "\u001b[34m[16]#011train-auc:0.96498#011validation-auc:0.97563\u001b[0m\n", - "\u001b[34m[17]#011train-auc:0.96619#011validation-auc:0.97724\u001b[0m\n", - "\u001b[34m[18]#011train-auc:0.96492#011validation-auc:0.97681\u001b[0m\n", - "\u001b[34m[19]#011train-auc:0.96406#011validation-auc:0.97584\u001b[0m\n", - "\u001b[34m[20]#011train-auc:0.96365#011validation-auc:0.97584\u001b[0m\n", - "\u001b[34m[21]#011train-auc:0.96428#011validation-auc:0.97381\u001b[0m\n", - "\u001b[34m[22]#011train-auc:0.96540#011validation-auc:0.97348\u001b[0m\n", - "\u001b[34m[23]#011train-auc:0.96511#011validation-auc:0.97445\u001b[0m\n", - "\u001b[34m[24]#011train-auc:0.96481#011validation-auc:0.97450\u001b[0m\n", - "\u001b[34m[25]#011train-auc:0.96465#011validation-auc:0.97504\u001b[0m\n", - "\u001b[34m[26]#011train-auc:0.96503#011validation-auc:0.97461\u001b[0m\n", - "\u001b[34m[27]#011train-auc:0.96627#011validation-auc:0.97364\u001b[0m\n", - "\u001b[34m[28]#011train-auc:0.96733#011validation-auc:0.97289\u001b[0m\n", - "\u001b[34m[29]#011train-auc:0.96781#011validation-auc:0.97354\u001b[0m\n", - "\u001b[34m[30]#011train-auc:0.96757#011validation-auc:0.97300\u001b[0m\n", - "\u001b[34m[31]#011train-auc:0.96827#011validation-auc:0.97300\u001b[0m\n", - "\u001b[34m[32]#011train-auc:0.96887#011validation-auc:0.97332\u001b[0m\n", - "\u001b[34m[33]#011train-auc:0.96900#011validation-auc:0.97354\u001b[0m\n", - "\u001b[34m[34]#011train-auc:0.96905#011validation-auc:0.97332\u001b[0m\n", - "\u001b[34m[35]#011train-auc:0.96980#011validation-auc:0.97440\u001b[0m\n", - "\u001b[34m[36]#011train-auc:0.96945#011validation-auc:0.97354\u001b[0m\n", - "\u001b[34m[37]#011train-auc:0.96924#011validation-auc:0.97354\u001b[0m\n", - "\u001b[34m[38]#011train-auc:0.96936#011validation-auc:0.97418\u001b[0m\n", - "\u001b[34m[39]#011train-auc:0.96936#011validation-auc:0.97418\u001b[0m\n", - "\u001b[34m[40]#011train-auc:0.96933#011validation-auc:0.97407\u001b[0m\n", - "\u001b[34m[41]#011train-auc:0.96896#011validation-auc:0.97343\u001b[0m\n", - "\u001b[34m[42]#011train-auc:0.96899#011validation-auc:0.97348\u001b[0m\n", - "\u001b[34m[43]#011train-auc:0.96945#011validation-auc:0.97359\u001b[0m\n", - "\u001b[34m[44]#011train-auc:0.96924#011validation-auc:0.97391\u001b[0m\n", - "\u001b[34m[45]#011train-auc:0.96974#011validation-auc:0.97423\u001b[0m\n", - "\u001b[34m[46]#011train-auc:0.97061#011validation-auc:0.97477\u001b[0m\n", - "\u001b[34m[47]#011train-auc:0.97083#011validation-auc:0.97467\u001b[0m\n", - "\u001b[34m[48]#011train-auc:0.97080#011validation-auc:0.97467\u001b[0m\n", - "\u001b[34m[49]#011train-auc:0.97067#011validation-auc:0.97456\u001b[0m\n", - "\u001b[34m[50]#011train-auc:0.97121#011validation-auc:0.97456\u001b[0m\n", - "\u001b[34m[51]#011train-auc:0.97121#011validation-auc:0.97456\u001b[0m\n", - "\u001b[34m[52]#011train-auc:0.97181#011validation-auc:0.97461\u001b[0m\n", - "\u001b[34m[53]#011train-auc:0.97159#011validation-auc:0.97461\u001b[0m\n", - "\u001b[34m[54]#011train-auc:0.97159#011validation-auc:0.97461\u001b[0m\n", - "\u001b[34m[55]#011train-auc:0.97246#011validation-auc:0.97504\u001b[0m\n", - "\u001b[34m[56]#011train-auc:0.97246#011validation-auc:0.97504\u001b[0m\n", - "\u001b[34m[57]#011train-auc:0.97246#011validation-auc:0.97504\u001b[0m\n", - "\u001b[34m[58]#011train-auc:0.97323#011validation-auc:0.97493\u001b[0m\n", - "\u001b[34m[59]#011train-auc:0.97323#011validation-auc:0.97493\u001b[0m\n", - "\u001b[34m[60]#011train-auc:0.97323#011validation-auc:0.97493\u001b[0m\n", - "\u001b[34m[61]#011train-auc:0.97323#011validation-auc:0.97493\u001b[0m\n", - "\u001b[34m[62]#011train-auc:0.97333#011validation-auc:0.97515\u001b[0m\n", - "\u001b[34m[63]#011train-auc:0.97349#011validation-auc:0.97515\u001b[0m\n", - "\u001b[34m[64]#011train-auc:0.97359#011validation-auc:0.97515\u001b[0m\n", - "\u001b[34m[65]#011train-auc:0.97353#011validation-auc:0.97525\u001b[0m\n", - "\u001b[34m[66]#011train-auc:0.97353#011validation-auc:0.97525\u001b[0m\n", - "\u001b[34m[67]#011train-auc:0.97353#011validation-auc:0.97525\u001b[0m\n", - "\n", - "2022-04-29 22:06:07 Uploading - Uploading generated training model\n", - "2022-04-29 22:06:07 Completed - Training job completed\n", - "Training seconds: 113\n", - "Billable seconds: 113\n", - "CPU times: user 994 ms, sys: 74.8 ms, total: 1.07 s\n", - "Wall time: 4min 13s\n" - ] - }, - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
TrialComponentNameDisplayNameSourceArnSageMaker.ImageUriSageMaker.InstanceCountSageMaker.InstanceTypeSageMaker.VolumeSizeInGBearly_stopping_roundsetaeval_metric...train:auc - Lasttrain:auc - Counttrain - MediaTypetrain - Valuevalidation - MediaTypevalidation - ValueSageMaker.ModelArtifact - MediaTypeSageMaker.ModelArtifact - ValueTrialsExperiments
0sagemaker-xgboost-2022-04-29-22-02-17-343-aws-...churn-xgboostarn:aws:sagemaker:us-west-2:688520471316:train...246618743249.dkr.ecr.us-west-2.amazonaws.com/s...1.0ml.m4.xlarge30.050.00.08auc...0.9690535csvs3://sagemaker-us-west-2-688520471316/music-st...csvs3://sagemaker-us-west-2-688520471316/music-st...Nones3://sagemaker-us-west-2-688520471316/music-st...[xgboost][music-streaming-churn-exp]
\n", - "

1 rows × 36 columns

\n", - "
" - ], - "text/plain": [ - " TrialComponentName DisplayName \\\n", - "0 sagemaker-xgboost-2022-04-29-22-02-17-343-aws-... churn-xgboost \n", - "\n", - " SourceArn \\\n", - "0 arn:aws:sagemaker:us-west-2:688520471316:train... \n", - "\n", - " SageMaker.ImageUri SageMaker.InstanceCount \\\n", - "0 246618743249.dkr.ecr.us-west-2.amazonaws.com/s... 1.0 \n", - "\n", - " SageMaker.InstanceType SageMaker.VolumeSizeInGB early_stopping_rounds \\\n", - "0 ml.m4.xlarge 30.0 50.0 \n", - "\n", - " eta eval_metric ... train:auc - Last train:auc - Count \\\n", - "0 0.08 auc ... 0.96905 35 \n", - "\n", - " train - MediaType train - Value \\\n", - "0 csv s3://sagemaker-us-west-2-688520471316/music-st... \n", - "\n", - " validation - MediaType validation - Value \\\n", - "0 csv s3://sagemaker-us-west-2-688520471316/music-st... \n", - "\n", - " SageMaker.ModelArtifact - MediaType \\\n", - "0 None \n", - "\n", - " SageMaker.ModelArtifact - Value Trials \\\n", - "0 s3://sagemaker-us-west-2-688520471316/music-st... [xgboost] \n", - "\n", - " Experiments \n", - "0 [music-streaming-churn-exp] \n", - "\n", - "[1 rows x 36 columns]" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "%%time\n", "from smexperiments import experiment, trial\n", @@ -678,7 +430,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -710,7 +462,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -770,25 +522,17 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Stored 'tuning_job_name' (str)\n" - ] - } - ], + "outputs": [], "source": [ "# custom a tuner job name\n", - "tuning_job_name = \"ChurnPrediction-Tuning-Job-{}\".format(datetime.now().strftime(\"%Y-%m-%d-%H-%M-%S\"))" + "tuning_job_name = \"ChurnPredictTune-{}\".format(datetime.now().strftime(\"%Y%m%d-%H%M%S\"))" ] }, { "cell_type": "code", - "execution_count": 18, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -799,45 +543,18 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'HyperParameterTuningJobSummaries': [],\n", - " 'NextToken': 'cIws2QhTXUIa8bi8W47LEKvF+FCR8eCxw7lm05/6M4GEnbWgUtoUJdlQBSv7kOsUKyeD3vlHXjc+jwuuBpymNWHzVbJTQRgpv3gKfmL4gypEQUDRwvEqhJzEDswtvI3HovY77Q4w795ItXG+PyA0eT/CNgcnCrkGC1ZBCjvUDG3ik8HgfI2+WPs8rSJrNtI86VXlB+tKqBzfn6e0wkIVyMjnAtA653gJLJ6HYJjCA4wq7Q5HqeZyUP62UPhU2KKXNbvdlD2x/3WC9Z37Re53/rYLhSnzqCBH0BVz1OS0vsRuL4QUzHmrVw/b6rngygpW57lbB2WQkZJqB9yyBXjOO/G3BELqDX7SKGDYEQw6j3jklpEwBM//HEqMOppRWmDr7bpGrVFs1aWy/a79jjTWTMe2916jd/I5RWvegPXL1o5E6lfkb+7ZbMelxH2Idtj8LF6B38/DNdYEDXnjeNoXRTjUTPBb5ay0ExcwPqHQs3wSax6Js7KazMxNQBDSVOcFJ7FfjGA/CTd71ya/S6l23g5PtLj8bPbn97oJn2Xej6tvFumWLATRDxWFTQIgE9mZylxrQEYM3kVymvSvzVg42WJdbtFzikOsFPjzyaO/T7lll9K2XUY7SWsybD+NQ/JNBSimd73sbOfy',\n", - " 'ResponseMetadata': {'RequestId': '86f3a2ff-4ab7-40af-9262-0c8034a1347f',\n", - " 'HTTPStatusCode': 200,\n", - " 'HTTPHeaders': {'x-amzn-requestid': '86f3a2ff-4ab7-40af-9262-0c8034a1347f',\n", - " 'content-type': 'application/x-amz-json-1.1',\n", - " 'content-length': '706',\n", - " 'date': 'Fri, 29 Apr 2022 22:06:30 GMT'},\n", - " 'RetryAttempts': 0}}" - ] - }, - "execution_count": 19, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "smclient.list_hyper_parameter_tuning_jobs(NameContains=tuning_job_name)" ] }, { "cell_type": "code", - "execution_count": 20, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Create tuning job ChurnPrediction-Tuning-Job: SUCCESSFUL\n" - ] - } - ], + "outputs": [], "source": [ "from sagemaker.tuner import HyperparameterTuner\n", "\n", @@ -881,44 +598,9 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "InProgress\n", - "InProgress\n", - "InProgress\n", - "InProgress\n", - "InProgress\n", - "InProgress\n", - "InProgress\n", - "InProgress\n", - "InProgress\n", - "InProgress\n", - "InProgress\n", - "InProgress\n", - "InProgress\n", - "InProgress\n", - "InProgress\n", - "InProgress\n", - "InProgress\n", - "InProgress\n", - "InProgress\n", - "InProgress\n", - "InProgress\n", - "InProgress\n", - "InProgress\n", - "InProgress\n", - "InProgress\n", - "Completed\n", - "CPU times: user 556 ms, sys: 53.8 ms, total: 610 ms\n", - "Wall time: 25min 4s\n" - ] - } - ], + "outputs": [], "source": [ "%%time\n", "# check status\n", @@ -940,40 +622,9 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "ChurnPrediction-Tuning-Job-010-f4b35971\n", - "\n", - "2022-04-29 22:19:24 Starting - Preparing the instances for training\n", - "2022-04-29 22:19:24 Downloading - Downloading input data\n", - "2022-04-29 22:19:24 Training - Training image download completed. Training in progress.\n", - "2022-04-29 22:19:24 Uploading - Uploading generated training model\n", - "2022-04-29 22:19:24 Completed - Training job completed\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "INFO:sagemaker:Creating model with name: sagemaker-xgboost-2022-04-29-22-31-36-166\n", - "INFO:sagemaker:Creating endpoint-config with name sagemaker-xgboost-2022-04-29-22-31-36-813\n", - "INFO:sagemaker:Creating endpoint with name sagemaker-xgboost-2022-04-29-22-31-36-813\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "-----!CPU times: user 306 ms, sys: 12.4 ms, total: 318 ms\n", - "Wall time: 2min 32s\n" - ] - } - ], + "outputs": [], "source": [ "%%time\n", "# Attach to an existing hyperparameter tuning job.\n", @@ -1012,7 +663,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -1026,7 +677,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -1051,213 +702,11 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": null, "metadata": { "scrolled": true }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "INFO:sagemaker:Creating model with name: sagemaker-xgboost-2022-04-29-22-34-07-950\n", - "INFO:sagemaker:Creating transform job with name: sagemaker-xgboost-2022-04-29-22-34-08-567\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - ".................................\n", - ".\u001b[34m[2022-04-29:22:39:30:INFO] No GPUs detected (normal if no gpus installed)\u001b[0m\n", - "\u001b[34m[2022-04-29:22:39:30:INFO] No GPUs detected (normal if no gpus installed)\u001b[0m\n", - "\u001b[34m[2022-04-29:22:39:30:INFO] nginx config: \u001b[0m\n", - "\u001b[34mworker_processes auto;\u001b[0m\n", - "\u001b[34mdaemon off;\u001b[0m\n", - "\u001b[34mpid /tmp/nginx.pid;\u001b[0m\n", - "\u001b[34merror_log /dev/stderr;\u001b[0m\n", - "\u001b[34mworker_rlimit_nofile 4096;\u001b[0m\n", - "\u001b[35m[2022-04-29:22:39:30:INFO] No GPUs detected (normal if no gpus installed)\u001b[0m\n", - "\u001b[35m[2022-04-29:22:39:30:INFO] No GPUs detected (normal if no gpus installed)\u001b[0m\n", - "\u001b[35m[2022-04-29:22:39:30:INFO] nginx config: \u001b[0m\n", - "\u001b[35mworker_processes auto;\u001b[0m\n", - "\u001b[35mdaemon off;\u001b[0m\n", - "\u001b[35mpid /tmp/nginx.pid;\u001b[0m\n", - "\u001b[35merror_log /dev/stderr;\u001b[0m\n", - "\u001b[35mworker_rlimit_nofile 4096;\u001b[0m\n", - "\u001b[34mevents {\n", - " worker_connections 2048;\u001b[0m\n", - "\u001b[34m}\u001b[0m\n", - "\u001b[34mhttp {\n", - " include /etc/nginx/mime.types;\n", - " default_type application/octet-stream;\n", - " access_log /dev/stdout combined;\n", - " upstream gunicorn {\n", - " server unix:/tmp/gunicorn.sock;\n", - " }\n", - " server {\n", - " listen 8080 deferred;\n", - " client_max_body_size 0;\n", - " keepalive_timeout 3;\n", - " location ~ ^/(ping|invocations|execution-parameters) {\n", - " proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n", - " proxy_set_header Host $http_host;\n", - " proxy_redirect off;\n", - " proxy_read_timeout 60s;\n", - " proxy_pass http://gunicorn;\n", - " }\n", - " location / {\n", - " return 404 \"{}\";\n", - " }\n", - " }\u001b[0m\n", - "\u001b[34m}\u001b[0m\n", - "\u001b[34m[2022-04-29 22:39:31 +0000] [18] [INFO] Starting gunicorn 19.10.0\u001b[0m\n", - "\u001b[34m[2022-04-29 22:39:31 +0000] [18] [INFO] Listening at: unix:/tmp/gunicorn.sock (18)\u001b[0m\n", - "\u001b[34m[2022-04-29 22:39:31 +0000] [18] [INFO] Using worker: gevent\u001b[0m\n", - "\u001b[34m[2022-04-29 22:39:31 +0000] [25] [INFO] Booting worker with pid: 25\u001b[0m\n", - "\u001b[35mevents {\n", - " worker_connections 2048;\u001b[0m\n", - "\u001b[35m}\u001b[0m\n", - "\u001b[35mhttp {\n", - " include /etc/nginx/mime.types;\n", - " default_type application/octet-stream;\n", - " access_log /dev/stdout combined;\n", - " upstream gunicorn {\n", - " server unix:/tmp/gunicorn.sock;\n", - " }\n", - " server {\n", - " listen 8080 deferred;\n", - " client_max_body_size 0;\n", - " keepalive_timeout 3;\n", - " location ~ ^/(ping|invocations|execution-parameters) {\n", - " proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n", - " proxy_set_header Host $http_host;\n", - " proxy_redirect off;\n", - " proxy_read_timeout 60s;\n", - " proxy_pass http://gunicorn;\n", - " }\n", - " location / {\n", - " return 404 \"{}\";\n", - " }\n", - " }\u001b[0m\n", - "\u001b[35m}\u001b[0m\n", - "\u001b[35m[2022-04-29 22:39:31 +0000] [18] [INFO] Starting gunicorn 19.10.0\u001b[0m\n", - "\u001b[35m[2022-04-29 22:39:31 +0000] [18] [INFO] Listening at: unix:/tmp/gunicorn.sock (18)\u001b[0m\n", - "\u001b[35m[2022-04-29 22:39:31 +0000] [18] [INFO] Using worker: gevent\u001b[0m\n", - "\u001b[35m[2022-04-29 22:39:31 +0000] [25] [INFO] Booting worker with pid: 25\u001b[0m\n", - "\u001b[34m[2022-04-29 22:39:31 +0000] [26] [INFO] Booting worker with pid: 26\u001b[0m\n", - "\u001b[34m[2022-04-29 22:39:31 +0000] [31] [INFO] Booting worker with pid: 31\u001b[0m\n", - "\u001b[34m[2022-04-29 22:39:31 +0000] [30] [INFO] Booting worker with pid: 30\u001b[0m\n", - "\u001b[35m[2022-04-29 22:39:31 +0000] [26] [INFO] Booting worker with pid: 26\u001b[0m\n", - "\u001b[35m[2022-04-29 22:39:31 +0000] [31] [INFO] Booting worker with pid: 31\u001b[0m\n", - "\u001b[35m[2022-04-29 22:39:31 +0000] [30] [INFO] Booting worker with pid: 30\u001b[0m\n", - "\u001b[34m[2022-04-29:22:39:37:INFO] No GPUs detected (normal if no gpus installed)\u001b[0m\n", - "\u001b[34m169.254.255.130 - - [29/Apr/2022:22:39:37 +0000] \"GET /ping HTTP/1.1\" 200 0 \"-\" \"Go-http-client/1.1\"\u001b[0m\n", - "\u001b[35m[2022-04-29:22:39:37:INFO] No GPUs detected (normal if no gpus installed)\u001b[0m\n", - "\u001b[35m169.254.255.130 - - [29/Apr/2022:22:39:37 +0000] \"GET /ping HTTP/1.1\" 200 0 \"-\" \"Go-http-client/1.1\"\u001b[0m\n", - "\u001b[34m169.254.255.130 - - [29/Apr/2022:22:39:37 +0000] \"GET /execution-parameters HTTP/1.1\" 200 84 \"-\" \"Go-http-client/1.1\"\u001b[0m\n", - "\u001b[34m[2022-04-29:22:39:37:INFO] Determined delimiter of CSV input is ','\u001b[0m\n", - "\u001b[34m169.254.255.130 - - [29/Apr/2022:22:39:37 +0000] \"POST /invocations HTTP/1.1\" 200 2006 \"-\" \"Go-http-client/1.1\"\u001b[0m\n", - "\u001b[35m169.254.255.130 - - [29/Apr/2022:22:39:37 +0000] \"GET /execution-parameters HTTP/1.1\" 200 84 \"-\" \"Go-http-client/1.1\"\u001b[0m\n", - "\u001b[35m[2022-04-29:22:39:37:INFO] Determined delimiter of CSV input is ','\u001b[0m\n", - "\u001b[35m169.254.255.130 - - [29/Apr/2022:22:39:37 +0000] \"POST /invocations HTTP/1.1\" 200 2006 \"-\" \"Go-http-client/1.1\"\u001b[0m\n", - "\u001b[32m2022-04-29T22:39:37.156:[sagemaker logs]: MaxConcurrentTransforms=4, MaxPayloadInMB=6, BatchStrategy=MULTI_RECORD\u001b[0m\n", - "\u001b[34m[2022-04-29:22:39:30:INFO] No GPUs detected (normal if no gpus installed)\u001b[0m\n", - "\u001b[34m[2022-04-29:22:39:30:INFO] No GPUs detected (normal if no gpus installed)\u001b[0m\n", - "\u001b[34m[2022-04-29:22:39:30:INFO] nginx config: \u001b[0m\n", - "\u001b[34mworker_processes auto;\u001b[0m\n", - "\u001b[34mdaemon off;\u001b[0m\n", - "\u001b[34mpid /tmp/nginx.pid;\u001b[0m\n", - "\u001b[34merror_log /dev/stderr;\u001b[0m\n", - "\u001b[34mworker_rlimit_nofile 4096;\u001b[0m\n", - "\u001b[35m[2022-04-29:22:39:30:INFO] No GPUs detected (normal if no gpus installed)\u001b[0m\n", - "\u001b[35m[2022-04-29:22:39:30:INFO] No GPUs detected (normal if no gpus installed)\u001b[0m\n", - "\u001b[35m[2022-04-29:22:39:30:INFO] nginx config: \u001b[0m\n", - "\u001b[35mworker_processes auto;\u001b[0m\n", - "\u001b[35mdaemon off;\u001b[0m\n", - "\u001b[35mpid /tmp/nginx.pid;\u001b[0m\n", - "\u001b[35merror_log /dev/stderr;\u001b[0m\n", - "\u001b[35mworker_rlimit_nofile 4096;\u001b[0m\n", - "\u001b[34mevents {\n", - " worker_connections 2048;\u001b[0m\n", - "\u001b[34m}\u001b[0m\n", - "\u001b[34mhttp {\n", - " include /etc/nginx/mime.types;\n", - " default_type application/octet-stream;\n", - " access_log /dev/stdout combined;\n", - " upstream gunicorn {\n", - " server unix:/tmp/gunicorn.sock;\n", - " }\n", - " server {\n", - " listen 8080 deferred;\n", - " client_max_body_size 0;\n", - " keepalive_timeout 3;\n", - " location ~ ^/(ping|invocations|execution-parameters) {\n", - " proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n", - " proxy_set_header Host $http_host;\n", - " proxy_redirect off;\n", - " proxy_read_timeout 60s;\n", - " proxy_pass http://gunicorn;\n", - " }\n", - " location / {\n", - " return 404 \"{}\";\n", - " }\n", - " }\u001b[0m\n", - "\u001b[34m}\u001b[0m\n", - "\u001b[34m[2022-04-29 22:39:31 +0000] [18] [INFO] Starting gunicorn 19.10.0\u001b[0m\n", - "\u001b[34m[2022-04-29 22:39:31 +0000] [18] [INFO] Listening at: unix:/tmp/gunicorn.sock (18)\u001b[0m\n", - "\u001b[34m[2022-04-29 22:39:31 +0000] [18] [INFO] Using worker: gevent\u001b[0m\n", - "\u001b[34m[2022-04-29 22:39:31 +0000] [25] [INFO] Booting worker with pid: 25\u001b[0m\n", - "\u001b[35mevents {\n", - " worker_connections 2048;\u001b[0m\n", - "\u001b[35m}\u001b[0m\n", - "\u001b[35mhttp {\n", - " include /etc/nginx/mime.types;\n", - " default_type application/octet-stream;\n", - " access_log /dev/stdout combined;\n", - " upstream gunicorn {\n", - " server unix:/tmp/gunicorn.sock;\n", - " }\n", - " server {\n", - " listen 8080 deferred;\n", - " client_max_body_size 0;\n", - " keepalive_timeout 3;\n", - " location ~ ^/(ping|invocations|execution-parameters) {\n", - " proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n", - " proxy_set_header Host $http_host;\n", - " proxy_redirect off;\n", - " proxy_read_timeout 60s;\n", - " proxy_pass http://gunicorn;\n", - " }\n", - " location / {\n", - " return 404 \"{}\";\n", - " }\n", - " }\u001b[0m\n", - "\u001b[35m}\u001b[0m\n", - "\u001b[35m[2022-04-29 22:39:31 +0000] [18] [INFO] Starting gunicorn 19.10.0\u001b[0m\n", - "\u001b[35m[2022-04-29 22:39:31 +0000] [18] [INFO] Listening at: unix:/tmp/gunicorn.sock (18)\u001b[0m\n", - "\u001b[35m[2022-04-29 22:39:31 +0000] [18] [INFO] Using worker: gevent\u001b[0m\n", - "\u001b[35m[2022-04-29 22:39:31 +0000] [25] [INFO] Booting worker with pid: 25\u001b[0m\n", - "\u001b[34m[2022-04-29 22:39:31 +0000] [26] [INFO] Booting worker with pid: 26\u001b[0m\n", - "\u001b[34m[2022-04-29 22:39:31 +0000] [31] [INFO] Booting worker with pid: 31\u001b[0m\n", - "\u001b[34m[2022-04-29 22:39:31 +0000] [30] [INFO] Booting worker with pid: 30\u001b[0m\n", - "\u001b[35m[2022-04-29 22:39:31 +0000] [26] [INFO] Booting worker with pid: 26\u001b[0m\n", - "\u001b[35m[2022-04-29 22:39:31 +0000] [31] [INFO] Booting worker with pid: 31\u001b[0m\n", - "\u001b[35m[2022-04-29 22:39:31 +0000] [30] [INFO] Booting worker with pid: 30\u001b[0m\n", - "\u001b[34m[2022-04-29:22:39:37:INFO] No GPUs detected (normal if no gpus installed)\u001b[0m\n", - "\u001b[34m169.254.255.130 - - [29/Apr/2022:22:39:37 +0000] \"GET /ping HTTP/1.1\" 200 0 \"-\" \"Go-http-client/1.1\"\u001b[0m\n", - "\u001b[35m[2022-04-29:22:39:37:INFO] No GPUs detected (normal if no gpus installed)\u001b[0m\n", - "\u001b[35m169.254.255.130 - - [29/Apr/2022:22:39:37 +0000] \"GET /ping HTTP/1.1\" 200 0 \"-\" \"Go-http-client/1.1\"\u001b[0m\n", - "\u001b[34m169.254.255.130 - - [29/Apr/2022:22:39:37 +0000] \"GET /execution-parameters HTTP/1.1\" 200 84 \"-\" \"Go-http-client/1.1\"\u001b[0m\n", - "\u001b[34m[2022-04-29:22:39:37:INFO] Determined delimiter of CSV input is ','\u001b[0m\n", - "\u001b[34m169.254.255.130 - - [29/Apr/2022:22:39:37 +0000] \"POST /invocations HTTP/1.1\" 200 2006 \"-\" \"Go-http-client/1.1\"\u001b[0m\n", - "\u001b[35m169.254.255.130 - - [29/Apr/2022:22:39:37 +0000] \"GET /execution-parameters HTTP/1.1\" 200 84 \"-\" \"Go-http-client/1.1\"\u001b[0m\n", - "\u001b[35m[2022-04-29:22:39:37:INFO] Determined delimiter of CSV input is ','\u001b[0m\n", - "\u001b[35m169.254.255.130 - - [29/Apr/2022:22:39:37 +0000] \"POST /invocations HTTP/1.1\" 200 2006 \"-\" \"Go-http-client/1.1\"\u001b[0m\n", - "\u001b[32m2022-04-29T22:39:37.156:[sagemaker logs]: MaxConcurrentTransforms=4, MaxPayloadInMB=6, BatchStrategy=MULTI_RECORD\u001b[0m\n" - ] - } - ], + "outputs": [], "source": [ "transformer = best_model.transformer(\n", " instance_count=1, instance_type=\"ml.m4.xlarge\", output_path=batch_output\n", @@ -1277,7 +726,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -1286,7 +735,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -1308,129 +757,9 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
user_churnedpredicted_resultspredicted_binary
00.00.0690900
10.00.0267260
20.00.0276860
30.00.0806750
40.00.0174450
............
971.00.9866431
981.00.9644941
991.00.1064480
1001.00.9756531
1011.00.9897801
\n", - "

102 rows × 3 columns

\n", - "
" - ], - "text/plain": [ - " user_churned predicted_results predicted_binary\n", - "0 0.0 0.069090 0\n", - "1 0.0 0.026726 0\n", - "2 0.0 0.027686 0\n", - "3 0.0 0.080675 0\n", - "4 0.0 0.017445 0\n", - ".. ... ... ...\n", - "97 1.0 0.986643 1\n", - "98 1.0 0.964494 1\n", - "99 1.0 0.106448 0\n", - "100 1.0 0.975653 1\n", - "101 1.0 0.989780 1\n", - "\n", - "[102 rows x 3 columns]" - ] - }, - "execution_count": 28, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "test_data[\"predicted_results\"] = pd.to_numeric(results)\n", "# define a threshold to convert probability to class, you can set as 0.5 by default\n", @@ -1447,20 +776,9 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Test Evaluation: \n", - "Average F1 Score: 0.8861607142857144\n", - "Precision Score: 0.9310344827586207\n", - "Recall Score: 0.7714285714285715\n" - ] - } - ], + "outputs": [], "source": [ "from sklearn import metrics\n", "\n", @@ -1492,18 +810,9 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "INFO:sagemaker.image_uris:Defaulting to the only supported framework/algorithm version: 1.0.\n", - "INFO:sagemaker.image_uris:Ignoring unnecessary instance type: None.\n" - ] - } - ], + "outputs": [], "source": [ "from sagemaker import clarify\n", "\n", @@ -1514,7 +823,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -1527,7 +836,7 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -1538,38 +847,18 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'predicted_binary', 'predicted_results', 'user_churned'}" - ] - }, - "execution_count": 33, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "set(test_data.columns) - set(test_set.columns)" ] }, { "cell_type": "code", - "execution_count": 34, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "WARNING:sagemaker.deprecations:DataConfig will be deprecated on 15 Mar 2022.s3_data_distribution_type parameter will no longer be supported. Everything else will remain as is in sagemaker>=2.\n", - "See: https://sagemaker.readthedocs.io/en/stable/v2.html for details.\n" - ] - } - ], + "outputs": [], "source": [ "shap_config = clarify.SHAPConfig(\n", " baseline=[test_set.iloc[0].values.tolist()], num_samples=100, agg_method=\"mean_abs\"\n", @@ -1595,151 +884,11 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": null, "metadata": { "scrolled": true }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "INFO:sagemaker:Creating processing-job with name Clarify-Explainability-2022-04-29-22-39-55-756\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "Job Name: Clarify-Explainability-2022-04-29-22-39-55-756\n", - "Inputs: [{'InputName': 'dataset', 'AppManaged': False, 'S3Input': {'S3Uri': 's3://sagemaker-us-west-2-688520471316/music-streaming/train/train.csv', 'LocalPath': '/opt/ml/processing/input/data', 'S3DataType': 'S3Prefix', 'S3InputMode': 'File', 'S3DataDistributionType': 'FullyReplicated', 'S3CompressionType': 'None'}}, {'InputName': 'analysis_config', 'AppManaged': False, 'S3Input': {'S3Uri': 's3://sagemaker-us-west-2-688520471316/music-streaming/clarify-explainability/analysis_config.json', 'LocalPath': '/opt/ml/processing/input/config', 'S3DataType': 'S3Prefix', 'S3InputMode': 'File', 'S3DataDistributionType': 'FullyReplicated', 'S3CompressionType': 'None'}}]\n", - "Outputs: [{'OutputName': 'analysis_result', 'AppManaged': False, 'S3Output': {'S3Uri': 's3://sagemaker-us-west-2-688520471316/music-streaming/clarify-explainability', 'LocalPath': '/opt/ml/processing/output', 'S3UploadMode': 'EndOfJob'}}]\n", - ".....................................\u001b[34m2022-04-29 22:45:55,125 logging.conf not found when configuring logging, using default logging configuration.\u001b[0m\n", - "\u001b[34m2022-04-29 22:45:55,125 Starting SageMaker Clarify Processing job\u001b[0m\n", - "\u001b[34m2022-04-29 22:45:55,126 Analysis config path: /opt/ml/processing/input/config/analysis_config.json\u001b[0m\n", - "\u001b[34m2022-04-29 22:45:55,126 Analysis result path: /opt/ml/processing/output\u001b[0m\n", - "\u001b[34m2022-04-29 22:45:55,127 This host is algo-1.\u001b[0m\n", - "\u001b[34m2022-04-29 22:45:55,127 This host is the leader.\u001b[0m\n", - "\u001b[34m2022-04-29 22:45:55,127 Number of hosts in the cluster is 1.\u001b[0m\n", - "\u001b[34m2022-04-29 22:45:55,287 Running Python / Pandas based analyzer.\u001b[0m\n", - "\u001b[34m2022-04-29 22:45:55,288 Dataset type: text/csv uri: /opt/ml/processing/input/data\u001b[0m\n", - "\u001b[34m2022-04-29 22:45:55,296 Loading dataset...\u001b[0m\n", - "\u001b[34m2022-04-29 22:45:55,314 Loaded dataset. Dataset info:\u001b[0m\n", - "\u001b[34m\u001b[0m\n", - "\u001b[34mRangeIndex: 708 entries, 0 to 707\u001b[0m\n", - "\u001b[34mData columns (total 25 columns):\n", - " # Column Non-Null Count Dtype \u001b[0m\n", - "\u001b[34m--- ------ -------------- ----- \n", - " 0 average_events_weekend 708 non-null float64\n", - " 1 average_events_weekday 708 non-null float64\n", - " 2 num_songs_played_7d 708 non-null int64 \n", - " 3 num_ads_7d 708 non-null int64 \n", - " 4 num_error_7d 708 non-null int64 \n", - " 5 num_songs_played_30d 708 non-null int64 \n", - " 6 num_songs_played_90d 708 non-null int64 \n", - " 7 num_sessions 708 non-null int64 \n", - " 8 avg_time_per_session 708 non-null float64\n", - " 9 avg_events_per_session 708 non-null float64\n", - " 10 avg_gap_between_session 708 non-null float64\n", - " 11 num_events 708 non-null int64 \n", - " 12 num_songs 708 non-null int64 \n", - " 13 num_artists 708 non-null int64 \n", - " 14 num_thumbs_down 708 non-null int64 \n", - " 15 num_thumbs_up 708 non-null int64 \n", - " 16 num_add_to_playlist 708 non-null int64 \n", - " 17 num_ads 708 non-null int64 \n", - " 18 num_add_friend 708 non-null int64 \n", - " 19 num_downgrade 708 non-null int64 \n", - " 20 num_upgrade 708 non-null int64 \n", - " 21 num_error 708 non-null int64 \n", - " 22 percentage_ad 708 non-null float64\n", - " 23 days_since_active 708 non-null int64 \n", - " 24 repeats_ratio 708 non-null float64\u001b[0m\n", - "\u001b[34mdtypes: float64(7), int64(18)\u001b[0m\n", - "\u001b[34mmemory usage: 138.4 KB\u001b[0m\n", - "\u001b[34m2022-04-29 22:45:55,482 Spinning up shadow endpoint\u001b[0m\n", - "\u001b[34m2022-04-29 22:45:55,483 Creating endpoint-config with name sm-clarify-config-1651272355-5750\u001b[0m\n", - "\u001b[34m2022-04-29 22:45:55,567 Creating endpoint: 'sm-clarify-sagemaker-xgboost-2022-04-29-22-34-0-1651272355-86d1'\u001b[0m\n", - "\u001b[34m2022-04-29 22:45:55,831 Using endpoint name: sm-clarify-sagemaker-xgboost-2022-04-29-22-34-0-1651272355-86d1\u001b[0m\n", - "\u001b[34m2022-04-29 22:45:55,832 Waiting for endpoint ...\u001b[0m\n", - "\u001b[34m2022-04-29 22:45:55,832 Checking endpoint status:\u001b[0m\n", - "\u001b[34mLegend:\u001b[0m\n", - "\u001b[34m(OutOfService: x, Creating: -, Updating: -, InService: !, RollingBack: <, Deleting: o, Failed: *)\u001b[0m\n", - "\u001b[34m2022-04-29 22:49:56,323 Endpoint is in service after 240 seconds\u001b[0m\n", - "\u001b[34m2022-04-29 22:49:56,323 Endpoint ready.\u001b[0m\n", - "\u001b[34m2022-04-29 22:49:56,326 SHAP n_samples 100\u001b[0m\n", - "\u001b[34m2022-04-29 22:49:56,480 =====================================================\u001b[0m\n", - "\u001b[34m2022-04-29 22:49:56,480 Shap analyzer: explaining 708 rows, 25 columns...\u001b[0m\n", - "\u001b[34m2022-04-29 22:49:56,480 =====================================================\n", - " 0% (0 of 708) | | Elapsed Time: 0:00:00 ETA: --:--:--\u001b[0m\n", - "\u001b[34m100% (708 of 708) |######################| Elapsed Time: 0:00:16 Time: 0:00:16\u001b[0m\n", - "\u001b[34m2022-04-29 22:50:13,201 getting explanations took 16.72 seconds.\u001b[0m\n", - "\u001b[34m2022-04-29 22:50:13,201 ===================================================\u001b[0m\n", - "\u001b[34m2022-04-29 22:50:13,271 converting explanations to tabular took 0.07 seconds.\u001b[0m\n", - "\u001b[34m2022-04-29 22:50:13,271 ===================================================\u001b[0m\n", - "\u001b[34m2022-04-29 22:50:13,274 Wrote baseline used to compute explanations to: /opt/ml/processing/output/explanations_shap/baseline.csv\u001b[0m\n", - "\u001b[34m2022-04-29 22:50:13,298 Wrote 708 local explanations to: /opt/ml/processing/output/explanations_shap/out.csv\u001b[0m\n", - "\u001b[34m2022-04-29 22:50:13,299 writing local explanations took 0.03 seconds.\u001b[0m\n", - "\u001b[34m2022-04-29 22:50:13,299 ===================================================\u001b[0m\n", - "\u001b[34m2022-04-29 22:50:13,301 aggregating local explanations took 0.00 seconds.\u001b[0m\n", - "\u001b[34m2022-04-29 22:50:13,301 ===================================================\u001b[0m\n", - "\u001b[34m2022-04-29 22:50:13,301 Shap analysis finished.\u001b[0m\n", - "\u001b[34m2022-04-29 22:50:13,302 Stop using endpoint: sm-clarify-sagemaker-xgboost-2022-04-29-22-34-0-1651272355-86d1\u001b[0m\n", - "\u001b[34m2022-04-29 22:50:13,302 Deleting endpoint configuration with name: sm-clarify-config-1651272355-5750\u001b[0m\n", - "\u001b[34m2022-04-29 22:50:13,407 Deleting endpoint with name: sm-clarify-sagemaker-xgboost-2022-04-29-22-34-0-1651272355-86d1\u001b[0m\n", - "\u001b[34m2022-04-29 22:50:13,528 Model endpoint delivered 41.81825 requests per second and a total of 710 requests over 17 seconds\u001b[0m\n", - "\u001b[34m2022-04-29 22:50:17,557 Stop using endpoint: None\u001b[0m\n", - "\n", - "\u001b[34m2022-04-29 22:51:24,009 jupyter nbconvert --to html --output /opt/ml/processing/output/report.html /opt/ml/processing/output/report.ipynb --template sagemaker-xai\u001b[0m\n", - "\u001b[34m[NbConvertApp] Converting notebook /opt/ml/processing/output/report.ipynb to html\u001b[0m\n", - "\u001b[34m[NbConvertApp] Writing 534585 bytes to /opt/ml/processing/output/report.html\u001b[0m\n", - "\u001b[34m2022-04-29 22:51:25,148 HTML report '/opt/ml/processing/output/report.html' generated successfully.\u001b[0m\n", - "\u001b[34m2022-04-29 22:51:25,148 wkhtmltopdf -q /opt/ml/processing/output/report.html /opt/ml/processing/output/report.pdf\u001b[0m\n", - "\u001b[34m2022-04-29 22:51:25,809 PDF report '/opt/ml/processing/output/report.pdf' generated successfully.\u001b[0m\n", - "\u001b[34m2022-04-29 22:51:25,810 Collected analyses: \u001b[0m\n", - "\u001b[34m{\n", - " \"version\": \"1.0\",\n", - " \"explanations\": {\n", - " \"kernel_shap\": {\n", - " \"label0\": {\n", - " \"global_shap_values\": {\n", - " \"average_events_weekend\": 0.0021496041898171037,\n", - " \"average_events_weekday\": 0.016587702435219898,\n", - " \"num_songs_played_7d\": 0.0017402863783950045,\n", - " \"num_ads_7d\": 0.00868551809925556,\n", - " \"num_error_7d\": 0.0027734162552485904,\n", - " \"num_songs_played_30d\": 0.0011722262443900576,\n", - " \"num_songs_played_90d\": 0.0012286071144514484,\n", - " \"num_sessions\": 0.0011579425530513692,\n", - " \"avg_time_per_session\": 0.010029593923214982,\n", - " \"avg_events_per_session\": 0.003897350916100629,\n", - " \"avg_gap_between_session\": 0.0448815105461223,\n", - " \"num_events\": 0.0010721354650824332,\n", - " \"num_songs\": 0.0010824064811915818,\n", - " \"num_artists\": 0.0011435948826397841,\n", - " \"num_thumbs_down\": 0.0022934530809684047,\n", - " \"num_thumbs_up\": 0.0010034211293590313,\n", - " \"num_add_to_playlist\": 0.001163952744967679,\n", - " \"num_ads\": 0.0010789208860291896,\n", - " \"num_add_friend\": 0.005465492600682657,\n", - " \"num_downgrade\": 0.001048377920399092,\n", - " \"num_upgrade\": 0.002673292801161779,\n", - " \"num_error\": 0.0011517907319294842,\n", - " \"percentage_ad\": 0.02861671713801823,\n", - " \"days_since_active\": 0.29080317686448776,\n", - " \"repeats_ratio\": 0.001010326307543725\n", - " },\n", - " \"expected_value\": 0.06909029185771942\n", - " }\n", - " }\n", - " }\u001b[0m\n", - "\u001b[34m}\u001b[0m\n", - "\u001b[34m2022-04-29 22:51:25,810 exit_message: Completed: SageMaker XAI Analyzer ran successfully\u001b[0m\n", - "\u001b[34m----!\u001b[0m\n" - ] - } - ], + "outputs": [], "source": [ "clarify_processor.run_explainability(\n", " data_config=explainability_data_config,\n", @@ -1769,7 +918,7 @@ }, { "cell_type": "code", - "execution_count": 39, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -1793,7 +942,7 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ From 2cbe3579c28b8a547d29e257a2e1ddef89a5e9eb Mon Sep 17 00:00:00 2001 From: atqy Date: Mon, 2 May 2022 16:18:21 +0000 Subject: [PATCH 11/27] notebook edits --- .../0_cust_churn_overview_dw.ipynb | 1454 +++++++++++++++-- 1 file changed, 1353 insertions(+), 101 deletions(-) diff --git a/use-cases/customer_churn/0_cust_churn_overview_dw.ipynb b/use-cases/customer_churn/0_cust_churn_overview_dw.ipynb index a00ba25e52..b7c984fb4d 100644 --- a/use-cases/customer_churn/0_cust_churn_overview_dw.ipynb +++ b/use-cases/customer_churn/0_cust_churn_overview_dw.ipynb @@ -160,9 +160,52 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "/opt/conda/lib/python3.7/site-packages/secretstorage/dhcrypto.py:16: CryptographyDeprecationWarning: int_from_bytes is deprecated, use int.from_bytes instead\n", + " from cryptography.utils import int_from_bytes\n", + "/opt/conda/lib/python3.7/site-packages/secretstorage/util.py:25: CryptographyDeprecationWarning: int_from_bytes is deprecated, use int.from_bytes instead\n", + " from cryptography.utils import int_from_bytes\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\u001b[33m\n", + "\u001b[0m/opt/conda/lib/python3.7/site-packages/secretstorage/dhcrypto.py:16: CryptographyDeprecationWarning: int_from_bytes is deprecated, use int.from_bytes instead\n", + " from cryptography.utils import int_from_bytes\n", + "/opt/conda/lib/python3.7/site-packages/secretstorage/util.py:25: CryptographyDeprecationWarning: int_from_bytes is deprecated, use int.from_bytes instead\n", + " from cryptography.utils import int_from_bytes\n", + "Requirement already satisfied: sagemaker in /opt/conda/lib/python3.7/site-packages (2.88.1)\n", + "Requirement already satisfied: boto3 in /opt/conda/lib/python3.7/site-packages (1.22.4)\n", + "Requirement already satisfied: pandas in /opt/conda/lib/python3.7/site-packages (from sagemaker) (1.0.1)\n", + "Requirement already satisfied: google-pasta in /opt/conda/lib/python3.7/site-packages (from sagemaker) (0.2.0)\n", + "Requirement already satisfied: attrs==20.3.0 in /opt/conda/lib/python3.7/site-packages (from sagemaker) (20.3.0)\n", + "Requirement already satisfied: smdebug-rulesconfig==1.0.1 in /opt/conda/lib/python3.7/site-packages (from sagemaker) (1.0.1)\n", + "Requirement already satisfied: importlib-metadata>=1.4.0 in /opt/conda/lib/python3.7/site-packages (from sagemaker) (1.5.0)\n", + "Requirement already satisfied: packaging>=20.0 in /opt/conda/lib/python3.7/site-packages (from sagemaker) (20.1)\n", + "Requirement already satisfied: pathos in /opt/conda/lib/python3.7/site-packages (from sagemaker) (0.2.8)\n", + "Requirement already satisfied: protobuf>=3.1 in /opt/conda/lib/python3.7/site-packages (from sagemaker) (3.20.0)\n", + "Requirement already satisfied: numpy>=1.9.0 in /opt/conda/lib/python3.7/site-packages (from sagemaker) (1.21.5)\n", + "Requirement already satisfied: protobuf3-to-dict>=0.1.5 in /opt/conda/lib/python3.7/site-packages (from sagemaker) (0.1.5)\n", + "Requirement already satisfied: s3transfer<0.6.0,>=0.5.0 in /opt/conda/lib/python3.7/site-packages (from boto3) (0.5.2)\n", + "Requirement already satisfied: botocore<1.26.0,>=1.25.4 in /opt/conda/lib/python3.7/site-packages (from boto3) (1.25.4)\n", + "Requirement already satisfied: jmespath<2.0.0,>=0.7.1 in /opt/conda/lib/python3.7/site-packages (from boto3) (1.0.0)\n", + "Requirement already satisfied: urllib3<1.27,>=1.25.4 in /opt/conda/lib/python3.7/site-packages (from botocore<1.26.0,>=1.25.4->boto3) (1.26.9)\n", + "Requirement already satisfied: python-dateutil<3.0.0,>=2.1 in /opt/conda/lib/python3.7/site-packages (from botocore<1.26.0,>=1.25.4->boto3) (2.8.1)\n", + "Requirement already satisfied: zipp>=0.5 in /opt/conda/lib/python3.7/site-packages (from importlib-metadata>=1.4.0->sagemaker) (2.2.0)\n", + "Requirement already satisfied: pyparsing>=2.0.2 in /opt/conda/lib/python3.7/site-packages (from packaging>=20.0->sagemaker) (2.4.6)\n", + "Requirement already satisfied: six in /opt/conda/lib/python3.7/site-packages (from packaging>=20.0->sagemaker) (1.14.0)\n", + "Requirement already satisfied: pytz>=2017.2 in /opt/conda/lib/python3.7/site-packages (from pandas->sagemaker) (2019.3)\n", + "Requirement already satisfied: dill>=0.3.4 in /opt/conda/lib/python3.7/site-packages (from pathos->sagemaker) (0.3.4)\n", + "Requirement already satisfied: ppft>=1.6.6.4 in /opt/conda/lib/python3.7/site-packages (from pathos->sagemaker) (1.6.6.4)\n", + "Requirement already satisfied: multiprocess>=0.70.12 in /opt/conda/lib/python3.7/site-packages (from pathos->sagemaker) (0.70.12.2)\n", + "Requirement already satisfied: pox>=0.3.0 in /opt/conda/lib/python3.7/site-packages (from pathos->sagemaker) (0.3.0)\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\u001b[33m\n", + "\u001b[0m" + ] + } + ], "source": [ "!pip install -q 's3fs==0.4.2' 'sagemaker-experiments'\n", "!pip install --upgrade sagemaker boto3\n", @@ -171,7 +214,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ @@ -180,7 +223,8 @@ "import pandas as pd\n", "import glob\n", "import s3fs\n", - "import boto3" + "import boto3\n", + "import numpy as np" ] }, { @@ -193,7 +237,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ @@ -221,9 +265,17 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "download: s3://sagemaker-sample-files/datasets/tabular/customer-churn/customer-churn-data.zip to data/raw/customer-churn-data.zip\n" + ] + } + ], "source": [ "##### Alternative: copy data from a public S3 bucket to your own bucket\n", "##### data file should include full_data.csv and sample.json\n", @@ -233,18 +285,53 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Archive: ./data/raw/customer-churn-data.zip\n", + " inflating: ./data/data_wrangler_output.csv \n", + " inflating: ./data/full_feature_data.csv \n", + " inflating: ./data/sample.csv \n", + " extracting: ./data/sample.zip \n", + " extracting: ./data/simu-1.zip \n", + " extracting: ./data/simu-2.zip \n", + " extracting: ./data/simu-3.zip \n", + " extracting: ./data/simu-4.zip \n", + " inflating: ./data/test.csv \n", + " inflating: ./data/test_updated.csv \n", + " inflating: ./data/train_updated.csv \n", + " inflating: ./data/validation_updated.csv \n" + ] + } + ], "source": [ "!unzip -o ./data/raw/customer-churn-data.zip -d ./data" ] }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Archive: data/simu-1.zip\n", + " inflating: data/raw/simu-1.json \n", + "Archive: data/simu-2.zip\n", + " inflating: data/raw/simu-2.json \n", + "Archive: data/simu-3.zip\n", + " inflating: data/raw/simu-3.json \n", + "Archive: data/simu-4.zip\n", + " inflating: data/raw/simu-4.json \n" + ] + } + ], "source": [ "# unzip the partitioned data files into the same folder\n", "!unzip -o data/simu-1.zip -d data/raw\n", @@ -255,7 +342,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "metadata": {}, "outputs": [], "source": [ @@ -264,18 +351,39 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Archive: data/sample.zip\n", + " inflating: data/raw/sample.json \n" + ] + } + ], "source": [ "!unzip -o data/sample.zip -d data/raw" ] }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "upload: data/raw/simu-1.json to s3://sagemaker-us-west-2-688520471316/music-streaming/data/json/simu-1.json\n", + "upload: data/raw/sample.json to s3://sagemaker-us-west-2-688520471316/music-streaming/data/json/sample.json\n", + "upload: data/raw/simu-2.json to s3://sagemaker-us-west-2-688520471316/music-streaming/data/json/simu-2.json\n", + "upload: data/raw/simu-4.json to s3://sagemaker-us-west-2-688520471316/music-streaming/data/json/simu-4.json\n", + "upload: data/raw/simu-3.json to s3://sagemaker-us-west-2-688520471316/music-streaming/data/json/simu-3.json\n" + ] + } + ], "source": [ "!aws s3 cp ./data/raw s3://$bucket/$prefix/data/json/ --recursive" ] @@ -293,7 +401,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, "metadata": {}, "outputs": [], "source": [ @@ -310,7 +418,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "metadata": {}, "outputs": [], "source": [ @@ -322,9 +430,124 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
tsuserIdsessionIdpageauthmethodstatuslevelitemInSessionlocationuserAgentlastNamefirstNameregistrationgenderartistsonglength
0159214626773112065118NextSongLogged InPUT200paid0Richmond, VA\"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebK...DavisBristol1.591971e+12MPeter ToshWanted Dread And Alive (2002 Digital Remaster)267.85914
1159214626873112065118Thumbs DownLogged InPUT307paid1Richmond, VA\"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebK...DavisBristol1.591971e+12MNaNNaNNaN
\n", + "
" + ], + "text/plain": [ + " ts userId sessionId page auth method status \\\n", + "0 1592146267731 12065 118 NextSong Logged In PUT 200 \n", + "1 1592146268731 12065 118 Thumbs Down Logged In PUT 307 \n", + "\n", + " level itemInSession location \\\n", + "0 paid 0 Richmond, VA \n", + "1 paid 1 Richmond, VA \n", + "\n", + " userAgent lastName firstName \\\n", + "0 \"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebK... Davis Bristol \n", + "1 \"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebK... Davis Bristol \n", + "\n", + " registration gender artist \\\n", + "0 1.591971e+12 M Peter Tosh \n", + "1 1.591971e+12 M NaN \n", + "\n", + " song length \n", + "0 Wanted Dread And Alive (2002 Digital Remaster) 267.85914 \n", + "1 NaN NaN " + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "sample.head(2)" ] @@ -340,7 +563,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 13, "metadata": {}, "outputs": [], "source": [ @@ -359,9 +582,41 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "percentage of the value missing in each column is: \n" + ] + }, + { + "data": { + "text/plain": [ + "ts 0.000000\n", + "userId 0.000000\n", + "sessionId 0.000000\n", + "page 0.000000\n", + "auth 0.000000\n", + "level 0.000000\n", + "itemInSession 0.000000\n", + "location 0.025447\n", + "userAgent 0.025447\n", + "registration 0.025447\n", + "gender 0.025447\n", + "artist 0.210330\n", + "song 0.210330\n", + "length 0.210330\n", + "dtype: float64" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "print(\"percentage of the value missing in each column is: \")\n", "sample.isnull().sum() / len(sample)" @@ -369,7 +624,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 15, "metadata": {}, "outputs": [], "source": [ @@ -388,9 +643,28 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The unique values in column page are: ['NextSong' 'Thumbs Down' 'Home' 'Settings' 'Thumbs Up' 'Add to Playlist'\n", + " 'Roll Advert' 'Save Settings' 'Help' 'Logout' 'Add Friend' 'Downgrade'\n", + " 'About' 'Upgrade' 'Error' 'Submit Upgrade' 'Submit Downgrade' 'Cancel'\n", + " 'Cancellation Confirmation']\n", + "The unique values in column auth are: ['Logged In' 'Cancelled']\n", + "The unique values in column level are: ['paid' 'free']\n", + "The unique values in column gender are: ['M' 'F']\n", + "There are 72 unique values in column location\n", + "There are 37 unique values in column userAgent\n", + "There are 16207 unique values in column artist\n", + "There are 51447 unique values in column song\n", + "There are 101 unique values in column userId\n" + ] + } + ], "source": [ "cat_columns = [\"page\", \"auth\", \"level\", \"gender\"]\n", "cat_columns_long = [\"location\", \"userAgent\", \"artist\", \"song\", \"userId\"]\n", @@ -422,7 +696,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 17, "metadata": {}, "outputs": [], "source": [ @@ -439,7 +713,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 18, "metadata": {}, "outputs": [], "source": [ @@ -466,9 +740,17 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "There are 12.87% of users churned in this dataset\n" + ] + } + ], "source": [ "print(\n", " \"There are {:.2f}% of users churned in this dataset\".format(\n", @@ -490,7 +772,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 20, "metadata": {}, "outputs": [], "source": [ @@ -544,9 +826,20 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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c+Jmj9ciESqX1q1bJwAkISFBndagQQMZM2aMXLp0SfR6vWzcuFGdV6tWLWnWrJlNH4MGDRJXV1d56aWXJCkpSZYvXy533XWX+Pv7y5kzZ9R28fHxoiiKREVFybp16+SLL76Qbt26iV6vl+TkZLXda6+9JgDk/PnzIiKybds2qVixovTp00euXLli127YsGGSlJQkH330kQQGBoqfn5/cf//9arvMzEwZNGiQLFu2TLZv3y6JiYkSFxcnLi4usnz5crXdmjVrBIBNLSIi69evFwCyefPmQrflK6+8IgBk//79NtOff/55ASC///57ocvPmzdPAMiDDz4oX375pVy6dKnQ9kWRnZ0t7u7u0qdPH3XapEmTJCQkREREunfvLoMGDVLnvfTSSwJAjh49qk5bv3696HQ66dKli6xevVo2bdokjz76qCiKImvXrlXbHTlyRKpUqSL16tWTxYsXy5dffimvvfaa6PV6GTVqlNru+++/FwCyYMECERE5ffq0NG/eXIKDg+XAgQMl7q9mzZrSu3dv+fzzz2X16tUSEhIiAQEBkpWVpbZ97733BIA8/vjjsmnTJomPj5fg4GAJDAyU8PBwtd3cuXNFURQZPXq0bN26VbZs2SKzZ8+W0aNH39Tr4cjGjRsFgEyePFlERHr06CGurq42x47FYpHg4GDp2bOn3fIdO3aUevXqqY93794tJpNJmjdvLitWrJDNmzfLk08+KQBk9uzZarv//e9/AkACAwMlJiZGNm7cKOvWrZMrV67Inj17ZOTIkfLpp59KSkqKrF+/Xvr27Stubm7y/fffq32cPXtW/P39xd/fXxYuXChJSUkybNgwqVmzpgCQL774Qm27Zs0a0el0EhkZKWvWrJGNGzfKww8/LC4uLvL5558Xuo3MZrO0aNFCvL29ZerUqbJ161aZM2eO+Pj4SIsWLSQnJ0dERBYtWiQAbGoUEdmxY4fN+5zFYpE+ffqI0WiUsWPHypYtW2TJkiUSHBwstWvXlgsXLqjLhoaGir+/v9x1112yePFi2bJli/Tv318AqO+NmZmZ6r41ffp02b17t+zevVv++usvuXr1qvj7+0vDhg1lxYoVkpqaKv/73//kmWeekb179xb6vLt37y4VKlSQwMBAmTFjhmzZskVeeuklURRFYmJi1HbFfT5+fn4SHBws8+fPl+TkZNm9e3eBNbzz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6svaB4Byd8Yg//PJ9zTLrPw1lpfXwvY1KFsvQmkeR7eyTyIqfQxx5BQiglmzZhU4fdq0aVAUpcj9ZWdnq7/37t27VGosi65aAKOzi7iBq5b/fi+Pr0V2djbc3d2dXQaA0j+OblWfRHRr8HIqOcXx48eRlpaGvLw8m+l5eXlIS0vD8ePHnVQZkXbciuOIxyaRdvBMHDlFUFAQmjVrhn379tl8WOh0OkRERCAoKKhY/RkMBvX3devWwWgs6+eris5sNqtntNw08LUrf43l5bXI/xrk39ecrbSPo1vVJxHdGgxx5BSKomDkyJGIiYlxOL24l2vytzcajTCZTKVSZ1mjhatY+Wssj69FWbqUWNrH0a3qk4huDQ18r6fyqnr16ujXr5/6oaAoCvr164fAwEAnV0akHbfiOOKxSaQNDHHkVNHR0fDx8QEA+Pr6ol+/fk6uiEh7bsVxxGOTqOxjiCOnMhqNiIuLg5+fH55//vlyMX6K6Ha7FccRj02iso9j4sjpWrVqhVatWjm7DCJNuxXHEY9NorKNZ+KIiIiINIghjoiIiEiDGOKIiIiINIghjoiIiEiDGOKIiIiINIghjoiIiEiDGOKIiIiINIghjoiIiEiDGOKIiIiINIghjoiIiEiDGOKIiIiINIghjoiIiEiDGOKIiIiINEjv7AKISoPRaERiYqL6O1Fp4v5FRGURQxyVC4qiwGQyObsMKqe4fxFRWcTLqUREREQaxBBHREREpEEMcUREREQaxBBHREREpEEMcUREREQaxBBHREREpEEMcUREREQaxBBHREREpEEMcUREREQaxBBHREREpEEMcUREREQaxBBHREREpEEMcUREREQapHd2AURUdNl5CgC5xetw/HvRl1dKrxgiIioQQxyRhjyzs/JtXp/PbV0fEREVHS+nEhEREWkQz8QRlXFGoxGJiYm3bX0iguzsbACAwWCAopT88qjRaCytsoiI6DoMcURlnKIoMJlMt3Wd7u7ut3V9RERUfLycSkRERKRBDHFEREREGsQQR0RERKRBDHFEREREGsQQR0RERKRBDHFEREREGsQQR0RERKRBDHFEREREGsQQR0RERKRBDHFEREREGsQQR0RERKRBDHFEREREGsQQR0RERKRBDHFEREREGsQQR0RERKRBDHFEREREGsQQR0RERKRBDHFEREREGsQQR0RERKRB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7qty5+/kzv2KqKbeffddk2et3alv377YunWrWduxNrt378aAAQPg5+dX4Y0S5sAijsjM7hzibmdnB51OZ8Fo7o0ZnmH5QNwZt7XmvirmeLAoPXwmTJhQ5VsOanN81FU71iY0NNSi96OyiCMiImqgnJ2d4ezsXG/aodqx0osiRERERA0bizgiIiIiK8QijoiIiMgKsYgjIiIiskIs4oiIiIisEIs4IiIiqjWDwYDy8nKz/RgMhnuKc/HixcrbEYKDg5GSklLHmbAcPmKEiIiIasVgMGD0/41BYcHvZtumvqkz1n/7jcnL7auzdu1aTJs2DYsXL0bv3r2xZMkSDBkyBBkZGfD29n6A0ZoHizgiIiKqFRFBYcHvKA56BlCZ4aKeGIH0L2v9YN0FCxbg2WefxXPPPQcAWLhwIX744Qd8/vnneO+99x5EpGbFy6lERER0b1Q2gI0Zfu6hUCwrK0NaWhrCwsJMpoeFhWHPnj11lQGLYhFHRERED53Lly/DYDDAzc3NZLqbmxsuXrxooajqFos4IiIiemj9+b3CIvLQvGuYRRwRERE9dFxcXKBWqyucdcvLy6twds5asYgjIiKih45Go0FwcDASExNNpicmJqJXr14WiqpucXQqERERPZSmT5+OyMhIdO3aFSEhIVi6dClycnIwYcIES4dWJ1jEERER0b0RI2A003buwdixY5Gfn4+5c+ciNzcXAQEB2LJlC3x8fOo4QMtgEUdkZnZ2dti6davymehecV8iS1GpVNA3dQbSvzTbNvVNne9pQMLEiRMxceLEBxCR5bGIIzIzlUoFnU5n6TDoIcB9iSxFrVZj/bff1Prhu/dDpVLV6m0NDQGLOCIiIqo1FlSWx9GpRERERFaIRRwRERGRFWIRR0RERGSFWMQRERERWSEWcURERERWiEUcERERkRViEUdERERkhVjEEREREVkhFnFERERUawaDAeXl5Wb7MRgMtYpv586dGDZsGDw9PaFSqfDdd989oExYDt/YQERERLViMBgwdsxoXP690GzbdHHWY+0362v8poiSkhIEBgbi73//O/72t7894Ogsg0UcERER1YqI4PLvhVjWPx/q2r+TvtYMAsTsQK3e1TpkyBAMGTLkAUZleSziiIiI6J6oVUAjc9yYZTTDNqwQ74kjIiIiskI8E0dEtVZqUAGQO76j0s/1za24iYge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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "import seaborn as sns\n", "import matplotlib.pyplot as plt\n", @@ -595,9 +888,20 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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kQELCwu9AiIiIiIiIiIyVaKTcA8PD/z4449a1x09ehReXl56B0VERERERERkikTPEx4WFobJkyfDysoKISEhAICHDx/ixIkTSExMRHR0tMGDJCIiIiIiIjIFopPwXr164c6dO4iNjcWGDRsAAOPGjYNMJkNkZCQCAgIMHiQRERERERGRKRCdhAPA6NGj0adPHxw9ehQpKSmwt7dH+/bt4erqauj4iIiIiIiIiEyGTkk4ANSsWRMDBgwwZCxEREREREREJk2nJFypVGLfvn04efIk0tPTYWdnh9atW6Nnz54wM9M5ryciIiIiIiIyaaIz5tTUVIwYMQIXLlyAmZkZ7OzskJ6ejm+//RZr1qzBqlWr4ODgYIxYiYiIiIiIiCo00VOUffbZZ7h58yYWLlyIc+fO4ZdffsG5c+ewYMEC3Lp1C5999pkx4iQiIiIiIiKq8ETXhB8+fBgTJkxA79691ctkMhmCgoKQkpKC2NhYgwZIREREREREZCpE14QLgoCGDRtqXefu7g5BEPQOioiIiIiIiMgUiU7C27Vrh2PHjmld9+uvv8LPz0/voIiIiIiIiIhMkejm6OHh4Rg7diyUSiWCgoLg6OiI5ORk7N69G/v370dsbCzS09PV5e3s7AwaMBEREREREVFFJToJ79u3LwBg7dq1iI+PVy/Pb4ber18/jfIXL17UIzwiIiIiIiIi0yE6CY+IiIBEIjFGLEREREREREQmTXQSPm7cOGPEQURERERERGTyRA/MRkRERERERES6YRJOREREREREVEqYhBMRERERERGVEibhRERERERERKWESTgRERERERFRKWESTkRERERERFRKRCfhx48fx759+9Svk5OT8f7778Pf3x8ffPABcnJyDBogERERERERkakQnYRHR0fj+vXr6tcLFizAmTNn4OPjgx9++AGrVq0yaIBEREREREREpkJ0En7r1i14enoCABQKBQ4cOIApU6YgNjYWkZGR2Lt3r8GDJCIiIiIiIjIFopPwrKws2NjYAAD+/vtvvHjxAl27dgUAeHt74+HDh4aNkIiIiIiIiMhEiE7Cq1evjlu3bgEAjh07BhcXF9SsWRMA8OzZM5iZmRk0QCIiIiIiIiJTITpj7tChAxYtWoRr165h+/bt6NOnj3rdjRs34OrqatAAiYiIiIiIiEyF6CR84sSJePDgAb755ht4e3tjzJgx6nV79uyBj4+PQQMkIiIiIiIiMhWik3AHBwesXr1a67r169fD0tJS76CIiIiIiIiITJHoPuFRUVG4e/eu1nUZGRmYPXu23kERERERERERmSLRSfj27duRlpamdV1aWhp27Nihd1BEREREREREpkh0El6UjIwMWFhYGHKXRERERERERCajRH3CT58+jZMnT6pff/vtt/j55581yuTk5ODgwYOoX7++YSMkIiIiIiIiMhElSsJPnjyJ2NhYAIBEIsG3336rtZyLiwv7hBMREREREREVokRJ+IgRIzB06FAIgoB27dph9erV8PT01ChjYWGBqlWrGiVIIiIiIiIiIlNQoiRcLpdDLpcDAA4ePAgnJyf2/SYiIiIiIiISSfQ84a6ursaIg4iIiIiIiMjkiU7C8/LyEBcXhz179uDBgwfIycnRWC+RSHDhwgWDBUhEREREaIXQjQAAIABJREFURERkKkQn4V9++SXi4+PRsWNHdOvWjc3SiYiIiIiIiEpIdBK+b98+REREYOzYscaIh4iIiIiIiMhkScVukJGRAV9fX2PEQkRERERERGTSRCfhrVq1wqVLl4wRCxEREREREZFJE52Ez5w5EwkJCdi/fz9yc3ONERMRERERERGRSRLdJzwkJAQKhQLjx4+HRCJRzx+eTyKR4LfffjNYgERERERERESmQnQS3qNHD0gkEmPEQkRERERERGTSRCfh8+bNM0YcRERERERERCZPdJ9wIiIiIiIiItKNTkn49evXMWnSJLRv3x5NmjTB33//DQCIjY3FiRMnDBogERERERERkakQnYRfvHgRb775Jk6dOgU/Pz8olUr1umfPnmHLli0GDZCIiIiIiIjIVIhOwhcuXAgPDw8cOHAA8+fPhyAI6nXe3t74888/DRogERERERERkakQnYSfPXsWI0aMgJWVVYFR0h0dHZGcnGyw4IiIiIiIiIhMiU59ws3NzbUuz8jIgIWFhV4BEREREREREZkq0Um4h4cHfvzxR63rjh49Ci8vL72DIiIiIiIiIjJFoucJDwsLw+TJk2FlZYWQkBAAwMOHD3HixAkkJiYiOjra4EESERERERERmQLRSXivXr1w584dxMbGYsOGDQCAcePGQSaTITIyEgEBAQYPkoiIiIiIiMgUiE7CAWD06NHo06cPjh49ipSUFNjb26N9+/ZwdXU1dHxEREREREREJkN0Eq5UKiGTyVCzZk0MGDDAGDERERERERERmSTRA7N16tQJ8+fPx7Vr14wRDxEREREREZHJEp2E+/r6YuPGjQgKCsLAgQOxdev/tXffcU3c/x/AX2FEcAAOioKbFoqggv0iIrjQOlGraN27UEXEitqqX1uqdRetglqp6+usVAUt1FFHxVFRf+5aB8MK2spSjGwS7veHD9JGQEkkuUBfz8ejj5rPXS4vksvdvXOf+1wEsrOztZGNiIiIiIiIqFpRuwhfvXo1zp07h88//xyCICA4OBienp6YPXs2zp8/r42MRERERERERNWCRgOz1alTByNHjsTIkSORkJCA/fv3Izo6GjExMWjUqBFOnjxZ2TmJiIiIiIiIqjy1z4S/7O2338ann36Kr776ClZWVvjrr78qIxcRERERERFRtaPRmfASf/zxB6KionDgwAGkpaXBysoKfn5+lZWNiIiIiIiIqFpRuwjPycnB4cOHERkZiatXr8LY2Bjdu3fH4MGD4enpCYlEoo2cRERERERERFWe2kW4p6cn8vPz4eDggPnz56N///4wMzPTRjYiIiIiIiKiakXtInzo0KHw8fGBvb29NvIQERERERERVVtqF+Hz5s3TRg4iIiIiIiKiak+j0dGfPHmClStXYtiwYejVqxfi4+MBAHv27MHvv/9eqQGJiIiIiIiIqgu1i/CUlBQMGDAAO3bsgEQiQXJyMgoLCwEAd+/exY4dOyo9JBEREREREVF1oHYR/vXXX8PMzAxHjx7Fzp07IQiCctp7772HK1euVGpAIiIiIiIioupC7SI8Li4OAQEBsLKyKnU7MktLS6SlpVVaOCIiIiIiIqLqRO0ivKCgAObm5mVOy8vL433CiYiIiIiIiMqhdhHeokUL/Prrr2VOu3TpEuzs7N44FBEREREREVF1pHYRPnToUGzfvh3btm3Ds2fPAABFRUU4cuQIdu/ejWHDhlV6SCIiIiIiIqLqQO37hI8aNQp37tzB0qVLsXz5cgDAyJEjIQgChg4dikGDBlV6SCIiIiIiIqLqQO0iHAC++uor+Pj44NSpU8jMzETdunXRtWtXtGvXrrLzEREREREREVUbGhXhAODs7AxnZ+fKzEJERERERERUral9TTgRERERERERaYZFOBEREREREZGOsAgnIiIiIiIi0hEW4UREREREREQ6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2oW8vDyV+VxcXGBubo7g4GAEBATAyMgI0dHRpX7w+KexY8dixowZkEgkGDlyZIX/JiIiojfFIpyIiOgNSCQSrF+/HmFhYYiMjMSGDRtgYWGBgQMHIigoSOXs7KRJk5CdnY2oqChs2bIFbdq0wZo1a1RG9tZEvXr1sGfPHqxcuRIrV65ETk4OGjdujNmzZ2PChAkaL/e///0vFixYgKCgIOTl5aF9+/bYsWOHWsv4+OOPYWtri+3bt+Onn35CYWEhLC0t4eTkpBwkrTx2dnbYunUrli9fjs8++wzm5uYYMGAAevXqhc8//1w5X926dREeHo7ly5dj9uzZMDU1Rffu3fHNN99g0KBBZS67R48ekEqlcHNzQ/PmzdX6m4iIiN6ERBAEQewQRERERLp08uRJTJkyBd999x26dOkidhwiIvoXYRFORERE/xoJCQl49OgRlixZAlNTU0RFRVV4ADwiIqLKwO7oRERE9K+xYMECXLlyBa1atcKyZctYgBMRkc7xTDgRERERERGRjvAWZUREREREREQ6wiKciIiIiIiISEdYhBMRERERERHpCItwIiIiIiIiIh1hEU5ERERERESkIyzCiYiIiIiIiHSERTgRERERERGRjrAIJyIiIiIiItKR/werA1cRZFcNWAAAAABJRU5ErkJggg==\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "events_per_hour_per_user = (\n", " sample.groupby([\"userId\", \"ts_date_day\", \"ts_hour\", \"user_churned\"])\n", @@ -637,9 +941,83 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Average total: number of sessions, App usage length, number of songs listened, number of artists listened per user, days active: \n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
user_churnedsessionIdsongartistlengthts_date_day
002044.6363641434.1022731067.602273412310.6420962044.636364
113260.3846152173.1538461493.230769656340.5865223260.384615
\n", + "
" + ], + "text/plain": [ + " user_churned sessionId song artist length \\\n", + "0 0 2044.636364 1434.102273 1067.602273 412310.642096 \n", + "1 1 3260.384615 2173.153846 1493.230769 656340.586522 \n", + "\n", + " ts_date_day \n", + "0 2044.636364 \n", + "1 3260.384615 " + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "stats_per_user = (\n", " sample.groupby([\"userId\", \"user_churned\"])\n", @@ -676,9 +1054,76 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 24, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Average daily: number of sessions, App usage length, number of songs listened, number of artists listened per user: \n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
user_churnedsessionIdsongartistlength
0076.59770161.30183159.32311615446.290551
1192.74617174.29102871.50109418670.519967
\n", + "
" + ], + "text/plain": [ + " user_churned sessionId song artist length\n", + "0 0 76.597701 61.301831 59.323116 15446.290551\n", + "1 1 92.746171 74.291028 71.501094 18670.519967" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "stats_per_user = (\n", " sample.groupby([\"userId\", \"ts_date_day\", \"user_churned\"])\n", @@ -707,7 +1152,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 25, "metadata": {}, "outputs": [], "source": [ @@ -731,7 +1176,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 26, "metadata": {}, "outputs": [], "source": [ @@ -740,9 +1185,88 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 27, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
user_churnednextsongthumbs_downthumbs_upadd_to_playlistroll_advertadd_frienddowngradeupgradeerror
001656.20454516.988636150.47727350.8636367.61363629.1818189.5681821.9545452.193182
112645.53846228.076923239.61538580.84615410.92307748.92307712.6153852.4615383.461538
\n", + "
" + ], + "text/plain": [ + " user_churned nextsong thumbs_down thumbs_up add_to_playlist \\\n", + "0 0 1656.204545 16.988636 150.477273 50.863636 \n", + "1 1 2645.538462 28.076923 239.615385 80.846154 \n", + "\n", + " roll_advert add_friend downgrade upgrade error \n", + "0 7.613636 29.181818 9.568182 1.954545 2.193182 \n", + "1 10.923077 48.923077 12.615385 2.461538 3.461538 " + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "app_use_group = app_use_per_user.groupby([\"user_churned\"])[usage_column_name].mean().reset_index()\n", "app_use_group" @@ -764,9 +1288,17 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 28, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting preprocessing_predw.py\n" + ] + } + ], "source": [ "%%writefile preprocessing_predw.py\n", "\n", @@ -807,7 +1339,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 29, "metadata": {}, "outputs": [], "source": [ @@ -820,9 +1352,24 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 30, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['s3://sagemaker-us-west-2-688520471316/music-streaming/data/json/sample.json',\n", + " 's3://sagemaker-us-west-2-688520471316/music-streaming/data/json/simu-1.json',\n", + " 's3://sagemaker-us-west-2-688520471316/music-streaming/data/json/simu-2.json',\n", + " 's3://sagemaker-us-west-2-688520471316/music-streaming/data/json/simu-3.json',\n", + " 's3://sagemaker-us-west-2-688520471316/music-streaming/data/json/simu-4.json']" + ] + }, + "execution_count": 30, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "s3_client = boto3.client(\"s3\")\n", "list_response = s3_client.list_objects_v2(Bucket=bucket, Prefix=f\"{prefix}/data/json\")\n", @@ -832,7 +1379,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 31, "metadata": {}, "outputs": [], "source": [ @@ -849,9 +1396,30 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 32, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Job Name: sagemaker-scikit-learn-2022-04-30-01-59-49-481\n", + "Inputs: [{'InputName': 'sample', 'AppManaged': False, 'S3Input': {'S3Uri': 's3://sagemaker-us-west-2-688520471316/music-streaming/data/json/sample.json', 'LocalPath': '/opt/ml/processing/input/data/sample', 'S3DataType': 'S3Prefix', 'S3InputMode': 'File', 'S3DataDistributionType': 'FullyReplicated', 'S3CompressionType': 'None'}}, {'InputName': 'simu-1', 'AppManaged': False, 'S3Input': {'S3Uri': 's3://sagemaker-us-west-2-688520471316/music-streaming/data/json/simu-1.json', 'LocalPath': '/opt/ml/processing/input/data/simu-1', 'S3DataType': 'S3Prefix', 'S3InputMode': 'File', 'S3DataDistributionType': 'FullyReplicated', 'S3CompressionType': 'None'}}, {'InputName': 'simu-2', 'AppManaged': False, 'S3Input': {'S3Uri': 's3://sagemaker-us-west-2-688520471316/music-streaming/data/json/simu-2.json', 'LocalPath': '/opt/ml/processing/input/data/simu-2', 'S3DataType': 'S3Prefix', 'S3InputMode': 'File', 'S3DataDistributionType': 'FullyReplicated', 'S3CompressionType': 'None'}}, {'InputName': 'simu-3', 'AppManaged': False, 'S3Input': {'S3Uri': 's3://sagemaker-us-west-2-688520471316/music-streaming/data/json/simu-3.json', 'LocalPath': '/opt/ml/processing/input/data/simu-3', 'S3DataType': 'S3Prefix', 'S3InputMode': 'File', 'S3DataDistributionType': 'FullyReplicated', 'S3CompressionType': 'None'}}, {'InputName': 'simu-4', 'AppManaged': False, 'S3Input': {'S3Uri': 's3://sagemaker-us-west-2-688520471316/music-streaming/data/json/simu-4.json', 'LocalPath': '/opt/ml/processing/input/data/simu-4', 'S3DataType': 'S3Prefix', 'S3InputMode': 'File', 'S3DataDistributionType': 'FullyReplicated', 'S3CompressionType': 'None'}}, {'InputName': 'code', 'AppManaged': False, 'S3Input': {'S3Uri': 's3://sagemaker-us-west-2-688520471316/sagemaker-scikit-learn-2022-04-30-01-59-49-481/input/code/preprocessing_predw.py', 'LocalPath': '/opt/ml/processing/input/code', 'S3DataType': 'S3Prefix', 'S3InputMode': 'File', 'S3DataDistributionType': 'FullyReplicated', 'S3CompressionType': 'None'}}]\n", + "Outputs: [{'OutputName': 'processed_data', 'AppManaged': False, 'S3Output': {'S3Uri': 's3://sagemaker-us-west-2-688520471316/music-streaming/data/processing', 'LocalPath': '/opt/ml/processing/output', 'S3UploadMode': 'EndOfJob'}}]\n", + "...........................\u001b[34mReceived arguments Namespace(processing_output_filename='full_data.csv')\u001b[0m\n", + "\u001b[34mStarting file: /opt/ml/processing/input/data/simu-3/simu-3.json\u001b[0m\n", + "\u001b[34mStarting file: /opt/ml/processing/input/data/simu-1/simu-1.json\u001b[0m\n", + "\u001b[34mStarting file: /opt/ml/processing/input/data/sample/sample.json\u001b[0m\n", + "\u001b[34mStarting file: /opt/ml/processing/input/data/simu-4/simu-4.json\u001b[0m\n", + "\u001b[34mStarting file: /opt/ml/processing/input/data/simu-2/simu-2.json\u001b[0m\n", + "\u001b[34mSaving processed data to /opt/ml/processing/output/full_data.csv\u001b[0m\n", + "\n", + "CPU times: user 932 ms, sys: 81.8 ms, total: 1.01 s\n", + "Wall time: 8min 26s\n" + ] + } + ], "source": [ "%%time\n", "processing_output_path = f\"s3://{bucket}/{prefix}/data/processing\"\n", @@ -913,9 +1481,20 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 33, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'s3://sagemaker-us-west-2-688520471316/music-streaming/data/processing/full_data.csv'" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "processing_output_filename = f\"{processing_output_path}/{final_features_filename}\"\n", "processing_output_filename" @@ -923,9 +1502,139 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 34, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'metadata': {'version': 1},\n", + " 'nodes': [{'node_id': '660c3ee3-5207-4ded-b92f-7059831a7aed',\n", + " 'type': 'SOURCE',\n", + " 'operator': 'sagemaker.s3_source_0.1',\n", + " 'parameters': {'dataset_definition': {'__typename': 'S3CreateDatasetDefinitionOutput',\n", + " 'datasetSourceType': 'S3',\n", + " 'name': 'full_data.csv',\n", + " 'description': None,\n", + " 's3ExecutionContext': {'__typename': 'S3ExecutionContext',\n", + " 's3Uri': 's3://sagemaker-us-west-2-688520471316/music-streaming/data/processing/full_data.csv',\n", + " 's3ContentType': 'csv',\n", + " 's3HasHeader': True}}},\n", + " 'inputs': [],\n", + " 'outputs': [{'name': 'default',\n", + " 'sampling': {'sampling_method': 'sample_by_ratio',\n", + " 'sample_ratio': 0.06460757939298588}}]},\n", + " {'node_id': 'd04eac2a-92a9-4539-b22f-f0f30aa29877',\n", + " 'type': 'TRANSFORM',\n", + " 'operator': 'sagemaker.spark.infer_and_cast_type_0.1',\n", + " 'parameters': {},\n", + " 'trained_parameters': {'schema': {'ts': 'long',\n", + " 'userId': 'long',\n", + " 'sessionId': 'long',\n", + " 'page': 'string',\n", + " 'auth': 'string',\n", + " 'method': 'string',\n", + " 'status': 'long',\n", + " 'level': 'string',\n", + " 'itemInSession': 'long',\n", + " 'location': 'string',\n", + " 'userAgent': 'string',\n", + " 'lastName': 'string',\n", + " 'firstName': 'string',\n", + " 'registration': 'float',\n", + " 'gender': 'string',\n", + " 'artist': 'string',\n", + " 'song': 'string',\n", + " 'length': 'long'}},\n", + " 'inputs': [{'name': 'default',\n", + " 'node_id': '660c3ee3-5207-4ded-b92f-7059831a7aed',\n", + " 'output_name': 'default'}],\n", + " 'outputs': [{'name': 'default'}]},\n", + " {'node_id': 'd1b462ec-bbae-466d-afbd-39e5eab8dcc9',\n", + " 'type': 'TRANSFORM',\n", + " 'operator': 'sagemaker.spark.manage_columns_0.1',\n", + " 'parameters': {'operator': 'Drop column',\n", + " 'drop_column_parameters': {'column_to_drop': 'method'}},\n", + " 'inputs': [{'name': 'df',\n", + " 'node_id': 'd04eac2a-92a9-4539-b22f-f0f30aa29877',\n", + " 'output_name': 'default'}],\n", + " 'outputs': [{'name': 'default'}]},\n", + " {'node_id': '4dfd1354-1904-4fa4-bff7-56a9e0e50d0a',\n", + " 'type': 'TRANSFORM',\n", + " 'operator': 'sagemaker.spark.manage_columns_0.1',\n", + " 'parameters': {'operator': 'Drop column',\n", + " 'drop_column_parameters': {'column_to_drop': 'status'}},\n", + " 'inputs': [{'name': 'df',\n", + " 'node_id': 'd1b462ec-bbae-466d-afbd-39e5eab8dcc9',\n", + " 'output_name': 'default'}],\n", + " 'outputs': [{'name': 'default'}]},\n", + " {'node_id': '92ac4b28-bfb1-47bf-848a-de23735a2570',\n", + " 'type': 'TRANSFORM',\n", + " 'operator': 'sagemaker.spark.manage_columns_0.1',\n", + " 'parameters': {'operator': 'Drop column',\n", + " 'drop_column_parameters': {'column_to_drop': 'location'}},\n", + " 'inputs': [{'name': 'df',\n", + " 'node_id': '4dfd1354-1904-4fa4-bff7-56a9e0e50d0a',\n", + " 'output_name': 'default'}],\n", + " 'outputs': [{'name': 'default'}]},\n", + " {'node_id': 'e1fd74c7-8240-4e99-876e-73b42a063e65',\n", + " 'type': 'TRANSFORM',\n", + " 'operator': 'sagemaker.spark.manage_columns_0.1',\n", + " 'parameters': {'operator': 'Drop column',\n", + " 'drop_column_parameters': {'column_to_drop': 'userAgent'}},\n", + " 'inputs': [{'name': 'df',\n", + " 'node_id': '92ac4b28-bfb1-47bf-848a-de23735a2570',\n", + " 'output_name': 'default'}],\n", + " 'outputs': [{'name': 'default'}]},\n", + " {'node_id': '1550cb2f-c734-46f8-bfdc-4f8614c30c09',\n", + " 'type': 'TRANSFORM',\n", + " 'operator': 'sagemaker.spark.manage_columns_0.1',\n", + " 'parameters': {'operator': 'Drop column',\n", + " 'drop_column_parameters': {'column_to_drop': 'lastName'}},\n", + " 'inputs': [{'name': 'df',\n", + " 'node_id': 'e1fd74c7-8240-4e99-876e-73b42a063e65',\n", + " 'output_name': 'default'}],\n", + " 'outputs': [{'name': 'default'}]},\n", + " {'node_id': '32405a27-8e85-4c9b-8142-dd75d56fa75d',\n", + " 'type': 'TRANSFORM',\n", + " 'operator': 'sagemaker.spark.manage_columns_0.1',\n", + " 'parameters': {'operator': 'Drop column',\n", + " 'drop_column_parameters': {'column_to_drop': 'firstName'}},\n", + " 'inputs': [{'name': 'df',\n", + " 'node_id': '1550cb2f-c734-46f8-bfdc-4f8614c30c09',\n", + " 'output_name': 'default'}],\n", + " 'outputs': [{'name': 'default'}]},\n", + " {'node_id': '7b74dbbc-6f7e-4656-8f78-25272604bc45',\n", + " 'type': 'TRANSFORM',\n", + " 'operator': 'sagemaker.spark.handle_missing_0.1',\n", + " 'parameters': {'operator': 'Drop missing',\n", + " 'drop_missing_parameters': {'dimension': 'Drop Rows',\n", + " 'drop_rows_parameters': {'input_column': 'userId'}},\n", + " 'impute_parameters': {'column_type': 'Numeric',\n", + " 'numeric_parameters': {'strategy': 'Approximate Median'}}},\n", + " 'inputs': [{'name': 'df',\n", + " 'node_id': '32405a27-8e85-4c9b-8142-dd75d56fa75d',\n", + " 'output_name': 'default'}],\n", + " 'outputs': [{'name': 'default'}]},\n", + " {'node_id': '82cb5ad3-3b9c-428d-9260-ce6efcd4c4f8',\n", + " 'type': 'TRANSFORM',\n", + " 'operator': 'sagemaker.spark.handle_missing_0.1',\n", + " 'parameters': {'operator': 'Drop missing',\n", + " 'drop_missing_parameters': {'dimension': 'Drop Rows',\n", + " 'drop_rows_parameters': {'input_column': 'registration'}},\n", + " 'impute_parameters': {'column_type': 'Numeric',\n", + " 'numeric_parameters': {'strategy': 'Approximate Median'}}},\n", + " 'inputs': [{'name': 'df',\n", + " 'node_id': '7b74dbbc-6f7e-4656-8f78-25272604bc45',\n", + " 'output_name': 'default'}],\n", + " 'outputs': [{'name': 'default'}]}]}" + ] + }, + "execution_count": 34, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "flow_file = \"dw_example.flow\"\n", "\n", @@ -952,7 +1661,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 35, "metadata": {}, "outputs": [], "source": [ @@ -969,7 +1678,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 36, "metadata": {}, "outputs": [], "source": [ @@ -979,9 +1688,17 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 37, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting preprocessing.py\n" + ] + } + ], "source": [ "%%writefile preprocessing.py\n", "\n", @@ -1232,7 +1949,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 38, "metadata": {}, "outputs": [], "source": [ @@ -1241,11 +1958,256 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 39, "metadata": { "scrolled": true }, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Job Name: sagemaker-scikit-learn-2022-04-30-02-08-16-353\n", + "Inputs: [{'InputName': 'code', 'AppManaged': False, 'S3Input': {'S3Uri': 's3://sagemaker-us-west-2-688520471316/sagemaker-scikit-learn-2022-04-30-02-08-16-353/input/code/preprocessing.py', 'LocalPath': '/opt/ml/processing/input/code', 'S3DataType': 'S3Prefix', 'S3InputMode': 'File', 'S3DataDistributionType': 'FullyReplicated', 'S3CompressionType': 'None'}}]\n", + "Outputs: [{'OutputName': 'processed_data', 'AppManaged': False, 'S3Output': {'S3Uri': 's3://sagemaker-us-west-2-688520471316/music-streaming/data/processing', 'LocalPath': '/opt/ml/processing/output', 'S3UploadMode': 'EndOfJob'}}]\n", + "...........................\u001b[34mRequirement already satisfied: pandas in /miniconda3/lib/python3.7/site-packages (1.1.3)\u001b[0m\n", + "\u001b[34mCollecting pandas\n", + " Downloading pandas-1.3.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (11.3 MB)\n", + " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 11.3/11.3 MB 87.5 MB/s eta 0:00:00\u001b[0m\n", + "\u001b[34mRequirement already satisfied: numpy>=1.17.3 in /miniconda3/lib/python3.7/site-packages (from pandas) (1.21.0)\u001b[0m\n", + "\u001b[34mRequirement already satisfied: python-dateutil>=2.7.3 in /miniconda3/lib/python3.7/site-packages (from pandas) (2.8.1)\u001b[0m\n", + "\u001b[34mRequirement already satisfied: pytz>=2017.3 in /miniconda3/lib/python3.7/site-packages (from pandas) (2022.1)\u001b[0m\n", + "\u001b[34mRequirement already satisfied: six>=1.5 in /miniconda3/lib/python3.7/site-packages (from python-dateutil>=2.7.3->pandas) (1.15.0)\u001b[0m\n", + "\u001b[34mInstalling collected packages: pandas\n", + " Attempting uninstall: pandas\n", + " Found existing installation: pandas 1.1.3\n", + " Uninstalling pandas-1.1.3:\n", + " Successfully uninstalled pandas-1.1.3\u001b[0m\n", + "\u001b[34mERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.\u001b[0m\n", + "\u001b[34msagemaker-sklearn-container 2.0 requires pandas==1.1.3, but you have pandas 1.3.5 which is incompatible.\u001b[0m\n", + "\u001b[34mSuccessfully installed pandas-1.3.5\u001b[0m\n", + "\u001b[34mWARNING: 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\n", + "\u001b[34mCollecting awswrangler\n", + " Downloading awswrangler-2.15.1-py3-none-any.whl (239 kB)\u001b[0m\n", + "\u001b[34m ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 239.6/239.6 KB 8.2 MB/s eta 0:00:00\u001b[0m\n", + "\u001b[34mCollecting openpyxl<3.1.0,>=3.0.0\n", + " Downloading openpyxl-3.0.9-py2.py3-none-any.whl (242 kB)\n", + " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 242.2/242.2 KB 33.7 MB/s eta 0:00:00\u001b[0m\n", + "\u001b[34mCollecting pyarrow<7.1.0,>=2.0.0\n", + " Downloading pyarrow-7.0.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (26.7 MB)\n", + " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 26.7/26.7 MB 52.3 MB/s eta 0:00:00\u001b[0m\n", + "\u001b[34mCollecting redshift-connector<2.1.0,>=2.0.889\n", + " Downloading redshift_connector-2.0.906-py3-none-any.whl (109 kB)\n", + " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 109.8/109.8 KB 19.9 MB/s eta 0:00:00\u001b[0m\n", + "\u001b[34mCollecting botocore<2.0.0,>=1.23.17\n", + " Downloading botocore-1.25.4-py3-none-any.whl (8.7 MB)\n", + " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 8.7/8.7 MB 96.3 MB/s eta 0:00:00\u001b[0m\n", + "\u001b[34mCollecting opensearch-py<2.0.0,>=1.0.0\n", + " Downloading opensearch_py-1.1.0-py2.py3-none-any.whl (207 kB)\n", + " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 207.5/207.5 KB 30.5 MB/s eta 0:00:00\u001b[0m\n", + "\u001b[34mCollecting progressbar2<5.0.0,>=4.0.0\n", + " Downloading progressbar2-4.0.0-py2.py3-none-any.whl (26 kB)\u001b[0m\n", + "\u001b[34mRequirement already satisfied: pandas<2.0.0,>=1.2.0 in /miniconda3/lib/python3.7/site-packages (from awswrangler) (1.3.5)\u001b[0m\n", + "\u001b[34mCollecting backoff<2.0.0,>=1.11.1\n", + " Downloading backoff-1.11.1-py2.py3-none-any.whl (13 kB)\u001b[0m\n", + "\u001b[34mCollecting gremlinpython<4.0.0,>=3.5.2\n", + " Downloading gremlinpython-3.6.0-py2.py3-none-any.whl (72 kB)\n", + " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 72.8/72.8 KB 14.3 MB/s eta 0:00:00\u001b[0m\n", + "\u001b[34mRequirement already satisfied: numpy<2.0.0,>=1.21.0 in /miniconda3/lib/python3.7/site-packages (from awswrangler) (1.21.0)\u001b[0m\n", + "\u001b[34mCollecting pg8000<2.0.0,>=1.20.0\n", + " Downloading pg8000-1.26.1-py3-none-any.whl (33 kB)\u001b[0m\n", + "\u001b[34mCollecting pymysql<2.0.0,>=1.0.0\n", + " Downloading PyMySQL-1.0.2-py3-none-any.whl (43 kB)\n", + " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 43.8/43.8 KB 7.2 MB/s eta 0:00:00\u001b[0m\n", + "\u001b[34mCollecting jsonpath-ng<2.0.0,>=1.5.3\n", + " Downloading jsonpath_ng-1.5.3-py3-none-any.whl (29 kB)\u001b[0m\n", + "\u001b[34mCollecting boto3<2.0.0,>=1.20.17\n", + " Downloading boto3-1.22.4-py3-none-any.whl (132 kB)\n", + " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 132.5/132.5 KB 20.2 MB/s eta 0:00:00\u001b[0m\n", + "\u001b[34mCollecting requests-aws4auth<2.0.0,>=1.1.1\n", + " Downloading requests_aws4auth-1.1.2-py2.py3-none-any.whl (24 kB)\u001b[0m\n", + "\u001b[34mRequirement already satisfied: jmespath<2.0.0,>=0.7.1 in /miniconda3/lib/python3.7/site-packages (from boto3<2.0.0,>=1.20.17->awswrangler) (0.10.0)\u001b[0m\n", + "\u001b[34mCollecting s3transfer<0.6.0,>=0.5.0\n", + " Downloading s3transfer-0.5.2-py3-none-any.whl (79 kB)\n", + " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 79.5/79.5 KB 19.0 MB/s eta 0:00:00\u001b[0m\n", + "\u001b[34mRequirement already satisfied: python-dateutil<3.0.0,>=2.1 in /miniconda3/lib/python3.7/site-packages (from botocore<2.0.0,>=1.23.17->awswrangler) (2.8.1)\u001b[0m\n", + "\u001b[34mRequirement already satisfied: urllib3<1.27,>=1.25.4 in /miniconda3/lib/python3.7/site-packages (from botocore<2.0.0,>=1.23.17->awswrangler) (1.25.11)\u001b[0m\n", + "\u001b[34mCollecting nest-asyncio\n", + " Downloading nest_asyncio-1.5.5-py3-none-any.whl (5.2 kB)\u001b[0m\n", + "\u001b[34mCollecting isodate<1.0.0,>=0.6.0\n", + " Downloading isodate-0.6.1-py2.py3-none-any.whl (41 kB)\n", + " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 41.7/41.7 KB 7.8 MB/s eta 0:00:00\u001b[0m\n", + "\u001b[34mCollecting aiohttp<=3.8.1,>=3.8.0\n", + " Downloading aiohttp-3.8.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl (1.1 MB)\n", + " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.1/1.1 MB 74.4 MB/s eta 0:00:00\u001b[0m\n", + "\u001b[34mCollecting aenum<4.0.0,>=1.4.5\n", + " Downloading aenum-3.1.11-py3-none-any.whl (131 kB)\n", + " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 131.5/131.5 KB 26.2 MB/s eta 0:00:00\u001b[0m\n", + "\u001b[34mCollecting decorator\n", + " Downloading decorator-5.1.1-py3-none-any.whl (9.1 kB)\u001b[0m\n", + "\u001b[34mCollecting ply\n", + " Downloading ply-3.11-py2.py3-none-any.whl (49 kB)\n", + " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 49.6/49.6 KB 9.7 MB/s eta 0:00:00\u001b[0m\n", + "\u001b[34mRequirement already satisfied: six in /miniconda3/lib/python3.7/site-packages (from jsonpath-ng<2.0.0,>=1.5.3->awswrangler) (1.15.0)\u001b[0m\n", + "\u001b[34mCollecting et-xmlfile\n", + " Downloading et_xmlfile-1.1.0-py3-none-any.whl (4.7 kB)\u001b[0m\n", + "\u001b[34mRequirement already satisfied: certifi in /miniconda3/lib/python3.7/site-packages (from opensearch-py<2.0.0,>=1.0.0->awswrangler) (2021.10.8)\u001b[0m\n", + "\u001b[34mRequirement already satisfied: pytz>=2017.3 in /miniconda3/lib/python3.7/site-packages (from pandas<2.0.0,>=1.2.0->awswrangler) (2022.1)\u001b[0m\n", + "\u001b[34mCollecting scramp>=1.4.1\n", + " Downloading scramp-1.4.1-py3-none-any.whl (8.5 kB)\u001b[0m\n", + "\u001b[34mCollecting python-utils>=3.0.0\n", + " Downloading python_utils-3.1.0-py2.py3-none-any.whl (19 kB)\u001b[0m\n", + "\u001b[34mCollecting lxml>=4.6.5\n", + " Downloading lxml-4.8.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl (6.4 MB)\n", + " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 6.4/6.4 MB 103.0 MB/s eta 0:00:00\u001b[0m\n", + "\u001b[34mCollecting packaging\n", + " Downloading packaging-21.3-py3-none-any.whl (40 kB)\n", + " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 40.8/40.8 KB 6.9 MB/s eta 0:00:00\u001b[0m\n", + "\u001b[34mRequirement already satisfied: requests<2.27.2,>=2.23.0 in /miniconda3/lib/python3.7/site-packages (from redshift-connector<2.1.0,>=2.0.889->awswrangler) (2.27.1)\u001b[0m\n", + "\u001b[34mCollecting beautifulsoup4<5.0.0,>=4.7.0\n", + " Downloading beautifulsoup4-4.11.1-py3-none-any.whl (128 kB)\n", + " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 128.2/128.2 KB 21.6 MB/s eta 0:00:00\u001b[0m\n", + "\u001b[34mCollecting yarl<2.0,>=1.0\n", + " Downloading yarl-1.7.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl (271 kB)\n", + " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 271.8/271.8 KB 35.1 MB/s eta 0:00:00\u001b[0m\n", + "\u001b[34mCollecting asynctest==0.13.0\n", + " Downloading asynctest-0.13.0-py3-none-any.whl (26 kB)\u001b[0m\n", + "\u001b[34mCollecting aiosignal>=1.1.2\n", + " Downloading aiosignal-1.2.0-py3-none-any.whl (8.2 kB)\u001b[0m\n", + "\u001b[34mRequirement already satisfied: charset-normalizer<3.0,>=2.0 in /miniconda3/lib/python3.7/site-packages (from aiohttp<=3.8.1,>=3.8.0->gremlinpython<4.0.0,>=3.5.2->awswrangler) (2.0.4)\u001b[0m\n", + "\u001b[34mCollecting attrs>=17.3.0\n", + " Downloading attrs-21.4.0-py2.py3-none-any.whl (60 kB)\n", + " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 60.6/60.6 KB 13.3 MB/s eta 0:00:00\u001b[0m\n", + "\u001b[34mCollecting multidict<7.0,>=4.5\n", + " Downloading multidict-6.0.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (94 kB)\n", + " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 94.8/94.8 KB 15.3 MB/s eta 0:00:00\u001b[0m\n", + "\u001b[34mCollecting frozenlist>=1.1.1\n", + " Downloading frozenlist-1.3.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl (144 kB)\n", + " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 144.8/144.8 KB 15.0 MB/s eta 0:00:00\u001b[0m\n", + "\u001b[34mRequirement already satisfied: typing-extensions>=3.7.4 in /miniconda3/lib/python3.7/site-packages (from aiohttp<=3.8.1,>=3.8.0->gremlinpython<4.0.0,>=3.5.2->awswrangler) (4.1.1)\u001b[0m\n", + "\u001b[34mCollecting async-timeout<5.0,>=4.0.0a3\n", + " Downloading async_timeout-4.0.2-py3-none-any.whl (5.8 kB)\u001b[0m\n", + "\u001b[34mCollecting soupsieve>1.2\n", + " Downloading soupsieve-2.3.2.post1-py3-none-any.whl (37 kB)\u001b[0m\n", + "\u001b[34mRequirement already satisfied: idna<4,>=2.5 in /miniconda3/lib/python3.7/site-packages (from requests<2.27.2,>=2.23.0->redshift-connector<2.1.0,>=2.0.889->awswrangler) (3.3)\u001b[0m\n", + "\u001b[34mCollecting asn1crypto>=1.4.0\n", + " Downloading asn1crypto-1.5.1-py2.py3-none-any.whl (105 kB)\n", + " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 105.0/105.0 KB 11.9 MB/s eta 0:00:00\u001b[0m\n", + "\u001b[34mCollecting pyparsing!=3.0.5,>=2.0.2\n", + " Downloading pyparsing-3.0.8-py3-none-any.whl (98 kB)\n", + " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 98.5/98.5 KB 11.6 MB/s eta 0:00:00\u001b[0m\n", + "\u001b[34mInstalling collected packages: ply, asn1crypto, aenum, soupsieve, scramp, python-utils, pyparsing, pymysql, pyarrow, opensearch-py, nest-asyncio, multidict, lxml, isodate, frozenlist, et-xmlfile, decorator, backoff, attrs, asynctest, async-timeout, yarl, requests-aws4auth, progressbar2, pg8000, packaging, openpyxl, jsonpath-ng, botocore, beautifulsoup4, aiosignal, s3transfer, aiohttp, gremlinpython, boto3, redshift-connector, awswrangler\u001b[0m\n", + "\u001b[34m Attempting uninstall: pyarrow\n", + " Found existing installation: pyarrow 0.16.0\n", + " Uninstalling pyarrow-0.16.0:\n", + " Successfully uninstalled pyarrow-0.16.0\u001b[0m\n", + "\u001b[34m Attempting uninstall: botocore\n", + " Found existing installation: botocore 1.19.4\n", + " Uninstalling botocore-1.19.4:\n", + " Successfully uninstalled botocore-1.19.4\u001b[0m\n", + "\u001b[34m Attempting uninstall: s3transfer\n", + " Found existing installation: s3transfer 0.3.7\n", + " Uninstalling s3transfer-0.3.7:\n", + " Successfully uninstalled s3transfer-0.3.7\n", + " Attempting uninstall: boto3\n", + " Found existing installation: boto3 1.16.4\n", + " Uninstalling boto3-1.16.4:\n", + " Successfully uninstalled boto3-1.16.4\u001b[0m\n", + "\u001b[34mERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.\u001b[0m\n", + "\u001b[34msagemaker-sklearn-container 2.0 requires boto3==1.16.4, but you have boto3 1.22.4 which is incompatible.\u001b[0m\n", + "\u001b[34msagemaker-sklearn-container 2.0 requires botocore==1.19.4, but you have botocore 1.25.4 which is incompatible.\u001b[0m\n", + "\u001b[34msagemaker-sklearn-container 2.0 requires pandas==1.1.3, but you have pandas 1.3.5 which is incompatible.\u001b[0m\n", + "\u001b[34mSuccessfully installed aenum-3.1.11 aiohttp-3.8.1 aiosignal-1.2.0 asn1crypto-1.5.1 async-timeout-4.0.2 asynctest-0.13.0 attrs-21.4.0 awswrangler-2.15.1 backoff-1.11.1 beautifulsoup4-4.11.1 boto3-1.22.4 botocore-1.25.4 decorator-5.1.1 et-xmlfile-1.1.0 frozenlist-1.3.0 gremlinpython-3.6.0 isodate-0.6.1 jsonpath-ng-1.5.3 lxml-4.8.0 multidict-6.0.2 nest-asyncio-1.5.5 openpyxl-3.0.9 opensearch-py-1.1.0 packaging-21.3 pg8000-1.26.1 ply-3.11 progressbar2-4.0.0 pyarrow-7.0.0 pymysql-1.0.2 pyparsing-3.0.8 python-utils-3.1.0 redshift-connector-2.0.906 requests-aws4auth-1.1.2 s3transfer-0.5.2 scramp-1.4.1 soupsieve-2.3.2.post1 yarl-1.7.2\u001b[0m\n", + "\u001b[34mWARNING: 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\n", + "\u001b[34mReceived arguments Namespace(dw_output_path='s3://sagemaker-us-west-2-688520471316/music-streaming/data/processing', processing_output_filename='processing_job_output.csv')\u001b[0m\n", + "\n", + "\u001b[34mTraceback (most recent call last):\n", + " File \"/miniconda3/lib/python3.7/site-packages/pandas/core/groupby/generic.py\", line 341, in array_func\n", + " \"aggregate\", values, how, axis=data.ndim - 1, min_count=min_count\n", + " File \"/miniconda3/lib/python3.7/site-packages/pandas/core/groupby/ops.py\", line 1016, in _cython_operation\n", + " **kwargs,\n", + " File \"/miniconda3/lib/python3.7/site-packages/pandas/core/groupby/ops.py\", line 677, in cython_operation\n", + " **kwargs,\n", + " File \"/miniconda3/lib/python3.7/site-packages/pandas/core/groupby/ops.py\", line 508, in _cython_op_ndim_compat\n", + " **kwargs,\n", + " File \"/miniconda3/lib/python3.7/site-packages/pandas/core/groupby/ops.py\", line 563, in _call_cython_op\n", + " func, values = self.get_cython_func_and_vals(values, is_numeric)\n", + " File \"/miniconda3/lib/python3.7/site-packages/pandas/core/groupby/ops.py\", line 205, in get_cython_func_and_vals\n", + " func = self._get_cython_function(kind, how, values.dtype, is_numeric)\n", + " File \"/miniconda3/lib/python3.7/site-packages/pandas/core/groupby/ops.py\", line 171, in _get_cython_function\n", + " f\"function is not implemented for this dtype: \"\u001b[0m\n", + "\u001b[34mNotImplementedError: function is not implemented for this dtype: [how->max,dtype->object]\u001b[0m\n", + "\u001b[34mDuring handling of the above exception, another exception occurred:\u001b[0m\n", + "\u001b[34mTraceback (most recent call last):\n", + " File \"/opt/ml/processing/input/code/preprocessing.py\", line 126, in \n", + " \"user_churned\": \"max\",\n", + " File \"/miniconda3/lib/python3.7/site-packages/pandas/core/groupby/generic.py\", line 979, in aggregate\n", + " result = op.agg()\n", + " File \"/miniconda3/lib/python3.7/site-packages/pandas/core/apply.py\", line 161, in agg\n", + " return self.agg_dict_like()\n", + " File \"/miniconda3/lib/python3.7/site-packages/pandas/core/apply.py\", line 436, in agg_dict_like\n", + " key: obj._gotitem(key, ndim=1).agg(how) for key, how in arg.items()\n", + " File \"/miniconda3/lib/python3.7/site-packages/pandas/core/apply.py\", line 436, in \n", + " key: obj._gotitem(key, ndim=1).agg(how) for key, how in arg.items()\n", + " File \"/miniconda3/lib/python3.7/site-packages/pandas/core/groupby/generic.py\", line 243, in aggregate\n", + " return getattr(self, func)(*args, **kwargs)\n", + " File \"/miniconda3/lib/python3.7/site-packages/pandas/core/groupby/groupby.py\", line 1880, in max\n", + " numeric_only=numeric_only, min_count=min_count, alias=\"max\", npfunc=np.max\n", + " File \"/miniconda3/lib/python3.7/site-packages/pandas/core/groupby/groupby.py\", line 1368, in _agg_general\n", + " min_count=min_count,\n", + " File \"/miniconda3/lib/python3.7/site-packages/pandas/core/groupby/generic.py\", line 352, in _cython_agg_general\n", + " result = array_func(objvals)\n", + " File \"/miniconda3/lib/python3.7/site-packages/pandas/core/groupby/generic.py\", line 348, in array_func\n", + " result = self._agg_py_fallback(values, ndim=data.ndim, alt=alt)\n", + " File \"/miniconda3/lib/python3.7/site-packages/pandas/core/groupby/groupby.py\", line 1398, in _agg_py_fallback\n", + " res_values = self.grouper.agg_series(ser, alt, preserve_dtype=True)\n", + " File \"/miniconda3/lib/python3.7/site-packages/pandas/core/groupby/ops.py\", line 1060, in agg_series\n", + " result = self._aggregate_series_fast(obj, func)\n", + " File \"/miniconda3/lib/python3.7/site-packages/pandas/core/groupby/ops.py\", line 1085, in _aggregate_series_fast\n", + " result, _ = sgrouper.get_result()\n", + " File \"pandas/_libs/reduction.pyx\", line 281, in pandas._libs.reduction.SeriesGrouper.get_result\n", + " File \"pandas/_libs/reduction.pyx\", line 88, in pandas._libs.reduction._BaseGrouper._apply_to_group\n", + " File \"<__array_function__ internals>\", line 6, in amax\n", + " File \"/miniconda3/lib/python3.7/site-packages/numpy/core/fromnumeric.py\", line 2755, in amax\n", + " keepdims=keepdims, initial=initial, where=where)\n", + " File \"/miniconda3/lib/python3.7/site-packages/numpy/core/fromnumeric.py\", line 84, in _wrapreduction\n", + " return reduction(axis=axis, out=out, **passkwargs)\n", + " File \"/miniconda3/lib/python3.7/site-packages/pandas/core/generic.py\", line 10819, in max\u001b[0m\n", + "\u001b[34m return NDFrame.max(self, axis, skipna, level, numeric_only, **kwargs)\n", + " File \"/miniconda3/lib/python3.7/site-packages/pandas/core/generic.py\", line 10365, in max\n", + " \"max\", nanops.nanmax, axis, skipna, level, numeric_only, **kwargs\n", + " File \"/miniconda3/lib/python3.7/site-packages/pandas/core/generic.py\", line 10355, in _stat_function\n", + " func, name=name, axis=axis, skipna=skipna, numeric_only=numeric_only\n", + " File \"/miniconda3/lib/python3.7/site-packages/pandas/core/series.py\", line 4392, in _reduce\n", + " return op(delegate, skipna=skipna, **kwds)\n", + " File \"/miniconda3/lib/python3.7/site-packages/pandas/core/nanops.py\", line 156, in f\n", + " result = alt(values, axis=axis, skipna=skipna, **kwds)\n", + " File \"/miniconda3/lib/python3.7/site-packages/pandas/core/nanops.py\", line 411, in new_func\n", + " result = func(values, axis=axis, skipna=skipna, mask=mask, **kwargs)\n", + " File \"/miniconda3/lib/python3.7/site-packages/pandas/core/nanops.py\", line 1018, in reduction\n", + " result = getattr(values, meth)(axis)\n", + " File \"/miniconda3/lib/python3.7/site-packages/numpy/core/_methods.py\", line 40, in _amax\n", + " return umr_maximum(a, axis, None, out, keepdims, initial, where)\u001b[0m\n", + "\u001b[34mTypeError: '>=' not supported between instances of 'datetime.date' and 'float'\u001b[0m\n" + ] + }, + { + "ename": "UnexpectedStatusException", + "evalue": "Error for Processing job sagemaker-scikit-learn-2022-04-30-02-08-16-353: Failed. Reason: AlgorithmError: See job logs for more information", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mUnexpectedStatusException\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n", + "\u001b[0;32m/opt/conda/lib/python3.7/site-packages/sagemaker/processing.py\u001b[0m in \u001b[0;36mrun\u001b[0;34m(self, code, inputs, outputs, arguments, wait, logs, job_name, experiment_config, kms_key)\u001b[0m\n\u001b[1;32m 557\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mjobs\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mappend\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mlatest_job\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 558\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mwait\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 559\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mlatest_job\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mwait\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mlogs\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mlogs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 560\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 561\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m_include_code_in_inputs\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0minputs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mcode\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mkms_key\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mNone\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m/opt/conda/lib/python3.7/site-packages/sagemaker/processing.py\u001b[0m in \u001b[0;36mwait\u001b[0;34m(self, logs)\u001b[0m\n\u001b[1;32m 966\u001b[0m \"\"\"\n\u001b[1;32m 967\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mlogs\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 968\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msagemaker_session\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mlogs_for_processing_job\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mjob_name\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mwait\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mTrue\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 969\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 970\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msagemaker_session\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mwait_for_processing_job\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mjob_name\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m/opt/conda/lib/python3.7/site-packages/sagemaker/session.py\u001b[0m in \u001b[0;36mlogs_for_processing_job\u001b[0;34m(self, job_name, wait, poll)\u001b[0m\n\u001b[1;32m 3885\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3886\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mwait\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 3887\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_check_job_status\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mjob_name\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdescription\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"ProcessingJobStatus\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 3888\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mdot\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3889\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m/opt/conda/lib/python3.7/site-packages/sagemaker/session.py\u001b[0m in \u001b[0;36m_check_job_status\u001b[0;34m(self, job, desc, status_key_name)\u001b[0m\n\u001b[1;32m 3337\u001b[0m \u001b[0mmessage\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mmessage\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3338\u001b[0m \u001b[0mallowed_statuses\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"Completed\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"Stopped\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 3339\u001b[0;31m \u001b[0mactual_status\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mstatus\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 3340\u001b[0m )\n\u001b[1;32m 3341\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mUnexpectedStatusException\u001b[0m: Error for Processing job sagemaker-scikit-learn-2022-04-30-02-08-16-353: Failed. Reason: AlgorithmError: See job logs for more information" + ] + } + ], "source": [ "%%time\n", "from sagemaker.processing import ProcessingInput, ProcessingOutput\n", @@ -1274,9 +2236,95 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 40, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'ProcessingInputs': [{'InputName': 'sample',\n", + " 'AppManaged': False,\n", + " 'S3Input': {'S3Uri': 's3://sagemaker-us-west-2-688520471316/music-streaming/data/json/sample.json',\n", + " 'LocalPath': '/opt/ml/processing/input/data/sample',\n", + " 'S3DataType': 'S3Prefix',\n", + " 'S3InputMode': 'File',\n", + " 'S3DataDistributionType': 'FullyReplicated',\n", + " 'S3CompressionType': 'None'}},\n", + " {'InputName': 'simu-1',\n", + " 'AppManaged': False,\n", + " 'S3Input': {'S3Uri': 's3://sagemaker-us-west-2-688520471316/music-streaming/data/json/simu-1.json',\n", + " 'LocalPath': '/opt/ml/processing/input/data/simu-1',\n", + " 'S3DataType': 'S3Prefix',\n", + " 'S3InputMode': 'File',\n", + " 'S3DataDistributionType': 'FullyReplicated',\n", + " 'S3CompressionType': 'None'}},\n", + " {'InputName': 'simu-2',\n", + " 'AppManaged': False,\n", + " 'S3Input': {'S3Uri': 's3://sagemaker-us-west-2-688520471316/music-streaming/data/json/simu-2.json',\n", + " 'LocalPath': '/opt/ml/processing/input/data/simu-2',\n", + " 'S3DataType': 'S3Prefix',\n", + " 'S3InputMode': 'File',\n", + " 'S3DataDistributionType': 'FullyReplicated',\n", + " 'S3CompressionType': 'None'}},\n", + " {'InputName': 'simu-3',\n", + " 'AppManaged': False,\n", + " 'S3Input': {'S3Uri': 's3://sagemaker-us-west-2-688520471316/music-streaming/data/json/simu-3.json',\n", + " 'LocalPath': '/opt/ml/processing/input/data/simu-3',\n", + " 'S3DataType': 'S3Prefix',\n", + " 'S3InputMode': 'File',\n", + " 'S3DataDistributionType': 'FullyReplicated',\n", + " 'S3CompressionType': 'None'}},\n", + " {'InputName': 'simu-4',\n", + " 'AppManaged': False,\n", + " 'S3Input': {'S3Uri': 's3://sagemaker-us-west-2-688520471316/music-streaming/data/json/simu-4.json',\n", + " 'LocalPath': '/opt/ml/processing/input/data/simu-4',\n", + " 'S3DataType': 'S3Prefix',\n", + " 'S3InputMode': 'File',\n", + " 'S3DataDistributionType': 'FullyReplicated',\n", + " 'S3CompressionType': 'None'}},\n", + " {'InputName': 'code',\n", + " 'AppManaged': False,\n", + " 'S3Input': {'S3Uri': 's3://sagemaker-us-west-2-688520471316/sagemaker-scikit-learn-2022-04-30-01-59-49-481/input/code/preprocessing_predw.py',\n", + " 'LocalPath': '/opt/ml/processing/input/code',\n", + " 'S3DataType': 'S3Prefix',\n", + " 'S3InputMode': 'File',\n", + " 'S3DataDistributionType': 'FullyReplicated',\n", + " 'S3CompressionType': 'None'}}],\n", + " 'ProcessingOutputConfig': {'Outputs': [{'OutputName': 'processed_data',\n", + " 'S3Output': {'S3Uri': 's3://sagemaker-us-west-2-688520471316/music-streaming/data/processing',\n", + " 'LocalPath': '/opt/ml/processing/output',\n", + " 'S3UploadMode': 'EndOfJob'},\n", + " 'AppManaged': False}]},\n", + " 'ProcessingJobName': 'sagemaker-scikit-learn-2022-04-30-01-59-49-481',\n", + " 'ProcessingResources': {'ClusterConfig': {'InstanceCount': 1,\n", + " 'InstanceType': 'ml.m5.xlarge',\n", + " 'VolumeSizeInGB': 30}},\n", + " 'StoppingCondition': {'MaxRuntimeInSeconds': 86400},\n", + " 'AppSpecification': {'ImageUri': '246618743249.dkr.ecr.us-west-2.amazonaws.com/sagemaker-scikit-learn:0.23-1-cpu-py3',\n", + " 'ContainerEntrypoint': ['python3',\n", + " '/opt/ml/processing/input/code/preprocessing_predw.py'],\n", + " 'ContainerArguments': ['--processing-output-filename', 'full_data.csv']},\n", + " 'RoleArn': 'arn:aws:iam::688520471316:role/service-role/AmazonSageMaker-ExecutionRole-20211229T100947',\n", + " 'ProcessingJobArn': 'arn:aws:sagemaker:us-west-2:688520471316:processing-job/sagemaker-scikit-learn-2022-04-30-01-59-49-481',\n", + " 'ProcessingJobStatus': 'Completed',\n", + " 'ProcessingEndTime': datetime.datetime(2022, 4, 30, 2, 7, 45, 433000, tzinfo=tzlocal()),\n", + " 'ProcessingStartTime': datetime.datetime(2022, 4, 30, 2, 3, 35, 10000, tzinfo=tzlocal()),\n", + " 'LastModifiedTime': datetime.datetime(2022, 4, 30, 2, 7, 45, 721000, tzinfo=tzlocal()),\n", + " 'CreationTime': datetime.datetime(2022, 4, 30, 1, 59, 49, 808000, tzinfo=tzlocal()),\n", + " 'ResponseMetadata': {'RequestId': 'cb40881c-69d9-4c7f-9a9c-d97153d589a5',\n", + " 'HTTPStatusCode': 200,\n", + " 'HTTPHeaders': {'x-amzn-requestid': 'cb40881c-69d9-4c7f-9a9c-d97153d589a5',\n", + " 'content-type': 'application/x-amz-json-1.1',\n", + " 'content-length': '3223',\n", + " 'date': 'Sat, 30 Apr 2022 02:08:14 GMT'},\n", + " 'RetryAttempts': 0}}" + ] + }, + "execution_count": 40, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "preprocessing_job_description" ] @@ -1310,9 +2358,20 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 41, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'s3://sagemaker-us-west-2-688520471316/music-streaming/data/processing/processing_job_output.csv'" + ] + }, + "execution_count": 41, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "processing_job_output_uri = f\"{processing_job_output_path}/{processing_job_output_name}\"\n", "processing_job_output_uri" @@ -1320,16 +2379,24 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 42, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "download: s3://sagemaker-us-west-2-688520471316/music-streaming/data/processing/processing_job_output.csv to data/processing_job_output.csv\n" + ] + } + ], "source": [ "!aws s3 cp $processing_job_output_uri ./data" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 43, "metadata": {}, "outputs": [], "source": [ @@ -1338,7 +2405,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 44, "metadata": {}, "outputs": [], "source": [ @@ -1348,9 +2415,194 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 45, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
userIduser_churnedaverage_events_weekendaverage_events_weekdaynum_songs_played_7dnum_ads_7dnum_error_7dnum_songs_played_30dnum_songs_played_90dnum_sessions...num_thumbs_upnum_add_to_playlistnum_adsnum_add_friendnum_downgradenum_upgradenum_errorpercentage_addays_since_activerepeats_ratio
0110010.094.937576.3043484135714135413551...2931407811110.0013923590.179444
1110020.070.500076.666667476104764767...41161141000.0016642650.052521
2110031.098.7500120.87500038671293867386737...27210312691190.002576660.175330
3110041.070.0000120.444444108421108410847...6830291010.001546480.076568
\n", + "

4 rows × 27 columns

\n", + "
" + ], + "text/plain": [ + " userId user_churned average_events_weekend average_events_weekday \\\n", + "0 11001 0.0 94.9375 76.304348 \n", + "1 11002 0.0 70.5000 76.666667 \n", + "2 11003 1.0 98.7500 120.875000 \n", + "3 11004 1.0 70.0000 120.444444 \n", + "\n", + " num_songs_played_7d num_ads_7d num_error_7d num_songs_played_30d \\\n", + "0 4135 7 1 4135 \n", + "1 476 1 0 476 \n", + "2 3867 12 9 3867 \n", + "3 1084 2 1 1084 \n", + "\n", + " num_songs_played_90d num_sessions ... num_thumbs_up \\\n", + "0 4135 51 ... 293 \n", + "1 476 7 ... 41 \n", + "2 3867 37 ... 272 \n", + "3 1084 7 ... 68 \n", + "\n", + " num_add_to_playlist num_ads num_add_friend num_downgrade num_upgrade \\\n", + "0 140 7 81 1 1 \n", + "1 16 1 14 1 0 \n", + "2 103 12 69 1 1 \n", + "3 30 2 9 1 0 \n", + "\n", + " num_error percentage_ad days_since_active repeats_ratio \n", + "0 1 0.001392 359 0.179444 \n", + "1 0 0.001664 265 0.052521 \n", + "2 9 0.002576 66 0.175330 \n", + "3 1 0.001546 48 0.076568 \n", + "\n", + "[4 rows x 27 columns]" + ] + }, + "execution_count": 45, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "processed_data.head(4)" ] @@ -1364,7 +2616,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 46, "metadata": {}, "outputs": [], "source": [ @@ -1379,7 +2631,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 47, "metadata": {}, "outputs": [], "source": [ @@ -1391,7 +2643,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 48, "metadata": {}, "outputs": [], "source": [ @@ -1414,7 +2666,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 49, "metadata": {}, "outputs": [], "source": [ From 4ba8ff661183d5cfa159e97571277ca0139ed1b1 Mon Sep 17 00:00:00 2001 From: atqy Date: Mon, 2 May 2022 16:20:09 +0000 Subject: [PATCH 12/27] clear outputs --- .../0_cust_churn_overview_dw.ipynb | 1451 ++--------------- 1 file changed, 100 insertions(+), 1351 deletions(-) diff --git a/use-cases/customer_churn/0_cust_churn_overview_dw.ipynb b/use-cases/customer_churn/0_cust_churn_overview_dw.ipynb index b7c984fb4d..d4e6436eed 100644 --- a/use-cases/customer_churn/0_cust_churn_overview_dw.ipynb +++ b/use-cases/customer_churn/0_cust_churn_overview_dw.ipynb @@ -160,52 +160,9 @@ }, { "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "/opt/conda/lib/python3.7/site-packages/secretstorage/dhcrypto.py:16: CryptographyDeprecationWarning: int_from_bytes is deprecated, use int.from_bytes instead\n", - " from cryptography.utils import int_from_bytes\n", - "/opt/conda/lib/python3.7/site-packages/secretstorage/util.py:25: CryptographyDeprecationWarning: int_from_bytes is deprecated, use int.from_bytes instead\n", - " from cryptography.utils import int_from_bytes\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\u001b[33m\n", - "\u001b[0m/opt/conda/lib/python3.7/site-packages/secretstorage/dhcrypto.py:16: CryptographyDeprecationWarning: int_from_bytes is deprecated, use int.from_bytes instead\n", - " from cryptography.utils import int_from_bytes\n", - "/opt/conda/lib/python3.7/site-packages/secretstorage/util.py:25: CryptographyDeprecationWarning: int_from_bytes is deprecated, use int.from_bytes instead\n", - " from cryptography.utils import int_from_bytes\n", - "Requirement already satisfied: sagemaker in /opt/conda/lib/python3.7/site-packages (2.88.1)\n", - "Requirement already satisfied: boto3 in /opt/conda/lib/python3.7/site-packages (1.22.4)\n", - "Requirement already satisfied: pandas in /opt/conda/lib/python3.7/site-packages (from sagemaker) (1.0.1)\n", - "Requirement already satisfied: google-pasta in /opt/conda/lib/python3.7/site-packages (from sagemaker) (0.2.0)\n", - "Requirement already satisfied: attrs==20.3.0 in /opt/conda/lib/python3.7/site-packages (from sagemaker) (20.3.0)\n", - "Requirement already satisfied: smdebug-rulesconfig==1.0.1 in /opt/conda/lib/python3.7/site-packages (from sagemaker) (1.0.1)\n", - "Requirement already satisfied: importlib-metadata>=1.4.0 in /opt/conda/lib/python3.7/site-packages (from sagemaker) (1.5.0)\n", - "Requirement already satisfied: packaging>=20.0 in /opt/conda/lib/python3.7/site-packages (from sagemaker) (20.1)\n", - "Requirement already satisfied: pathos in /opt/conda/lib/python3.7/site-packages (from sagemaker) (0.2.8)\n", - "Requirement already satisfied: protobuf>=3.1 in /opt/conda/lib/python3.7/site-packages (from sagemaker) (3.20.0)\n", - "Requirement already satisfied: numpy>=1.9.0 in /opt/conda/lib/python3.7/site-packages (from sagemaker) (1.21.5)\n", - "Requirement already satisfied: protobuf3-to-dict>=0.1.5 in /opt/conda/lib/python3.7/site-packages (from sagemaker) (0.1.5)\n", - "Requirement already satisfied: s3transfer<0.6.0,>=0.5.0 in /opt/conda/lib/python3.7/site-packages (from boto3) (0.5.2)\n", - "Requirement already satisfied: botocore<1.26.0,>=1.25.4 in /opt/conda/lib/python3.7/site-packages (from boto3) (1.25.4)\n", - "Requirement already satisfied: jmespath<2.0.0,>=0.7.1 in /opt/conda/lib/python3.7/site-packages (from boto3) (1.0.0)\n", - "Requirement already satisfied: urllib3<1.27,>=1.25.4 in /opt/conda/lib/python3.7/site-packages (from botocore<1.26.0,>=1.25.4->boto3) (1.26.9)\n", - "Requirement already satisfied: python-dateutil<3.0.0,>=2.1 in /opt/conda/lib/python3.7/site-packages (from botocore<1.26.0,>=1.25.4->boto3) (2.8.1)\n", - "Requirement already satisfied: zipp>=0.5 in /opt/conda/lib/python3.7/site-packages (from importlib-metadata>=1.4.0->sagemaker) (2.2.0)\n", - "Requirement already satisfied: pyparsing>=2.0.2 in /opt/conda/lib/python3.7/site-packages (from packaging>=20.0->sagemaker) (2.4.6)\n", - "Requirement already satisfied: six in /opt/conda/lib/python3.7/site-packages (from packaging>=20.0->sagemaker) (1.14.0)\n", - "Requirement already satisfied: pytz>=2017.2 in /opt/conda/lib/python3.7/site-packages (from pandas->sagemaker) (2019.3)\n", - "Requirement already satisfied: dill>=0.3.4 in /opt/conda/lib/python3.7/site-packages (from pathos->sagemaker) (0.3.4)\n", - "Requirement already satisfied: ppft>=1.6.6.4 in /opt/conda/lib/python3.7/site-packages (from pathos->sagemaker) (1.6.6.4)\n", - "Requirement already satisfied: multiprocess>=0.70.12 in /opt/conda/lib/python3.7/site-packages (from pathos->sagemaker) (0.70.12.2)\n", - "Requirement already satisfied: pox>=0.3.0 in /opt/conda/lib/python3.7/site-packages (from pathos->sagemaker) (0.3.0)\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\u001b[33m\n", - "\u001b[0m" - ] - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "!pip install -q 's3fs==0.4.2' 'sagemaker-experiments'\n", "!pip install --upgrade sagemaker boto3\n", @@ -214,7 +171,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -237,7 +194,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -265,17 +222,9 @@ }, { "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "download: s3://sagemaker-sample-files/datasets/tabular/customer-churn/customer-churn-data.zip to data/raw/customer-churn-data.zip\n" - ] - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "##### Alternative: copy data from a public S3 bucket to your own bucket\n", "##### data file should include full_data.csv and sample.json\n", @@ -285,53 +234,18 @@ }, { "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Archive: ./data/raw/customer-churn-data.zip\n", - " inflating: ./data/data_wrangler_output.csv \n", - " inflating: ./data/full_feature_data.csv \n", - " inflating: ./data/sample.csv \n", - " extracting: ./data/sample.zip \n", - " extracting: ./data/simu-1.zip \n", - " extracting: ./data/simu-2.zip \n", - " extracting: ./data/simu-3.zip \n", - " extracting: ./data/simu-4.zip \n", - " inflating: ./data/test.csv \n", - " inflating: ./data/test_updated.csv \n", - " inflating: ./data/train_updated.csv \n", - " inflating: ./data/validation_updated.csv \n" - ] - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "!unzip -o ./data/raw/customer-churn-data.zip -d ./data" ] }, { "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Archive: data/simu-1.zip\n", - " inflating: data/raw/simu-1.json \n", - "Archive: data/simu-2.zip\n", - " inflating: data/raw/simu-2.json \n", - "Archive: data/simu-3.zip\n", - " inflating: data/raw/simu-3.json \n", - "Archive: data/simu-4.zip\n", - " inflating: data/raw/simu-4.json \n" - ] - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "# unzip the partitioned data files into the same folder\n", "!unzip -o data/simu-1.zip -d data/raw\n", @@ -342,7 +256,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -351,39 +265,18 @@ }, { "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Archive: data/sample.zip\n", - " inflating: data/raw/sample.json \n" - ] - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "!unzip -o data/sample.zip -d data/raw" ] }, { "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "upload: data/raw/simu-1.json to s3://sagemaker-us-west-2-688520471316/music-streaming/data/json/simu-1.json\n", - "upload: data/raw/sample.json to s3://sagemaker-us-west-2-688520471316/music-streaming/data/json/sample.json\n", - "upload: data/raw/simu-2.json to s3://sagemaker-us-west-2-688520471316/music-streaming/data/json/simu-2.json\n", - "upload: data/raw/simu-4.json to s3://sagemaker-us-west-2-688520471316/music-streaming/data/json/simu-4.json\n", - "upload: data/raw/simu-3.json to s3://sagemaker-us-west-2-688520471316/music-streaming/data/json/simu-3.json\n" - ] - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "!aws s3 cp ./data/raw s3://$bucket/$prefix/data/json/ --recursive" ] @@ -401,7 +294,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -418,7 +311,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -430,124 +323,9 @@ }, { "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
tsuserIdsessionIdpageauthmethodstatuslevelitemInSessionlocationuserAgentlastNamefirstNameregistrationgenderartistsonglength
0159214626773112065118NextSongLogged InPUT200paid0Richmond, VA\"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebK...DavisBristol1.591971e+12MPeter ToshWanted Dread And Alive (2002 Digital Remaster)267.85914
1159214626873112065118Thumbs DownLogged InPUT307paid1Richmond, VA\"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebK...DavisBristol1.591971e+12MNaNNaNNaN
\n", - "
" - ], - "text/plain": [ - " ts userId sessionId page auth method status \\\n", - "0 1592146267731 12065 118 NextSong Logged In PUT 200 \n", - "1 1592146268731 12065 118 Thumbs Down Logged In PUT 307 \n", - "\n", - " level itemInSession location \\\n", - "0 paid 0 Richmond, VA \n", - "1 paid 1 Richmond, VA \n", - "\n", - " userAgent lastName firstName \\\n", - "0 \"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebK... Davis Bristol \n", - "1 \"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebK... Davis Bristol \n", - "\n", - " registration gender artist \\\n", - "0 1.591971e+12 M Peter Tosh \n", - "1 1.591971e+12 M NaN \n", - "\n", - " song length \n", - "0 Wanted Dread And Alive (2002 Digital Remaster) 267.85914 \n", - "1 NaN NaN " - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "sample.head(2)" ] @@ -563,7 +341,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -582,41 +360,9 @@ }, { "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "percentage of the value missing in each column is: \n" - ] - }, - { - "data": { - "text/plain": [ - "ts 0.000000\n", - "userId 0.000000\n", - "sessionId 0.000000\n", - "page 0.000000\n", - "auth 0.000000\n", - "level 0.000000\n", - "itemInSession 0.000000\n", - "location 0.025447\n", - "userAgent 0.025447\n", - "registration 0.025447\n", - "gender 0.025447\n", - "artist 0.210330\n", - "song 0.210330\n", - "length 0.210330\n", - "dtype: float64" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "print(\"percentage of the value missing in each column is: \")\n", "sample.isnull().sum() / len(sample)" @@ -624,7 +370,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -643,28 +389,9 @@ }, { "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "The unique values in column page are: ['NextSong' 'Thumbs Down' 'Home' 'Settings' 'Thumbs Up' 'Add to Playlist'\n", - " 'Roll Advert' 'Save Settings' 'Help' 'Logout' 'Add Friend' 'Downgrade'\n", - " 'About' 'Upgrade' 'Error' 'Submit Upgrade' 'Submit Downgrade' 'Cancel'\n", - " 'Cancellation Confirmation']\n", - "The unique values in column auth are: ['Logged In' 'Cancelled']\n", - "The unique values in column level are: ['paid' 'free']\n", - "The unique values in column gender are: ['M' 'F']\n", - "There are 72 unique values in column location\n", - "There are 37 unique values in column userAgent\n", - "There are 16207 unique values in column artist\n", - "There are 51447 unique values in column song\n", - "There are 101 unique values in column userId\n" - ] - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "cat_columns = [\"page\", \"auth\", \"level\", \"gender\"]\n", "cat_columns_long = [\"location\", \"userAgent\", \"artist\", \"song\", \"userId\"]\n", @@ -696,7 +423,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -713,7 +440,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -740,17 +467,9 @@ }, { "cell_type": "code", - "execution_count": 19, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "There are 12.87% of users churned in this dataset\n" - ] - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "print(\n", " \"There are {:.2f}% of users churned in this dataset\".format(\n", @@ -772,7 +491,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -826,20 +545,9 @@ }, { "cell_type": "code", - "execution_count": 21, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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c+Jmj9ciESqX1q1bJwAkISFBndagQQMZM2aMXLp0SfR6vWzcuFGdV6tWLWnWrJlNH4MGDRJXV1d56aWXJCkpSZYvXy533XWX+Pv7y5kzZ9R28fHxoiiKREVFybp16+SLL76Qbt26iV6vl+TkZLXda6+9JgDk/PnzIiKybds2qVixovTp00euXLli127YsGGSlJQkH330kQQGBoqfn5/cf//9arvMzEwZNGiQLFu2TLZv3y6JiYkSFxcnLi4usnz5crXdmjVrBIBNLSIi69evFwCyefPmQrflK6+8IgBk//79NtOff/55ASC///57ocvPmzdPAMiDDz4oX375pVy6dKnQ9kWRnZ0t7u7u0qdPH3XapEmTJCQkREREunfvLoMGDVLnvfTSSwJAjh49qk5bv3696HQ66dKli6xevVo2bdokjz76qCiKImvXrlXbHTlyRKpUqSL16tWTxYsXy5dffimvvfaa6PV6GTVqlNru+++/FwCyYMECERE5ffq0NG/eXIKDg+XAgQMl7q9mzZrSu3dv+fzzz2X16tUSEhIiAQEBkpWVpbZ97733BIA8/vjjsmnTJomPj5fg4GAJDAyU8PBwtd3cuXNFURQZPXq0bN26VbZs2SKzZ8+W0aNH39Tr4cjGjRsFgEyePFlERHr06CGurq42x47FYpHg4GDp2bOn3fIdO3aUevXqqY93794tJpNJmjdvLitWrJDNmzfLk08+KQBk9uzZarv//e9/AkACAwMlJiZGNm7cKOvWrZMrV67Inj17ZOTIkfLpp59KSkqKrF+/Xvr27Stubm7y/fffq32cPXtW/P39xd/fXxYuXChJSUkybNgwqVmzpgCQL774Qm27Zs0a0el0EhkZKWvWrJGNGzfKww8/LC4uLvL5558Xuo3MZrO0aNFCvL29ZerUqbJ161aZM2eO+Pj4SIsWLSQnJ0dERBYtWiQAbGoUEdmxY4fN+5zFYpE+ffqI0WiUsWPHypYtW2TJkiUSHBwstWvXlgsXLqjLhoaGir+/v9x1112yePFi2bJli/Tv318AqO+NmZmZ6r41ffp02b17t+zevVv++usvuXr1qvj7+0vDhg1lxYoVkpqaKv/73//kmWeekb179xb6vLt37y4VKlSQwMBAmTFjhmzZskVeeuklURRFYmJi1HbFfT5+fn4SHBws8+fPl+TkZNm9e3eBNbzzzjsCQKKjo2XTpk2yePFiCQoKksDAQImIiFDbZWdnS//+/eXjjz+Wbdu2SVJSkrz66qvi6uoqc+bMUdtt27ZNAMi6dets1pOamioAZNWqVYVuk9DQUKlWrZoEBgbK/PnzJTExUYYMGSIAZMKECWq7ou4zIiIeHh4SGBgo9evXl08++USSk5MlPT29wBo8PDzkhRdecFhbVFSU+viFF14Qg8EgU6dOle3bt8vGjRvlnXfekenTp6ttSuN4LQqGuHLq3Llz4uLiIk888YSIXHtTVhRFkpKSRETk3nvvlRdffFFERI4fPy4AbD7IvvrqKwEgs2bNsun36NGjYjAY5NVXXxURkUuXLknFihWld+/eNu1yc3MlNDRUWrVqpU7LH+KWLFkirq6uMmrUKMnLy1PbnD17VgwGgzzyyCM2/VnfCPKHuOvl5uZKTk6OxMTE2ATS3NxcCQ4OtjkIRUQ6deok9erVE4vFUmCfIiK///67AJC4uDh12tWrV6VKlSrSrl27QpcVEcnLy5OBAweKoigCQFxcXCQsLExeeukl+eOPP264fEEeeOAB8fX1Vevv0KGDDB06VEREpk2bJjVr1lTbNmvWTGrXrq0+zsnJkcDAQGnbtq3d82/Tpo1NeOjbt6/4+PjI6dOnbdqNHTtWdDqd+hzyh7iff/5ZatWqJREREXLq1Cmb5YrbX/59SERky5YtAkASExNFRCQrK0u8vb2lY8eONu1++uknURTFJsT179/fZrvcSn369BGdTicnTpwQkf++WL377rs27caPHy96vd5mexw9elQURZEpU6ao0yIiIuTuu+8Ws9lss/xjjz0mlSpVUqdbPxTyB/yCWI+ZRo0aybBhw9TpkyZNEgDy7bff2rSPioqyCXHWINOxY0e7/ahly5Zyzz33FLr+Dz74QABIamqqzfSkpCQBIJ9++qmIXHuf8fDwkOeee86m3eDBg8Xb21sN9F988YUAkI8//tim3YEDB8TFxUWmTZumTgsNDRW9Xi+//vqrOi0nJ0cCAgIkOjpanZaYmGgXXEVEfvnlFwEgy5YtK/Q5OtK9e3cBYPNlU+S/L4Y///xziZ6PoiiFhhSry5cvS4UKFaRTp04209PT00VRFJsQd728vDzJycmR5557Tv3SaBUWFmb3Hv3II49IQECATbhyJDQ0VADI119/bTO9d+/eYjKZ1C//Rd1nRK6FMnd3d7v3oIIUNcS1aNFCHnjggUL7uhXHqyMMceVY48aN1Q/jNWvWiF6vV88CvfTSS+qB+vHHH9t8KIqIvPzyy+Li4iJnz56VnJwcm5+mTZuqH6zWN7jPPvvMrt0LL7wgOp1OfYO1hrhRo0aJTqeT999/367mzz//XO3veoGBgXZvECtXrpSWLVuKu7u7AFB/PD09bdpNmzZN9Hq9+oH666+/iqIodiG1IG3btpWqVavK1atXRURk7dq1Dt9cC5ORkSEffPCBxMTESK1atQSAeHl5yZ49e4rcR36TJ08WAPLjjz+K2WwWk8mkfqDs2bNHAMiRI0fkwoULotPpZMiQIeqyaWlpAkAWLlxo97q99dZbAkD+/PNPERHx9PSUAQMG2LX7+uuvbc6CWENXTEyMVKpUSXr06CH//vuvXd3F7e/tt9+2Wf6vv/4SADJv3jwREdm1a5f6XK4XHh5uE+Lef/99ASCDBg2SDRs2yLlz54q0ra1hx/qT/4uHI2fOnBFXV1fp1q2bOi0nJ0f8/PwkNDTUpu3hw4dFURSbcDdp0iTR6XRy8uRJERE5deqUAJCJEyfabbelS5faBC7rh8L1AUHk2gfwO++8I2FhYeLm5mZzzLRu3Vpt17FjR6lVq5bd8qtXr7YJNLt37xYAsmTJEru6Xn/9dQFgc+bxel26dJHatWvbLZudnS0Gg0GGDx+uto2JiREfHx/Jzs4WkWtBxMvLS55++mm1zZNPPilGo1GuXLli12fdunVtXo/Q0FAJCwuzq+mBBx6w2RYFhbgrV66In5+f1K5dW2bPni0//fTTDb8QWnXv3l30er1dsLEel3Pnzi3R87k+VBXEegZzyZIldvNCQ0PtQtz69eulTZs24unpabPPALB5DvPnz7cJoSdPnhS9Xi+TJk26YU2hoaEOv2BZ92fr51Nx9hkPDw+7oFqYooa45557TvR6vcTFxcnWrVvt3udK63gtCo6JK8c6dOiA3377DX/++SeSk5MREREBT09PANfGiX3//fe4cOECkpOTodfr0bp1a3XZ06dPw2KxwNfXF66urjY/3333Hc6ePau2A679KYfr27377rvIy8vD+fPnbepatmwZgoKC8Oijj9rVnJmZCQCoVq2a3bzrp3366ad47LHHEBQUhOXLl2P37t1IS0vDwIED7QavDh06FG5ubvjoo48AALNnz4a7uztiY2OLtC2HDBmCM2fOYOPGjQCA+Ph4VKhQwWacxI3UqVMHzzzzDJYsWYLDhw8jPj4ely5dwiuvvFLkPvKzjm9LTk7GN998g6ysLHXcSZMmTeDp6YmUlBR89dVXyMvLsxkPZ33dhg4dave6vfrqqwCAs2fP4vLly/j333/xySef2LW777771Hb5bdy4EefPn8dTTz0FDw8Pm3kl6c/Hx8fmscFgAAD1NS7OPjNixAjMmTMH+/fvx0MPPQRfX1+0adMGO3fuLHhDA4iIiLCpNS4urtD2S5cuRU5ODh555BH8888/+Oeff/Dvv/8iKioKBw4cwJ49e9S2tWrVQvv27dXxNCKCpUuXokuXLggICADw3+s1ceJEu+02cOBAAPbbzd/f366usWPHYvTo0ejSpQvWr1+PPXv2IC0tDS1atLA5ZjIzM+Hn52e3/PXTrHXFxsba1TV+/HiHdV2//OHDh+2WNRgMyM7Otll28ODByMzMVMe8rl69GpcuXcKgQYNs+jObzXB3d7frMyMj44b7FnBt/yrK4HeTyYTU1FS0bNkSEyZMQIMGDeDv749XXnkFZrP5hsv7+vpCr9fbTLPur9Z9urjPx9Fr7khxjpnNmzejV69eqFSpEj7++GPs2rULaWlpGDFiBADYPNf+/fvDx8dHHVc4b948KIqCJ554okh1FVZP/m1S1H0GKPo2KY6pU6diwoQJ2Lx5Mx544AFUrlwZ3bt3x8GDB9UagZs/XotCf+MmpFUdOnTAjBkzkJKSgpSUFHTr1k2dZw1sO3bsUG94sAY84NobjIuLC3bu3AlXV1e7vo1Go9oOAD788EM0a9bMYR3WNlZbtmxBVFQU2rRpg23btqFGjRrqPOub6l9//WXXz19//QVvb2/18bJlyxASEoIVK1ZAURR1enZ2tt2ylSpVwoABAzB//nyMGjUKH3/8MQYMGIAKFSo4rPl6Dz/8MJ599lksXrwYLVu2RGJiIoYMGQJ3d/ciLe9IbGwsJk+ejP3795doeetrlpKSgvPnz6N27dqoXr06AECv1+O+++5DcnIyqlatCuDan56xsr4mb731Fjp16uSwf+ugXZPJhG7duhUYNoOCgmwev/HGG0hNTUWfPn2wYsUKm7+h5+7uXuz+buRG+0x+Li4uGD58OIYPH46LFy8iOTkZ48aNQ2RkJI4cOWK3r1olJCTY3OnnKODkZ72BYdCgQTYhw2rRokVo3ry5+njQoEEYOHAgvvvuO1y+fBmHDh3C22+/rc631jVq1ChER0c7XGdISIjN4/zHhNWyZcsQFRWF6dOn20w/f/68zfHv4+Pj8Aam67enta6pU6eiY8eODuuqVauWw+nW5evUqYOVK1c6nJ//ZqC2bduiTp06iI+PR1RUFOLj4xEaGmrzvuPr6wt3d3ekpqY67O/6LxU366677sKyZcsgIjhw4ACWL1+Ot99+G3q9Hm+++Wahy549exa5ubk2Qc66fa37dHGfj6PX3JEbHTPW93cAWL58OapWrYq1a9fa3MDl6O9BmkwmDB06FHPnzsXEiROxYMECREVFOQxnjhR2DOffJkXdZ4CibxPg2ueao8+P6wOX0WjE2LFjMXbsWJw9exabN2/GK6+8gq5du+LYsWOldrwWSYnO35EmWC+j9e7dWxRFkU2bNtnMb9KkiTrGxTrGzSolJUUAyJo1a264jgoVKsizzz57w3ryj4k7ceKE3H333RIcHCwZGRlqm7Nnz4qbm1uRxsQ99NBDdpemTp48KR4eHqLT6ezWf+DAAQEgHTp0cHijwo08+eST6il0AEW+DGq9LHm98+fPi6enp83lvuLq2rWr+Pj4SLt27WTw4ME289566y2pUaOGNG3a1GaMm8h/Y5kef/zxG67j4Ycflrp16zq8NJpf/jFxeXl5MmTIENHpdHZjhkrSX37nz58XAPLee++JyLUxcRUrVrQbE7d//367MXGOLFmyxOFYnJKyXt6NiYmR5ORku5/mzZuLl5eXXL58WV3GOkZp+PDhEhsba3PZ0KpRo0bSrl27G17KtV6euf5GHhGRoKAgm/FeIv8d6/kvoRV1TFx2drZUrVpVBgwYUKRtc72ZM2eKq6urevntRt544w3R6XTy9ddf212CFhH57LPPBIBs27bthn2FhoY6HNPavXt3m22xfft2u7FWhalevbpERkYW2qaoY+JK4/k4Yr0UXZQxcQMHDpTq1avbXCo+d+6cVK5cWQDY3ah1/Phx0ev16vvszp07i1RTYWPijEajOiauOPuMh4eHzY0iNxIRESFt27a1mWYdmnL9mOrrjR07VgCoY1tL43gtCoa4cq5Zs2aiKIrodDqbO5lErr1hWAfbf/nll3bLDh48WDw8PGT06NGyYcMG2b59uyxfvlyeeuop+eijj9R2S5YsERcXF3n88cdl9erV6l1aY8eOlREjRqjtrr879cyZM9KoUSPx9/e3uXvRejfoje5OtY6/eOaZZ2Tbtm0SHx8vtWrVkpCQEIchTuTazQwApH379sXelt9++60AEEVRHI6lEbl2R6JOp7P5cImJiZGmTZvKtGnTZPPmzZKcnCwLFy6UBg0aiKIoNndtzZkzR3Q63Q3Ds9XUqVPVmq4fn2cdY6Yoijz55JN2y65fv170er307NlTVq5cKampqbJmzRp5/fXXZeDAgWq7w4cPi5+fn4SHh8uCBQskOTlZNmzYILNmzZLOnTur+9X1octiscjIkSPFxcXFZn8paX9W14c4EZHp06fb3J1qvYPv+rtTH3vsMYmLi5NPP/1UUlNTJSEhQe6++26pWrVqqdw1LCLqHXW//PKLw/nWMTHXj0caNmyYeHt7i6enp90AfpFr48/c3d2lffv28sknn0hKSop89tlnMnXqVOnRo4farrAPheHDh4ter5epU6fKtm3b5L333pMqVapIcHCwzQf333//rd6dumjRIvXu1ODgYJu7N0WujQ+1fllctWqVuh9NnDjR5g5pR7KysqRVq1ZSrVo1mTp1qmzevFm2bNkiixYtkscff1y2b99u0/6PP/4QFxcXqV69ut3NICLX9rmoqCipWLGijBs3TjZt2iTbtm2TpUuXypAhQ2xCU1FD3F9//SUuLi7StWtX2bFjh6SlpcmZM2dk+/bt0qlTJ5k7d65s3rxZtm7dKqNGjRIAMmPGjEKft/Xu1ICAAPXu1NGjR4uiKDaBuDSeT0Hefvtt9e7UxMTEAu9O/fTTT9VxpFu3bpVPPvlE6tevLyEhIQ5DnMi1mxkASKNGjYpcT/67UxcsWCBJSUnqsTRu3Di1XXH2meKGuJkzZ6o3+W3dulXmzp0rISEh4uPjYxPi2rZtK+PHj5e1a9dKamqqLFy4UPz8/KRhw4Zqm9I4XouCIa6cGz16tACQpk2b2s2zfstzc3OzOStgZbFYZMGCBXLvvfeKu7u7mEwmqVu3rsTExMi+ffts2iYnJ0tkZKRUqlRJ3NzcpHr16tKjRw+bMHJ9iBO59oHcsmVL8fX1Vfu0WCzy5ptvSvXq1cXNzU3Cw8Nl06ZNct9999mEOIvFIpMnT5aaNWuKwWCQe+65RxYtWiSvvfZagSFu4cKFAkBWr15dvA35/xo2bFjom7T1brJ33nlHnfbVV1/JU089JQ0aNJBKlSqJXq+XqlWrSo8ePWTr1q02y1vvvPrf//5XpHqswRKw/fMhItfOtllv+FixYoXD5ffs2SO9evUSX19fcXV1lYCAAOnSpYssXbrUpt3JkyflqaeekqCgIHF1dRUfHx9p3ry5TJgwQb3Zo6DQZf2Gmj/Y3kx/jkKciMisWbOkVq1a4ubmJvfcc4+sWrVKevbsafcnRqw3qbi5uUlgYKAMGDDA5g7Fm/Hvv/+Kp6dnoV8SsrKypFKlStKmTRub6dYzeID9n9KwOnjwoPTr10+qVasmer1e/Pz8pF27djY36BT2oXDp0iV5+umnxc/PT0wmk7Ro0UK2bt1qF1xErt38ExkZKSaTSby9vWXAgAGyatUqh2dLdu/eLT179hQfHx91P+ratWuR7tzMysqS119/Xe655x4xGAzi5eUloaGh8uyzzzq8e7tz584CwOGfZRG5dvPGBx98IE2aNBGTySQeHh5Sr149GTp0qM2XxaKGOBGRjz76SOrUqSN6vV4AyAcffCCHDx+W6OhoCQkJEXd3d6lQoYJERETIRx99dMMbHLp37y7BwcGye/duadGihRiNRqlataq88MILdmdgb/b5FGbGjBlSs2ZNcXNzk9DQUFm9erXD5z9z5kypXbu2GAwGqVevnnzwwQfqnyhxFOKsN8A4utmoINb6v/jiC2nYsKG4ublJjRo1ZMqUKXbbs6j7THFDXE5OjowbN06Cg4PFaDTKfffdJ99++63djQ0TJ06U5s2bS+XKlcVgMEjNmjVlxIgR8tdff9n0d7PHa1EoIjf4a51E5UjPnj3x/fff4/Dhw3aDiomocHFxcfjwww/x999/w8vLy9nlaFaPHj2wf/9+HD161Nml3BIxMTHYsGEDTpw4AZPJVKRlwsLC4OvrW6Q/Ek//4acYlXvZ2dnYt28fdu/ejS+++ALvv/8+AxzRDUydOhXe3t4ICQnBlStXkJSUhHnz5uG5555jgCM7ubm5+O6775Ceno7ly5dj4sSJRQ5wVHL8JKNy748//kCrVq1QoUIF9c5EIiqcwWDAzJkz8ccffyAnJwe1a9fG5MmTHf5bIqJ//vkHLVu2hKenJwYMGHDDf/lFpYOXU4mIiIg0iH/sl4iIiEiDGOKIiIiINIghjoiIiEiDeGNDOWSxWPDnn3/Cy8ur5P/Kg4iIiG4rEcGlS5cQEBAAF5cbn2djiCuH/vzzT5v/R0pERETa8ccff6j/C7swDHHlkPVvOP3xxx9F/gfvRERE5FwXL15EjRo1ivy3GBniyiHrJdQKFSowxBEREWlMUYdC8cYGIiIiIg1iiCMiIiLSIIY4IiIiIg1iiCMiIiLSIIY4IiIiIg1iiCMiIiLSIIY4IiIiIg1iiCMiIiLSIIY4IiIiIg1iiCMiIiLSIIY4IiIiIg1iiCMiIiLSIIY4IiIiIg1iiCMiIiLSIL2zCyC6U4kIzGazs8uwIyLIzs4GABgMBiiKcv6rYhcAACAASURBVMvWZTQab2n/RETlGUMckZOYzWZERkY6uwynSkxMhMlkcnYZRESaxMupRERERBrEM3FEZcC/jR6HuJSRwzEvB14/rAQAXAp/DNC5lmr3iiUXnukrSrVPIqI7URn51CC6s4mLvtTDUqnQuZZ6XVKqvRER3bl4OZWIiIhIgxjiiIiIiDSIIY6IiIhIgxjiiIiIiDSIIY6IiIhIgxjiiIiIiDSIIY6IiIhIgxjiiIiIiDSIIY6IiIhIgxjiiIiIiDSIIY6IiIhIgxjiiIiIiDSIIY6IiIhIg/TOLoC0Q0RgNpsBAEajEYqiOLkiIuJxSXTn4pk4KjKz2YzIyEhERkaqHxpE5Fw8LonuXAxxRERERBrEEEdERESkQQxxRERERBrEEEdERESkQQxxRERERBrEEEdERESkQQxxRERERBrEEEdERESkQQxxRERERBrEEEdERESkQQxxRERERBrEEEdERESkQQxxRERERBrEEEdERESkQQxxRETkFLt27ULfvn2xa9cuZ5dCVCxlZd9liCMiotvObDZjxowZOH36NGbMmAGz2ezskoiKpCztuwxxRER02y1fvhyZmZkAgMzMTCQkJDi5IqKiKUv7rt5paybNERH1d35rvnk22zDfti33uB+VqvzbUDSyH504cQIJCQlqvSKChIQEdO7cGdWrV3dydUQFK2v7LkMcFVl2drb6e+/evZ1YSTlkyQXg5uwqbg9Lrvor96PSlZ2dDXd3d2eXUSgRwaxZswqcPm3aNCiK4oTKiApXFvddXk4lIqLb5vjx40hLS0NeXp7N9Ly8PKSlpeH48eNOqoyocGVx3+WZOCoyg8Gg/r5u3ToYjUYnVqN9ZrP5vzNRLnfQoZjvuXI/unn596P8x2hZFRQUhGbNmmHfvn02H4Y6nQ4REREICgpyYnVEBSuL++4d9MlBNyv/aWKj0QiTyeTEasqZO+nyEfejW0YLlyEVRcHIkSMRExPjcLoWngPdmcrivsvLqUREdFtVr14d/fr1Uz/0FEVBv379EBgY6OTKiApX1vZdhjgiIrrtoqOj4ePjAwDw9fVFv379nFwRUdGUpX2XIY6IiG47o9GIuLg4+Pn54fnnn+fYSNKMsrTvckwcERE5RatWrdCqVStnl0FUbGVl3+WZOCIiIiINYogjIiIi0iCGOCIiIiINYogjIiIi0iCGOCIiIiINYogjIiIi0iCGOCIiIiINYogjIiIi0iCGOCIiIiINYogjIiIi0iCGOCIiIiINYogjIiIi0iC9swsg7TAajUhMTFR/JyLn43FJdOdiiKMiUxQFJpPJ2WUQUT48LonuXLycSkRERKRBDHFEREREGsQQR0RERKRBDHFEREREGsQQR0RERKRBDHFEREREGsQQR0RERKRBDHFEREREGsQQR0RERKRBDHFEREREGqT5EKcoCj777DNnl0FERER0W2kmxE2cOBGNGjWym37q1ClERkY6oSIiIiIi59E7u4CbVa1aNWeXUOquXr0KNzc3Z5dBREREZdhtOxOXlJSE1q1bw9vbGz4+PujRowcOHTpk0+bEiRN47LHHULlyZXh4eKBp06bYs2cPlixZgkmTJuGHH36AoihQFAVLliwBYHs5tWXLlnjllVds+vz777/h6uqK5ORkANcC0ujRoxEYGAgPDw80b94cKSkpBdZ99OhRKIqC9PR0ddo///wDRVHU5c6fP4/o6GhUqVIFJpMJISEhiI+PV9ufPHkSffv2RaVKleDj44OePXvi6NGj6vzY2Fj06tULU6ZMQUBAAOrVqwcA+PDDDxESEgKj0Qg/Pz88/PDDxdrmpB2KJRfIyyk7P1a3oG/Fkuu8DU1EVI7ctjNxly9fRlxcHBo0aIDLly9j/Pjx6N27N9LT0+Hi4oJ///0X7dq1Q2BgID7//HNUq1YN+/btg8ViQd++fbF//34kJSVh69atAICKFSvarSM6OhrvvPMOpkyZAkVRAACrVq2Cn58f2rVrBwAYNGgQjh49ipUrVyIgIADr1q1D165d8dNPPyEkJKREz23cuHE4ePAgEhMT4evri4yMDGRlZQEArly5gg4dOqBNmzbYsWMH9Ho93nzzTXTt2hU//vijesZt27ZtqFChAr788kuICL777js899xz+OSTT9CqVSucO3cOX331VYnqo7LPM32Fs0twyOuHlc4ugYiICnDbQlxUVJTN40WLFqFq1ao4ePAgwsLCkJCQgL///htpaWmoXLkyAKBu3bpqe09PT+j1+kIvn/bt2xfPP/88du7ciTZt2gAAEhIS0K9fP7i4uODQoUNYsWIFTpw4gYCAAADAiy++iKSkJMTHx+Ott94q0XM7fvw4GjdujKZNmwIAatasqc5buXIlXFxcsHDhQjVYxsfHw9vbGykpKejcuTMAwMPDAwsXLlRD3dq1a+Hh4YEePXrAy8sLwcHBaNy4scP1Z2dnIzs7W3188eLFEj0PIiIi0o7bFuIOHTqEcePG4ZtvvsHZs2dhsVgAXAtAYWFhSE9PR+PGjdUAVxJVqlRBp06dsHz5crRp0wZHjhzB7t27MXfuXADAvn37ICLq5Uqr7Oxs+Pj4lHi9Tz/9NKKiorBv3z507twZvXr1QqtWrQAAe/fuRUZGBry8vGyWMZvNNpeTGzRoYDMOrlOnTggODkbt2rXRtWtXdO3aFb1794a7u7vd+qdMmYJJkyaVuH5yDqPRiMTExBu2M5vN6N27922oqPStW7cORqOxwPmFzSMiosLdthD34IMPokaNGliwYAECAgJgsVgQFhaGq1evAgBMJlOprCc6OhojR47EBx98gISEBISGhiI8PBwAYLFYoNPpsHfvXuh0OpvlPD09Hfbn4nJt2KCIqNNycnJs2kRGRuLYsWPYuHEjtm7divvvvx8jRozA9OnTYbFYEBERgeXLl9v1XaVKFfV3Dw8Pm3leXl7Yt28fUlJSsGXLFowfPx4TJ05EWloavL29bdqOGTMGcXFx6uOLFy+iRo0aBW4jKhsURSn2fj+79TkYdHLjhjchOw94ZqfP/68vEwbdDRawW17BMzuvfRkzGo2ldmwTEZGt23JjQ2ZmJn7++WeMHTsW999/P+rXr4/z58/btGnYsCHS09Nx7tw5h324ubkhLy/vhuvq1asXzGYzkpKSkJCQgP79+6vzGjdujLy8PJw5cwZ169a1+SnoMq01aJ06dUqdlv8mh/ztYmNjsWzZMsycORPz588HADRp0gS///47qlatardOR+P68tPr9XjggQcwbdo0/Pjjjzh69Ci2b99u185gMKBChQo2P1Q+GXQCgw63/Oe/9ZXk59aGTCIiuua2hDjrXZnz589HRkYGtm/fbnPmCAAef/xxVKtWDb169cLXX3+Nw4cPY82aNdi9ezeAa+PMjhw5gvT0dJw9e9ZmDFh+Hh4e6NmzJ8aNG4eff/4Z/fr1U+fVq1cP0dHRGDhwINauXYsjR44gLS0NU6dOxaZNmxz2ZzKZ0KJFC7z99ts4ePAgduzYgbFjx9q0GT9+PNavX4+MjAwcOHAAGzZsQP369QFcOzPo6+uLnj174quvvsKRI0eQmpqKkSNH4sSJEwVusw0bNuD9999Heno6jh07hqVLl8JiseCuu+668QYnIiKicu+2hDgXFxesXLkSe/fuRVhYGJ5//nm88847Nm3c3NywZcsWVK1aFd26dUODBg3w9ttvq5c9o6Ki0LVrV3To0AFVqlTBihUF380XHR2NH374AW3atEFQUJDNvPj4eAwcOBAvvPAC7rrrLjz00EPYs2dPoZcfFy9ejJycHDRt2hQjR47Em2++aVf7mDFj0LBhQ7Rt2xY6nQ4rV167q8/d3R07duxAUFAQ+vTpg/r162Pw4MHIysoq9IyZt7c31q5di44dO6J+/fqYN28eVqxYgdDQ0AKXISIiojuHIvkHe1G5cPHiRVSsWBEXLlzgpdVyICsrS/2vJAvaFX+MWnFl5wHDUn1KvL78yycmJnJMHBFRERX381sz/3aLiIiIiP7DEEdERESkQQxxRERERBrEEEdERESkQQxxRERERBrEEEdERESkQQxxRERERBrEEEdERESkQQxxRERERBrEEEdERESkQXpnF0BUGkQEZrMZAGA0GqEoipMrovKE+xcRlUU8E0flgtlsRmRkJCIjI9UPW6LSwv2LiMoihjgiIiIiDWKIIyIiItIghjgiIiIiDWKIIyIiItIghjgiIiIiDWKIIyIiItIghjgiIiIiDWKIIyIiItIghjgiIiIiDWKIIyIiItIghjgiIiIiDWKIIyIiItIghjgiIiIiDWKII6fbtWsX+vbti127djm7FCLNuhXHEY9NorKNIY6cymw2Y8aMGTh9+jRmzJgBs9ns7JKINOdWHEc8NonKPoY4cqrly5cjMzMTAJCZmYmEhAQnV0SkPbfiOOKxSVT26Z1dAN25Tpw4gYSEBIgIAEBEkJCQgM6dO6N69erF6svaB4Byd8Yg//PJ9zTLrPw1lpfXwvY1KFsvQmkeR7eyTyIqfQxx5BQiglmzZhU4fdq0aVAUpcj9ZWdnq7/37t27VGosi65aAKOzi7iBq5b/fi+Pr0V2djbc3d2dXQaA0j+OblWfRHRr8HIqOcXx48eRlpaGvLw8m+l5eXlIS0vD8ePHnVQZkXbciuOIxyaRdvBMHDlFUFAQmjVrhn379tl8WOh0OkRERCAoKKhY/RkMBvX3devWwWgs6+eris5sNqtntNw08LUrf43l5bXI/xrk39ecrbSPo1vVJxHdGgxx5BSKomDkyJGIiYlxOL24l2vytzcajTCZTKVSZ1mjhatY+Wssj69FWbqUWNrH0a3qk4huDQ18r6fyqnr16ujXr5/6oaAoCvr164fAwEAnV0akHbfiOOKxSaQNDHHkVNHR0fDx8QEA+Pr6ol+/fk6uiEh7bsVxxGOTqOxjiCOnMhqNiIuLg5+fH55//vlyMX6K6Ha7FccRj02iso9j4sjpWrVqhVatWjm7DCJNuxXHEY9NorKNZ+KIiIiINIghjoiIiEiDGOKIiIiINIghjoiIiEiDGOKIiIiINIghjoiIiEiDGOKIiIiINIghjoiIiEiDGOKIiIiINIghjoiIiEiDGOKIiIiINIghjoiIiEiDGOKIiIiINEjv7AKISoPRaERiYqL6O1Fp4v5FRGURQxyVC4qiwGQyObsMKqe4fxFRWcTLqUREREQaxBBHREREpEEMcUREREQaxBBHREREpEEMcUREREQaxBBHREREpEEMcUREREQaxBBHREREpEEMcUREREQaxBBHREREpEEMcUREREQaxBBHREREpEEMcUREREQapHd2AURUdNl5CgC5xetw/HvRl1dKrxgiIioQQxyRhjyzs/JtXp/PbV0fEREVHS+nEhEREWkQz8QRlXFGoxGJiYm3bX0iguzsbACAwWCAopT88qjRaCytsoiI6DoMcURlnKIoMJlMt3Wd7u7ut3V9RERUfLycSkRERKRBDHFEREREGsQQR0RERKRBDHFEREREGsQQR0RERKRBDHFEREREGsQQR0RERKRBDHFEREREGsQQR0RERKRBDHFEREREGsQQR0RERKRBDHFEREREGsQQR0RERKRBDHFEREREGsQQR0RERKRBDHFEREREGsQQR0RERKRBDHFEREREGsQQR0RERKRB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7qty5+/kzv2KqKbeffddk2et3alv377YunWrWduxNrt378aAAQPg5+dX4Y0S5sAijsjM7hzibmdnB51OZ8Fo7o0ZnmH5QNwZt7XmvirmeLAoPXwmTJhQ5VsOanN81FU71iY0NNSi96OyiCMiImqgnJ2d4ezsXG/aodqx0osiRERERA0bizgiIiIiK8QijoiIiMgKsYgjIiIiskIs4oiIiIisEIs4IiIiqjWDwYDy8nKz/RgMhnuKc/HixcrbEYKDg5GSklLHmbAcPmKEiIiIasVgMGD0/41BYcHvZtumvqkz1n/7jcnL7auzdu1aTJs2DYsXL0bv3r2xZMkSDBkyBBkZGfD29n6A0ZoHizgiIiKqFRFBYcHvKA56BlCZ4aKeGIH0L2v9YN0FCxbg2WefxXPPPQcAWLhwIX744Qd8/vnneO+99x5EpGbFy6lERER0b1Q2gI0Zfu6hUCwrK0NaWhrCwsJMpoeFhWHPnj11lQGLYhFHRERED53Lly/DYDDAzc3NZLqbmxsuXrxooajqFos4IiIiemj9+b3CIvLQvGuYRRwRERE9dFxcXKBWqyucdcvLy6twds5asYgjIiKih45Go0FwcDASExNNpicmJqJXr14WiqpucXQqERERPZSmT5+OyMhIdO3aFSEhIVi6dClycnIwYcIES4dWJ1jEERER0b0RI2A003buwdixY5Gfn4+5c+ciNzcXAQEB2LJlC3x8fOo4QMtgEUdkZnZ2dti6davymehecV8iS1GpVNA3dQbSvzTbNvVNne9pQMLEiRMxceLEBxCR5bGIIzIzlUoFnU5n6TDoIcB9iSxFrVZj/bff1Prhu/dDpVLV6m0NDQGLOCIiIqo1FlSWx9GpRERERFaIRRwRERGRFWIRR0RERGSFWMQRERERWSEWcURERERWiEUcERERkRViEUdERERkhVjEEREREVkhFnFERERUawaDAeXl5Wb7MRgMtYpv586dGDZsGDw9PaFSqfDdd989oExYDt/YQERERLViMBgwdsxoXP690GzbdHHWY+0362v8poiSkhIEBgbi73//O/72t7894Ogsg0UcERER1YqI4PLvhVjWPx/q2r+TvtYMAsTsQK3e1TpkyBAMGTLkAUZleSziiIiI6J6oVUAjc9yYZTTDNqwQ74kjIiIiskI8E0dEtVZqUAGQO76j0s/1za24iYge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\n", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "import seaborn as sns\n", "import matplotlib.pyplot as plt\n", @@ -888,20 +596,9 @@ }, { "cell_type": "code", - "execution_count": 22, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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kQELCwu9AiIiIiIiIiIyVaKTcA8PD/z4449a1x09ehReXl56B0VERERERERkikTPEx4WFobJkyfDysoKISEhAICHDx/ixIkTSExMRHR0tMGDJCIiIiIiIjIFopPwXr164c6dO4iNjcWGDRsAAOPGjYNMJkNkZCQCAgIMHiQRERERERGRKRCdhAPA6NGj0adPHxw9ehQpKSmwt7dH+/bt4erqauj4iIiIiIiIiEyGTkk4ANSsWRMDBgwwZCxEREREREREJk2nJFypVGLfvn04efIk0tPTYWdnh9atW6Nnz54wM9M5ryciIiIiIiIyaaIz5tTUVIwYMQIXLlyAmZkZ7OzskJ6ejm+//RZr1qzBqlWr4ODgYIxYiYiIiIiIiCo00VOUffbZZ7h58yYWLlyIc+fO4ZdffsG5c+ewYMEC3Lp1C5999pkx4iQiIiIiIiKq8ETXhB8+fBgTJkxA79691ctkMhmCgoKQkpKC2NhYgwZIREREREREZCpE14QLgoCGDRtqXefu7g5BEPQOioiIiIiIiMgUiU7C27Vrh2PHjmld9+uvv8LPz0/voIiIiIiIiIhMkejm6OHh4Rg7diyUSiWCgoLg6OiI5ORk7N69G/v370dsbCzS09PV5e3s7AwaMBEREREREVFFJToJ79u3LwBg7dq1iI+PVy/Pb4ber18/jfIXL17UIzwiIiIiIiIi0yE6CY+IiIBEIjFGLEREREREREQmTXQSPm7cOGPEQURERERERGTyRA/MRkRERERERES6YRJOREREREREVEqYhBMRERERERGVEibhRERERERERKWESTgRERERERFRKWESTkRERERERFRKRCfhx48fx759+9Svk5OT8f7778Pf3x8ffPABcnJyDBogERERERERkakQnYRHR0fj+vXr6tcLFizAmTNn4OPjgx9++AGrVq0yaIBEREREREREpkJ0En7r1i14enoCABQKBQ4cOIApU6YgNjYWkZGR2Lt3r8GDJCIiIiIiIjIFopPwrKws2NjYAAD+/vtvvHjxAl27dgUAeHt74+HDh4aNkIiIiIiIiMhEiE7Cq1evjlu3bgEAjh07BhcXF9SsWRMA8OzZM5iZmRk0QCIiIiIiIiJTITpj7tChAxYtWoRr165h+/bt6NOnj3rdjRs34OrqatAAiYiIiIiIiEyF6CR84sSJePDgAb755ht4e3tjzJgx6nV79uyBj4+PQQMkIiIiIiIiMhWik3AHBwesXr1a67r169fD0tJS76CIiIiIiIiITJHoPuFRUVG4e/eu1nUZGRmYPXu23kERERERERERmSLRSfj27duRlpamdV1aWhp27Nihd1BEREREREREpkh0El6UjIwMWFhYGHKXRERERERERCajRH3CT58+jZMnT6pff/vtt/j55581yuTk5ODgwYOoX7++YSMkIiIiIiIiMhElSsJPnjyJ2NhYAIBEIsG3336rtZyLiwv7hBMREREREREVokRJ+IgRIzB06FAIgoB27dph9erV8PT01ChjYWGBqlWrGiVIIiIiIiIiIlNQoiRcLpdDLpcDAA4ePAgnJyf2/SYiIiIiIiISSfQ84a6ursaIg4iIiIiIiMjkiU7C8/LyEBcXhz179uDBgwfIycnRWC+RSHDhwgWDBUhEREREaIXQjQAAIABJREFURERkKkQn4V9++SXi4+PRsWNHdOvWjc3SiYiIiIiIiEpIdBK+b98+REREYOzYscaIh4iIiIiIiMhkScVukJGRAV9fX2PEQkRERERERGTSRCfhrVq1wqVLl4wRCxEREREREZFJE52Ez5w5EwkJCdi/fz9yc3ONERMRERERERGRSRLdJzwkJAQKhQLjx4+HRCJRzx+eTyKR4LfffjNYgERERERERESmQnQS3qNHD0gkEmPEQkRERERERGTSRCfh8+bNM0YcRERERERERCZPdJ9wIiIiIiIiItKNTkn49evXMWnSJLRv3x5NmjTB33//DQCIjY3FiRMnDBogERERERERkakQnYRfvHgRb775Jk6dOgU/Pz8olUr1umfPnmHLli0GDZCIiIiIiIjIVIhOwhcuXAgPDw8cOHAA8+fPhyAI6nXe3t74888/DRogERERERERkakQnYSfPXsWI0aMgJWVVYFR0h0dHZGcnGyw4IiIiIiIiIhMiU59ws3NzbUuz8jIgIWFhV4BEREREREREZkq0Um4h4cHfvzxR63rjh49Ci8vL72DIiIiIiIiIjJFoucJDwsLw+TJk2FlZYWQkBAAwMOHD3HixAkkJiYiOjra4EESERERERERmQLRSXivXr1w584dxMbGYsOGDQCAcePGQSaTITIyEgEBAQYPkoiIiIiIiMgUiE7CAWD06NHo06cPjh49ipSUFNjb26N9+/ZwdXU1dHxEREREREREJkN0Eq5UKiGTyVCzZk0MGDDAGDERERERERERmSTRA7N16tQJ8+fPx7Vr14wRDxEREREREZHJEp2E+/r6YuPGjQgKCsLAgQOxdev/tXffcU3c/x/AX2FEcAAOioKbFoqggv0iIrjQOlGraN27UEXEitqqX1uqdRetglqp6+usVAUt1FFHxVFRf+5aB8MK2spSjGwS7veHD9JGQEkkuUBfz8ejj5rPXS4vksvdvXOf+1wEsrOztZGNiIiIiIiIqFpRuwhfvXo1zp07h88//xyCICA4OBienp6YPXs2zp8/r42MRERERERERNWCRgOz1alTByNHjsTIkSORkJCA/fv3Izo6GjExMWjUqBFOnjxZ2TmJiIiIiIiIqjy1z4S/7O2338ann36Kr776ClZWVvjrr78qIxcRERERERFRtaPRmfASf/zxB6KionDgwAGkpaXBysoKfn5+lZWNiIiIiIiIqFpRuwjPycnB4cOHERkZiatXr8LY2Bjdu3fH4MGD4enpCYlEoo2cRERERERERFWe2kW4p6cn8vPz4eDggPnz56N///4wMzPTRjYiIiIiIiKiakXtInzo0KHw8fGBvb29NvIQERERERERVVtqF+Hz5s3TRg4iIiIiIiKiak+j0dGfPHmClStXYtiwYejVqxfi4+MBAHv27MHvv/9eqQGJiIiIiIiIqgu1i/CUlBQMGDAAO3bsgEQiQXJyMgoLCwEAd+/exY4dOyo9JBEREREREVF1oHYR/vXXX8PMzAxHjx7Fzp07IQiCctp7772HK1euVGpAIiIiIiIioupC7SI8Li4OAQEBsLKyKnU7MktLS6SlpVVaOCIiIiIiIqLqRO0ivKCgAObm5mVOy8vL433CiYiIiIiIiMqhdhHeokUL/Prrr2VOu3TpEuzs7N44FBEREREREVF1pHYRPnToUGzfvh3btm3Ds2fPAABFRUU4cuQIdu/ejWHDhlV6SCIiIiIiIqLqQO37hI8aNQp37tzB0qVLsXz5cgDAyJEjIQgChg4dikGDBlV6SCIiIiIiIqLqQO0iHAC++uor+Pj44NSpU8jMzETdunXRtWtXtGvXrrLzEREREREREVUbGhXhAODs7AxnZ+fKzEJERERERERUral9TTgRERERERERaYZFOBEREREREZGOsAgnIiIiIiIi0hEW4UREREREREQ6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2oW8vDyV+VxcXGBubo7g4GAEBATAyMgI0dHRpX7w+KexY8dixowZkEgkGDlyZIX/JiIiojfFIpyIiOgNSCQSrF+/HmFhYYiMjMSGDRtgYWGBgQMHIigoSOXs7KRJk5CdnY2oqChs2bIFbdq0wZo1a1RG9tZEvXr1sGfPHqxcuRIrV65ETk4OGjdujNmzZ2PChAkaL/e///0vFixYgKCgIOTl5aF9+/bYsWOHWsv4+OOPYWtri+3bt+Onn35CYWEhLC0t4eTkpBwkrTx2dnbYunUrli9fjs8++wzm5uYYMGAAevXqhc8//1w5X926dREeHo7ly5dj9uzZMDU1Rffu3fHNN99g0KBBZS67R48ekEqlcHNzQ/PmzdX6m4iIiN6ERBAEQewQRERERLp08uRJTJkyBd999x26dOkidhwiIvoXYRFORERE/xoJCQl49OgRlixZAlNTU0RFRVV4ADwiIqLKwO7oRERE9K+xYMECXLlyBa1atcKyZctYgBMRkc7xTDgRERERERGRjvAWZUREREREREQ6wiKciIiIiIiISEdYhBMRERERERHpCItwIiIiIiIiIh1hEU5ERERERESkIyzCiYiIiIiIiHSERTgRERERERGRjrAIJyIiIiIiItKR/werA1cRZFcNWAAAAABJRU5ErkJggg==\n", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "events_per_hour_per_user = (\n", " sample.groupby([\"userId\", \"ts_date_day\", \"ts_hour\", \"user_churned\"])\n", @@ -941,83 +638,9 @@ }, { "cell_type": "code", - "execution_count": 23, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Average total: number of sessions, App usage length, number of songs listened, number of artists listened per user, days active: \n" - ] - }, - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
user_churnedsessionIdsongartistlengthts_date_day
002044.6363641434.1022731067.602273412310.6420962044.636364
113260.3846152173.1538461493.230769656340.5865223260.384615
\n", - "
" - ], - "text/plain": [ - " user_churned sessionId song artist length \\\n", - "0 0 2044.636364 1434.102273 1067.602273 412310.642096 \n", - "1 1 3260.384615 2173.153846 1493.230769 656340.586522 \n", - "\n", - " ts_date_day \n", - "0 2044.636364 \n", - "1 3260.384615 " - ] - }, - "execution_count": 23, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "stats_per_user = (\n", " sample.groupby([\"userId\", \"user_churned\"])\n", @@ -1054,76 +677,9 @@ }, { "cell_type": "code", - "execution_count": 24, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Average daily: number of sessions, App usage length, number of songs listened, number of artists listened per user: \n" - ] - }, - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
user_churnedsessionIdsongartistlength
0076.59770161.30183159.32311615446.290551
1192.74617174.29102871.50109418670.519967
\n", - "
" - ], - "text/plain": [ - " user_churned sessionId song artist length\n", - "0 0 76.597701 61.301831 59.323116 15446.290551\n", - "1 1 92.746171 74.291028 71.501094 18670.519967" - ] - }, - "execution_count": 24, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "stats_per_user = (\n", " sample.groupby([\"userId\", \"ts_date_day\", \"user_churned\"])\n", @@ -1152,7 +708,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -1176,7 +732,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -1185,88 +741,9 @@ }, { "cell_type": "code", - "execution_count": 27, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
user_churnednextsongthumbs_downthumbs_upadd_to_playlistroll_advertadd_frienddowngradeupgradeerror
001656.20454516.988636150.47727350.8636367.61363629.1818189.5681821.9545452.193182
112645.53846228.076923239.61538580.84615410.92307748.92307712.6153852.4615383.461538
\n", - "
" - ], - "text/plain": [ - " user_churned nextsong thumbs_down thumbs_up add_to_playlist \\\n", - "0 0 1656.204545 16.988636 150.477273 50.863636 \n", - "1 1 2645.538462 28.076923 239.615385 80.846154 \n", - "\n", - " roll_advert add_friend downgrade upgrade error \n", - "0 7.613636 29.181818 9.568182 1.954545 2.193182 \n", - "1 10.923077 48.923077 12.615385 2.461538 3.461538 " - ] - }, - "execution_count": 27, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "app_use_group = app_use_per_user.groupby([\"user_churned\"])[usage_column_name].mean().reset_index()\n", "app_use_group" @@ -1288,17 +765,9 @@ }, { "cell_type": "code", - "execution_count": 28, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Overwriting preprocessing_predw.py\n" - ] - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "%%writefile preprocessing_predw.py\n", "\n", @@ -1339,7 +808,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -1352,24 +821,9 @@ }, { "cell_type": "code", - "execution_count": 30, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "['s3://sagemaker-us-west-2-688520471316/music-streaming/data/json/sample.json',\n", - " 's3://sagemaker-us-west-2-688520471316/music-streaming/data/json/simu-1.json',\n", - " 's3://sagemaker-us-west-2-688520471316/music-streaming/data/json/simu-2.json',\n", - " 's3://sagemaker-us-west-2-688520471316/music-streaming/data/json/simu-3.json',\n", - " 's3://sagemaker-us-west-2-688520471316/music-streaming/data/json/simu-4.json']" - ] - }, - "execution_count": 30, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "s3_client = boto3.client(\"s3\")\n", "list_response = s3_client.list_objects_v2(Bucket=bucket, Prefix=f\"{prefix}/data/json\")\n", @@ -1379,7 +833,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -1396,30 +850,9 @@ }, { "cell_type": "code", - "execution_count": 32, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "Job Name: sagemaker-scikit-learn-2022-04-30-01-59-49-481\n", - "Inputs: [{'InputName': 'sample', 'AppManaged': False, 'S3Input': {'S3Uri': 's3://sagemaker-us-west-2-688520471316/music-streaming/data/json/sample.json', 'LocalPath': '/opt/ml/processing/input/data/sample', 'S3DataType': 'S3Prefix', 'S3InputMode': 'File', 'S3DataDistributionType': 'FullyReplicated', 'S3CompressionType': 'None'}}, {'InputName': 'simu-1', 'AppManaged': False, 'S3Input': {'S3Uri': 's3://sagemaker-us-west-2-688520471316/music-streaming/data/json/simu-1.json', 'LocalPath': '/opt/ml/processing/input/data/simu-1', 'S3DataType': 'S3Prefix', 'S3InputMode': 'File', 'S3DataDistributionType': 'FullyReplicated', 'S3CompressionType': 'None'}}, {'InputName': 'simu-2', 'AppManaged': False, 'S3Input': {'S3Uri': 's3://sagemaker-us-west-2-688520471316/music-streaming/data/json/simu-2.json', 'LocalPath': '/opt/ml/processing/input/data/simu-2', 'S3DataType': 'S3Prefix', 'S3InputMode': 'File', 'S3DataDistributionType': 'FullyReplicated', 'S3CompressionType': 'None'}}, {'InputName': 'simu-3', 'AppManaged': False, 'S3Input': {'S3Uri': 's3://sagemaker-us-west-2-688520471316/music-streaming/data/json/simu-3.json', 'LocalPath': '/opt/ml/processing/input/data/simu-3', 'S3DataType': 'S3Prefix', 'S3InputMode': 'File', 'S3DataDistributionType': 'FullyReplicated', 'S3CompressionType': 'None'}}, {'InputName': 'simu-4', 'AppManaged': False, 'S3Input': {'S3Uri': 's3://sagemaker-us-west-2-688520471316/music-streaming/data/json/simu-4.json', 'LocalPath': '/opt/ml/processing/input/data/simu-4', 'S3DataType': 'S3Prefix', 'S3InputMode': 'File', 'S3DataDistributionType': 'FullyReplicated', 'S3CompressionType': 'None'}}, {'InputName': 'code', 'AppManaged': False, 'S3Input': {'S3Uri': 's3://sagemaker-us-west-2-688520471316/sagemaker-scikit-learn-2022-04-30-01-59-49-481/input/code/preprocessing_predw.py', 'LocalPath': '/opt/ml/processing/input/code', 'S3DataType': 'S3Prefix', 'S3InputMode': 'File', 'S3DataDistributionType': 'FullyReplicated', 'S3CompressionType': 'None'}}]\n", - "Outputs: [{'OutputName': 'processed_data', 'AppManaged': False, 'S3Output': {'S3Uri': 's3://sagemaker-us-west-2-688520471316/music-streaming/data/processing', 'LocalPath': '/opt/ml/processing/output', 'S3UploadMode': 'EndOfJob'}}]\n", - "...........................\u001b[34mReceived arguments Namespace(processing_output_filename='full_data.csv')\u001b[0m\n", - "\u001b[34mStarting file: /opt/ml/processing/input/data/simu-3/simu-3.json\u001b[0m\n", - "\u001b[34mStarting file: /opt/ml/processing/input/data/simu-1/simu-1.json\u001b[0m\n", - "\u001b[34mStarting file: /opt/ml/processing/input/data/sample/sample.json\u001b[0m\n", - "\u001b[34mStarting file: /opt/ml/processing/input/data/simu-4/simu-4.json\u001b[0m\n", - "\u001b[34mStarting file: /opt/ml/processing/input/data/simu-2/simu-2.json\u001b[0m\n", - "\u001b[34mSaving processed data to /opt/ml/processing/output/full_data.csv\u001b[0m\n", - "\n", - "CPU times: user 932 ms, sys: 81.8 ms, total: 1.01 s\n", - "Wall time: 8min 26s\n" - ] - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "%%time\n", "processing_output_path = f\"s3://{bucket}/{prefix}/data/processing\"\n", @@ -1481,20 +914,9 @@ }, { "cell_type": "code", - "execution_count": 33, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'s3://sagemaker-us-west-2-688520471316/music-streaming/data/processing/full_data.csv'" - ] - }, - "execution_count": 33, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "processing_output_filename = f\"{processing_output_path}/{final_features_filename}\"\n", "processing_output_filename" @@ -1502,139 +924,9 @@ }, { "cell_type": "code", - "execution_count": 34, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'metadata': {'version': 1},\n", - " 'nodes': [{'node_id': '660c3ee3-5207-4ded-b92f-7059831a7aed',\n", - " 'type': 'SOURCE',\n", - " 'operator': 'sagemaker.s3_source_0.1',\n", - " 'parameters': {'dataset_definition': {'__typename': 'S3CreateDatasetDefinitionOutput',\n", - " 'datasetSourceType': 'S3',\n", - " 'name': 'full_data.csv',\n", - " 'description': None,\n", - " 's3ExecutionContext': {'__typename': 'S3ExecutionContext',\n", - " 's3Uri': 's3://sagemaker-us-west-2-688520471316/music-streaming/data/processing/full_data.csv',\n", - " 's3ContentType': 'csv',\n", - " 's3HasHeader': True}}},\n", - " 'inputs': [],\n", - " 'outputs': [{'name': 'default',\n", - " 'sampling': {'sampling_method': 'sample_by_ratio',\n", - " 'sample_ratio': 0.06460757939298588}}]},\n", - " {'node_id': 'd04eac2a-92a9-4539-b22f-f0f30aa29877',\n", - " 'type': 'TRANSFORM',\n", - " 'operator': 'sagemaker.spark.infer_and_cast_type_0.1',\n", - " 'parameters': {},\n", - " 'trained_parameters': {'schema': {'ts': 'long',\n", - " 'userId': 'long',\n", - " 'sessionId': 'long',\n", - " 'page': 'string',\n", - " 'auth': 'string',\n", - " 'method': 'string',\n", - " 'status': 'long',\n", - " 'level': 'string',\n", - " 'itemInSession': 'long',\n", - " 'location': 'string',\n", - " 'userAgent': 'string',\n", - " 'lastName': 'string',\n", - " 'firstName': 'string',\n", - " 'registration': 'float',\n", - " 'gender': 'string',\n", - " 'artist': 'string',\n", - " 'song': 'string',\n", - " 'length': 'long'}},\n", - " 'inputs': [{'name': 'default',\n", - " 'node_id': '660c3ee3-5207-4ded-b92f-7059831a7aed',\n", - " 'output_name': 'default'}],\n", - " 'outputs': [{'name': 'default'}]},\n", - " {'node_id': 'd1b462ec-bbae-466d-afbd-39e5eab8dcc9',\n", - " 'type': 'TRANSFORM',\n", - " 'operator': 'sagemaker.spark.manage_columns_0.1',\n", - " 'parameters': {'operator': 'Drop column',\n", - " 'drop_column_parameters': {'column_to_drop': 'method'}},\n", - " 'inputs': [{'name': 'df',\n", - " 'node_id': 'd04eac2a-92a9-4539-b22f-f0f30aa29877',\n", - " 'output_name': 'default'}],\n", - " 'outputs': [{'name': 'default'}]},\n", - " {'node_id': '4dfd1354-1904-4fa4-bff7-56a9e0e50d0a',\n", - " 'type': 'TRANSFORM',\n", - " 'operator': 'sagemaker.spark.manage_columns_0.1',\n", - " 'parameters': {'operator': 'Drop column',\n", - " 'drop_column_parameters': {'column_to_drop': 'status'}},\n", - " 'inputs': [{'name': 'df',\n", - " 'node_id': 'd1b462ec-bbae-466d-afbd-39e5eab8dcc9',\n", - " 'output_name': 'default'}],\n", - " 'outputs': [{'name': 'default'}]},\n", - " {'node_id': '92ac4b28-bfb1-47bf-848a-de23735a2570',\n", - " 'type': 'TRANSFORM',\n", - " 'operator': 'sagemaker.spark.manage_columns_0.1',\n", - " 'parameters': {'operator': 'Drop column',\n", - " 'drop_column_parameters': {'column_to_drop': 'location'}},\n", - " 'inputs': [{'name': 'df',\n", - " 'node_id': '4dfd1354-1904-4fa4-bff7-56a9e0e50d0a',\n", - " 'output_name': 'default'}],\n", - " 'outputs': [{'name': 'default'}]},\n", - " {'node_id': 'e1fd74c7-8240-4e99-876e-73b42a063e65',\n", - " 'type': 'TRANSFORM',\n", - " 'operator': 'sagemaker.spark.manage_columns_0.1',\n", - " 'parameters': {'operator': 'Drop column',\n", - " 'drop_column_parameters': {'column_to_drop': 'userAgent'}},\n", - " 'inputs': [{'name': 'df',\n", - " 'node_id': '92ac4b28-bfb1-47bf-848a-de23735a2570',\n", - " 'output_name': 'default'}],\n", - " 'outputs': [{'name': 'default'}]},\n", - " {'node_id': '1550cb2f-c734-46f8-bfdc-4f8614c30c09',\n", - " 'type': 'TRANSFORM',\n", - " 'operator': 'sagemaker.spark.manage_columns_0.1',\n", - " 'parameters': {'operator': 'Drop column',\n", - " 'drop_column_parameters': {'column_to_drop': 'lastName'}},\n", - " 'inputs': [{'name': 'df',\n", - " 'node_id': 'e1fd74c7-8240-4e99-876e-73b42a063e65',\n", - " 'output_name': 'default'}],\n", - " 'outputs': [{'name': 'default'}]},\n", - " {'node_id': '32405a27-8e85-4c9b-8142-dd75d56fa75d',\n", - " 'type': 'TRANSFORM',\n", - " 'operator': 'sagemaker.spark.manage_columns_0.1',\n", - " 'parameters': {'operator': 'Drop column',\n", - " 'drop_column_parameters': {'column_to_drop': 'firstName'}},\n", - " 'inputs': [{'name': 'df',\n", - " 'node_id': '1550cb2f-c734-46f8-bfdc-4f8614c30c09',\n", - " 'output_name': 'default'}],\n", - " 'outputs': [{'name': 'default'}]},\n", - " {'node_id': '7b74dbbc-6f7e-4656-8f78-25272604bc45',\n", - " 'type': 'TRANSFORM',\n", - " 'operator': 'sagemaker.spark.handle_missing_0.1',\n", - " 'parameters': {'operator': 'Drop missing',\n", - " 'drop_missing_parameters': {'dimension': 'Drop Rows',\n", - " 'drop_rows_parameters': {'input_column': 'userId'}},\n", - " 'impute_parameters': {'column_type': 'Numeric',\n", - " 'numeric_parameters': {'strategy': 'Approximate Median'}}},\n", - " 'inputs': [{'name': 'df',\n", - " 'node_id': '32405a27-8e85-4c9b-8142-dd75d56fa75d',\n", - " 'output_name': 'default'}],\n", - " 'outputs': [{'name': 'default'}]},\n", - " {'node_id': '82cb5ad3-3b9c-428d-9260-ce6efcd4c4f8',\n", - " 'type': 'TRANSFORM',\n", - " 'operator': 'sagemaker.spark.handle_missing_0.1',\n", - " 'parameters': {'operator': 'Drop missing',\n", - " 'drop_missing_parameters': {'dimension': 'Drop Rows',\n", - " 'drop_rows_parameters': {'input_column': 'registration'}},\n", - " 'impute_parameters': {'column_type': 'Numeric',\n", - " 'numeric_parameters': {'strategy': 'Approximate Median'}}},\n", - " 'inputs': [{'name': 'df',\n", - " 'node_id': '7b74dbbc-6f7e-4656-8f78-25272604bc45',\n", - " 'output_name': 'default'}],\n", - " 'outputs': [{'name': 'default'}]}]}" - ] - }, - "execution_count": 34, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "flow_file = \"dw_example.flow\"\n", "\n", @@ -1661,7 +953,7 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -1678,7 +970,7 @@ }, { "cell_type": "code", - "execution_count": 36, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -1688,17 +980,9 @@ }, { "cell_type": "code", - "execution_count": 37, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Overwriting preprocessing.py\n" - ] - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "%%writefile preprocessing.py\n", "\n", @@ -1949,7 +1233,7 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -1958,256 +1242,11 @@ }, { "cell_type": "code", - "execution_count": 39, + "execution_count": null, "metadata": { "scrolled": true }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "Job Name: sagemaker-scikit-learn-2022-04-30-02-08-16-353\n", - "Inputs: [{'InputName': 'code', 'AppManaged': False, 'S3Input': {'S3Uri': 's3://sagemaker-us-west-2-688520471316/sagemaker-scikit-learn-2022-04-30-02-08-16-353/input/code/preprocessing.py', 'LocalPath': '/opt/ml/processing/input/code', 'S3DataType': 'S3Prefix', 'S3InputMode': 'File', 'S3DataDistributionType': 'FullyReplicated', 'S3CompressionType': 'None'}}]\n", - "Outputs: [{'OutputName': 'processed_data', 'AppManaged': False, 'S3Output': {'S3Uri': 's3://sagemaker-us-west-2-688520471316/music-streaming/data/processing', 'LocalPath': '/opt/ml/processing/output', 'S3UploadMode': 'EndOfJob'}}]\n", - "...........................\u001b[34mRequirement already satisfied: pandas in /miniconda3/lib/python3.7/site-packages (1.1.3)\u001b[0m\n", - "\u001b[34mCollecting pandas\n", - " Downloading pandas-1.3.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (11.3 MB)\n", - " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 11.3/11.3 MB 87.5 MB/s eta 0:00:00\u001b[0m\n", - "\u001b[34mRequirement already satisfied: numpy>=1.17.3 in /miniconda3/lib/python3.7/site-packages (from pandas) (1.21.0)\u001b[0m\n", - "\u001b[34mRequirement already satisfied: python-dateutil>=2.7.3 in /miniconda3/lib/python3.7/site-packages (from pandas) (2.8.1)\u001b[0m\n", - "\u001b[34mRequirement already satisfied: pytz>=2017.3 in /miniconda3/lib/python3.7/site-packages (from pandas) (2022.1)\u001b[0m\n", - "\u001b[34mRequirement already satisfied: six>=1.5 in /miniconda3/lib/python3.7/site-packages (from python-dateutil>=2.7.3->pandas) (1.15.0)\u001b[0m\n", - "\u001b[34mInstalling collected packages: pandas\n", - " Attempting uninstall: pandas\n", - " Found existing installation: pandas 1.1.3\n", - " Uninstalling pandas-1.1.3:\n", - " Successfully uninstalled pandas-1.1.3\u001b[0m\n", - "\u001b[34mERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.\u001b[0m\n", - "\u001b[34msagemaker-sklearn-container 2.0 requires pandas==1.1.3, but you have pandas 1.3.5 which is incompatible.\u001b[0m\n", - "\u001b[34mSuccessfully installed pandas-1.3.5\u001b[0m\n", - "\u001b[34mWARNING: 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\n", - "\u001b[34mCollecting awswrangler\n", - " Downloading awswrangler-2.15.1-py3-none-any.whl (239 kB)\u001b[0m\n", - "\u001b[34m ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 239.6/239.6 KB 8.2 MB/s eta 0:00:00\u001b[0m\n", - "\u001b[34mCollecting openpyxl<3.1.0,>=3.0.0\n", - " Downloading openpyxl-3.0.9-py2.py3-none-any.whl (242 kB)\n", - " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 242.2/242.2 KB 33.7 MB/s eta 0:00:00\u001b[0m\n", - "\u001b[34mCollecting pyarrow<7.1.0,>=2.0.0\n", - " Downloading pyarrow-7.0.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (26.7 MB)\n", - " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 26.7/26.7 MB 52.3 MB/s eta 0:00:00\u001b[0m\n", - "\u001b[34mCollecting redshift-connector<2.1.0,>=2.0.889\n", - " Downloading redshift_connector-2.0.906-py3-none-any.whl (109 kB)\n", - " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 109.8/109.8 KB 19.9 MB/s eta 0:00:00\u001b[0m\n", - "\u001b[34mCollecting botocore<2.0.0,>=1.23.17\n", - " Downloading botocore-1.25.4-py3-none-any.whl (8.7 MB)\n", - " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 8.7/8.7 MB 96.3 MB/s eta 0:00:00\u001b[0m\n", - "\u001b[34mCollecting opensearch-py<2.0.0,>=1.0.0\n", - " Downloading opensearch_py-1.1.0-py2.py3-none-any.whl (207 kB)\n", - " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 207.5/207.5 KB 30.5 MB/s eta 0:00:00\u001b[0m\n", - "\u001b[34mCollecting progressbar2<5.0.0,>=4.0.0\n", - " Downloading progressbar2-4.0.0-py2.py3-none-any.whl (26 kB)\u001b[0m\n", - "\u001b[34mRequirement already satisfied: pandas<2.0.0,>=1.2.0 in /miniconda3/lib/python3.7/site-packages (from awswrangler) (1.3.5)\u001b[0m\n", - "\u001b[34mCollecting backoff<2.0.0,>=1.11.1\n", - " Downloading backoff-1.11.1-py2.py3-none-any.whl (13 kB)\u001b[0m\n", - "\u001b[34mCollecting gremlinpython<4.0.0,>=3.5.2\n", - " Downloading gremlinpython-3.6.0-py2.py3-none-any.whl (72 kB)\n", - " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 72.8/72.8 KB 14.3 MB/s eta 0:00:00\u001b[0m\n", - "\u001b[34mRequirement already satisfied: numpy<2.0.0,>=1.21.0 in /miniconda3/lib/python3.7/site-packages (from awswrangler) (1.21.0)\u001b[0m\n", - "\u001b[34mCollecting pg8000<2.0.0,>=1.20.0\n", - " Downloading pg8000-1.26.1-py3-none-any.whl (33 kB)\u001b[0m\n", - "\u001b[34mCollecting pymysql<2.0.0,>=1.0.0\n", - " Downloading PyMySQL-1.0.2-py3-none-any.whl (43 kB)\n", - " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 43.8/43.8 KB 7.2 MB/s eta 0:00:00\u001b[0m\n", - "\u001b[34mCollecting jsonpath-ng<2.0.0,>=1.5.3\n", - " Downloading jsonpath_ng-1.5.3-py3-none-any.whl (29 kB)\u001b[0m\n", - "\u001b[34mCollecting boto3<2.0.0,>=1.20.17\n", - " Downloading boto3-1.22.4-py3-none-any.whl (132 kB)\n", - " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 132.5/132.5 KB 20.2 MB/s eta 0:00:00\u001b[0m\n", - "\u001b[34mCollecting requests-aws4auth<2.0.0,>=1.1.1\n", - " Downloading requests_aws4auth-1.1.2-py2.py3-none-any.whl (24 kB)\u001b[0m\n", - "\u001b[34mRequirement already satisfied: jmespath<2.0.0,>=0.7.1 in /miniconda3/lib/python3.7/site-packages (from boto3<2.0.0,>=1.20.17->awswrangler) (0.10.0)\u001b[0m\n", - "\u001b[34mCollecting s3transfer<0.6.0,>=0.5.0\n", - " Downloading s3transfer-0.5.2-py3-none-any.whl (79 kB)\n", - " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 79.5/79.5 KB 19.0 MB/s eta 0:00:00\u001b[0m\n", - "\u001b[34mRequirement already satisfied: python-dateutil<3.0.0,>=2.1 in /miniconda3/lib/python3.7/site-packages (from botocore<2.0.0,>=1.23.17->awswrangler) (2.8.1)\u001b[0m\n", - "\u001b[34mRequirement already satisfied: urllib3<1.27,>=1.25.4 in /miniconda3/lib/python3.7/site-packages (from botocore<2.0.0,>=1.23.17->awswrangler) (1.25.11)\u001b[0m\n", - "\u001b[34mCollecting nest-asyncio\n", - " Downloading nest_asyncio-1.5.5-py3-none-any.whl (5.2 kB)\u001b[0m\n", - "\u001b[34mCollecting isodate<1.0.0,>=0.6.0\n", - " Downloading isodate-0.6.1-py2.py3-none-any.whl (41 kB)\n", - " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 41.7/41.7 KB 7.8 MB/s eta 0:00:00\u001b[0m\n", - "\u001b[34mCollecting aiohttp<=3.8.1,>=3.8.0\n", - " Downloading aiohttp-3.8.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl (1.1 MB)\n", - " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.1/1.1 MB 74.4 MB/s eta 0:00:00\u001b[0m\n", - "\u001b[34mCollecting aenum<4.0.0,>=1.4.5\n", - " Downloading aenum-3.1.11-py3-none-any.whl (131 kB)\n", - " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 131.5/131.5 KB 26.2 MB/s eta 0:00:00\u001b[0m\n", - "\u001b[34mCollecting decorator\n", - " Downloading decorator-5.1.1-py3-none-any.whl (9.1 kB)\u001b[0m\n", - "\u001b[34mCollecting ply\n", - " Downloading ply-3.11-py2.py3-none-any.whl (49 kB)\n", - " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 49.6/49.6 KB 9.7 MB/s eta 0:00:00\u001b[0m\n", - "\u001b[34mRequirement already satisfied: six in /miniconda3/lib/python3.7/site-packages (from jsonpath-ng<2.0.0,>=1.5.3->awswrangler) (1.15.0)\u001b[0m\n", - "\u001b[34mCollecting et-xmlfile\n", - " Downloading et_xmlfile-1.1.0-py3-none-any.whl (4.7 kB)\u001b[0m\n", - "\u001b[34mRequirement already satisfied: certifi in /miniconda3/lib/python3.7/site-packages (from opensearch-py<2.0.0,>=1.0.0->awswrangler) (2021.10.8)\u001b[0m\n", - "\u001b[34mRequirement already satisfied: pytz>=2017.3 in /miniconda3/lib/python3.7/site-packages (from pandas<2.0.0,>=1.2.0->awswrangler) (2022.1)\u001b[0m\n", - "\u001b[34mCollecting scramp>=1.4.1\n", - " Downloading scramp-1.4.1-py3-none-any.whl (8.5 kB)\u001b[0m\n", - "\u001b[34mCollecting python-utils>=3.0.0\n", - " Downloading python_utils-3.1.0-py2.py3-none-any.whl (19 kB)\u001b[0m\n", - "\u001b[34mCollecting lxml>=4.6.5\n", - " Downloading lxml-4.8.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl (6.4 MB)\n", - " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 6.4/6.4 MB 103.0 MB/s eta 0:00:00\u001b[0m\n", - "\u001b[34mCollecting packaging\n", - " Downloading packaging-21.3-py3-none-any.whl (40 kB)\n", - " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 40.8/40.8 KB 6.9 MB/s eta 0:00:00\u001b[0m\n", - "\u001b[34mRequirement already satisfied: requests<2.27.2,>=2.23.0 in /miniconda3/lib/python3.7/site-packages (from redshift-connector<2.1.0,>=2.0.889->awswrangler) (2.27.1)\u001b[0m\n", - "\u001b[34mCollecting beautifulsoup4<5.0.0,>=4.7.0\n", - " Downloading beautifulsoup4-4.11.1-py3-none-any.whl (128 kB)\n", - " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 128.2/128.2 KB 21.6 MB/s eta 0:00:00\u001b[0m\n", - "\u001b[34mCollecting yarl<2.0,>=1.0\n", - " Downloading yarl-1.7.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl (271 kB)\n", - " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 271.8/271.8 KB 35.1 MB/s eta 0:00:00\u001b[0m\n", - "\u001b[34mCollecting asynctest==0.13.0\n", - " Downloading asynctest-0.13.0-py3-none-any.whl (26 kB)\u001b[0m\n", - "\u001b[34mCollecting aiosignal>=1.1.2\n", - " Downloading aiosignal-1.2.0-py3-none-any.whl (8.2 kB)\u001b[0m\n", - "\u001b[34mRequirement already satisfied: charset-normalizer<3.0,>=2.0 in /miniconda3/lib/python3.7/site-packages (from aiohttp<=3.8.1,>=3.8.0->gremlinpython<4.0.0,>=3.5.2->awswrangler) (2.0.4)\u001b[0m\n", - "\u001b[34mCollecting attrs>=17.3.0\n", - " Downloading attrs-21.4.0-py2.py3-none-any.whl (60 kB)\n", - " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 60.6/60.6 KB 13.3 MB/s eta 0:00:00\u001b[0m\n", - "\u001b[34mCollecting multidict<7.0,>=4.5\n", - " Downloading multidict-6.0.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (94 kB)\n", - " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 94.8/94.8 KB 15.3 MB/s eta 0:00:00\u001b[0m\n", - "\u001b[34mCollecting frozenlist>=1.1.1\n", - " Downloading frozenlist-1.3.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl (144 kB)\n", - " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 144.8/144.8 KB 15.0 MB/s eta 0:00:00\u001b[0m\n", - "\u001b[34mRequirement already satisfied: typing-extensions>=3.7.4 in /miniconda3/lib/python3.7/site-packages (from aiohttp<=3.8.1,>=3.8.0->gremlinpython<4.0.0,>=3.5.2->awswrangler) (4.1.1)\u001b[0m\n", - "\u001b[34mCollecting async-timeout<5.0,>=4.0.0a3\n", - " Downloading async_timeout-4.0.2-py3-none-any.whl (5.8 kB)\u001b[0m\n", - "\u001b[34mCollecting soupsieve>1.2\n", - " Downloading soupsieve-2.3.2.post1-py3-none-any.whl (37 kB)\u001b[0m\n", - "\u001b[34mRequirement already satisfied: idna<4,>=2.5 in /miniconda3/lib/python3.7/site-packages (from requests<2.27.2,>=2.23.0->redshift-connector<2.1.0,>=2.0.889->awswrangler) (3.3)\u001b[0m\n", - "\u001b[34mCollecting asn1crypto>=1.4.0\n", - " Downloading asn1crypto-1.5.1-py2.py3-none-any.whl (105 kB)\n", - " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 105.0/105.0 KB 11.9 MB/s eta 0:00:00\u001b[0m\n", - "\u001b[34mCollecting pyparsing!=3.0.5,>=2.0.2\n", - " Downloading pyparsing-3.0.8-py3-none-any.whl (98 kB)\n", - " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 98.5/98.5 KB 11.6 MB/s eta 0:00:00\u001b[0m\n", - "\u001b[34mInstalling collected packages: ply, asn1crypto, aenum, soupsieve, scramp, python-utils, pyparsing, pymysql, pyarrow, opensearch-py, nest-asyncio, multidict, lxml, isodate, frozenlist, et-xmlfile, decorator, backoff, attrs, asynctest, async-timeout, yarl, requests-aws4auth, progressbar2, pg8000, packaging, openpyxl, jsonpath-ng, botocore, beautifulsoup4, aiosignal, s3transfer, aiohttp, gremlinpython, boto3, redshift-connector, awswrangler\u001b[0m\n", - "\u001b[34m Attempting uninstall: pyarrow\n", - " Found existing installation: pyarrow 0.16.0\n", - " Uninstalling pyarrow-0.16.0:\n", - " Successfully uninstalled pyarrow-0.16.0\u001b[0m\n", - "\u001b[34m Attempting uninstall: botocore\n", - " Found existing installation: botocore 1.19.4\n", - " Uninstalling botocore-1.19.4:\n", - " Successfully uninstalled botocore-1.19.4\u001b[0m\n", - "\u001b[34m Attempting uninstall: s3transfer\n", - " Found existing installation: s3transfer 0.3.7\n", - " Uninstalling s3transfer-0.3.7:\n", - " Successfully uninstalled s3transfer-0.3.7\n", - " Attempting uninstall: boto3\n", - " Found existing installation: boto3 1.16.4\n", - " Uninstalling boto3-1.16.4:\n", - " Successfully uninstalled boto3-1.16.4\u001b[0m\n", - "\u001b[34mERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.\u001b[0m\n", - "\u001b[34msagemaker-sklearn-container 2.0 requires boto3==1.16.4, but you have boto3 1.22.4 which is incompatible.\u001b[0m\n", - "\u001b[34msagemaker-sklearn-container 2.0 requires botocore==1.19.4, but you have botocore 1.25.4 which is incompatible.\u001b[0m\n", - "\u001b[34msagemaker-sklearn-container 2.0 requires pandas==1.1.3, but you have pandas 1.3.5 which is incompatible.\u001b[0m\n", - "\u001b[34mSuccessfully installed aenum-3.1.11 aiohttp-3.8.1 aiosignal-1.2.0 asn1crypto-1.5.1 async-timeout-4.0.2 asynctest-0.13.0 attrs-21.4.0 awswrangler-2.15.1 backoff-1.11.1 beautifulsoup4-4.11.1 boto3-1.22.4 botocore-1.25.4 decorator-5.1.1 et-xmlfile-1.1.0 frozenlist-1.3.0 gremlinpython-3.6.0 isodate-0.6.1 jsonpath-ng-1.5.3 lxml-4.8.0 multidict-6.0.2 nest-asyncio-1.5.5 openpyxl-3.0.9 opensearch-py-1.1.0 packaging-21.3 pg8000-1.26.1 ply-3.11 progressbar2-4.0.0 pyarrow-7.0.0 pymysql-1.0.2 pyparsing-3.0.8 python-utils-3.1.0 redshift-connector-2.0.906 requests-aws4auth-1.1.2 s3transfer-0.5.2 scramp-1.4.1 soupsieve-2.3.2.post1 yarl-1.7.2\u001b[0m\n", - "\u001b[34mWARNING: 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\n", - "\u001b[34mReceived arguments Namespace(dw_output_path='s3://sagemaker-us-west-2-688520471316/music-streaming/data/processing', processing_output_filename='processing_job_output.csv')\u001b[0m\n", - "\n", - "\u001b[34mTraceback (most recent call last):\n", - " File \"/miniconda3/lib/python3.7/site-packages/pandas/core/groupby/generic.py\", line 341, in array_func\n", - " \"aggregate\", values, how, axis=data.ndim - 1, min_count=min_count\n", - " File \"/miniconda3/lib/python3.7/site-packages/pandas/core/groupby/ops.py\", line 1016, in _cython_operation\n", - " **kwargs,\n", - " File \"/miniconda3/lib/python3.7/site-packages/pandas/core/groupby/ops.py\", line 677, in cython_operation\n", - " **kwargs,\n", - " File \"/miniconda3/lib/python3.7/site-packages/pandas/core/groupby/ops.py\", line 508, in _cython_op_ndim_compat\n", - " **kwargs,\n", - " File \"/miniconda3/lib/python3.7/site-packages/pandas/core/groupby/ops.py\", line 563, in _call_cython_op\n", - " func, values = self.get_cython_func_and_vals(values, is_numeric)\n", - " File \"/miniconda3/lib/python3.7/site-packages/pandas/core/groupby/ops.py\", line 205, in get_cython_func_and_vals\n", - " func = self._get_cython_function(kind, how, values.dtype, is_numeric)\n", - " File \"/miniconda3/lib/python3.7/site-packages/pandas/core/groupby/ops.py\", line 171, in _get_cython_function\n", - " f\"function is not implemented for this dtype: \"\u001b[0m\n", - "\u001b[34mNotImplementedError: function is not implemented for this dtype: [how->max,dtype->object]\u001b[0m\n", - "\u001b[34mDuring handling of the above exception, another exception occurred:\u001b[0m\n", - "\u001b[34mTraceback (most recent call last):\n", - " File \"/opt/ml/processing/input/code/preprocessing.py\", line 126, in \n", - " \"user_churned\": \"max\",\n", - " File \"/miniconda3/lib/python3.7/site-packages/pandas/core/groupby/generic.py\", line 979, in aggregate\n", - " result = op.agg()\n", - " File \"/miniconda3/lib/python3.7/site-packages/pandas/core/apply.py\", line 161, in agg\n", - " return self.agg_dict_like()\n", - " File \"/miniconda3/lib/python3.7/site-packages/pandas/core/apply.py\", line 436, in agg_dict_like\n", - " key: obj._gotitem(key, ndim=1).agg(how) for key, how in arg.items()\n", - " File \"/miniconda3/lib/python3.7/site-packages/pandas/core/apply.py\", line 436, in \n", - " key: obj._gotitem(key, ndim=1).agg(how) for key, how in arg.items()\n", - " File \"/miniconda3/lib/python3.7/site-packages/pandas/core/groupby/generic.py\", line 243, in aggregate\n", - " return getattr(self, func)(*args, **kwargs)\n", - " File \"/miniconda3/lib/python3.7/site-packages/pandas/core/groupby/groupby.py\", line 1880, in max\n", - " numeric_only=numeric_only, min_count=min_count, alias=\"max\", npfunc=np.max\n", - " File \"/miniconda3/lib/python3.7/site-packages/pandas/core/groupby/groupby.py\", line 1368, in _agg_general\n", - " min_count=min_count,\n", - " File \"/miniconda3/lib/python3.7/site-packages/pandas/core/groupby/generic.py\", line 352, in _cython_agg_general\n", - " result = array_func(objvals)\n", - " File \"/miniconda3/lib/python3.7/site-packages/pandas/core/groupby/generic.py\", line 348, in array_func\n", - " result = self._agg_py_fallback(values, ndim=data.ndim, alt=alt)\n", - " File \"/miniconda3/lib/python3.7/site-packages/pandas/core/groupby/groupby.py\", line 1398, in _agg_py_fallback\n", - " res_values = self.grouper.agg_series(ser, alt, preserve_dtype=True)\n", - " File \"/miniconda3/lib/python3.7/site-packages/pandas/core/groupby/ops.py\", line 1060, in agg_series\n", - " result = self._aggregate_series_fast(obj, func)\n", - " File \"/miniconda3/lib/python3.7/site-packages/pandas/core/groupby/ops.py\", line 1085, in _aggregate_series_fast\n", - " result, _ = sgrouper.get_result()\n", - " File \"pandas/_libs/reduction.pyx\", line 281, in pandas._libs.reduction.SeriesGrouper.get_result\n", - " File \"pandas/_libs/reduction.pyx\", line 88, in pandas._libs.reduction._BaseGrouper._apply_to_group\n", - " File \"<__array_function__ internals>\", line 6, in amax\n", - " File \"/miniconda3/lib/python3.7/site-packages/numpy/core/fromnumeric.py\", line 2755, in amax\n", - " keepdims=keepdims, initial=initial, where=where)\n", - " File \"/miniconda3/lib/python3.7/site-packages/numpy/core/fromnumeric.py\", line 84, in _wrapreduction\n", - " return reduction(axis=axis, out=out, **passkwargs)\n", - " File \"/miniconda3/lib/python3.7/site-packages/pandas/core/generic.py\", line 10819, in max\u001b[0m\n", - "\u001b[34m return NDFrame.max(self, axis, skipna, level, numeric_only, **kwargs)\n", - " File \"/miniconda3/lib/python3.7/site-packages/pandas/core/generic.py\", line 10365, in max\n", - " \"max\", nanops.nanmax, axis, skipna, level, numeric_only, **kwargs\n", - " File \"/miniconda3/lib/python3.7/site-packages/pandas/core/generic.py\", line 10355, in _stat_function\n", - " func, name=name, axis=axis, skipna=skipna, numeric_only=numeric_only\n", - " File \"/miniconda3/lib/python3.7/site-packages/pandas/core/series.py\", line 4392, in _reduce\n", - " return op(delegate, skipna=skipna, **kwds)\n", - " File \"/miniconda3/lib/python3.7/site-packages/pandas/core/nanops.py\", line 156, in f\n", - " result = alt(values, axis=axis, skipna=skipna, **kwds)\n", - " File \"/miniconda3/lib/python3.7/site-packages/pandas/core/nanops.py\", line 411, in new_func\n", - " result = func(values, axis=axis, skipna=skipna, mask=mask, **kwargs)\n", - " File \"/miniconda3/lib/python3.7/site-packages/pandas/core/nanops.py\", line 1018, in reduction\n", - " result = getattr(values, meth)(axis)\n", - " File \"/miniconda3/lib/python3.7/site-packages/numpy/core/_methods.py\", line 40, in _amax\n", - " return umr_maximum(a, axis, None, out, keepdims, initial, where)\u001b[0m\n", - "\u001b[34mTypeError: '>=' not supported between instances of 'datetime.date' and 'float'\u001b[0m\n" - ] - }, - { - "ename": "UnexpectedStatusException", - "evalue": "Error for Processing job sagemaker-scikit-learn-2022-04-30-02-08-16-353: Failed. Reason: AlgorithmError: See job logs for more information", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mUnexpectedStatusException\u001b[0m Traceback (most recent call last)", - "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n", - "\u001b[0;32m/opt/conda/lib/python3.7/site-packages/sagemaker/processing.py\u001b[0m in \u001b[0;36mrun\u001b[0;34m(self, code, inputs, outputs, arguments, wait, logs, job_name, experiment_config, kms_key)\u001b[0m\n\u001b[1;32m 557\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mjobs\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mappend\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mlatest_job\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 558\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mwait\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 559\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mlatest_job\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mwait\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mlogs\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mlogs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 560\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 561\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m_include_code_in_inputs\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0minputs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mcode\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mkms_key\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mNone\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m/opt/conda/lib/python3.7/site-packages/sagemaker/processing.py\u001b[0m in \u001b[0;36mwait\u001b[0;34m(self, logs)\u001b[0m\n\u001b[1;32m 966\u001b[0m \"\"\"\n\u001b[1;32m 967\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mlogs\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 968\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msagemaker_session\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mlogs_for_processing_job\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mjob_name\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mwait\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mTrue\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 969\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 970\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msagemaker_session\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mwait_for_processing_job\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mjob_name\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m/opt/conda/lib/python3.7/site-packages/sagemaker/session.py\u001b[0m in \u001b[0;36mlogs_for_processing_job\u001b[0;34m(self, job_name, wait, poll)\u001b[0m\n\u001b[1;32m 3885\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3886\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mwait\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 3887\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_check_job_status\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mjob_name\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdescription\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"ProcessingJobStatus\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 3888\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mdot\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3889\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m/opt/conda/lib/python3.7/site-packages/sagemaker/session.py\u001b[0m in \u001b[0;36m_check_job_status\u001b[0;34m(self, job, desc, status_key_name)\u001b[0m\n\u001b[1;32m 3337\u001b[0m \u001b[0mmessage\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mmessage\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3338\u001b[0m \u001b[0mallowed_statuses\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"Completed\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"Stopped\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 3339\u001b[0;31m \u001b[0mactual_status\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mstatus\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 3340\u001b[0m )\n\u001b[1;32m 3341\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;31mUnexpectedStatusException\u001b[0m: Error for Processing job sagemaker-scikit-learn-2022-04-30-02-08-16-353: Failed. Reason: AlgorithmError: See job logs for more information" - ] - } - ], + "outputs": [], "source": [ "%%time\n", "from sagemaker.processing import ProcessingInput, ProcessingOutput\n", @@ -2236,95 +1275,9 @@ }, { "cell_type": "code", - "execution_count": 40, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'ProcessingInputs': [{'InputName': 'sample',\n", - " 'AppManaged': False,\n", - " 'S3Input': {'S3Uri': 's3://sagemaker-us-west-2-688520471316/music-streaming/data/json/sample.json',\n", - " 'LocalPath': '/opt/ml/processing/input/data/sample',\n", - " 'S3DataType': 'S3Prefix',\n", - " 'S3InputMode': 'File',\n", - " 'S3DataDistributionType': 'FullyReplicated',\n", - " 'S3CompressionType': 'None'}},\n", - " {'InputName': 'simu-1',\n", - " 'AppManaged': False,\n", - " 'S3Input': {'S3Uri': 's3://sagemaker-us-west-2-688520471316/music-streaming/data/json/simu-1.json',\n", - " 'LocalPath': '/opt/ml/processing/input/data/simu-1',\n", - " 'S3DataType': 'S3Prefix',\n", - " 'S3InputMode': 'File',\n", - " 'S3DataDistributionType': 'FullyReplicated',\n", - " 'S3CompressionType': 'None'}},\n", - " {'InputName': 'simu-2',\n", - " 'AppManaged': False,\n", - " 'S3Input': {'S3Uri': 's3://sagemaker-us-west-2-688520471316/music-streaming/data/json/simu-2.json',\n", - " 'LocalPath': '/opt/ml/processing/input/data/simu-2',\n", - " 'S3DataType': 'S3Prefix',\n", - " 'S3InputMode': 'File',\n", - " 'S3DataDistributionType': 'FullyReplicated',\n", - " 'S3CompressionType': 'None'}},\n", - " {'InputName': 'simu-3',\n", - " 'AppManaged': False,\n", - " 'S3Input': {'S3Uri': 's3://sagemaker-us-west-2-688520471316/music-streaming/data/json/simu-3.json',\n", - " 'LocalPath': '/opt/ml/processing/input/data/simu-3',\n", - " 'S3DataType': 'S3Prefix',\n", - " 'S3InputMode': 'File',\n", - " 'S3DataDistributionType': 'FullyReplicated',\n", - " 'S3CompressionType': 'None'}},\n", - " {'InputName': 'simu-4',\n", - " 'AppManaged': False,\n", - " 'S3Input': {'S3Uri': 's3://sagemaker-us-west-2-688520471316/music-streaming/data/json/simu-4.json',\n", - " 'LocalPath': '/opt/ml/processing/input/data/simu-4',\n", - " 'S3DataType': 'S3Prefix',\n", - " 'S3InputMode': 'File',\n", - " 'S3DataDistributionType': 'FullyReplicated',\n", - " 'S3CompressionType': 'None'}},\n", - " {'InputName': 'code',\n", - " 'AppManaged': False,\n", - " 'S3Input': {'S3Uri': 's3://sagemaker-us-west-2-688520471316/sagemaker-scikit-learn-2022-04-30-01-59-49-481/input/code/preprocessing_predw.py',\n", - " 'LocalPath': '/opt/ml/processing/input/code',\n", - " 'S3DataType': 'S3Prefix',\n", - " 'S3InputMode': 'File',\n", - " 'S3DataDistributionType': 'FullyReplicated',\n", - " 'S3CompressionType': 'None'}}],\n", - " 'ProcessingOutputConfig': {'Outputs': [{'OutputName': 'processed_data',\n", - " 'S3Output': {'S3Uri': 's3://sagemaker-us-west-2-688520471316/music-streaming/data/processing',\n", - " 'LocalPath': '/opt/ml/processing/output',\n", - " 'S3UploadMode': 'EndOfJob'},\n", - " 'AppManaged': False}]},\n", - " 'ProcessingJobName': 'sagemaker-scikit-learn-2022-04-30-01-59-49-481',\n", - " 'ProcessingResources': {'ClusterConfig': {'InstanceCount': 1,\n", - " 'InstanceType': 'ml.m5.xlarge',\n", - " 'VolumeSizeInGB': 30}},\n", - " 'StoppingCondition': {'MaxRuntimeInSeconds': 86400},\n", - " 'AppSpecification': {'ImageUri': '246618743249.dkr.ecr.us-west-2.amazonaws.com/sagemaker-scikit-learn:0.23-1-cpu-py3',\n", - " 'ContainerEntrypoint': ['python3',\n", - " '/opt/ml/processing/input/code/preprocessing_predw.py'],\n", - " 'ContainerArguments': ['--processing-output-filename', 'full_data.csv']},\n", - " 'RoleArn': 'arn:aws:iam::688520471316:role/service-role/AmazonSageMaker-ExecutionRole-20211229T100947',\n", - " 'ProcessingJobArn': 'arn:aws:sagemaker:us-west-2:688520471316:processing-job/sagemaker-scikit-learn-2022-04-30-01-59-49-481',\n", - " 'ProcessingJobStatus': 'Completed',\n", - " 'ProcessingEndTime': datetime.datetime(2022, 4, 30, 2, 7, 45, 433000, tzinfo=tzlocal()),\n", - " 'ProcessingStartTime': datetime.datetime(2022, 4, 30, 2, 3, 35, 10000, tzinfo=tzlocal()),\n", - " 'LastModifiedTime': datetime.datetime(2022, 4, 30, 2, 7, 45, 721000, tzinfo=tzlocal()),\n", - " 'CreationTime': datetime.datetime(2022, 4, 30, 1, 59, 49, 808000, tzinfo=tzlocal()),\n", - " 'ResponseMetadata': {'RequestId': 'cb40881c-69d9-4c7f-9a9c-d97153d589a5',\n", - " 'HTTPStatusCode': 200,\n", - " 'HTTPHeaders': {'x-amzn-requestid': 'cb40881c-69d9-4c7f-9a9c-d97153d589a5',\n", - " 'content-type': 'application/x-amz-json-1.1',\n", - " 'content-length': '3223',\n", - " 'date': 'Sat, 30 Apr 2022 02:08:14 GMT'},\n", - " 'RetryAttempts': 0}}" - ] - }, - "execution_count": 40, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "preprocessing_job_description" ] @@ -2358,20 +1311,9 @@ }, { "cell_type": "code", - "execution_count": 41, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'s3://sagemaker-us-west-2-688520471316/music-streaming/data/processing/processing_job_output.csv'" - ] - }, - "execution_count": 41, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "processing_job_output_uri = f\"{processing_job_output_path}/{processing_job_output_name}\"\n", "processing_job_output_uri" @@ -2379,24 +1321,16 @@ }, { "cell_type": "code", - "execution_count": 42, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "download: s3://sagemaker-us-west-2-688520471316/music-streaming/data/processing/processing_job_output.csv to data/processing_job_output.csv\n" - ] - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "!aws s3 cp $processing_job_output_uri ./data" ] }, { "cell_type": "code", - "execution_count": 43, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -2405,7 +1339,7 @@ }, { "cell_type": "code", - "execution_count": 44, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -2415,194 +1349,9 @@ }, { "cell_type": "code", - "execution_count": 45, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
userIduser_churnedaverage_events_weekendaverage_events_weekdaynum_songs_played_7dnum_ads_7dnum_error_7dnum_songs_played_30dnum_songs_played_90dnum_sessions...num_thumbs_upnum_add_to_playlistnum_adsnum_add_friendnum_downgradenum_upgradenum_errorpercentage_addays_since_activerepeats_ratio
0110010.094.937576.3043484135714135413551...2931407811110.0013923590.179444
1110020.070.500076.666667476104764767...41161141000.0016642650.052521
2110031.098.7500120.87500038671293867386737...27210312691190.002576660.175330
3110041.070.0000120.444444108421108410847...6830291010.001546480.076568
\n", - "

4 rows × 27 columns

\n", - "
" - ], - "text/plain": [ - " userId user_churned average_events_weekend average_events_weekday \\\n", - "0 11001 0.0 94.9375 76.304348 \n", - "1 11002 0.0 70.5000 76.666667 \n", - "2 11003 1.0 98.7500 120.875000 \n", - "3 11004 1.0 70.0000 120.444444 \n", - "\n", - " num_songs_played_7d num_ads_7d num_error_7d num_songs_played_30d \\\n", - "0 4135 7 1 4135 \n", - "1 476 1 0 476 \n", - "2 3867 12 9 3867 \n", - "3 1084 2 1 1084 \n", - "\n", - " num_songs_played_90d num_sessions ... num_thumbs_up \\\n", - "0 4135 51 ... 293 \n", - "1 476 7 ... 41 \n", - "2 3867 37 ... 272 \n", - "3 1084 7 ... 68 \n", - "\n", - " num_add_to_playlist num_ads num_add_friend num_downgrade num_upgrade \\\n", - "0 140 7 81 1 1 \n", - "1 16 1 14 1 0 \n", - "2 103 12 69 1 1 \n", - "3 30 2 9 1 0 \n", - "\n", - " num_error percentage_ad days_since_active repeats_ratio \n", - "0 1 0.001392 359 0.179444 \n", - "1 0 0.001664 265 0.052521 \n", - "2 9 0.002576 66 0.175330 \n", - "3 1 0.001546 48 0.076568 \n", - "\n", - "[4 rows x 27 columns]" - ] - }, - "execution_count": 45, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "processed_data.head(4)" ] @@ -2616,7 +1365,7 @@ }, { "cell_type": "code", - "execution_count": 46, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -2631,7 +1380,7 @@ }, { "cell_type": "code", - "execution_count": 47, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -2643,7 +1392,7 @@ }, { "cell_type": "code", - "execution_count": 48, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -2666,7 +1415,7 @@ }, { "cell_type": "code", - "execution_count": 49, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ From f2626fa62e6d1c0976476ba7a5b529af91d55f48 Mon Sep 17 00:00:00 2001 From: atqy Date: Mon, 2 May 2022 20:01:46 +0000 Subject: [PATCH 13/27] final cleanup and reformat --- ...b => 1_cust_churn_overview_dataprep.ipynb} | 83 +++++++++++++------ .../2_cust_churn_train_deploy_infer.ipynb | 40 ++------- 2 files changed, 65 insertions(+), 58 deletions(-) rename use-cases/customer_churn/{0_cust_churn_overview_dw.ipynb => 1_cust_churn_overview_dataprep.ipynb} (91%) diff --git a/use-cases/customer_churn/0_cust_churn_overview_dw.ipynb b/use-cases/customer_churn/1_cust_churn_overview_dataprep.ipynb similarity index 91% rename from use-cases/customer_churn/0_cust_churn_overview_dw.ipynb rename to use-cases/customer_churn/1_cust_churn_overview_dataprep.ipynb index d4e6436eed..76bff32faf 100644 --- a/use-cases/customer_churn/0_cust_churn_overview_dw.ipynb +++ b/use-cases/customer_churn/1_cust_churn_overview_dataprep.ipynb @@ -6,26 +6,24 @@ "source": [ "# Build a Customer Churn Model for Music Streaming App Users: Overview and Data Preparation\n", "\n", - "In this demo, you are going to learn how to use various SageMaker functionalities to build, train, and deploy the model from end to end, including data pre-processing steps like ingestion, cleaning and processing, feature engineering, training and hyperparameter tuning, model explainability, and eventually deploy the model. There are two parts of the demo: in part 1: Prepare Data, you will process the data with the help of Data Wrangler, then create features from the cleaned data. By the end of part 1, you will have a complete feature data set that contains all attributes built for each user, and it is ready for modeling. Then in part 2: Modeling and Reference, you will use the data set built from part 1 to find an optimal model for the use case, then test the model predictability with the test data. To start with Part 2, you can either read in data from the output of your Part 1 results, or use the provided 'data/full_feature_data.csv' as the input for the next steps.\n", + "## Background\n", "\n", + "This notebook is one of a sequence of notebooks that show you how to use various SageMaker functionalities to build, train, and deploy the model from end to end, including data pre-processing steps like ingestion, cleaning and processing, feature engineering, training and hyperparameter tuning, model explainability, and eventually deploy the model. There are two parts of the demo: \n", + "\n", + "1. Build a Customer Churn Model for Music Streaming App Users: Overview and Data Preparation (current notebook) - you will process the data with the help of Data Wrangler, then create features from the cleaned data. By the end of part 1, you will have a complete feature data set that contains all attributes built for each user, and it is ready for modeling.\n", + "1. Build a Customer Churn Model for Music Streaming App Users: Model Selection and Model Explainability - you will use the data set built from part 1 to find an optimal model for the use case, then test the model predictability with the test data. \n", "\n", "For how to set up the SageMaker Studio Notebook environment, please check the [onboarding video]( https://www.youtube.com/watch?v=wiDHCWVrjCU&feature=youtu.be). And for a list of services covered in the use case demo, please check the documentation linked in each section.\n", "\n", "\n", "## Content\n", "* [Overview](#Overview)\n", - "* [Data Selection](#2)\n", - "* [Ingest Data](#4)\n", - "* [Data Cleaning and Data Exploration](#5)\n", - "* [Pre-processing with SageMaker Data Wrangler](#7)\n", - "* [Feature Engineering with SageMaker Processing](#6)\n", - "* [Data Splitting](#8)\n", - "* [Model Selection](#9)\n", - "* [Training with SageMaker Estimator and Experiment](#10)\n", - "* [Hyperparameter Tuning with SageMaker Hyperparameter Tuning Job](#11)\n", - "* [Deploy the model with SageMaker Batch-transform](#12)\n", - "* [Model Explainability with SageMaker Clarify](#15)\n", - "* [Optional: Automate your training and model selection with SageMaker Autopilot (Console)](#13)" + "* [Data Selection](#Data-Selection)\n", + "* [Ingest Data](#Ingest-Data)\n", + "* [Data Cleaning and Data Exploration](#Data-Cleaning)\n", + "* [Pre-processing with SageMaker Data Wrangler](#Pre-processing-with-SageMaker-Data-Wrangler)\n", + "* [Feature Engineering with SageMaker Processing](#Feature-Engineering-with-SageMaker-Processing)\n", + "* [Data Splitting](#Data-Splitting)" ] }, { @@ -87,8 +85,6 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "\n", - "\n", "## Data Selection\n", "\n", "You will use generated music streaming data that is simulated to imitate music streaming user behaviors. The data simulated contains 1100 users and their user behavior for one year (2019/10/28 - 2020/10/28). Data is simulated using the [EventSim](https://github.com/Interana/eventsim) and does not contain any real user data.\n", @@ -213,8 +209,6 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "\n", - "\n", "### Ingest Data\n", "\n", "We ingest the simulated data from the public SageMaker S3 training database." @@ -285,8 +279,6 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "\n", - "\n", "### Data Cleaning\n", "\n", "Due to the size of the data (~2GB), you will start exploring our data starting with a smaller sample, decide which pre-processing steps are necessary, and apply them to the whole dataset." @@ -753,8 +745,6 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "\n", - "\n", "## Pre-processing with SageMaker Data Wrangler\n", "\n", "Now that you have a good understanding of your data and decided which steps are needed to pre-process your data, you can utilize the new Amazon SageMaker GUI tool **Data Wrangler**, without writing all the code for the SageMaker Processing Job.\n", @@ -948,7 +938,52 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Preprocess the Data" + "## Feature Engineering with SageMaker Processing\n", + "\n", + "\n", + "For user churn analysis, usually, you can consider build features from the following aspects:\n", + "\n", + "* Generate base features:\n", + " * user behavior features (listening behavior, app behavior).\n", + " * customer demographic features.\n", + " * customer support features (interactions, ratings, etc.)\n", + "* Formulate time series as features:\n", + " * construct streaming time as time series.\n", + " * build features in the different time windows (e.g. total songs listened in the last 7 days, 30 days, 180 days, etc.)\n", + " \n", + "For this use case, after exploring the data and with all the findings you gathered, now is the time to create features used for your model. Since the data set is time series, you can enrich your features by adding a time factor to it: e.g., for the total number of songs listened, you can create features like total songs listened in the last 7 days, last 30 days, last 90 days, last 180 days, etc. The features built for these use cases will be at the user level - each row represents one user, and will include the following:\n", + "\n", + "* daily features:\n", + " * average_events_weekday (numerical): average number of events per day during weekday\n", + " * average_events_weekend (numerical): average number of events per day during the weekend\n", + " * num_ads_7d: number of ads in last 7 days\n", + " * num_error_7d: total errors encountered in last 7 days\n", + " * num_songs_played_7d: total songs played in last 7 days\n", + " * num_songs_played_30d: total songs played in last 30 days\n", + " * num_songs_played_90d: total songs played in last 90 days\n", + "* user features:\n", + " * num_artists (numerical): number of artists the user has listened to\n", + " * num_songs (numerical): number of songs played\n", + " * num_ads (numerical): number of ads played\n", + " * num_thumbsup (numerical): number of times the user likes a song\n", + " * num_thumbsdown (numerical): number of times the user dislikes a song\n", + " * num_playlist (numerical): number of times user adds a song to a playlist\n", + " * num_addfriend (numerical): number of times user adds a friend\n", + " * num_error (numerical): number of times user encountered an error\n", + " * user_downgrade (binary): user has downgraded plan\n", + " * user_upgrade (binary): user has upgraded plan\n", + " * percentage_song: percentage of the user's action is 'NextSong' (only listens to songs) \n", + " * percentage_ad: percentage of the user's action is 'Roll Advert'\n", + " * repeats_ratio: percentage of total songs that are repeats\n", + " * days_since_active: days since the user registered and leave (if the user cancels)\n", + "* Session features:\n", + " * num_sessions: number of total sessions\n", + " * avg_time_per_session: average time spent per session\n", + " * avg_events_per_session: average number of events per session\n", + " * avg_gap_between_session: average time between sessions\n", + " \n", + "The following function will create the processing job with SageMaker Processing, a new Python SDK that lets data scientists and ML engineers easily run preprocessing, postprocessing and model evaluation workloads on Amazon SageMaker. This SDK uses SageMaker’s built-in container for scikit-learn, possibly the most popular library for data set transformation.\n", + "You can find a complete guide to the SageMaker Processing job in [this blog](https://aws.amazon.com/blogs/aws/amazon-sagemaker-processing-fully-managed-data-processing-and-model-evaluation/)." ] }, { @@ -975,7 +1010,7 @@ "outputs": [], "source": [ "### SAVE THE OUTPUT FILE NAME FROM PROCESSING JOB\n", - "processing_job_output_name = 'processing_job_output.csv'" + "processing_job_output_name = \"processing_job_output.csv\"" ] }, { @@ -1293,8 +1328,6 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "\n", - "\n", "### Data Splitting\n", "\n", "You formulated the use case as a classification problem on user level, so you can randomly split your data from last step into train/validation/test. If you want to predict \"will user X churn in the next Y days\" on per user per day level, you should think about spliting data in chronological order instead of random. \n", diff --git a/use-cases/customer_churn/2_cust_churn_train_deploy_infer.ipynb b/use-cases/customer_churn/2_cust_churn_train_deploy_infer.ipynb index 0fe4474302..f60cd4b063 100644 --- a/use-cases/customer_churn/2_cust_churn_train_deploy_infer.ipynb +++ b/use-cases/customer_churn/2_cust_churn_train_deploy_infer.ipynb @@ -13,19 +13,12 @@ "\n", "\n", "## Content\n", - "* [Overview](#Overview)\n", - "* [Data Selection](#2)\n", - "* [Ingest Data](#4)\n", - "* [Data Cleaning and Data Exploration](#5)\n", - "* [Pre-processing with SageMaker Data Wrangler](#7)\n", - "* [Feature Engineering with SageMaker Processing](#6)\n", - "* [Data Splitting](#8)\n", - "* [Model Selection](#9)\n", - "* [Training with SageMaker Estimator and Experiment](#10)\n", - "* [Hyperparameter Tuning with SageMaker Hyperparameter Tuning Job](#11)\n", - "* [Deploy the model with SageMaker Batch-transform](#12)\n", - "* [Model Explainability with SageMaker Clarify](#15)\n", - "* [Optional: Automate your training and model selection with SageMaker Autopilot (Console)](#13)" + "* [Model Selection](#Model-Selection)\n", + "* [Training with SageMaker Estimator and Experiment](#Training-with-SageMaker-Estimator-and-Experiment)\n", + "* [Hyperparameter Tuning with SageMaker Hyperparameter Tuning Job](#Hyperparameter-Tuning-with-SageMaker-Hyperparameter-Tuning-Job)\n", + "* [Deploy the model with SageMaker Batch-transform](#Deploy-the-model-with-SageMaker-Batch-transform)\n", + "* [Model Explainability with SageMaker Clarify](#Model-Explainability-with-SageMaker-Clarify)\n", + "* [Optional: Automate your training and model selection with SageMaker Autopilot (Console)](#Optional:-Automate-your-training-and-model-selection-with-SageMaker-Autopilot-(Console))" ] }, { @@ -86,8 +79,6 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "\n", - "\n", "## Model Selection\n", "\n", "You can experiment with all your model choices and see which one gives better results. A few things to note when you choose algorithms:\n", @@ -104,8 +95,6 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "\n", - "\n", "## Training with SageMaker Estimator and Experiment\n", "\n", "Once you decide on a range of models you want to experiment with, you can start training and comparing model results to choose the best one. A few things left for you to make a decision:\n", @@ -173,7 +162,7 @@ "##### Alternative: copy data from a public S3 bucket to your own bucket\n", "##### data file should include full_data.csv and sample.json\n", "#### cell 5 - 7 is not needed; the processing job before data wrangler screenshots is not needed\n", - "!aws s3 cp s3://sagemaker-sample-files/datasets/tabular/customer-churn/customer-churn-data.zip ./data/raw/customer-churn-data.zip" + "!aws s3 cp s3://sagemaker-sample-files/datasets/tabular/customer-churn/customer-churn-data-v1.zip ./data/raw/customer-churn-data.zip" ] }, { @@ -412,8 +401,6 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "\n", - "\n", "## Hyperparameter Tuning with SageMaker Hyperparameter Tuning Job\n", "\n", "Now that you understand how training one model works and how to create a SageMaker experiment, and selected the XGBoost model as the final model, you will need to fine-tune the hyperparameters for the best model performances. For a xgboost model, you can start with defining ranges for the eta, alpha, min_child_weight, and max_depth. You can check the [documentation when considering what haperparameter to tune](https://docs.aws.amazon.com/sagemaker/latest/dg/automatic-model-tuning-considerations.html)." @@ -590,7 +577,6 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "\n", "## Deploy the model with SageMaker Batch-transform\n", "\n", "You can directly deploy the best model from your hyperparameter tuning job by getting the best training job from your tuner." @@ -801,8 +787,6 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "\n", - "\n", "## Model Explainability with SageMaker Clarify\n", "\n", "You can visualize which feature contributes most to your prediction results by using the new SageMaker feature SageMaker Clarify. It will provide SHAP values which measures the importance of a feature by replacing it with a dummy and seeing how it affects the prediciton. (In reality, SHAP is smart about the choice of dummy and also takes into account feature interactions.) For a more general overview of model interpretability, see [this post](https://towardsdatascience.com/guide-to-interpretable-machine-learning-d40e8a64b6cf). For other capabilities of SageMaker Clarify, please see the [documentation](https://docs.aws.amazon.com/sagemaker/latest/dg/clarify-fairness-and-explainability.html) and the [example notebook](https://github.com/aws/amazon-sagemaker-examples/blob/master/sagemaker_processing/fairness_and_explainability/fairness_and_explainability.ipynb)." @@ -901,8 +885,6 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "\n", - "\n", "## Optional: Automate your training and model selection with SageMaker Autopilot (Console)\n", "\n", "With [SageMaker Autopilot](https://aws.amazon.com/blogs/aws/amazon-sagemaker-autopilot-fully-managed-automatic-machine-learning/), you can skip all the steps above and let it automatically tracks the inputs, parameters, configurations, and results of your iterations as trials. Go to SageMaker Experiments List on the left navigation pane, then choose **Create Experiment**. You will be directed to the experiment creating page. All you need to do is do give the Experiment job a name, specify your input and output data location, specify your target variable, and choose your ML problem type (classification or regression), or leave it as auto.\n", @@ -1003,17 +985,9 @@ "\n", "The data used in this notebook is simulated using the [EventSim](https://github.com/Interana/eventsim)." ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { - "instance_type": "ml.t3.medium", "kernelspec": { "display_name": "Python 3 (Data Science)", "language": "python", From 55a4ac3924393d06347fd37b69ce837195420c15 Mon Sep 17 00:00:00 2001 From: atqy Date: Mon, 2 May 2022 20:04:15 +0000 Subject: [PATCH 14/27] delete file not needed anymore --- .../1_cust_churn_dataprep.ipynb | 1033 ----------------- 1 file changed, 1033 deletions(-) delete mode 100644 use-cases/customer_churn/1_cust_churn_dataprep.ipynb diff --git a/use-cases/customer_churn/1_cust_churn_dataprep.ipynb b/use-cases/customer_churn/1_cust_churn_dataprep.ipynb deleted file mode 100644 index 7221e8c63e..0000000000 --- a/use-cases/customer_churn/1_cust_churn_dataprep.ipynb +++ /dev/null @@ -1,1033 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Build a Customer Churn Model for Music Streaming App Users: Date Pre-processing with SageMaker Data Wrangler and Processing Job\n", - "\n", - "In this demo, you are going to learn how to use various SageMaker functionalities to build, train, and deploy the model from end to end, including data pre-processing steps like ingestion, cleaning and processing, feature engineering, training and hyperparameter tuning, model explainability, and eventually deploy the model. There are two parts of the demo: in part 1: Prepare Data, you will process the data with the help of Data Wrangler, then create features from the cleaned data. By the end of part 1, you will have a complete feature data set that contains all attributes built for each user, and it is ready for modeling. Then in part 2: Modeling and Reference, you will use the data set built from part 1 to find an optimal model for the use case, then test the model predictability with the test data. To start with Part 2, you can either read in data from the output of your Part 1 results, or use the provided 'data/full_feature_data.csv' as the input for the next steps.\n", - "\n", - "\n", - "For how to set up the SageMaker Studio Notebook environment, please check the [onboarding video]( https://www.youtube.com/watch?v=wiDHCWVrjCU&feature=youtu.be). And for a list of services covered in the use case demo, please check the documentation linked in each section.\n", - "\n", - "\n", - "## Content\n", - "\n", - "* [Overview](#Overview)\n", - "* [Data Selection](#2)\n", - "* [Ingest Data](#4)\n", - "* [Data Cleaning and Data Exploration](#5)\n", - "* [Pre-processing with SageMaker Data Wrangler](#7)\n", - "* [Feature Engineering with SageMaker Processing](#6)\n", - "* [Data Splitting](#8)\n", - "* [Model Selection](#9)\n", - "* [Training with SageMaker Estimator and Experiment](#10)\n", - "* [Hyperparameter Tuning with SageMaker Hyperparameter Tuning Job](#11)\n", - "* [Deploy the model with SageMaker Batch-transform](#12)\n", - "* [Model Explainability with SageMaker Clarify](#15)\n", - "* [Optional: Automate your training and model selection with SageMaker Autopilot (Console)](#13)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Overview\n", - "\n", - "### What is Customer Churn and why is it important for businesses?\n", - "\n", - "Customer churn, or customer retention/attrition, means a customer has the tendency to leave and stop paying for a business. It is one of the primary metrics companies want to track to get a sense of their customer satisfaction, especially for a subscription-based business model. The company can track churn rate (defined as the percentage of customers churned during a period) as a health indicator for the business, but we would love to identify the at-risk customers before they churn and offer appropriate treatment to keep them with the business, and this is where machine learning comes into play.\n", - "\n", - "### Use Cases for Customer Churn\n", - "\n", - "Any subscription-based business would track customer churn as one of the most critical Key Performance Indicators (KPIs). Such companies and industries include Telecom companies (cable, cell phone, internet, etc.), digital subscriptions of media (news, forums, blogposts platforms, etc.), music and video streaming services, and other Software as a Service (SaaS) providers (e-commerce, CRM, Mar-Tech, cloud computing, video conference provider, and visualization and data science tools, etc.)\n", - "\n", - "### Define Business problem\n", - "\n", - "To start with, here are some common business problems to consider depending on your specific use cases and your focus:\n", - " * Will this customer churn (cancel the plan, cancel the subscription)?\n", - " * Will this customer downgrade a pricing plan?\n", - " * For a subscription business model, will a customer renew his/her subscription?\n", - "\n", - "### Machine learning problem formulation\n", - "\n", - "#### Classification: will this customer churn?\n", - "\n", - "To goal of classification is to identify the at-risk customers and sometimes their unusual behavior, such as: will this customer churn or downgrade their plan? Is there any unusual behavior for a customer? The latter question can be formulated as an anomaly detection problem.\n", - "\n", - "#### Time Series: will this customer churn in the next X months? When will this customer churn?\n", - "\n", - "You can further explore your users by formulating the problem as a time series one and detect when will the customer churn.\n", - "\n", - "### Data Requirements\n", - "\n", - "#### Data collection Sources\n", - "\n", - "Some most common data sources used to construct a data set for churn analysis are:\n", - "* Customer Relationship Management platform (CRM), \n", - "* engagement and usage data (analytics services), \n", - "* passive feedback (ratings based on your request), and active feedback (customer support request, feedback on social media and review platforms).\n", - "\n", - "#### Construct a Data Set for Churn Analysis\n", - "\n", - "Most raw data collected from the sources mentioned above are huge and often needs a lot of cleaning and pre-processing. For example, usage data is usually event-based log data and can be more than a few gigabytes every day; you can aggregate the data to user-level daily for further analysis. Feedback and review data are mostly text data, so you would need to clean and pre-process the natural language data to be normalized, machine-readable data. If you are joining multiple data sources (especially from different platforms) together, you would want to make sure all data points are consistent, and the user identity can be matched across different platforms.\n", - " \n", - "#### Challenges with Customer Churn\n", - "\n", - "* Business related\n", - " * Importance of domain knowledge: this is critical when you start building features for the machine learning model. It is important to understand the business enough to decide which features would trigger retention.\n", - "* Data issues\n", - " * fewer churn data available (imbalanced classes): data for churn analysis is often very imbalanced as most of the customers of a business are happy customers (usually).\n", - " * User identity mapping problem: if you are joining data from different platforms (CRM, email, feedback, mobile app, and website usage data), you would want to make sure user A is recognized as the same user across multiple platforms. There are third-party solutions that help you tackle this problem.\n", - " * Not collecting the right data for the use case or Lacking enough data" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Use Case Study - Music Streaming User Churn Prediction\n", - "\n", - "\n", - "\n", - "## Data Selection\n", - "\n", - "You will use generated music streaming data that is simulated to imitate music streaming user behaviors. The data simulated contains 1100 users and their user behavior for one year (2019/10/28 - 2020/10/28). Data is simulated using the [EventSim](https://github.com/Interana/eventsim) and does not contain any real user data.\n", - "\n", - "* Observation window: you will use 1 year of data to generate predictions.\n", - "* Explanation of fields:\n", - " * `ts`: event UNIX timestamp\n", - " * `userId`: a randomly assigned unique user id\n", - " * `sessionId`: a randomly assigned session id unique to each user\n", - " * `page`: event taken by the user, e.g. \"next song\", \"upgrade\", \"cancel\"\n", - " * `auth`: whether the user is a logged-in user\n", - " * `method`: request method, GET or PUT\n", - " * `status`: request status\n", - " * `level`: if the user is a free or paid user\n", - " * `itemInSession`: event happened in the session\n", - " * `location`: location of the user's IP address\n", - " * `userAgent`: agent of the user's device\n", - " * `lastName`: user's last name\n", - " * `firstName`: user's first name\n", - " * `registration`: user's time of registration\n", - " * `gender`: gender of the user\n", - " * `artist`: artist of the song the user is playing at the event\n", - " * `song`: song title the user is playing at the event\n", - " * `length`: length of the session\n", - " \n", - " \n", - " * the data will be downloaded from Github and contained in an [_Amazon Simple Storage Service_](https://aws.amazon.com/s3/) (Amazon S3) bucket." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "For this specific use case, you will focus on a solution to predict whether a customer will cancel the subscription. Some possible expansion of the work includes:\n", - "* predict plan downgrading\n", - "* when a user will churn\n", - "* add song attributes (genre, playlist, charts) and user attributes (demographics) to the data\n", - "* add user feedback and customer service requests to the data\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Architecture Diagram\n", - "\n", - "The services covered in the use case and an architecture diagram is shown below.\n", - "\n", - "
\n", - " \n", - "\n", - "
" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "## The output from Data Wrangler is also provided in the github repo (data/data_wrangler_output.csv).\n", - "## You can also read the provided csv directly." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "\n", - "## Feature engineering with SageMaker Processing Job\n", - "\n", - "\n", - "For user churn analysis, usually, you can consider build features from the following aspects:\n", - "\n", - "* Generate base features:\n", - " * user behavior features (listening behavior, app behavior).\n", - " * customer demographic features.\n", - " * customer support features (interactions, ratings, etc.)\n", - "* Formulate time series as features:\n", - " * construct streaming time as time series.\n", - " * build features in the different time windows (e.g. total songs listened in the last 7 days, 30 days, 180 days, etc.)\n", - " \n", - "For this use case, after exploring the data and with all the findings you gathered, now is the time to create features used for your model. Since the data set is time series, you can enrich your features by adding a time factor to it: e.g., for the total number of songs listened, you can create features like total songs listened in the last 7 days, last 30 days, last 90 days, last 180 days, etc. The features built for these use cases will be at the user level - each row represents one user, and will include the following:\n", - "\n", - "* daily features:\n", - " * average_events_weekday (numerical): average number of events per day during weekday\n", - " * average_events_weekend (numerical): average number of events per day during the weekend\n", - " * num_ads_7d: number of ads in last 7 days\n", - " * num_error_7d: total errors encountered in last 7 days\n", - " * num_songs_played_7d: total songs played in last 7 days\n", - " * num_songs_played_30d: total songs played in last 30 days\n", - " * num_songs_played_90d: total songs played in last 90 days\n", - "* user features:\n", - " * num_artists (numerical): number of artists the user has listened to\n", - " * num_songs (numerical): number of songs played\n", - " * num_ads (numerical): number of ads played\n", - " * num_thumbsup (numerical): number of times the user likes a song\n", - " * num_thumbsdown (numerical): number of times the user dislikes a song\n", - " * num_playlist (numerical): number of times user adds a song to a playlist\n", - " * num_addfriend (numerical): number of times user adds a friend\n", - " * num_error (numerical): number of times user encountered an error\n", - " * user_downgrade (binary): user has downgraded plan\n", - " * user_upgrade (binary): user has upgraded plan\n", - " * percentage_song: percentage of the user's action is 'NextSong' (only listens to songs) \n", - " * percentage_ad: percentage of the user's action is 'Roll Advert'\n", - " * repeats_ratio: percentage of total songs that are repeats\n", - " * days_since_active: days since the user registered and leave (if the user cancels)\n", - "* Session features:\n", - " * num_sessions: number of total sessions\n", - " * avg_time_per_session: average time spent per session\n", - " * avg_events_per_session: average number of events per session\n", - " * avg_gap_between_session: average time between sessions\n", - " \n", - "The following function will create the processing job with SageMaker Processing, a new Python SDK that lets data scientists and ML engineers easily run preprocessing, postprocessing and model evaluation workloads on Amazon SageMaker. This SDK uses SageMaker’s built-in container for scikit-learn, possibly the most popular library for data set transformation.\n", - "You can find a complete guide to the SageMaker Processing job in [this blog](https://aws.amazon.com/blogs/aws/amazon-sagemaker-processing-fully-managed-data-processing-and-model-evaluation/)." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[31mERROR: pg8000 1.17.0 has requirement scramp==1.2.0, but you'll have scramp 1.2.2 which is incompatible.\u001b[0m\n" - ] - } - ], - "source": [ - "!pip install -q pandas=='1.1.5'" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": { - "scrolled": true - }, - "outputs": [], - "source": [ - "# !pip -uQ install s3fs" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%store -r\n", - "%store" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "processing_output_filename" - ] - }, - { - "cell_type": "code", - "execution_count": 27, - "metadata": {}, - "outputs": [], - "source": [ - "import sagemaker\n", - "import json\n", - "import pandas as pd\n", - "import numpy as np\n", - "import glob\n", - "import boto3" - ] - }, - { - "cell_type": "code", - "execution_count": 31, - "metadata": {}, - "outputs": [], - "source": [ - "sagemaker_session = sagemaker.Session()\n", - "s3 = sagemaker_session.boto_session.resource(\"s3\")\n", - "\n", - "region = boto3.Session().region_name\n", - "role = sagemaker.get_execution_role()\n", - "smclient = boto3.Session().client(\"sagemaker\")\n", - "\n", - "output_path = f\"s3://{bucket}/{prefix}/data/processing/\"" - ] - }, - { - "cell_type": "code", - "execution_count": 32, - "metadata": {}, - "outputs": [], - "source": [ - "from sagemaker.sklearn.processing import SKLearnProcessor\n", - "\n", - "sklearn_processor = SKLearnProcessor(\n", - " # framework_version='0.20.0',\n", - " framework_version=\"0.23-1\",\n", - " role=role,\n", - " instance_type=\"ml.m5.xlarge\",\n", - " instance_count=1,\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 34, - "metadata": {}, - "outputs": [], - "source": [ - "### SAVE THE OUTPUT FILE NAME FROM PROCESSING JOB\n", - "processing_job_output_name = 'processing_job_output.csv'\n", - "%store processing_job_output_name" - ] - }, - { - "cell_type": "code", - "execution_count": 35, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Overwriting preprocessing.py\n" - ] - } - ], - "source": [ - "%%writefile preprocessing.py\n", - "\n", - "import os\n", - "import warnings\n", - "import time\n", - "import pandas as pd\n", - "import argparse\n", - "import subprocess\n", - "import sys\n", - "\n", - "subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"awswrangler\"])\n", - "import awswrangler as wr\n", - "\n", - "start_time = time.time()\n", - "\n", - "if __name__ == \"__main__\":\n", - " parser = argparse.ArgumentParser()\n", - " parser.add_argument(\"--dw-output-path\")\n", - " parser.add_argument(\"--processing-output-filename\")\n", - "\n", - " args, _ = parser.parse_known_args()\n", - " print(\"Received arguments {}\".format(args))\n", - "\n", - " data_s3_uri = args.dw_output_path\n", - " output_filename = args.processing_output_filename\n", - "\n", - " # data_path = os.path.join('/opt/ml/processing/input', dw_output_name)\n", - " # df = pd.read_csv(data_path)\n", - " df = wr.s3.read_csv(path=data_s3_uri, dataset=True)\n", - " ## convert to time\n", - " df[\"date\"] = pd.to_datetime(df[\"ts\"], unit=\"ms\")\n", - " df[\"ts_dow\"] = df[\"date\"].dt.weekday\n", - " df[\"ts_date_day\"] = df[\"date\"].dt.date\n", - " df[\"ts_is_weekday\"] = [1 if x in [0, 1, 2, 3, 4] else 0 for x in df[\"ts_dow\"]]\n", - " df[\"registration_ts\"] = pd.to_datetime(df[\"registration\"], unit=\"ms\").dt.date\n", - " ## add labels\n", - " df[\"churned_event\"] = [1 if x == \"Cancellation Confirmation\" else 0 for x in df[\"page\"]]\n", - " df[\"user_churned\"] = df.groupby(\"userId\")[\"churned_event\"].transform(\"max\")\n", - "\n", - " ## convert pages categorical variables to numerical\n", - " events_list = [\n", - " \"NextSong\",\n", - " \"Thumbs Down\",\n", - " \"Thumbs Up\",\n", - " \"Add to Playlist\",\n", - " \"Roll Advert\",\n", - " \"Add Friend\",\n", - " \"Downgrade\",\n", - " \"Upgrade\",\n", - " \"Error\",\n", - " ]\n", - " usage_column_name = []\n", - " for event in events_list:\n", - " event_name = \"_\".join(event.split()).lower()\n", - " usage_column_name.append(event_name)\n", - " df[event_name] = [1 if x == event else 0 for x in df[\"page\"]]\n", - " ## feature engineering\n", - " # average_events_weekday (numerical): average number of events per day during weekday\n", - " # average_events_weekend (numerical): average number of events per day during the weekend\n", - " base_df = (\n", - " df.groupby([\"userId\", \"ts_date_day\", \"ts_is_weekday\"])\n", - " .agg({\"page\": \"count\"})\n", - " .groupby([\"userId\", \"ts_is_weekday\"])[\"page\"]\n", - " .mean()\n", - " .unstack(fill_value=0)\n", - " .reset_index()\n", - " .rename(columns={0: \"average_events_weekend\", 1: \"average_events_weekday\"})\n", - " )\n", - "\n", - " # num_ads_7d, num_songs_played_7d, num_songs_played_30d, num_songs_played_90d, num_ads_7d, num_error_7d\n", - " base_df_daily = (\n", - " df.groupby([\"userId\", \"ts_date_day\"])\n", - " .agg({\"page\": \"count\", \"nextsong\": \"sum\", \"roll_advert\": \"sum\", \"error\": \"sum\"})\n", - " .reset_index()\n", - " )\n", - " feature34 = (\n", - " base_df_daily.groupby([\"userId\", \"ts_date_day\"])\n", - " .tail(7)\n", - " .groupby([\"userId\"])\n", - " .agg({\"nextsong\": \"sum\", \"roll_advert\": \"sum\", \"error\": \"sum\"})\n", - " .reset_index()\n", - " .rename(\n", - " columns={\n", - " \"nextsong\": \"num_songs_played_7d\",\n", - " \"roll_advert\": \"num_ads_7d\",\n", - " \"error\": \"num_error_7d\",\n", - " }\n", - " )\n", - " )\n", - " feature5 = (\n", - " base_df_daily.groupby([\"userId\", \"ts_date_day\"])\n", - " .tail(30)\n", - " .groupby([\"userId\"])\n", - " .agg({\"nextsong\": \"sum\"})\n", - " .reset_index()\n", - " .rename(columns={\"nextsong\": \"num_songs_played_30d\"})\n", - " )\n", - " feature6 = (\n", - " base_df_daily.groupby([\"userId\", \"ts_date_day\"])\n", - " .tail(90)\n", - " .groupby([\"userId\"])\n", - " .agg({\"nextsong\": \"sum\"})\n", - " .reset_index()\n", - " .rename(columns={\"nextsong\": \"num_songs_played_90d\"})\n", - " )\n", - " # num_artists, num_songs, num_ads, num_thumbsup, num_thumbsdown, num_playlist, num_addfriend, num_error, user_downgrade,\n", - " # user_upgrade, percentage_ad, days_since_active\n", - " base_df_user = (\n", - " df.groupby([\"userId\"])\n", - " .agg(\n", - " {\n", - " \"page\": \"count\",\n", - " \"nextsong\": \"sum\",\n", - " \"artist\": \"nunique\",\n", - " \"song\": \"nunique\",\n", - " \"thumbs_down\": \"sum\",\n", - " \"thumbs_up\": \"sum\",\n", - " \"add_to_playlist\": \"sum\",\n", - " \"roll_advert\": \"sum\",\n", - " \"add_friend\": \"sum\",\n", - " \"downgrade\": \"max\",\n", - " \"upgrade\": \"max\",\n", - " \"error\": \"sum\",\n", - " \"ts_date_day\": \"max\",\n", - " \"registration_ts\": \"min\",\n", - " \"user_churned\": \"max\",\n", - " }\n", - " )\n", - " .reset_index()\n", - " )\n", - " base_df_user[\"percentage_ad\"] = base_df_user[\"roll_advert\"] / base_df_user[\"page\"]\n", - " base_df_user[\"days_since_active\"] = (\n", - " base_df_user[\"ts_date_day\"] - base_df_user[\"registration_ts\"]\n", - " ).dt.days\n", - " # repeats ratio\n", - " base_df_user[\"repeats_ratio\"] = 1 - base_df_user[\"song\"] / base_df_user[\"nextsong\"]\n", - "\n", - " # num_sessions, avg_time_per_session, avg_events_per_session,\n", - " base_df_session = (\n", - " df.groupby([\"userId\", \"sessionId\"])\n", - " .agg({\"length\": \"sum\", \"page\": \"count\", \"date\": \"min\"})\n", - " .reset_index()\n", - " )\n", - " base_df_session[\"prev_session_ts\"] = base_df_session.groupby([\"userId\"])[\"date\"].shift(1)\n", - " base_df_session[\"gap_session\"] = (\n", - " base_df_session[\"date\"] - base_df_session[\"prev_session_ts\"]\n", - " ).dt.days\n", - " user_sessions = (\n", - " base_df_session.groupby(\"userId\")\n", - " .agg({\"sessionId\": \"count\", \"length\": \"mean\", \"page\": \"mean\", \"gap_session\": \"mean\"})\n", - " .reset_index()\n", - " .rename(\n", - " columns={\n", - " \"sessionId\": \"num_sessions\",\n", - " \"length\": \"avg_time_per_session\",\n", - " \"page\": \"avg_events_per_session\",\n", - " \"gap_session\": \"avg_gap_between_session\",\n", - " }\n", - " )\n", - " )\n", - "\n", - " # merge features together\n", - " base_df[\"userId\"] = base_df[\"userId\"].astype(\"int\")\n", - " final_feature_df = base_df.merge(feature34, how=\"left\", on=\"userId\")\n", - " final_feature_df = final_feature_df.merge(feature5, how=\"left\", on=\"userId\")\n", - " final_feature_df = final_feature_df.merge(feature6, how=\"left\", on=\"userId\")\n", - " final_feature_df = final_feature_df.merge(user_sessions, how=\"left\", on=\"userId\")\n", - " final_feature_df = final_feature_df.merge(base_df_user, how=\"left\", on=\"userId\")\n", - "\n", - " final_feature_df = final_feature_df.fillna(0)\n", - " # renaming columns\n", - " final_feature_df.columns = [\n", - " \"userId\",\n", - " \"average_events_weekend\",\n", - " \"average_events_weekday\",\n", - " \"num_songs_played_7d\",\n", - " \"num_ads_7d\",\n", - " \"num_error_7d\",\n", - " \"num_songs_played_30d\",\n", - " \"num_songs_played_90d\",\n", - " \"num_sessions\",\n", - " \"avg_time_per_session\",\n", - " \"avg_events_per_session\",\n", - " \"avg_gap_between_session\",\n", - " \"num_events\",\n", - " \"num_songs\",\n", - " \"num_artists\",\n", - " \"num_unique_songs\",\n", - " \"num_thumbs_down\",\n", - " \"num_thumbs_up\",\n", - " \"num_add_to_playlist\",\n", - " \"num_ads\",\n", - " \"num_add_friend\",\n", - " \"num_downgrade\",\n", - " \"num_upgrade\",\n", - " \"num_error\",\n", - " \"ts_date_day\",\n", - " \"registration_ts\",\n", - " \"user_churned\",\n", - " \"percentage_ad\",\n", - " \"days_since_active\",\n", - " \"repeats_ratio\",\n", - " ]\n", - " # only keep created feature columns\n", - " final_feature_df = final_feature_df[\n", - " [\n", - " \"userId\",\n", - " \"user_churned\",\n", - " \"average_events_weekend\",\n", - " \"average_events_weekday\",\n", - " \"num_songs_played_7d\",\n", - " \"num_ads_7d\",\n", - " \"num_error_7d\",\n", - " \"num_songs_played_30d\",\n", - " \"num_songs_played_90d\",\n", - " \"num_sessions\",\n", - " \"avg_time_per_session\",\n", - " \"avg_events_per_session\",\n", - " \"avg_gap_between_session\",\n", - " \"num_events\",\n", - " \"num_songs\",\n", - " \"num_artists\",\n", - " \"num_thumbs_down\",\n", - " \"num_thumbs_up\",\n", - " \"num_add_to_playlist\",\n", - " \"num_ads\",\n", - " \"num_add_friend\",\n", - " \"num_downgrade\",\n", - " \"num_upgrade\",\n", - " \"num_error\",\n", - " \"percentage_ad\",\n", - " \"days_since_active\",\n", - " \"repeats_ratio\",\n", - " ]\n", - " ]\n", - "\n", - " print(\"shape of file to append:\\t\\t{}\".format(final_feature_df.shape))\n", - " iter_end_time = time.time()\n", - " end_time = time.time()\n", - " print(\"minutes elapsed: {}\".format(str((end_time - start_time) / 60)))\n", - "\n", - " final_features_output_path = os.path.join(\"/opt/ml/processing/output\", output_filename)\n", - " print(\"Saving processed data to {}\".format(final_features_output_path))\n", - " final_feature_df.to_csv(final_features_output_path, header=True, index=False)" - ] - }, - { - "cell_type": "code", - "execution_count": 36, - "metadata": {}, - "outputs": [], - "source": [ - "output_path = processing_output_filename" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "scrolled": true - }, - "outputs": [], - "source": [ - "%%time\n", - "from sagemaker.processing import ProcessingInput, ProcessingOutput\n", - "\n", - "processing_job_output_path = f\"s3://{bucket}/{prefix}/data/processing\"\n", - "\n", - "sklearn_processor.run(\n", - " code=\"preprocessing.py\",\n", - " outputs=[\n", - " ProcessingOutput(\n", - " output_name=\"processed_data\",\n", - " source=\"/opt/ml/processing/output\",\n", - " destination=processing_job_output_path,\n", - " )\n", - " ],\n", - " arguments=[\n", - " \"--dw-output-path\",\n", - " processing_job_output_path,\n", - " \"--processing-output-filename\",\n", - " processing_job_output_name,\n", - " ],\n", - ")\n", - "\n", - "preprocessing_job_description = sklearn_processor.jobs[-1].describe()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "preprocessing_job_description" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Congratulations! You have completed Part1: Prepare the data, and now you should have created the complete feature set that is ready for modeling. You can proceed to Part2: modeling and Reference." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## PART 2: Modeling and Reference\n", - "\n", - "now that you have created the complete feature set, you can start to explore and find a best-working model for your churn use case. By the end of part 2, you will select an algorithm, find the best sets of hyperparameter for the model, examine how well the model performs, and finally find the top influential features.\n", - "\n", - "To start with Part 2, you can either read in data from the output of your Part 1 results, or use the provided 'data/full_feature_data.csv' as the input (variable dataframe `processed_data`) for the next steps. " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "\n", - "### Data Splitting\n", - "\n", - "You formulated the use case as a classification problem on user level, so you can randomly split your data from last step into train/validation/test. If you want to predict \"will user X churn in the next Y days\" on per user per day level, you should think about spliting data in chronological order instead of random. \n", - "\n", - "You should split the data and make sure that data of both classes exist in your train, validation and test sets, to make sure both classes are represented in your data. " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Find the output of Processing Job" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "processing_job_output_uri = f\"{processing_job_output_path}/{processing_job_output_name}\"\n", - "processing_job_output_uri" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "!aws s3 cp $processing_job_output_uri ./data" - ] - }, - { - "cell_type": "code", - "execution_count": 48, - "metadata": {}, - "outputs": [], - "source": [ - "processed_data = pd.read_csv(processing_job_output_uri)" - ] - }, - { - "cell_type": "code", - "execution_count": 43, - "metadata": {}, - "outputs": [], - "source": [ - "# Optional: you can also load the processed data from the provided feature set\n", - "# processed_data = pd.read_csv('./data/full_feature_data.csv')" - ] - }, - { - "cell_type": "code", - "execution_count": 49, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
userIduser_churnedaverage_events_weekendaverage_events_weekdaynum_songs_played_7dnum_ads_7dnum_error_7dnum_songs_played_30dnum_songs_played_90dnum_sessions...num_thumbs_upnum_add_to_playlistnum_adsnum_add_friendnum_downgradenum_upgradenum_errorpercentage_addays_since_activerepeats_ratio
0110010.0189.875152.60869682701428270827051...586280141621120.0013923590.589722
1110020.0141.000153.333333952209529527...82322281000.0016642650.526261
2110031.0197.500241.750000773424187734773437...5442062413811180.002576660.587665
3110041.0140.000240.888889216842216821687...136604181020.001546480.538284
\n", - "

4 rows × 27 columns

\n", - "
" - ], - "text/plain": [ - " userId user_churned average_events_weekend average_events_weekday \\\n", - "0 11001 0.0 189.875 152.608696 \n", - "1 11002 0.0 141.000 153.333333 \n", - "2 11003 1.0 197.500 241.750000 \n", - "3 11004 1.0 140.000 240.888889 \n", - "\n", - " num_songs_played_7d num_ads_7d num_error_7d num_songs_played_30d \\\n", - "0 8270 14 2 8270 \n", - "1 952 2 0 952 \n", - "2 7734 24 18 7734 \n", - "3 2168 4 2 2168 \n", - "\n", - " num_songs_played_90d num_sessions ... num_thumbs_up \\\n", - "0 8270 51 ... 586 \n", - "1 952 7 ... 82 \n", - "2 7734 37 ... 544 \n", - "3 2168 7 ... 136 \n", - "\n", - " num_add_to_playlist num_ads num_add_friend num_downgrade num_upgrade \\\n", - "0 280 14 162 1 1 \n", - "1 32 2 28 1 0 \n", - "2 206 24 138 1 1 \n", - "3 60 4 18 1 0 \n", - "\n", - " num_error percentage_ad days_since_active repeats_ratio \n", - "0 2 0.001392 359 0.589722 \n", - "1 0 0.001664 265 0.526261 \n", - "2 18 0.002576 66 0.587665 \n", - "3 2 0.001546 48 0.538284 \n", - "\n", - "[4 rows x 27 columns]" - ] - }, - "execution_count": 49, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "processed_data.head(4)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Split data to train/validation/test by 70/20/10" - ] - }, - { - "cell_type": "code", - "execution_count": 50, - "metadata": {}, - "outputs": [], - "source": [ - "data = processed_data.sample(frac=1, random_state=1729)\n", - "grouped_df = data.groupby(\"user_churned\")\n", - "arr_list = [np.split(g, [int(0.7 * len(g)), int(0.9 * len(g))]) for i, g in grouped_df]\n", - "\n", - "train_data = pd.concat([t[0] for t in arr_list])\n", - "validation_data = pd.concat([t[1] for t in arr_list])\n", - "test_data = pd.concat([v[2] for v in arr_list])" - ] - }, - { - "cell_type": "code", - "execution_count": 51, - "metadata": {}, - "outputs": [], - "source": [ - "def process_data(data, name, header=False):\n", - " data = data.drop(columns=[\"userId\"])\n", - " data = pd.concat([data[\"user_churned\"], data.drop([\"user_churned\"], axis=1)], axis=1)\n", - " data.to_csv(name, header=header, index=False)" - ] - }, - { - "cell_type": "code", - "execution_count": 52, - "metadata": {}, - "outputs": [], - "source": [ - "process_data(train_data, \"data/train_updated.csv\")\n", - "process_data(validation_data, \"data/validation_updated.csv\")\n", - "process_data(test_data, \"data/test_updated.csv\")\n", - "\n", - "process_data(train_data, \"data/train_w_header.csv\", header=True)\n", - "process_data(validation_data, \"data/validation_w_header.csv\", header=True)\n", - "process_data(test_data, \"data/test_w_header.csv\", header=True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Save splitted data to S3\n", - "The splitted data is provided in the /data folder. You can also upload the provided files (`data/train_updated.csv`,`data/validation_updated.csv`, `data/test_updated.csv`) and proceed to the next step. " - ] - }, - { - "cell_type": "code", - "execution_count": 53, - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "\n", - "s3_input_train = (\n", - " boto3.Session()\n", - " .resource(\"s3\")\n", - " .Bucket(bucket)\n", - " .Object(os.path.join(prefix, \"train/train.csv\"))\n", - " .upload_file(\"data/train_updated.csv\")\n", - ")\n", - "s3_input_validation = (\n", - " boto3.Session()\n", - " .resource(\"s3\")\n", - " .Bucket(bucket)\n", - " .Object(os.path.join(prefix, \"validation/validation.csv\"))\n", - " .upload_file(\"data/validation_updated.csv\")\n", - ")\n", - "s3_input_validation = (\n", - " boto3.Session()\n", - " .resource(\"s3\")\n", - " .Bucket(bucket)\n", - " .Object(os.path.join(prefix, \"test/test_labeled.csv\"))\n", - " .upload_file(\"data/test_updated.csv\")\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Disclaimer\n", - "\n", - "The data used in this notebook is synthetic and does not contain real user data. The results (all the names, emails, IP addresses, and browser information) of this simulation are fake." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Citation\n", - "\n", - "The data used in this notebook is simulated using the [EventSim](https://github.com/Interana/eventsim)." - ] - }, - { - "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 -} From 279ab68c15f4c91c40b2d19f947d0b91177f56ce Mon Sep 17 00:00:00 2001 From: atqy Date: Mon, 2 May 2022 20:15:29 +0000 Subject: [PATCH 15/27] add background and revert code in second notebook --- .../2_cust_churn_train_deploy_infer.ipynb | 40 +++++++++---------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/use-cases/customer_churn/2_cust_churn_train_deploy_infer.ipynb b/use-cases/customer_churn/2_cust_churn_train_deploy_infer.ipynb index f60cd4b063..ef63fc15ba 100644 --- a/use-cases/customer_churn/2_cust_churn_train_deploy_infer.ipynb +++ b/use-cases/customer_churn/2_cust_churn_train_deploy_infer.ipynb @@ -6,8 +6,12 @@ "source": [ "# Build a Customer Churn Model for Music Streaming App Users: Model Selection and Model Explainability\n", "\n", - "In this demo, you are going to learn how to use various SageMaker functionalities to build, train, and deploy the model from end to end, including data pre-processing steps like ingestion, cleaning and processing, feature engineering, training and hyperparameter tuning, model explainability, and eventually deploy the model. There are two parts of the demo: in part 1: Prepare Data, you will process the data with the help of Data Wrangler, then create features from the cleaned data. By the end of part 1, you will have a complete feature data set that contains all attributes built for each user, and it is ready for modeling. Then in part 2: Modeling and Reference, you will use the data set built from part 1 to find an optimal model for the use case, then test the model predictability with the test data. To start with Part 2, you can either read in data from the output of your Part 1 results, or use the provided 'data/full_feature_data.csv' as the input for the next steps.\n", + "## Background\n", "\n", + "This notebook is one of a sequence of notebooks that show you how to use various SageMaker functionalities to build, train, and deploy the model from end to end, including data pre-processing steps like ingestion, cleaning and processing, feature engineering, training and hyperparameter tuning, model explainability, and eventually deploy the model. There are two parts of the demo: \n", + "\n", + "1. Build a Customer Churn Model for Music Streaming App Users: Overview and Data Preparation - you will process the data with the help of Data Wrangler, then create features from the cleaned data. By the end of part 1, you will have a complete feature data set that contains all attributes built for each user, and it is ready for modeling.\n", + "1. Build a Customer Churn Model for Music Streaming App Users: Model Selection and Model Explainability (current notebook) - you will use the data set built from part 1 to find an optimal model for the use case, then test the model predictability with the test data. \n", "\n", "For how to set up the SageMaker Studio Notebook environment, please check the [onboarding video]( https://www.youtube.com/watch?v=wiDHCWVrjCU&feature=youtu.be). And for a list of services covered in the use case demo, please check the documentation linked in each section.\n", "\n", @@ -898,19 +902,6 @@ "" ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "train_data = pd.read_csv(\"data/train_w_header.csv\")\n", - "validation_data = pd.read_csv(\"data/validation_w_header.csv\")\n", - "\n", - "data_for_experiment = pd.concat([train_data, validation_data])\n", - "data_for_experiment.to_csv(\"full_feature_data_temp.csv\", index=False)" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -928,14 +919,18 @@ "metadata": {}, "outputs": [], "source": [ - "# pd.read(\"full_feature_data.csv\")\n", - "# s3_input_full_set = (\n", - "# boto3.Session()\n", - "# .resource(\"s3\")\n", - "# .Bucket(bucket)\n", - "# .Object(os.path.join(prefix, \"full/fullset.csv\"))\n", - "# .upload_file(\"full_feature_data.csv\")\n", - "# )" + "train_data = pd.read_csv(\"data/train_w_header.csv\")\n", + "validation_data = pd.read_csv(\"data/validation_w_header.csv\")\n", + "\n", + "data_for_experiment = pd.concat([train_data, validation_data])\n", + "data_for_experiment.to_csv(\"full_feature_data.csv\", index=False)\n", + "s3_input_full_set = (\n", + " boto3.Session()\n", + " .resource(\"s3\")\n", + " .Bucket(bucket)\n", + " .Object(os.path.join(prefix, \"full/fullset.csv\"))\n", + " .upload_file(\"full_feature_data.csv\")\n", + ")" ] }, { @@ -988,6 +983,7 @@ } ], "metadata": { + "instance_type": "ml.t3.medium", "kernelspec": { "display_name": "Python 3 (Data Science)", "language": "python", From 6a510c7c103cdc1bdae6a25e50fce1198912d1fe Mon Sep 17 00:00:00 2001 From: atqy Date: Mon, 2 May 2022 20:19:58 +0000 Subject: [PATCH 16/27] change kernel --- .../customer_churn/1_cust_churn_overview_dataprep.ipynb | 6 +++--- .../customer_churn/2_cust_churn_train_deploy_infer.ipynb | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/use-cases/customer_churn/1_cust_churn_overview_dataprep.ipynb b/use-cases/customer_churn/1_cust_churn_overview_dataprep.ipynb index 76bff32faf..6ad512ecb2 100644 --- a/use-cases/customer_churn/1_cust_churn_overview_dataprep.ipynb +++ b/use-cases/customer_churn/1_cust_churn_overview_dataprep.ipynb @@ -1489,9 +1489,9 @@ "metadata": { "instance_type": "ml.t3.medium", "kernelspec": { - "display_name": "Python 3 (Data Science)", + "display_name": "conda_python3", "language": "python", - "name": "python3__SAGEMAKER_INTERNAL__arn:aws:sagemaker:us-west-2:236514542706:image/datascience-1.0" + "name": "conda_python3" }, "language_info": { "codemirror_mode": { @@ -1503,7 +1503,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.10" + "version": "3.6.13" } }, "nbformat": 4, diff --git a/use-cases/customer_churn/2_cust_churn_train_deploy_infer.ipynb b/use-cases/customer_churn/2_cust_churn_train_deploy_infer.ipynb index ef63fc15ba..ffb7d38a34 100644 --- a/use-cases/customer_churn/2_cust_churn_train_deploy_infer.ipynb +++ b/use-cases/customer_churn/2_cust_churn_train_deploy_infer.ipynb @@ -985,9 +985,9 @@ "metadata": { "instance_type": "ml.t3.medium", "kernelspec": { - "display_name": "Python 3 (Data Science)", + "display_name": "conda_python3", "language": "python", - "name": "python3__SAGEMAKER_INTERNAL__arn:aws:sagemaker:us-west-2:236514542706:image/datascience-1.0" + "name": "conda_python3" }, "language_info": { "codemirror_mode": { @@ -999,7 +999,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.10" + "version": "3.6.13" } }, "nbformat": 4, From 189d1cfb8d12289e0248fa6ea4f569e593fb06fa Mon Sep 17 00:00:00 2001 From: atqy Date: Mon, 2 May 2022 22:56:44 +0000 Subject: [PATCH 17/27] pip install --- .../customer_churn/2_cust_churn_train_deploy_infer.ipynb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/use-cases/customer_churn/2_cust_churn_train_deploy_infer.ipynb b/use-cases/customer_churn/2_cust_churn_train_deploy_infer.ipynb index ef63fc15ba..16281ca458 100644 --- a/use-cases/customer_churn/2_cust_churn_train_deploy_infer.ipynb +++ b/use-cases/customer_churn/2_cust_churn_train_deploy_infer.ipynb @@ -114,6 +114,15 @@ "#### Get ECR image URIs for pre-built SageMaker Docker images" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "! pip install sagemaker-experiments" + ] + }, { "cell_type": "code", "execution_count": null, From 378ce4f64950c857122c4f3f5f71d6b12ab1b5bc Mon Sep 17 00:00:00 2001 From: atqy Date: Mon, 2 May 2022 23:55:20 +0000 Subject: [PATCH 18/27] change file name --- use-cases/customer_churn/1_cust_churn_overview_dataprep.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/use-cases/customer_churn/1_cust_churn_overview_dataprep.ipynb b/use-cases/customer_churn/1_cust_churn_overview_dataprep.ipynb index 6ad512ecb2..a061ad6498 100644 --- a/use-cases/customer_churn/1_cust_churn_overview_dataprep.ipynb +++ b/use-cases/customer_churn/1_cust_churn_overview_dataprep.ipynb @@ -223,7 +223,7 @@ "##### Alternative: copy data from a public S3 bucket to your own bucket\n", "##### data file should include full_data.csv and sample.json\n", "#### cell 5 - 7 is not needed; the processing job before data wrangler screenshots is not needed\n", - "!aws s3 cp s3://sagemaker-sample-files/datasets/tabular/customer-churn/customer-churn-data.zip ./data/raw/customer-churn-data.zip" + "!aws s3 cp s3://sagemaker-sample-files/datasets/tabular/customer-churn/customer-churn-data-v2.zip ./data/raw/customer-churn-data.zip" ] }, { From 877a26ff57b736a6e5d6fd936a51fd4bfb71ca52 Mon Sep 17 00:00:00 2001 From: atqy Date: Tue, 3 May 2022 17:53:36 +0000 Subject: [PATCH 19/27] correct file name --- use-cases/customer_churn/2_cust_churn_train_deploy_infer.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/use-cases/customer_churn/2_cust_churn_train_deploy_infer.ipynb b/use-cases/customer_churn/2_cust_churn_train_deploy_infer.ipynb index 7e6229910f..5ffb2944bf 100644 --- a/use-cases/customer_churn/2_cust_churn_train_deploy_infer.ipynb +++ b/use-cases/customer_churn/2_cust_churn_train_deploy_infer.ipynb @@ -175,7 +175,7 @@ "##### Alternative: copy data from a public S3 bucket to your own bucket\n", "##### data file should include full_data.csv and sample.json\n", "#### cell 5 - 7 is not needed; the processing job before data wrangler screenshots is not needed\n", - "!aws s3 cp s3://sagemaker-sample-files/datasets/tabular/customer-churn/customer-churn-data-v1.zip ./data/raw/customer-churn-data.zip" + "!aws s3 cp s3://sagemaker-sample-files/datasets/tabular/customer-churn/customer-churn-data-v2.zip ./data/raw/customer-churn-data.zip" ] }, { From a56ae247c092b0bbea912481f6fb1e1ef887204e Mon Sep 17 00:00:00 2001 From: atqy Date: Tue, 3 May 2022 20:00:54 +0000 Subject: [PATCH 20/27] delete cell --- .../2_cust_churn_train_deploy_infer.ipynb | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/use-cases/customer_churn/2_cust_churn_train_deploy_infer.ipynb b/use-cases/customer_churn/2_cust_churn_train_deploy_infer.ipynb index 5ffb2944bf..4d5a420400 100644 --- a/use-cases/customer_churn/2_cust_churn_train_deploy_infer.ipynb +++ b/use-cases/customer_churn/2_cust_churn_train_deploy_infer.ipynb @@ -911,17 +911,6 @@ "" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Modeling and Reference\n", - "\n", - "Now that you have created the complete feature set, you can start to explore and find a best-working model for your churn use case. By the end of part 2, you will select an algorithm, find the best sets of hyperparameter for the model, examine how well the model performs, and finally find the top influential features.\n", - "\n", - "To start with Part 2, you can either read in data from the output of your Part 1 results, or use the provided 'data/full_feature_data.csv' as the input (variable dataframe `processed_data`) for the next steps. " - ] - }, { "cell_type": "code", "execution_count": null, From 8c5bc19f1445a6fcedcedcd23912626a1361dffc Mon Sep 17 00:00:00 2001 From: atqy Date: Tue, 3 May 2022 20:04:24 +0000 Subject: [PATCH 21/27] move general information --- .../1_cust_churn_overview_dataprep.ipynb | 14 -------------- use-cases/customer_churn/README.md | 4 ++++ 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/use-cases/customer_churn/1_cust_churn_overview_dataprep.ipynb b/use-cases/customer_churn/1_cust_churn_overview_dataprep.ipynb index a061ad6498..94b2aacc3a 100644 --- a/use-cases/customer_churn/1_cust_churn_overview_dataprep.ipynb +++ b/use-cases/customer_churn/1_cust_churn_overview_dataprep.ipynb @@ -126,20 +126,6 @@ "* add user feedback and customer service requests to the data\n" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Architecture Diagram\n", - "\n", - "The services covered in the use case and an architecture diagram is shown below.\n", - "\n", - "
\n", - " \n", - "\n", - "
" - ] - }, { "cell_type": "markdown", "metadata": {}, diff --git a/use-cases/customer_churn/README.md b/use-cases/customer_churn/README.md index 7beb65418b..9ba85789dd 100644 --- a/use-cases/customer_churn/README.md +++ b/use-cases/customer_churn/README.md @@ -56,6 +56,10 @@ As part of the solution, the following services are used: * [Amazon SageMaker Studio Notebooks](https://aws.amazon.com/sagemaker/): Used to preprocess and visualize the data, and to train model. * [Amazon SageMaker Endpoint](https://aws.amazon.com/sagemaker/): Used to deploy the trained model. +The diagram below shows how each service is used in relation to other services in different stages of this use case. +
+ +
## Cleaning Up From 91fe4c7020ec5fd8a9424f11dd7574623a0b6569 Mon Sep 17 00:00:00 2001 From: atqy Date: Wed, 4 May 2022 01:47:52 +0000 Subject: [PATCH 22/27] upgrade pandas --- use-cases/customer_churn/1_cust_churn_overview_dataprep.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/use-cases/customer_churn/1_cust_churn_overview_dataprep.ipynb b/use-cases/customer_churn/1_cust_churn_overview_dataprep.ipynb index 94b2aacc3a..f4c7cc089b 100644 --- a/use-cases/customer_churn/1_cust_churn_overview_dataprep.ipynb +++ b/use-cases/customer_churn/1_cust_churn_overview_dataprep.ipynb @@ -147,7 +147,7 @@ "outputs": [], "source": [ "!pip install -q 's3fs==0.4.2' 'sagemaker-experiments'\n", - "!pip install --upgrade sagemaker boto3\n", + "!pip install --upgrade sagemaker boto3 pandas\n", "# s3fs is needed for pandas to read files from S3" ] }, From c76bd328f9847456ed21d828e9571ef1f90992e0 Mon Sep 17 00:00:00 2001 From: atqy Date: Wed, 4 May 2022 06:50:51 +0000 Subject: [PATCH 23/27] remove pandas upgarde --- .../customer_churn/1_cust_churn_overview_dataprep.ipynb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/use-cases/customer_churn/1_cust_churn_overview_dataprep.ipynb b/use-cases/customer_churn/1_cust_churn_overview_dataprep.ipynb index f4c7cc089b..205e84aa6e 100644 --- a/use-cases/customer_churn/1_cust_churn_overview_dataprep.ipynb +++ b/use-cases/customer_churn/1_cust_churn_overview_dataprep.ipynb @@ -147,7 +147,7 @@ "outputs": [], "source": [ "!pip install -q 's3fs==0.4.2' 'sagemaker-experiments'\n", - "!pip install --upgrade sagemaker boto3 pandas\n", + "!pip install --upgrade sagemaker boto3\n", "# s3fs is needed for pandas to read files from S3" ] }, @@ -1014,9 +1014,9 @@ "import subprocess\n", "import sys\n", "\n", - "subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"--upgrade\", \"pandas\"])\n", - "subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"awswrangler\"])\n", "import pandas as pd\n", + "\n", + "subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"awswrangler\"])\n", "import awswrangler as wr\n", "\n", "start_time = time.time()\n", From 1a6bb8ab2517e309efdd605a6851710dbe8c3b09 Mon Sep 17 00:00:00 2001 From: atqy Date: Wed, 4 May 2022 16:24:48 +0000 Subject: [PATCH 24/27] change preprocessing.py --- .../1_cust_churn_overview_dataprep.ipynb | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/use-cases/customer_churn/1_cust_churn_overview_dataprep.ipynb b/use-cases/customer_churn/1_cust_churn_overview_dataprep.ipynb index 205e84aa6e..35be37cb9f 100644 --- a/use-cases/customer_churn/1_cust_churn_overview_dataprep.ipynb +++ b/use-cases/customer_churn/1_cust_churn_overview_dataprep.ipynb @@ -1007,18 +1007,16 @@ "source": [ "%%writefile preprocessing.py\n", "\n", + "import sys\n", + "import subprocess\n", + "\n", "import os\n", "import warnings\n", "import time\n", "import argparse\n", - "import subprocess\n", - "import sys\n", - "\n", + "import boto3\n", "import pandas as pd\n", "\n", - "subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"awswrangler\"])\n", - "import awswrangler as wr\n", - "\n", "start_time = time.time()\n", "\n", "if __name__ == \"__main__\":\n", @@ -1031,10 +1029,13 @@ "\n", " data_s3_uri = args.dw_output_path\n", " output_filename = args.processing_output_filename\n", - "\n", - " # data_path = os.path.join('/opt/ml/processing/input', dw_output_name)\n", - " # df = pd.read_csv(data_path)\n", - " df = wr.s3.read_csv(path=data_s3_uri, dataset=True)\n", + " \n", + " bucket = data_s3_uri.split(\"/\")[2]\n", + " key = '/'.join(data_s3_uri.split(\"/\")[3:] + [\"full_data.csv\"])\n", + " s3_client = boto3.client(\"s3\")\n", + " s3_client.download_file(bucket, key, \"full_data.csv\")\n", + " df = pd.read_csv(\"full_data.csv\")\n", + " \n", " ## convert to time\n", " df[\"date\"] = pd.to_datetime(df[\"ts\"], unit=\"ms\")\n", " df[\"ts_dow\"] = df[\"date\"].dt.weekday\n", From 18a01f9eb43a009bb8f994dd3ab077b91f0c700a Mon Sep 17 00:00:00 2001 From: atqy Date: Wed, 4 May 2022 10:50:04 -0700 Subject: [PATCH 25/27] reformat --- .../customer_churn/1_cust_churn_overview_dataprep.ipynb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/use-cases/customer_churn/1_cust_churn_overview_dataprep.ipynb b/use-cases/customer_churn/1_cust_churn_overview_dataprep.ipynb index 35be37cb9f..169b3f1168 100644 --- a/use-cases/customer_churn/1_cust_churn_overview_dataprep.ipynb +++ b/use-cases/customer_churn/1_cust_churn_overview_dataprep.ipynb @@ -1029,13 +1029,13 @@ "\n", " data_s3_uri = args.dw_output_path\n", " output_filename = args.processing_output_filename\n", - " \n", + "\n", " bucket = data_s3_uri.split(\"/\")[2]\n", - " key = '/'.join(data_s3_uri.split(\"/\")[3:] + [\"full_data.csv\"])\n", + " key = \"/\".join(data_s3_uri.split(\"/\")[3:] + [\"full_data.csv\"])\n", " s3_client = boto3.client(\"s3\")\n", " s3_client.download_file(bucket, key, \"full_data.csv\")\n", " df = pd.read_csv(\"full_data.csv\")\n", - " \n", + "\n", " ## convert to time\n", " df[\"date\"] = pd.to_datetime(df[\"ts\"], unit=\"ms\")\n", " df[\"ts_dow\"] = df[\"date\"].dt.weekday\n", From ba6ab570f293fc34affe90c8ac3ad95b38cbb316 Mon Sep 17 00:00:00 2001 From: atqy Date: Wed, 4 May 2022 10:51:44 -0700 Subject: [PATCH 26/27] rename dataprep notebook --- ..._churn_overview_dataprep.ipynb => 1_cust_churn_dataprep.ipynb} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename use-cases/customer_churn/{1_cust_churn_overview_dataprep.ipynb => 1_cust_churn_dataprep.ipynb} (100%) diff --git a/use-cases/customer_churn/1_cust_churn_overview_dataprep.ipynb b/use-cases/customer_churn/1_cust_churn_dataprep.ipynb similarity index 100% rename from use-cases/customer_churn/1_cust_churn_overview_dataprep.ipynb rename to use-cases/customer_churn/1_cust_churn_dataprep.ipynb From ae6cdd70b1414b0589843a2298204c03e351f9c2 Mon Sep 17 00:00:00 2001 From: atqy Date: Wed, 4 May 2022 10:53:18 -0700 Subject: [PATCH 27/27] change rst links --- use-cases/index.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/use-cases/index.rst b/use-cases/index.rst index 8e2cae295e..9ecbaea207 100644 --- a/use-cases/index.rst +++ b/use-cases/index.rst @@ -4,7 +4,6 @@ Music Streaming Service: Customer Churn Detection .. toctree:: :maxdepth: 1 - customer_churn/0_cust_churn_overview_dw customer_churn/1_cust_churn_dataprep customer_churn/2_cust_churn_train_deploy_infer