{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Tensorflow V2ë¡œ 학습한 모ë¸ì„ SageMakerë¡œ ë°°í¬í•˜ê¸°\n", "\n", "본 노트ë¶ì—서는 í•™ìŠµëœ ëª¨ë¸ì„ SageMaker endpointë¡œ ë°°í¬í•˜ëŠ” 프로세스를 살펴봅니다. [첫번째 노트ë¶](1.mnist_train.ipynb)ì—ì„œ 매ì§ëª…ë ¹ì–´ %store% ë¡œ ì €ìž¥í–ˆë˜ `model_data`ì˜ ëª¨ë¸ ì•„í‹°íŒ©íŠ¸ë¥¼ 로드하여 사용합니다. (만약 ì´ì „ì— ìƒì„±í•œ ëª¨ë¸ ì•„í‹°íŒ©íŠ¸ê°€ 없다면 공개 S3 버킷ì—ì„œ 해당 파ì¼ì„ 다운로드하게 ë©ë‹ˆë‹¤.)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import sagemaker \n", "sagemaker.__version__" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# setups\n", "\n", "import os\n", "import json\n", "\n", "import sagemaker\n", "from sagemaker.tensorflow import TensorFlowModel\n", "from sagemaker import get_execution_role, Session\n", "import boto3\n", "\n", "# Get global config\n", "with open('code/config.json', 'r') as f:\n", " CONFIG=json.load(f)\n", "\n", "sess = Session()\n", "role = get_execution_role()\n", "\n", "%store -r tf_mnist_model_data\n", "\n", "\n", "# store -r ì‹œë„ í›„ 모ë¸ì´ 없는 경우 publc s3 bucketì—ì„œ 다운로드\n", "try: \n", " tf_mnist_model_data\n", "except NameError:\n", " import json\n", " # copy a pretrained model from a public public to your default bucket\n", " s3 = boto3.client('s3')\n", " bucket = CONFIG['public_bucket']\n", " key = 'datasets/image/MNIST/model/tensorflow-training-2020-11-20-23-57-13-077/model.tar.gz'\n", " s3.download_file(bucket, key, 'model.tar.gz')\n", " tf_mnist_model_data = sess.upload_data(\n", " path='model.tar.gz', bucket=sess.default_bucket(), key_prefix='model/tensorflow')\n", " os.remove('model.tar.gz')\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(tf_mnist_model_data)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## TensorFlow Model Object\n", "\n", "SageMakerì—ì„œ ì œê³µí•˜ëŠ” `TensorFlowModel` í´ëž˜ìŠ¤ëŠ” ì—¬ëŸ¬ë¶„ì˜ ëª¨ë¸ ì•„í‹°íŒ©íŠ¸ë¥¼ ì´ìš©í•˜ì—¬ ì¶”ë¡ ì„ ì‹¤í–‰í•˜ëŠ” í™˜ê²½ì„ ì •ì˜í•˜ë„ë¡ í•´ ì¤ë‹ˆë‹¤. ì´ëŠ” [첫번째 노트ë¶](1.mnist_train.ipynb)ì—ì„œ `TensorFlow` estimator를 ì •ì˜í–ˆë˜ 것과 ìœ ì‚¬í•œ ë°©ì‹ìœ¼ë¡œ, í•™ìŠµëœ ëª¨ë¸ì„ SageMakerì—ì„œ 호스팅하ë„ë¡ ë„커 ì´ë¯¸ì§€ë¥¼ ì •ì˜í•˜ëŠ” 하ì´ë ˆë²¨ API입니다. \n", "\n", "해당 API를 통해 모ë¸ì„ ì¶”ë¡ í• í™˜ê²½ì„ ì„¤ì •í•˜ê³ ë‚˜ë©´ SageMakerì—ì„œ 관리하는 EC2 ì¸ìŠ¤í„´ìŠ¤ì—ì„œ SageMaker Endpoint 형태로 ì‹¤í–‰í• ìˆ˜ 있습니다. SageMaker Endpoint는 í•™ìŠµëœ ëª¨ë¸ì„ RESTful API를 통해 ì¶”ë¡ í•˜ë„ë¡ í•˜ëŠ” 컨테ì´ë„ˆê¸°ë°˜ 환경입니다. \n", "\n", "`TensorFlowModel` í´ëž˜ìŠ¤ë¥¼ ì´ˆê¸°í™”í• ë•Œ 사용ë˜ëŠ” 파ë¼ë¯¸í„°ë“¤ì€ 다ìŒê³¼ 같습니다.\n", "- role: AWS 리소스 ì‚¬ìš©ì„ ìœ„í•œ An IAM ì—í• (role) \n", "- model_data: ì••ì¶•ëœ ëª¨ë¸ ì•„í‹°íŒ©íŠ¸ê°€ 있는 S3 bucket URI. local modeë¡œ 실행시ì—는 로컬 파ì¼ê²½ë¡œ 사용가능함\n", "- framework_version: 사용하 í”„ë ˆìž„ì›Œí¬ì˜ ë²„ì „\n", "- py_version: 파ì´ì¬ ë²„ì „" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\n", "model = TensorFlowModel(\n", " role=role,\n", " model_data=tf_mnist_model_data,\n", " framework_version='2.3.1'\n", ")\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ì¶”ë¡ ì»¨í…Œì´ë„ˆ 실행\n", "\n", "`TensorFlowModel` í´ëž˜ìŠ¤ê°€ 초기화ë˜ê³ 나면 `deploy`메소드를 ì´ìš©í•˜ì—¬ 호스팅용 컨테ì´ë„ˆë¥¼ ì‹¤í–‰í• ìˆ˜ 있습니다. \n", "\n", "`deploy`메소드 실행시 사용ë˜ëŠ” 파ë¼ë¯¸í„°ë“¤ì€ 다ìŒê³¼ 같습니다.\n", "- initial_instance_count: 호스팅 ì„œë¹„ìŠ¤ì— ì‚¬ìš©í• SageMaker ì¸ìŠ¤í„´ìŠ¤ì˜ ìˆ«ìž \n", "- instance_type: 호스팅 서비스를 ì‹¤í–‰í• SageMaker ì¸ìŠ¤í„´ìŠ¤ 타입. ì´ ê°’ì„ `local` ë¡œ ì„ íƒí•˜ë©´ 로컬 ì¸ìŠ¤í„´ìŠ¤(SageMaker Jupyter notebook)ì— í˜¸ìŠ¤íŒ… 컨테ì´ë„ˆê°€ 실행ë©ë‹ˆë‹¤. local mode는 주로 디버깅 단계ì—ì„œ 사용하게 ë©ë‹ˆë‹¤. \n", "\n", "<span style=\"color:red\"> ì£¼ì˜ : SageMaker Studio 환경ì—서는 local mode ê°€ 지ì›ë˜ì§€ 않습니다. </span>" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# from sagemaker.serializers import JSONSerializer\n", "# from sagemaker.deserializers import JSONDeserializer\n", "\n", "instance_type='ml.c4.xlarge'\n", "\n", "predictor = model.deploy(\n", " initial_instance_count=1,\n", " instance_type=instance_type,\n", " )" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## SageMaker endpoint를 ì´ìš©í•œ 예측 실행\n", "\n", "`model.deploy(...)`ì— ì˜í•´ ë¦¬í„´ëœ `Predictor` ì¸ìŠ¤í„´ìŠ¤ë¥¼ ì´ìš©í•˜ì—¬ 예측 ìš”ì²ì„ endpointì— ë³´ë‚¼ 수 있습니다. ì´ ê²½ìš° 모ë¸ì€ ì •ê·œí™” ëœ ë°°ì¹˜ ì´ë¯¸ì§€ë¥¼ 받습니다.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# use some dummy inputs\n", "import numpy as np\n", "\n", "dummy_inputs = {\n", " 'instances': np.random.rand(4, 28, 28, 1).tolist()\n", "}\n", "\n", "res = predictor.predict(dummy_inputs)\n", "print(res)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "ìž…ì¶œë ¥ ë°ì´í„° í¬ë§·ì´ [TensorFlow Serving REST API](https://www.tensorflow.org/tfx/serving/api_rest)ì˜ `Predict`ì—ì„œ ì •ì˜ëœ request, respoinst í¬ë§·ê³¼ ì¼ì¹˜í•˜ëŠ” 지 확ì¸í•©ë‹ˆë‹¤. \n", "\n", "예를 들어 본 코드ì—ì„œ `dummy_inputs`ì€ `instances`를 키로 하여 ë°°ì—´ì˜ í˜•íƒœë¡œ ì „ë‹¬í•˜ê³ ìžˆìŠµë‹ˆë‹¤. ë˜í•œ ìž…ë ¥ë°ì´í„°ëŠ” batch dimensionì„ í¬í•¨í•œ 4ì°¨ì› ë°°ì—´ë¡œ 구성ë˜ì–´ 있습니다." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# # Uncomment the following lines to see an example that cannot be processed by the endpoint\n", "\n", "# dummy_data = {\n", "# 'instances': np.random.rand(28, 28, 1).tolist()\n", "# }\n", "# print(predictor.predict(dummy_data))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "ì´ì œ ì‹¤ì œ MNIST 테스트 ë°ì´í„°ë¡œ 엔드í¬ì¸íŠ¸ë¥¼ 호출해 봅니다. 여기서는 MNIST ë°ì´í„°ë¥¼ ë‹¤ìš´ë¡œë“œí•˜ê³ normalize하기 위해 `code.utils` ì˜ í—¬í¼í•¨ìˆ˜ë¥¼ 사용하였습니다." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from utils.mnist import mnist_to_numpy, normalize\n", "import random\n", "import matplotlib.pyplot as plt\n", "%matplotlib inline\n", "\n", "data_dir = '/tmp/data'\n", "X, _ = mnist_to_numpy(data_dir, train=False)\n", "\n", "# randomly sample 16 images to inspect\n", "mask = random.sample(range(X.shape[0]), 16)\n", "samples = X[mask]\n", "\n", "# plot the images \n", "fig, axs = plt.subplots(nrows=1, ncols=16, figsize=(16, 1))\n", "\n", "for i, splt in enumerate(axs):\n", " splt.imshow(samples[i])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "모ë¸ì´ nomalized ëœ ìž…ë ¥ì„ ë°›ê²Œ ë˜ì–´ìžˆìœ¼ë¯€ë¡œ normalize 처리 후 엔디í¬ì¸íŠ¸ë¥¼ 호출합니다. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "samples = normalize(samples, axis=(1, 2))\n", "predictions = predictor.predict(\n", " np.expand_dims(samples, 3) # add channel dim\n", ")['predictions'] \n", "\n", "# softmax to logit\n", "predictions = np.array(predictions, dtype=np.float32)\n", "predictions = np.argmax(predictions, axis=1)\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(\"Predictions: \", predictions.tolist())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## (Optional) 새로운 환경ì—ì„œ ì¶”ë¡ endpoint 호출\n", "\n", "SageMaker는 ë°°í¬ëœ endpoint를 호출하는 `ReatTimePredictor` í´ëž˜ìŠ¤ë¥¼ ì œê³µí•©ë‹ˆë‹¤. ì´ëŠ” 별ë„ì˜ ìƒˆë¡œìš´ 환경ì—ì„œ endpoint를 호출하는 ë°©ì‹ì„ ì˜ˆì œë¡œ ë³´ì—¬ì¤ë‹ˆë‹¤.\n", "\n", "ë¨¼ì € ìƒì„±ëœ endpointì˜ ì´ë¦„ì„ ê¸°ì–µí•©ë‹ˆë‹¤. 여기서는 ì•žì„œ ìƒì„±í•œ predictorê°ì²´ë¡œë¶€í„° ê°€ì ¸ì˜¤ê² ìŠµë‹ˆë‹¤." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "my_endpoint = predictor.endpoint_name\n", "print(my_endpoint)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`endpoint_name`ì„ ì´ìš©í•˜ì—¬ `ReatTimePredictor` 오브ì 트를 ìƒì„±í•©ë‹ˆë‹¤. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from sagemaker.predictor import Predictor \n", "from sagemaker.serializers import JSONSerializer\n", "from sagemaker.deserializers import JSONDeserializer\n", "\n", "my_predictor = Predictor(endpoint_name=my_endpoint, \n", " sagemaker_session=sess, \n", " serializer=JSONSerializer(), \n", " deserializer=JSONDeserializer())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "ì´ì œ predict 함수를 ì´ìš©í•˜ì—¬ ì¶”ë¡ ì„ ìš”ì²í• 수 있습니다. ì´ì „ 코드ì—ì„œ ì‚¬ìš©í–ˆë˜ dummy_inputsì„ í…ŒìŠ¤íŠ¸ìš© ë°ì´í„°ë¡œ ì´ìš©í•˜ê² 습니다." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# dummy_inputs = {\n", "# 'instances': np.random.rand(4, 28, 28, 1).tolist()\n", "# }\n", "\n", "my_predictor.predict(dummy_inputs)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "ì¶”ë¡ ì‹¤í–‰ì„ ìœ„í•´ boto3 SDK를 ì´ìš©í• ìˆ˜ë„ ìžˆìŠµë‹ˆë‹¤. 아래 코드를 참조합니다." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import boto3\n", "sm_runtime = boto3.Session().client(service_name='sagemaker-runtime',region_name=sess.boto_region_name)\n", "\n", "response = sm_runtime.invoke_endpoint(EndpointName=my_endpoint, \n", " ContentType='application/json', \n", " Accept='application/json',\n", " Body=json.dumps(dummy_inputs))\n", "\n", "json.loads(response.get('Body').read().decode())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 리소스 ì‚ì œ\n", "\n", "endpointì˜ ì‚¬ìš©ì´ ë나면 ì¶”ê°€ê³¼ê¸ˆì„ ë§‰ê¸° 위해 endpoint를 ì‚ì œí•©ë‹ˆë‹¤. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "predictor.delete_endpoint()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## (Optional) Hander debugging in local mode\n", "\n", "본 섹션ì—서는 Tensorflow Serving containerì— hander 코드를 ì¶”ê°€í•˜ê³ í…ŒìŠ¤íŠ¸í•˜ëŠ” ë°©ë²•ì„ ì‚´íŽ´ë³´ê² ìŠµë‹ˆë‹¤. \n", "\n", "ë¨¼ì € 로컬모드ì—ì„œ handler를 테스트하기 위해 S3ì˜ model.tar.gz파ì¼ì„ 로컬로 복사합니다." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# !rm -Rf model_with_handler" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "!mkdir -p model_with_handler/model\n", "!mkdir -p model_with_handler/code\n", "!aws s3 cp {tf_mnist_model_data} model_with_handler/model.tar.gz\n", "!tar -zvxf model_with_handler/model.tar.gz -C model_with_handler/model\n", "!rm model_with_handler/model.tar.gz" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### hanlder 코드 ìƒì„±\n", "\n", "다ìŒì€ ì´ model.tar.gzì— request handler 코드를 ì¶”ê°€í•˜ê² ìŠµë‹ˆë‹¤. Tensorflow serving container v1.11부터 사용ë˜ê³ 있는 model.tar.gz 구조는 다ìŒê³¼ 같습니다. (í”„ë ˆìž„ì›Œí¬ì˜ 종류와 ë²„ì „ë³„ë¡œ 여러가지 ë°©ì‹ì´ 가능합니다. Tensorflow구성과 ê´€ë ¨ëœ ë‚´ìš©ì€ https://github.com/aws/sagemaker-tensorflow-serving-container 를 참조합니다. í•´ë‹¹ê²½ë¡œì˜ ê°€ì´ë“œë¥¼ 통해 inference.py 파ì¼ì˜ ìƒì„±ë°©ë²• ë˜í•œ ì°¸ê³ í• ìˆ˜ 있습니다.)\n", "\n", "```\n", " model.tar.gz/\n", " |- model\n", " | |--[model_version_number]\n", " | |--variables\n", " | |--saved_model.pb\n", " |- code\n", " |--inference.py\n", " |--requirements.txt\n", "```\n", "\n", "ë‹¤ìŒ ì…€ì˜ ì½”ë“œë¥¼ 통해 커스텀 inference.py 파ì¼ì„ ìƒì„±í•©ë‹ˆë‹¤. Serving containerì—ì„œ Request를 받아 ë””ì½”ë”©í•˜ê³ ì¶”ë¡ Responseì—ì„œ content 부분만 추출하는 간단한 handler입니다. (디버깅 í™•ì¸ ëª©ì 으로 위해 간단한 printë¬¸ì„ ë„£ì—ˆìŠµë‹ˆë‹¤.)\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%writefile model_with_handler/code/inference.py\n", "\n", "import json\n", "\n", "def input_handler(data, context):\n", " # decode request payload\n", " payload = data.read().decode('utf-8')\n", " print(\"========== my debugging message ===============\")\n", " print(payload[:30])\n", " return payload\n", " \n", "\n", "def output_handler(response, context):\n", " # Serialize the prediction result\n", " response_content_type = context.accept_header\n", " prediction = response.content\n", "\n", " return prediction, response_content_type\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "방금 ìƒì„±í•œ handler를 í¬í•¨í•˜ì—¬ `model.tar.gz` 파ì¼ì„ 다시 ìƒì„±í•©ë‹ˆë‹¤." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%sh\n", "cd model_with_handler\n", "tar -czvf model.tar.gz model code" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 로컬모드 테스트\n", "\n", "로컬모드로 컨테ì´ë„ˆë¥¼ ì‹¤í–‰í•˜ê¸°ì— ì•žì„œ 기존 ì‹¤í–‰ì¤‘ì¸ ë¡œì»¬ 컨테ì´ë„ˆê°€ 있다면 중지합니다. (기존 로컬모드로 8080 í¬íŠ¸ë¥¼ 사용하는 Docker 컨테ì´ë„ˆë¥¼ ì‚ì œí•©ë‹ˆë‹¤.)\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import os\n", "os.system(\"docker container ls | grep 8080 | awk '{print $1}' | xargs docker container rm -f\")\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "ì „ 단계ì—ì„œ ìƒì„±í•œ model.tar.gz 로컬파ì¼ì„ ì´ìš©í•˜ì—¬ 로컬모드로 Tensorflow ì¶”ë¡ ì»¨í…Œì´ë„ˆë¥¼ 실행합니다." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "local_model = TensorFlowModel(role=role,\n", " model_data='file://model_with_handler/model.tar.gz',\n", " framework_version='2.3.1'\n", " )\n", "\n", "instance_type='local'\n", "\n", "local_predictor = local_model.deploy(initial_instance_count=1,\n", " instance_type=instance_type,\n", " )" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "로컬 ì¶”ë¡ ì»¨í…Œì´ë„ˆë¡œ ì¶”ë¡ ìš”ì²ì„ 보냅니다." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "res = local_predictor.predict(dummy_inputs)\n", "print(res)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "ì‚¬ìš©ìž ë©”ì‹œì§€ê°€ ì¶œë ¥ë˜ì—ˆìŠµë‹ˆë‹¤. ìœ ì‚¬í•œ ë°©ì‹ìœ¼ë¡œ 다른 로그를 ì¶œë ¥í•´ 봅니다.\n", "\n", "### 로컬 리소스 ì‚ì œ\n", "\n", "테스트가 완료ë˜ë©´ ë¡œì»¬ì˜ ì»¨í…Œì´ë„ˆë¥¼ ì‚ì œí•©ë‹ˆë‹¤." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import os\n", "os.system(\"docker container ls | grep 8080 | awk '{print $1}' | xargs docker container rm -f\")\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "instance_type": "ml.t3.medium", "kernelspec": { "display_name": "conda_tensorflow2_p36", "language": "python", "name": "conda_tensorflow2_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" } }, "nbformat": 4, "nbformat_minor": 4 }