Amazon SageMaker를 사용해서 기계학습 모델을 구축, 학습 및 배포를 해보았습니다

Amazon SageMaker를 사용해서 기계 학습 모델인 XGBoost를 구축, 학습 및 배포를 하는 핸즈온에 대한 포스트입니다.
2020.06.09

안녕하세요 클래스메소드 송영진입니다.

이번 포스팅은 종합 관리형 기계학습 서비스인 Amazon SageMaker의 실습에 대한 내용입니다.

핸즈온이 어떻게 진행되는지에 대한 설명은 이 링크에서 확인하실 수 있습니다.

요약

  • 목표 : Amazon SageMaker를 사용한 기계학습 모델 구축, 학습 및 배포
  • 사용하는 데이터 셋 : Bank Marketing Data Set
    • 고객 인구 통계 정보, 마케팅 행사에 대한 반응, 외부 요소에 대한 정보가 포함된 마케팅 데이터 셋
  • 예측할 문제 : 고객이 은행에서 제안하는 상품에 등록할지 여부(Binary Classification, 이진분류문제)
  • 사용하는 기계학습 모델 : XGBoost ML 알고리즘
  • 사용하는 노트북 인스턴스 유형 : ml.t2.medium
  • 배포 엔드포인트 인스턴스 유형 : ml.t2.medium

 

노트북 인스턴스 생성

본 포스트에서는 AWS 계정을 보유하고 있다는 전제하에 진행합니다. 만약 계정을 아직 만들지 않은 경우에는 다음 링크에서 생성하실 수 있습니다.

SageMaker 대시보드에서 노트북 인스턴스를 클릭하고 노트북 인스턴스 생성을 누르시면 다음과 같은 화면을 보실 수 있습니다.

노트북 인스턴스 이름을 설정하시고 유형을 고를 수 있게 되어있는데, 저희는 가장 요금이 저렴한 ml.t2.medium을 선택하도록 하겠습니다.

이번 포스팅에서 다루지는 않지만 탄력적인 추론에 관해서는 다음 링크에서 자세한 내용을 확인하실 수 있습니다.

학습 데이터를 S3에 업로드 하기 위해서 IAM 롤을 생성해야 합니다.

노트북 인스턴스를 생성하시게되면 잠시동안 Pending 상태가 이어지는데, 2분 이내에 InService 상태가 되므로 다음 실습을 진행하시면 됩니다.

 

데이터 처리

인스턴스의 상태가 InService가 되면 작업이 활성화됩니다. Jupyter 열기를 클릭하셔서 Jupyter Notebook을 사용합니다.

저희가 이번에 사용할 모델은 XGBoost라서 conda_python3 환경으로 파일을 생성합니다.

# import libraries
import boto3, re, sys, math, json, os, sagemaker, urllib.request
from sagemaker.amazon.amazon_estimator import get_image_uri
from sagemaker import get_execution_role
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from IPython.display import Image
from IPython.display import display
from time import gmtime, strftime
from sagemaker.predictor import csv_serializer

# Define IAM role
role = get_execution_role()
prefix = 'sagemaker/DEMO-xgboost-dm'
my_region = boto3.session.Session().region_name # set the region of the instancer
container = get_image_uri(my_region ,'xgboost',repo_version='0.90-2');
print("Success - the MySageMakerInstance is in the " + my_region + " region. You will use the " + container + " container for your SageMaker endpoint.")

라이브러리를 import하고 IAM role를 정의하는 코드입니다.

hands-on에서 정의된 containers에는 아시아 리전이 없으므로 get_image_uri 함수를 사용하여 XGBoost 0.90-2버전을 명시하여 컨테이너를 정의했습니다.

제 리전은 도쿄 리전임을 확인 하실 수 있습니다.

bucket_name = 'yjsong-sagemaker-bucket' # <--- CHANGE THIS VARIABLE TO A UNIQUE NAME FOR YOUR BUCKET
s3 = boto3.resource('s3')
try:
    if my_region == 'us-east-1':
    s3.create_bucket(Bucket=bucket_name)
else:
    s3.create_bucket(Bucket=bucket_name, CreateBucketConfiguration={ 'LocationConstraint': my_region })
    print('S3 bucket created successfully')
