SageMaker SDKを利用してタイタニック号の生存者を予測してみる
概要
AWS の AI/ML サービスは以下の3つのレイヤーで構成されている
- AI サービス
- ML サービス
- ML フレームワーク / インフラストラクチャ
MLサービスに位置し、機械学習モデルの構築、トレーニング、デプロイを簡単に行えるフルマネージドサービスとして SageMaker がある
今回はこれを利用して、機械学習における定番チュートリアルである「タイタニック号の生存者予測」に取り組んでみる
範囲は、モデルのトレーニングからデプロイまでとする
前提条件
- Python がインストールされていること
- AWS CLI がインストールされていること
- AWSアカウントを保有しており、プロファイルを作成済みであること
(optional) 仮想環境を構築
目的はホストOSのPython環境を汚さないようにすることなので不要なら省略しても問題なし
$ cd /path/to/project
$ python3 -m venv .venv
$ source .venv/bin/activate
上記以外でも rye
を使うなどしてもOK
パッケージのインストール
SDK とその他必要なパッケージをインストール
$ pip install sagemaker boto3 pandas
S3バケットの生成
IaC使ってもマネージメントコンソールからでもなんでもOKなので適当にバケットを作る
バケットにはテストデータをアップロードしたり、SageMakerがトレーニングジョブによって作成したモデルアーティファクトがアップロードされたりする
データセットのダウンロード
以下からタイタニック号の生存者予測のデータセットをダウンロードする
ダウンロードしたアーカイブを解凍すると以下のファイルがある
- gender_submission.csv
- test.csv
- train.csv
test.csv
と train.csv
をプロジェクトルートに配置する
前処理
preprocessing.py
というスクリプトを作成する
import pandas as pd
def preprocess_data(input_file, output_file, target_column='Survived'):
data = pd.read_csv(input_file)
if target_column and target_column in data.columns:
# NOTE: SageMakerのXGBoostは、デフォルトではCSVファイルの最初の列を目的変数として扱う
target = data[target_column]
data = data.drop(columns=[target_column])
data.insert(0, target_column, target)
data = data.drop(columns=['Name', 'Ticket'])
data['Sex'] = data['Sex'].map({'male': 0, 'female': 1})
data['Embarked'] = data['Embarked'].map({'C': 0, 'Q': 1, 'S': 2})
data["Cabin"] = data["Cabin"].map(lambda x: 0 if x is None else 1)
data.fillna(data.select_dtypes(include=['float64', 'int64']).mean(), inplace=True)
data = data.fillna(0)
data.to_csv(output_file, index=False, header=False)
print(f"{output_file} has been created.")
preprocess_data('train.csv', 'train_processed.csv')
preprocess_data('test.csv', 'test_processed.csv')
これを実行すると、前処理されたデータがプロジェクトルートに出力される
$ python preprocessing.py
train_processed.csv has been created.
test_processed.csv has been created.
教師データの作成は非常に重要な作業だが、今回は精度を出すことを目的としないので内容は適当
データセットのアップロード
プロファイルはデフォルトのものが適用されるので、適当に export AWS_PROFILE=sagemaker-handson
などしておく
$ aws s3 cp train_processed.csv s3://your-bucket-name
upload: ./train_processed.csv to s3://your-bucket-name/train_processed.csv
IAM ロールの作成
SDKが隠蔽しているものもあるが、SageMakerはS3,ECR,EC2など様々なサービスを呼び出している
それらのファイルやイメージなどへのアクセス許可が必要
今回は簡便のため、最小権限の設定などは行わない
こちらでは sagemaker-role
っていう名前のロールを作り、AWSマネージドの AmazonSageMakerFullAccess
というポリシーを当てた
モデルの訓練
さっき作ったS3バケットの名前とロールのARNを変数に設定した上で training.py
というスクリプトを作る
import sagemaker
from sagemaker.estimator import Estimator
from sagemaker.inputs import TrainingInput
role = 'arn:aws:iam::************:role/sagemaker-role'
bucket_name = 'your-bucket-name'
train_data_path = f's3://{bucket_name}/train_processed.csv'
output_path = f's3://{bucket_name}/output'
sagemaker_session = sagemaker.Session()
xgboost_estimator = Estimator(
image_uri=sagemaker.image_uris.retrieve(framework='xgboost', region=sagemaker_session.boto_region_name, version='1.2-1'),
role=role,
instance_count=1,
instance_type='ml.m5.xlarge',
output_path=f's3://{bucket_name}/output',
sagemaker_session=sagemaker_session
)
xgboost_estimator.set_hyperparameters(
num_round=100
)
train_input = TrainingInput(s3_data=train_data_path, content_type='csv')
xgboost_estimator.fit({'train': train_input})
これを実行する
$ python training.py
sagemaker.config INFO - Not applying SDK defaults from location: /Library/Application Support/sagemaker/config.yaml
sagemaker.config INFO - Not applying SDK defaults from location: /Users/k0kishima/Library/Application Support/sagemaker/config.yaml
INFO:sagemaker:Creating training-job with name: sagemaker-xgboost-2024-06-09-21-49-28-231
# 以下省略
実行後に、バケットに "output/sagemaker-xgboost-2024-06-09-11-45-33-292/output/model.tar.gz" のようなロケーションに出来上がったモデルアーティファクトがアップロードされる
モデルのデプロイ
ロール・前節でアップロードされたモデルアーティファクトのパス・エンドポイント名をそれぞれ指定した上で deploy.py
を作る
import sagemaker
from sagemaker.model import Model
import boto3
role = 'arn:aws:iam::************:role/sagemaker-role'
model_artifact = 's3://your-backet-name/output/sagemaker-xgboost-2024-06-09-13-24-57-794/output/model.tar.gz'
endpoint_name = 'your-endpoint-name'
sm_client = boto3.client('sagemaker')
endpoints = sm_client.list_endpoints(NameContains=endpoint_name)
existing_endpoints = [ep for ep in endpoints['Endpoints'] if ep['EndpointName'] == endpoint_name]
if existing_endpoints:
print(f"Endpoint {endpoint_name} already exists.")
else:
sagemaker_session = sagemaker.Session()
model = Model(
model_data=model_artifact,
image_uri=sagemaker.image_uris.retrieve(framework='xgboost', region=sagemaker_session.boto_region_name, version='1.2-1'),
role=role,
sagemaker_session=sagemaker_session
)
predictor = model.deploy(
initial_instance_count=1,
instance_type='ml.m5.xlarge',
endpoint_name=endpoint_name
)
print(f"Endpoint {endpoint_name} has been created.")
実行するとエンドポイントが作成される
$ python deploy.py
sagemaker.config INFO - Not applying SDK defaults from location: /Library/Application Support/sagemaker/config.yaml
sagemaker.config INFO - Not applying SDK defaults from location: /Users/k0kishima/Library/Application Support/sagemaker/config.yaml
-----!Endpoint your-endpoint-name has been created.
動作確認
エンドポイントから予測結果を取得してみる
$ aws sagemaker-runtime invoke-endpoint \
--endpoint-name your-endpoint-name \
--body fileb://test_processed.csv \
--content-type text/csv \
output.json
{
"ContentType": "text/csv; charset=utf-8",
"InvokedProductionVariant": "AllTraffic"
}
$ cat output.json
0.036303937435150146,0.19049838185310364,0.285813570022583,0.26095426082611084,0.4801110327243805, # 以下省略
後始末
エンドポイントを削除する
エンドポイントは稼働時間に基づいて従量課金されるので削除する
- SageMakerのダッシュボード、もしくはサイドメニューからエンドポイント一覧へアクセスする
- 今回作成したエンドポイントを選択し、削除する
S3の削除
トレーニングジョブによりアップロードされたデータは、S3のストレージ使用量に基づいて課金されるので削除する
- 不要ならS3バケットを消す
- バケット自体は残すなら不要なオブジェクトは全部消す
おまけ(IaCコード)
モデルアーティファクトがすでにS3にアップロードされているなら以下でエンドポイントを作れる
terraform {
required_version = "~> 1.7"
required_providers {
aws = {
source = "hashicorp/aws",
version = "5.40"
}
}
}
provider "aws" {
region = "ap-northeast-1"
}
variable "container_image" {
type = string
}
variable "model_data_url" {
type = string
}
variable "model_name" {
type = string
}
variable "endpoint_name" {
type = string
}
resource "aws_iam_role" "sagemaker_execution" {
name = "sagemaker-role"
assume_role_policy = jsonencode({
Version = "2012-10-17",
Statement = [
{
Effect = "Allow",
Principal = {
Service = "sagemaker.amazonaws.com"
},
Action = "sts:AssumeRole"
}
]
})
}
resource "aws_iam_role_policy_attachment" "this" {
role = aws_iam_role.sagemaker_execution.name
policy_arn = "arn:aws:iam::aws:policy/AmazonSageMakerFullAccess"
}
resource "aws_sagemaker_model" "this" {
name = var.model_name
execution_role_arn = aws_iam_role.sagemaker_execution.arn
primary_container {
image = var.container_image
model_data_url = var.model_data_url
}
}
resource "aws_sagemaker_endpoint_configuration" "this" {
name = "sagemaker-endpoint-config"
production_variants {
variant_name = "AllTraffic"
model_name = aws_sagemaker_model.this.name
initial_instance_count = 1
instance_type = "ml.m5.xlarge"
}
}
resource "aws_sagemaker_endpoint" "this" {
name = var.endpoint_name
endpoint_config_name = aws_sagemaker_endpoint_configuration.this.name
}
Discussion