Amazon SageMaker Autopilotを使ってみた

こんにちは、小澤です。

re:Invent 2019にて発表されて、すでに利用可能な状態となっているSageMaker上でAutoMLを実現するための仕組みであるAutopilotを早速使ってみようかと思った今日この頃です。

やってみよう

すでにSageMakerのExamplesにもAutopilotの使い方を教えてくれるサンプルがあるようなので、 こちらを動かしてみてその流れを確認していきます。

最初にやることはいつも通りSageMakerを利用するために必要ライブラリのインポートやSessionの作成です。 このサンプルではBoto3を使ってSageMakerを操作しているため、そちらもインポートしておきます。

import sagemaker
import boto3
from sagemaker import get_execution_role

region = boto3.Session().region_name

session = sagemaker.Session()
bucket = session.default_bucket()
prefix = 'sagemaker/autopilot-dm'

role = get_execution_role()

sm = boto3.Session().client(service_name='sagemaker',region_name=region)

続いて、データの取得を行います。 今回利用するデータはUCI Machine Learning RepositoryにあるBank Marketing Data Setとなります。 このデータセットは銀行の電話でのダイレクトマーケティングを行った結果のものとなっとり、定期預金がサブスクライブされるかをyes or noで予測するものとなっています。

!wget -N https://archive.ics.uci.edu/ml/machine-learning-databases/00222/bank-additional.zip
!unzip -o bank-additional.zip

local_data_path = './bank-additional/bank-additional-full.csv'

データをPandasのDataFrameとして読み込んで確認すると、以下のように数値だったり文字列だったりのデータとなっています。

import pandas as pd

# 元データの区切り文字が「;」なので、sepでそれを指定
data = pd.read_csv(local_data_path, sep=';')
data.head()

これを7:3の割合で学習データとテストデータに分割して、SageMakerで利用するためにS3に保存します。

train_data = data.sample(frac=0.8,random_state=200)
test_data = data.drop(train_data.index)

# 目的変数である「y」列は推論時に渡すデータには含めない
test_data_no_target = test_data.drop(columns=['y'])

# 学習データ、テストデータをそれぞれS3に保存
train_file = 'train_data.csv';
train_data.to_csv(train_file, index=False, header=True)
train_data_s3_path = session.upload_data(path=train_file, key_prefix=prefix + "/train")
print('Train data uploaded to: ' + train_data_s3_path)

test_file = 'test_data.csv';
test_data_no_target.to_csv(test_file, index=False, header=False)
test_data_s3_path = session.upload_data(path=test_file, key_prefix=prefix + "/test")
print('Test data uploaded to: ' + test_data_s3_path)

これで、Autopilotで利用するデータの準備が完了しました。

ここからいよいよAutopilotを利用していきます。 まずは実行するAutopilotに渡す各種設定を記載したdictを作成します。

input_data_config = [{
      'DataSource': {
        'S3DataSource': {
          'S3DataType': 'S3Prefix',
          'S3Uri': 's3://{}/{}/train'.format(bucket,prefix)
        }
      },
      'TargetAttributeName': 'y' # 学習データ中の目的変数となる列名
    }
  ]

output_data_config = {
    'S3OutputPath': 's3://{}/{}/output'.format(bucket,prefix)
  }

Exampleのノートブック中では、このほかにも対象のタスク(回帰・二値分類・多値分類)や評価指標などが設定可能との記載があります。 Autopilotジョブの実行は後続の処理でBoto3を使って実行しますので、 指定可能な設定値はパラメータはBoto3のドキュメントをご確認ください。

この設定を使って、ジョブの作成と実行を行います。

from time import gmtime, strftime, sleep
timestamp_suffix = strftime('%d-%H-%M-%S', gmtime())

auto_ml_job_name = 'automl-banking-' + timestamp_suffix
print('AutoMLJobName: ' + auto_ml_job_name)

# ジョブの実行
# 設定値として先ほど作成したinput_data_configとoutput_data_configを渡す
sm.create_auto_ml_job(AutoMLJobName=auto_ml_job_name,
                        InputDataConfig=input_data_config,
                        OutputDataConfig=output_data_config,
                        RoleArn=role)

# 定期的にステータスを確認しながら完了までwhileループで待つ
print ('JobStatus - Secondary Status')
print('------------------------------')

describe_response = sm.describe_auto_ml_job(AutoMLJobName=auto_ml_job_name)
print (describe_response['AutoMLJobStatus'] + " - " + describe_response['AutoMLJobSecondaryStatus'])
job_run_status = describe_response['AutoMLJobStatus']