except Exception as e:
    print('S3 error: ',e)

다음은 데이터를 저장할 S3 버켓을 생성하는 코드입니다.

코드를 실행한 후 무사히 버켓이 생성되었음을 확인하실 수 있습니다.

try:
    urllib.request.urlretrieve ("https://d1.awsstatic.com/tmt/build-train-deploy-machine-learning-model-sagemaker/bank_clean.27f01fbbdf43271788427f3682996ae29ceca05d.csv", "bank_clean.csv")
    print('Success: downloaded bank_clean.csv.')
except Exception as e:
    print('Data load error: ',e)

try:
    model_data = pd.read_csv('./bank_clean.csv',index_col=0)
    print('Success: Data loaded into dataframe.')
except Exception as e:
    print('Data load error: ',e)

다음은 데이터를 다운받는 코드인데요 직접 Bank Marketing Data Set를 사용하는 것이 아니라 aws에서 전처리해둔 csv파일을 사용하도록 하겠습니다.

데이터의 형식은 다음과 같습니다. 61개의 column을 가진 onehot encoding된 데이터인데 마지막 두 column은 이벤트로 사용될 y값이기 때문에 제외하고 59개의 column을 가진 41188개의 데이터입니다.

train_data, test_data = np.split(model_data.sample(frac=1, random_state=1729), [int(0.7 * len(model_data))])
print(train_data.shape, test_data.shape)

이 코드는 데이터를 학습 데이터와 테스트 데이터로 7:3 비율로 나눠주고 순서를 무작위로 섞는 코드입니다. 학습 데이터는 학습에 사용되고 테스트 데이터는 모델의 성능을 확인할 때 사용합니다.

7:3으로 나뉘어진 데이터의 수는 다음과 같습니다.

모델 생성 및 학습

이번 단계는 XGBoost 모델을 생성하고 생성된 모델을 학습시키는 단계입니다.

pd.concat([train_data['y_yes'], train_data.drop(['y_no', 'y_yes'], axis=1)], axis=1).to_csv('train.csv', index=False, header=False)
boto3.Session().resource('s3').Bucket(bucket_name).Object(os.path.join(prefix, 'train/train.csv')).upload_file('train.csv')
s3_input_train = sagemaker.s3_input(s3_data='s3://{}/{}/train'.format(bucket_name, prefix), content_type='csv')

SageMaker의 사전 구축된 XGBoost 모델을 사용하려면 S3 버킷에서 데이터를 불러와야 합니다.

이 코드는 정답 레이블인 y_no와 y_yes를 제거한 학습 데이터를 S3 버킷에 업로드하고 SageMaker용 입력 데이터로 바꾸는 과정입니다.

sess = sagemaker.Session()
xgb = sagemaker.estimator.Estimator(container, role, train_instance_count=1, train_instance_type='ml.m5.large',output_path='s3://{}/{}/output'.format(bucket_name, prefix),sagemaker_session=sess)
xgb.set_hyperparameters(max_depth=5,eta=0.2,gamma=4,min_child_weight=6,subsample=0.8,silent=0,objective='binary:logistic',num_round=100)
xgb.fit({'train': s3_input_train})

다음 코드는 SageMaker의 세션을 작성한 후 학습시킬 설정과 하이퍼파라미터를 입력한 후 학습시키는 과정입니다. 하이퍼파라미터에 따라서 학습이 100번 이루어집니다. 학습시킬 인스턴스의 유형은 가장 저렴한 ml.m5.large를 사용하였습니다. 처음에 설정해 둔 컨테이너에 따라서 XGBoost 모델로 학습이 이뤄지게 됩니다.

학습시간은 72초가 걸렸고 모델의 학습 데이터에 대한 에러도 0.095로 잘 학습이 된 상태입니다.

모델 배포 및 성능 평가

학습된 모델을 엔드포인트로 배포한 후 모델의 성능을 테스트 데이터로 평가하는 단계입니다.

xgb_predictor = xgb.deploy(initial_instance_count=1,instance_type='ml.t2.medium')

이 코드는 엔드포인트의 유형을 지정하고 학습된 모델을 배포하는 코드로 인스턴스 유형에서 가장 저렴한 ml.t2.medium를 사용하였습니다.

