{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Amazon SageMaker Autopilot による顧客離反予測\n", "_**AutoPilot を用いた携帯電話契約の離反予測**_\n", "\n", "\n", "---\n", "\n", "## コンテンツ\n", "\n", "1. [はじめに](#introduction)\n", "1. [セットアップ](#setup)\n", "1. [データ](#data)\n", "1. [学習](#trainsetup)\n", "1. [結果](#result)\n", "1. [評価](#evaluation)\n", "1. [後片付け](#cleanup)\n", "\n", "\n", "---\n", "\n", "## 1. はじめに \n", "\n", "Amazon SageMaker Autopilot は、表形式のデータセット用の自動機械学習(一般にAutoMLと呼ばれる)ソリューションです。 SageMaker Autopilot は、さまざまな方法で使用できます。Autopilot(本機能の名前)または人間のガイダンス、コードを使わないSageMaker Studio 経由、または AWS SDK の使用、など様々な方法があります。このノートブックは、 AWS SDK を使用して、機械学習モデルを簡単に作成およびデプロイします。\n", "\n", "ビジネスにおいて、顧客を失うことはどんなビジネスにとってもコストがかかります。不満を持っている顧客を早期に特定することで、彼らに滞在するインセンティブを提供する機会が与えられます。このノートブックでは、顧客離反予測とも呼ばれる、不幸な顧客の自動識別のための機械学習(ML)の使用について説明します。ただし、ML モデルが完全な予測を行うことはめったにありません。そのため、このノートブックでは、MLを使用した場合の財務結果を決定するときに、予測ミスの相対コストを組み込む方法についても説明します。\n", "\n", "私たち全員に馴染みのある携帯電話会社解約の例を使用します。プロバイダーの本日の障害を常に見つけることができるようです!そして、私のプロバイダーが解約しようとしていることを知っていれば、タイムリーなインセンティブを提供できます。いつでも電話のアップグレードを使用したり、新しい機能を有効にしたりできます。インセンティブは多くの場合、顧客を失って再獲得するよりもはるかに費用対効果が高くなります。\n", "\n", "---\n", "## 2. セットアップ \n", "\n", "_このノートブックは、ml.m4.xlarge ノートブックインスタンスで作成およびテストされました。_ \n", "\n", "まずは次のように指定します。\n", "\n", "- トレーニングとモデルデータに使用する S3 バケットとプレフィックスは、ノートブックインスタンス、トレーニング、ホスティングと同じリージョン内にある必要があります。\n", "- データへのトレーニングとホスティングアクセスを提供するために使用される IAM role の作成方法については、ドキュメントを参照してください。 ノートブックインスタンス、トレーニング、ホスティングに複数の role が必要な場合は、boto regexp を適切な完全な IAM role の文字列に置き換えてください。" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "isConfigCell": true }, "outputs": [], "source": [ "import sagemaker\n", "import boto3\n", "from sagemaker import get_execution_role\n", "\n", "region = boto3.Session().region_name\n", "session = sagemaker.Session()\n", "\n", "# You can modify the following to use a bucket of your choosing\n", "bucket = session.default_bucket()\n", "prefix = 'sagemaker/DEMO-autopilot-churn'" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "role = get_execution_role()\n", "\n", "# This is the client we will use to interact with SageMaker AutoPilot\n", "sm = boto3.Session().client(service_name='sagemaker',region_name=region)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "次に、残りの演習で必要になる Python ライブラリをインポートします。" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import pandas as pd\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "import io\n", "import os\n", "import sys\n", "import time\n", "import json\n", "from IPython.display import display\n", "from time import strftime, gmtime\n", "import sagemaker\n", "from sagemaker.predictor import csv_serializer" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 3. データ \n", "\n", "モバイルオペレーターには、顧客が最終的に解約した場合と、サービスを継続して使用した場合の記録があります。この履歴情報を使用して、機械学習により、1つのモバイルオペレーターの解約予測モデルを構築できます。モデルをトレーニングした後、任意の顧客のプロファイル情報(モデルのトレーニングに使用したのと同じプロファイル情報)をモデルに渡し、この顧客が解約するかどうかをモデルに予測させることができます。 もちろん、私たちはモデルが間違いを犯すことを期待しています。結局のところ、将来を予測することは難しいビジネスです!ただし、予測エラーに対処する方法も示します。\n", "\n", "私たちが使用するデータセットは公開されており、Daniel T. Larose 著の [Discovering Knowledge in Data](https://www.amazon.com/dp/0470908742/) で言及されています。 著者はカリフォルニア大学アーバインの機械学習データセットリポジトリに帰属します。 それでは、そのデータセットをダウンロードして読みましょう。\n", "\n", "Jupyter notebook では、冒頭に ! を入力することで、シェルコマンドを実行することができます。AWS CLIを用いてS3からデータをダウンロードします。" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "!aws s3 cp s3://sagemaker-sample-files/datasets/tabular/synthetic/churn.txt ./" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 3.1. 学習に使うデータセットの確認\n", "\n", "データセットに対して Autopilot を実行する前に、まずデータセットをチェックして、明らかなエラーがないことを確認します。 Autopilot のプロセスには時間がかかる場合があります。ジョブを開始する前にデータセットを検査することをお勧めします。 この特定のデータセットは小さいため、ノートブックインスタンス自体で検査できます。ノートブックインスタンスのメモリに収まらない大きなデータセットがある場合は、Apache Spark などのビッグデータ分析ツールを使用して、データセットをオフラインで検査します。[Deequ](https://github.com/awslabs/deequ) は、Apache Spark の上に構築されたライブラリで、大きなデータセットのチェックを実行するのに役立ちます。 Autopilot は、最大 5GB のデータセットを処理できます。\n", "\n", "データを Pandas データフレームに読み込み、確認します。" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "churn = pd.read_csv('./churn.txt')\n", "pd.set_option('display.max_columns', 500)\n", "churn" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "現代の基準からすると、これらは比較的小さなデータセットであり、3,333レコードしかありません。各レコードは21の属性を使用して、未知の米国の携帯電話会社の顧客のプロファイルを記述しています。 属性は次のとおりです。\n", "\n", "- `State`: 顧客が居住する米国の州。2文字の略語で示されます。 たとえば、OHまたはNJ\n", "- `Account Length`: このアカウントがアクティブであった日数\n", "- `Area Code`: 対応する顧客の電話番号の3桁の市外局番\n", "- `Phone`: 残りの7桁の電話番号\n", "- `Int’l Plan`: 顧客が国際通話プランを持っているかどうか: yes/no\n", "- `VMail Plan`: 顧客がボイスメール機能を持っているかどうか: yes/no\n", "- `VMail Message`: 月あたりのボイスメールメッセージの平均数\n", "- `Day Mins`: 日中に使用された通話時間の合計(分)\n", "- `Day Calls`: 日中の通話数の合計\n", "- `Day Charge`: 日中の通話料金\n", "- `Eve Mins, Eve Calls, Eve Charge`: 夕刻の通話料金\n", "- `Night Mins`, `Night Calls`, `Night Charge`: 夜間の通話料金\n", "- `Intl Mins`, `Intl Calls`, `Intl Charge`: 国際電話の請求費用\n", "- `CustServ Calls`: カスタマーサービスにかけられた通話の数\n", "- `Churn?`: 顧客がサービスを終了したかどうか: true/false\n", "\n", "最後の属性 `Churn?`は、これからMLモデルで予測する属性ターゲット属性です。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 3.2. データを S3 にアップロード\n", "\n", "データをトレーニングとテストの分割に分割します。 トレーニング分割は SageMaker Autopilot で使用されます。 テストスプリットは、推奨モデルを使用して推論を実行するために予約されています。" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "train_data = churn.sample(frac=0.8,random_state=200)\n", "\n", "test_data = churn.drop(train_data.index)\n", "\n", "test_data_no_target = test_data.drop(columns=['Churn?'])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "次に、これらのファイルをS3にアップロードします。" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "train_file = 'train_data.csv';\n", "train_data.to_csv(train_file, index=False, header=True)\n", "train_data_s3_path = session.upload_data(path=train_file, key_prefix=prefix + \"/train\")\n", "print('Train data uploaded to: ' + train_data_s3_path)\n", "\n", "test_file = 'test_data.csv';\n", "test_data_no_target.to_csv(test_file, index=False, header=False)\n", "test_data_s3_path = session.upload_data(path=test_file, key_prefix=prefix + \"/test\")\n", "print('Test data uploaded to: ' + test_data_s3_path)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 4. 学習\n", "### 4.1. SageMaker Autopilot ジョブの設定 \n", "\n", "データセットを Amazon S3 にアップロードした後、Autopilot を呼び出して、このデータセットでモデルをトレーニングするのに最適な ML パイプラインを見つけることができます。\n", "\n", "Autopilot ジョブを呼び出すために必要な入力は次のとおりです。\n", "\n", "* 入力データセットおよびすべてのアーティファクト出力のための Amazon S3 ロケーション\n", "* 予測するデータセットの列の名前(この場合は `Churn?` )\n", "* IAM role\n", "\n", "現在、Autopilot はCSV形式の表形式のデータセットのみをサポートしています。 すべてのファイルにヘッダー行があるか、データセットの最初のファイルが名前のアルファベット順または字句順に並べ替えられている場合、ヘッダー行があると予想されます。" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "input_data_config = [{\n", " 'DataSource': {\n", " 'S3DataSource': {\n", " 'S3DataType': 'S3Prefix',\n", " 'S3Uri': 's3://{}/{}/train'.format(bucket,prefix)\n", " }\n", " },\n", " 'TargetAttributeName': 'Churn?'\n", " }\n", " ]\n", "\n", "output_data_config = {\n", " 'S3OutputPath': 's3://{}/{}/output'.format(bucket,prefix)\n", " }" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`Regression`,`MulticlassClassification`, `BinaryClassification` など、データセットで解決する問題のタイプを指定することもできます 。不明な場合、SageMaker Autopilot はターゲット列(予測する列)の統計に基づいて問題のタイプを推測します。\n", "\n", "ターゲット属性 ```Churn?``` はバイナリであるため、このモデルではバイナリ予測(バイナリ分類とも呼ばれます)を実行します。この例では、AutoPilot に問題の種類を推測させます。\n", "\n", "SageMaker Autopilot ジョブの実行時間を制限するには、パイプライン評価または候補の最大数を提供するか(1つのパイプライン評価は候補モデルを生成するため `Candidate` と呼ばれます)、または Autopilot ジョブ全体。デフォルト設定では、このジョブの実行には約4時間かかります。これは、 Autopilot が最適なトレーニングパラメーターを見つけるために使用する探索的プロセスの性質により、実行ごとに異なります。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 4.2. SageMaker Autopilot ジョブの起動 \n", "\n", "これで、 `create_auto_ml_job` API を呼び出して Autopilot ジョブを起動できます。 学習ジョブ数の最大値を20に制限して、ジョブが数分で完了するようにします。" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from time import gmtime, strftime, sleep\n", "timestamp_suffix = strftime('%d-%H-%M-%S', gmtime())\n", "\n", "auto_ml_job_name = 'automl-churn-' + timestamp_suffix\n", "print('AutoMLJobName: ' + auto_ml_job_name)\n", "\n", "sm.create_auto_ml_job(AutoMLJobName=auto_ml_job_name,\n", " InputDataConfig=input_data_config,\n", " OutputDataConfig=output_data_config,\n", " AutoMLJobConfig={'CompletionCriteria':\n", " {'MaxCandidates': 20}\n", " },\n", " RoleArn=role)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 4.3. SageMaker Autopilot ジョブの進行状況の追跡 \n", "SageMaker Autopilot ジョブは、次の大まかな手順で構成されています。\n", "\n", "* Analyzing Data: データセットが分析され、Autopilot が試す必要がある ML パイプラインのリストを表示します。 データセットは、学習用データセットと検証データセットにも分割されます。\n", "* Feature Engineering: Autopilot は、データセットおよび個々のデータの変換を行います。\n", "* Model Tuning: パフォーマンスが最も高いパイプラインが、学習アルゴリズム(パイプラインの最終ステージ)に最適なハイパーパラメーターとともに選択されます。" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print ('JobStatus - Secondary Status')\n", "print('------------------------------')\n", "\n", "\n", "describe_response = sm.describe_auto_ml_job(AutoMLJobName=auto_ml_job_name)\n", "print (describe_response['AutoMLJobStatus'] + \" - \" + describe_response['AutoMLJobSecondaryStatus'])\n", "job_run_status = describe_response['AutoMLJobStatus']\n", " \n", "while job_run_status not in ('Failed', 'Completed', 'Stopped'):\n", " describe_response = sm.describe_auto_ml_job(AutoMLJobName=auto_ml_job_name)\n", " job_run_status = describe_response['AutoMLJobStatus']\n", " \n", " print (describe_response['AutoMLJobStatus'] + \" - \" + describe_response['AutoMLJobSecondaryStatus'])\n", " sleep(30)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 5. 結果\n", "\n", "次に、describe_auto_ml_job API を使用して、SageMaker Autopilot ジョブによって選択された最適な候補を検索します。" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "best_candidate = sm.describe_auto_ml_job(AutoMLJobName=auto_ml_job_name)['BestCandidate']\n", "best_candidate_name = best_candidate['CandidateName']\n", "print(best_candidate)\n", "print('\\n')\n", "print(\"CandidateName: \" + best_candidate_name)\n", "print(\"FinalAutoMLJobObjectiveMetricName: \" + best_candidate['FinalAutoMLJobObjectiveMetric']['MetricName'])\n", "print(\"FinalAutoMLJobObjectiveMetricValue: \" + str(best_candidate['FinalAutoMLJobObjectiveMetric']['Value']))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 6. 評価\n", "### 6.1. ホスティング\n", "\n", "アルゴリズムのトレーニングが完了したので、モデルを作成して、ホストされたエンドポイントにデプロイします。" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "timestamp_suffix = strftime('%d-%H-%M-%S', gmtime())\n", "model_name = best_candidate_name + timestamp_suffix + \"-model\"\n", "model_arn = sm.create_model(Containers=best_candidate['InferenceContainers'],\n", " ModelName=model_name,\n", " ExecutionRoleArn=role)\n", "\n", "epc_name = best_candidate_name + timestamp_suffix + \"-epc\"\n", "ep_config = sm.create_endpoint_config(EndpointConfigName = epc_name,\n", " ProductionVariants=[{'InstanceType': 'ml.m5.2xlarge',\n", " 'InitialInstanceCount': 1,\n", " 'ModelName': model_name,\n", " 'VariantName': 'main'}])\n", "\n", "ep_name = best_candidate_name + timestamp_suffix + \"-ep\"\n", "create_endpoint_response = sm.create_endpoint(EndpointName=ep_name,\n", " EndpointConfigName=epc_name)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sm.get_waiter('endpoint_in_service').wait(EndpointName=ep_name)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 6.2. 評価\n", "\n", "ホストされたエンドポイントが実行されたので、http POST リクエストを行うだけで、モデルからリアルタイム予測を非常に簡単に行うことができます。 しかし、最初に、 `test_data` NumPy 配列をエンドポイントの背後のモデルに渡すために、シリアライザーとデシリアライザーをセットアップする必要があります。" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from io import StringIO\n", "from sagemaker.predictor import Predictor\n", "from sagemaker.serializers import CSVSerializer\n", "\n", "\n", "predictor = Predictor(\n", " endpoint_name=ep_name,\n", " sagemaker_session=session,\n", " serializer=CSVSerializer()\n", ")\n", "\n", "# Remove the target column from the test data\n", "test_data_inference = test_data.drop('Churn?', axis=1)\n", "\n", "# Obtain predictions from SageMaker endpoint\n", "prediction = predictor.predict(test_data_inference.to_csv(sep=',', header=False, index=False)).decode('utf-8')\n", "\n", "# Load prediction in pandas and compare to ground truth\n", "prediction_df = pd.read_csv(StringIO(prediction), header=None)\n", "accuracy = (test_data.reset_index()['Churn?'] == prediction_df[0]).sum() / len(test_data_inference)\n", "print('Accuracy: {}'.format(accuracy))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 6.3. SageMaker Autopilot が探索した他のチューニングジョブ\n", "SageMaker Autopilot によって探索されたすべての候補(さまざまなハイパーパラメーターの組み合わせによるパイプライン評価)を表示し、それらを最終的なパフォーマンスメトリックで並べ替えることができます。" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "candidates = sm.list_candidates_for_auto_ml_job(AutoMLJobName=auto_ml_job_name, SortBy='FinalObjectiveMetricValue')['Candidates']\n", "index = 1\n", "for candidate in candidates:\n", " print (str(index) + \" \" + candidate['CandidateName'] + \" \" + str(candidate['FinalAutoMLJobObjectiveMetric']['Value']))\n", " index += 1" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 6.4. 自動生成ノートブック\n", "#### 候補生成ノートブック\n", "Sagemaker AutoPilot は、候補探索過程のノートブックも自動生成します。 このノートブックを使用すると、Sagemaker Autopilot が実行するさまざまな手順をインタラクティブに実行して、最適な候補を見つけることができます。 このノートブックは、並列処理、使用されるハードウェア、探索されるアルゴリズム、特徴抽出スクリプトなどのさまざまなランタイムパラメーターをオーバーライドするためにも使用できます。" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "candidate_definition = sm.describe_auto_ml_job(AutoMLJobName=auto_ml_job_name)['AutoMLJobArtifacts']['CandidateDefinitionNotebookLocation']\n", "!aws s3 cp {candidate_definition} ./" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### データ探索ノートブック\n", "Sagemaker Autopilot は、Data Exploration の過程のノートブックも自動生成します。" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "data_exploration = sm.describe_auto_ml_job(AutoMLJobName=auto_ml_job_name)['AutoMLJobArtifacts']['DataExplorationNotebookLocation']\n", "!aws s3 cp {data_exploration} ./" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`SageMakerAutopilotCandidateDefinitionNotebook.ipynb` と `SageMakerAutopilotDataExplorationNotebook.ipynb` が、本ノートブックと同じローカルフォルダに出力されました。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 7. 後片付け\n", "\n", "Autopilot ジョブは、データセットの分割、前処理スクリプト、前処理されたデータなど、多くの主要なアーティファクトを作成します。下記のコードのコメントを外すと、これらは削除されます。 この操作により、生成されたすべてのモデルと自動生成されたノートブックも削除されます。" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "s3 = boto3.resource('s3')\n", "s3_bucket = s3.Bucket(bucket)\n", "\n", "job_outputs_prefix = '{}/output/{}'.format(prefix, auto_ml_job_name)\n", "s3_bucket.objects.filter(Prefix=job_outputs_prefix).delete()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "最後に、エンドポイントと関連するリソースを削除します。" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sm.delete_endpoint(EndpointName=ep_name)\n", "sm.delete_endpoint_config(EndpointConfigName=epc_name)\n", "sm.delete_model(ModelName=model_name)" ] } ], "metadata": { "celltoolbar": "Tags", "instance_type": "ml.t3.medium", "kernelspec": { "display_name": "conda_mxnet_p36", "language": "python", "name": "conda_mxnet_p36" }, "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" }, "notice": "Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. Licensed under the Apache License, Version 2.0 (the \"License\"). You may not use this file except in compliance with the License. A copy of the License is located at http://aws.amazon.com/apache2.0/ or in the \"license\" file accompanying this file. This file is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License." }, "nbformat": 4, "nbformat_minor": 4 }