while job_run_status not in ('Failed', 'Completed', 'Stopped'):
    describe_response = sm.describe_auto_ml_job(AutoMLJobName=auto_ml_job_name)
    job_run_status = describe_response['AutoMLJobStatus']

    print (describe_response['AutoMLJobStatus'] + " - " + describe_response['AutoMLJobSecondaryStatus'])
    sleep(30)

ステータスは30秒ごとに以下のように出力されていきます。

JobStatus - Secondary Status
------------------------------
InProgress - AnalyzingData
InProgress - AnalyzingData
...
InProgress - AnalyzingData
InProgress - FeatureEngineering
InProgress - FeatureEngineering
...
InProgress - FeatureEngineering
InProgress - ModelTuning
InProgress - ModelTuning
...
InProgress - ModelTuning
Completed - MaxCandidatesReached

ステータスがCompletedになれば実行完了です。

InProgress中の処理がAnalyzingData, FeatureEngineering, ModelTuningと変化しており、 適切な処理のためのEDAやFeature Engineering, 学習時のハイパーパラメータチューニングなどがすべてお任せで実行できていることがわかります。

なお、ステータスがFailedになった場合、以下で詳細を取得できます。

sm.describe_auto_ml_job(AutoMLJobName=auto_ml_job_name)['FailureReason']

また、ジョブの実行中は以下のように並列して大量の学習処理が実行されていることが確認できます。

ここでの処理内容はSageMakerの通常利用の時同様、使用しているコンテナやパラメータの設置値などが確認可能です。

ジョブの実行が完了したのちは、もちろん推論で利用可能です。 最も精度の高かったモデルの情報は以下のように、結果からBestCandidateを取得することで確認できます。

best_candidate = sm.describe_auto_ml_job(AutoMLJobName=auto_ml_job_name)['BestCandidate']
best_candidate_name = best_candidate['CandidateName']
print("CandidateName: " + best_candidate_name)
print("FinalAutoMLJobObjectiveMetricName: " + best_candidate['FinalAutoMLJobObjectiveMetric']['MetricName'])
print("FinalAutoMLJobObjectiveMetricValue: " + str(best_candidate['FinalAutoMLJobObjectiveMetric']['Value']))
CandidateName: automl-banki-tuning-job-1-ee131-016-c36a778d
FinalAutoMLJobObjectiveMetricName: validation:accuracy
FinalAutoMLJobObjectiveMetricValue: 0.9153130054473877

今回最もいい結果のモデルは正解率91.5%となっているようです。 なお、 best_candidate 変数はこれら以外にも様々な情報が含まれています。 dict形式となっているのでpprintをつかって確認すると良いでしょう。

from pprint import pprint
pprint(best_candidate)

出力結果をすべて記載するとゴチャゴチャするため割愛します。

また、以下のような処理で最も精度が高かったもの以外の情報も取得可能です。

candidates = sm.list_candidates_for_auto_ml_job(AutoMLJobName=auto_ml_job_name, SortBy='FinalObjectiveMetricValue')['Candidates']
for index, candidate in enumerate(candidates):
  print (str(index+1) + "  " + candidate['CandidateName'] + "  " + str(candidate['FinalAutoMLJobObjectiveMetric']['Value']))

推論で利用するには、まずモデルを作成します。

model_name = 'automl-banking-model-' + timestamp_suffix
model = sm.create_model(Containers=best_candidate['InferenceContainers'],
                            ModelName=model_name,
                            ExecutionRoleArn=role)
print('Model ARN corresponding to the best candidate is : {}'.format(model['ModelArn']))

使ってモデル作成に必要なコンテナ情報を best_candidate['InferenceContainers'] を使って渡しています。 best_candidate['InferenceContainers'] の中身は配列になっており、推論の処理だけでなく一連のプロセスがパイプライン化されたものになっています。