생성된 엔드포인트는 SageMaker의 콘솔에서 확인하실 수 있습니다. 엔드포인트의 상태가 Creating에서 InService로 바뀌면 다음으로 진행하시면 됩니다. 이미지에서 확인하실 수 있듯이 엔드포인트의 생성은 약 10분정도 시간이 걸렸습니다.

test_data_array = test_data.drop(['y_no', 'y_yes'], axis=1).values #load the data into an array
xgb_predictor.content_type = 'text/csv' # set the data type for an inference
xgb_predictor.serializer = csv_serializer # set the serializer type
predictions = xgb_predictor.predict(test_data_array).decode('utf-8') # predict!
predictions_array = np.fromstring(predictions[1:], sep=',') # and turn the prediction into an array
print(predictions_array.shape)

다음은 분리해둔 테스트 데이터로 고객이 은행에 가입하는지 여부를 예측하는 코드입니다.

cm = pd.crosstab(index=test_data['y_yes'], columns=np.round(predictions_array), rownames=['Observed'], colnames=['Predicted'])
tn = cm.iloc[0,0]; fn = cm.iloc[1,0]; tp = cm.iloc[1,1]; fp = cm.iloc[0,1]; p = (tp+tn)/(tp+tn+fp+fn)*100
print("\n{0:<20}{1:<4.1f}%\n".format("Overall Classification Rate: ", p))
print("{0:<15}{1:<15}{2:>8}".format("Predicted", "No Purchase", "Purchase"))
print("Observed")
print("{0:<15}{1:<2.0f}% ({2:<}){3:>6.0f}% ({4:<})".format("No Purchase", tn/(tn+fn)*100,tn, fp/(tp+fp)*100, fp))
print("{0:<16}{1:<1.0f}% ({2:<}){3:>7.0f}% ({4:<}) \n".format("Purchase", fn/(tn+fn)*100,fn, tp/(tp+fp)*100, tp))

다음은 예측 결과와 실제 결과를 갖고 성능 평가를 하는 코드입니다.

테스트 데이터 12357개의 결과로 오차행렬(Confusion Matrix)를 만들 수 있었습니다.

가입하지 않을거라 예측하고 실제로 가입하지 않은 비율이 90%이고 가입할거라 예측하고 실제로 가입한 비율이 62%로 유의미한 성능을 보이는 모델로 학습이 되었습니다.

 

다음으로 가장 중요한 리소스 종료입니다.

Amazon SageMaker는 사용하지 않더라도인스턴스가 세워져있으면 요금을 지불해야하는 서비스로 사용하지 않는 경우에는 인스턴스를 제거하는 것이 매우 중요합니다.

sagemaker.Session().delete_endpoint(xgb_predictor.endpoint)
bucket_to_delete = boto3.resource('s3').Bucket(bucket_name)
bucket_to_delete.objects.all().delete()

이 코드는 사용했던 엔드포인트와 S3 버켓 내부의 객체를 제거하는 코드입니다.

여기서 종료되는 서비스 뿐만 아니라 실습에 사용했던 노트북 인스턴스도 중지 또는 종료해야 더 이상 요금이 발생하지 않게 됩니다.

자세한 SageMaker의 요금은 링크에서 확인하실 수 있습니다.

마지막으로

이상으로 Amazon SageMaker를 이용하여 기계학습 모델을 구축하고 학습 및 배포하는 방법을 알아보았습니다. 고성능의 리소스를 사용하여 빠르게 모델을 구축하고 학습된 모델을 배포하여 사용할 수 있는 서비스인데요 고성능 리소스를 사용하게 되면 당연히 높은 가격의 요금이 책정되어있으므로 얼마나 모델에 적합한 리소스를 얼마나 오래 사용하느냐가 매우 중요한 포인트가 됩니다. 인스턴스 유형에 따른 요금을 확인하다가 시간당 약 47달러짜리 인스턴스를 보고 깜짝 놀랐는데요 그걸 확인하면서 사용한 리소스에서 더 이상 필요하지 않게 되었을때 중지하거나 종료하는 것이 얼마나 중요한지 알게 되었습니다.