pprint(best_candidate['InferenceContainers'])
[{'Environment': {'AUTOML_TRANSFORM_MODE': 'feature-transform',
                  'SAGEMAKER_DEFAULT_INVOCATIONS_ACCEPT': 'application/x-recordio-protobuf',
                  'SAGEMAKER_PROGRAM': 'sagemaker_serve',
                  'SAGEMAKER_SUBMIT_DIRECTORY': '/opt/ml/model/sagemaker_serve.py'},
  'Image': '354813040037.dkr.ecr.ap-northeast-1.amazonaws.com/sagemaker-sklearn-automl:0.1.0-cpu-py3',
  'ModelDataUrl': 's3://<bueckt_name>/sagemaker/autopilot-dm/output/automl-banking-05-07-35-13/data-processor-models/automl-banking-05-07-35-13-automl-ban-dpp9-1-6d31216e86e5423d8e/output/model.tar.gz'},
 {'Environment': {'MAX_CONTENT_LENGTH': '20971520',
                  'SAGEMAKER_DEFAULT_INVOCATIONS_ACCEPT': 'text/csv'},
  'Image': '354813040037.dkr.ecr.ap-northeast-1.amazonaws.com/sagemaker-xgboost:0.90-1-cpu-py3',
  'ModelDataUrl': 's3://<bueckt_name>/sagemaker/autopilot-dm/output/automl-banking-05-07-35-13/tuning/automl-ban-dpp9-xgb/automl-banki-tuning-job-1-ee131-016-c36a778d/output/model.tar.gz'},
 {'Environment': {'AUTOML_TRANSFORM_MODE': 'inverse-label-transform',
                  'SAGEMAKER_DEFAULT_INVOCATIONS_ACCEPT': 'text/csv',
                  'SAGEMAKER_PROGRAM': 'sagemaker_serve',
                  'SAGEMAKER_SUBMIT_DIRECTORY': '/opt/ml/model/sagemaker_serve.py'},
  'Image': '354813040037.dkr.ecr.ap-northeast-1.amazonaws.com/sagemaker-sklearn-automl:0.1.0-cpu-py3',
  'ModelDataUrl': 's3://<bueckt_name>/sagemaker/autopilot-dm/output/automl-banking-05-07-35-13/data-processor-models/automl-banking-05-07-35-13-automl-ban-dpp9-1-6d31216e86e5423d8e/output/model.tar.gz'}]

このモデルを使ってエンドポイント作成やバッチ変換ジョブの実施が可能です。 Exampleではバッチ変換ジョブで利用しています。

transform_job_name = 'automl-banking-transform-' + timestamp_suffix

transform_input = {
        'DataSource': {
            'S3DataSource': {
                'S3DataType': 'S3Prefix',
                'S3Uri': test_data_s3_path
            }
        },
        'ContentType': 'text/csv',
        'CompressionType': 'None',
        'SplitType': 'Line'
    }

transform_output = {
        'S3OutputPath': 's3://{}/{}/inference-results'.format(bucket,prefix),
    }

transform_resources = {
        'InstanceType': 'ml.m5.4xlarge',
        'InstanceCount': 1
    }

sm.create_transform_job(TransformJobName = transform_job_name,
                        ModelName = model_name,
                        TransformInput = transform_input,
                        TransformOutput = transform_output,
                        TransformResources = transform_resources
)

処理が完了したのちは、通常通りS3に出力されるので結果を取得して確認可能です。

s3_output_key = '{}/inference-results/test_data.csv.out'.format(prefix)
local_inference_results_path = 'inference_results.csv'

s3 = boto3.resource('s3')
inference_results_bucket = s3.Bucket(session.default_bucket())
inference_results_bucket.download_file(s3_output_key, local_inference_results_path);

data = pd.read_csv(local_inference_results_path, names=['predicted_label'])
data

Autopilotがどんな処理を行っているのか

ここまでで、Autopilotを使った一連の流れを見ていきました。 いやー、AutoMLと呼ばれる領域はほんと楽で素晴らしいですね。

しかし、よくよく考えてみると内部でどんな処理が行われてるのか気になりません? 私は気になります。 実は、Autopilotにはそれを確認する方法があるんです。

Autopilotを実行すると結果として2つの.ipynbファイルがS3に出力されます。 これらはそれぞれ以下の処理でどこに出力されたか確認できます。

print(sm.describe_auto_ml_job(AutoMLJobName=auto_ml_job_name)['AutoMLJobArtifacts']['DataExplorationNotebookLocation'])
print(sm.describe_auto_ml_job(AutoMLJobName=auto_ml_job_name)['AutoMLJobArtifacts']['CandidateDefinitionNotebookLocation'])

実際の中身としては、DataExplorationNotebookLocationはデータに対するEDAを行ったものが出力されています。 内容を確認すると、各種統計情報などの他「Suggested Action Items」としてアドバイス的な情報も含まれていることがわかります。

もう一つのCandidateDefinitionNotebookLocationは実際に行った処理が記載されたものになります。 こちらの内容を確認することでどのような設定でどのような手法が使われているのかを確認することが可能になっています。 また、このファイルをベースに一部パラメータを書き換えてそのまま実行するようなことも可能です。

おわりに

今回はre:Invent 2019で発表されたSageMaker Autopilotのサンプルを実行してみました。

実際にやってみると、数多くの処理が並列して実行されていることがわかります。 Autopilotを本格的に活用する場合は並列数を高めるためにインスタンス数の上限緩和なども検討しておくといいかもしれません。