{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# 이미지 분류 Transfer learning 데모 \n", "\n", "### [(원본)](https://github.com/awslabs/amazon-sagemaker-examples/blob/master/introduction_to_amazon_algorithms/imageclassification_caltech/Image-classification-transfer-learning.ipynb)\n", "\n", "1. [소개](#소개)\n", "2. [사전조건 및 전처리](#사전조건-및-전처리)\n", "3. [이미지 분류 모델을 미세 조정하기](#이미지-분류-모델을-미세-조정하기)\n", "4. [훈련 파라미터](#훈련-파라미터)\n", "5. [훈련](#훈련)\n", "6. [모델 배포하기](#모델-배포하기)\n", " 1. [모델 생성](#모델-생성)\n", " 2. [Batch transform](#Batch-transform)\n", " 3. [실시간 추론](#실시간-추론)\n", "7. [정리](#정리)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 소개\n", "\n", "Transfer learning에서의 분산된 이미지 분류를 위한 end-to-end 예제에 오신 것을 환영합니다. 이 데모에서는 새로운 데이터셋의 분류를 학습하기 위해서, Amazon SageMaker의 이미지 분류 알고리즘의 Transfer learning 모드를 사용하여 사전 훈련된 모델(Imagenet에서 훈련)을 미세 조정할 것입니다. 특히 사전 훈련된 모델은 [caltech-256 dataset](http://www.vision.caltech.edu/Image_Datasets/Caltech256/)을 사용하여 미세 조정됩니다. \n", "\n", "\n", "시작을 위해서는 권한, 구성 등에 대한 몇 가지 사전조건과 환경을 설정해야 합니다. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 사전조건 및 전처리\n", "\n", "### 권한 및 환경 변수\n", "\n", "여기에서 AWS 서비스에 대한 연결과 인증을 설정합니다. 이것은 세 가지 항목이 포합니다.\n", "\n", "* 학습과 호스팅 시 데이터를 접근하기 위해 사용되는 role. 이것은 노트북을 시작하는데 사용된 role에서 자동으로 가져옵니다. \n", "* 훈련과 모델 데이터를 위해 사용되는 S3 버킷\n", "* 변경할 필요가 없는 Amazon SageMaker 이미지 분류 docker image" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "tags": [ "parameters" ] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "811284229777.dkr.ecr.us-east-1.amazonaws.com/image-classification:1\n", "CPU times: user 947 ms, sys: 76.1 ms, total: 1.02 s\n", "Wall time: 1.08 s\n" ] } ], "source": [ "%%time\n", "import boto3\n", "import re\n", "from sagemaker import get_execution_role\n", "from sagemaker.amazon.amazon_estimator import get_image_uri\n", "\n", "role = get_execution_role()\n", "\n", "bucket='<>' # customize to your bucket\n", "\n", "training_image = get_image_uri(boto3.Session().region_name, 'image-classification')\n", "\n", "print(training_image)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 이미지 분류 모델을 미세 조정하기\n", "\n", "caltech 256 데이터셋은 257개의 카테고리(마지막은 잡다한 데이터를 포함한 카테고리임)로 구성되며 카테고리별로 최소 80개의 이미지에서 최대 약 800개의 이미지를 갖는 30k이미지를 가지고 있습니다. \n", "\n", "이미지 분류 알고리즘은 2가지 입력 포맷을 이용할 수 있습니다. 첫 번째는 [recordio format](https://mxnet.incubator.apache.org/tutorials/basic/record_io.html) 이고 다른 하나는 [lst format](https://mxnet.incubator.apache.org/how_to/recordio.html?highlight=im2rec) 입니다. 두 형식의 파일은 http://data.dmlc.ml/mxnet/data/caltech-256/ 에서 제공합니다. 이 예제에서는 훈련을 위해 recordio 형식을 사용할 것이며 분할한 훈련/검증 [specified here](http://data.dmlc.ml/mxnet/data/caltech-256/) 을 사용할 것입니다. " ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "import os \n", "import urllib.request\n", "import boto3\n", "\n", "def download(url):\n", " filename = url.split(\"/\")[-1]\n", " if not os.path.exists(filename):\n", " urllib.request.urlretrieve(url, filename)\n", "\n", " \n", "def upload_to_s3(channel, file):\n", " s3 = boto3.resource('s3')\n", " data = open(file, \"rb\")\n", " key = channel + '/' + file\n", " s3.Bucket(bucket).put_object(Key=key, Body=data)\n", "\n", "\n", "# caltech-256\n", "s3_train_key = \"image-classification-transfer-learning/train\"\n", "s3_validation_key = \"image-classification-transfer-learning/validation\"\n", "s3_train = 's3://{}/{}/'.format(bucket, s3_train_key)\n", "s3_validation = 's3://{}/{}/'.format(bucket, s3_validation_key)\n", "\n", "download('http://data.mxnet.io/data/caltech-256/caltech-256-60-train.rec')\n", "upload_to_s3(s3_train_key, 'caltech-256-60-train.rec')\n", "download('http://data.mxnet.io/data/caltech-256/caltech-256-60-val.rec')\n", "upload_to_s3(s3_validation_key, 'caltech-256-60-val.rec')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "훈련을 위한 올바른 형식의 데이터를 확보했다면 다음 단계는 데이터를 사용하여 실제로 모델을 훈련하는 것입니다. 모델을 훈련하기 전에, 우리는 훈련 파라미터를 설정할 필요가 있습니다. 다음 섹션에서는 파라미터를 상세하게 설명합니다. \n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 훈련 파라미터\n", "\n", "훈련을 위해서 설정이 필요한 파라미터는 두가지 종류가 있습니다. 첫 번째는 Training job을 위한 파라미터들로서 아래 항목들이 포함됩니다: \n", "\n", "* **Input specification**: 훈련데이터가 존재하는 경로를 명시하는 훈련과 검증 채널입니다. 이것은 \"InputDataConfig\" 섹션에서 지정합니다. 설정이 필요한 주요 파라미터는 \"ContentType\"로서 입력 데이터 유형에 따라 \"application/x-recordio\" 이나 \"application/x-image\"로 설정할 수 있고 데이터가 존재하는 버킷과 폴더를 지정하는 S3Uri가 있습니다. \n", "* **Output specification**: 이것은 \"OutputDataConfig\" 섹션에서 명시할 수 있습니다. 훈련 후에 출력을 저장할 수 있는 경로를 지정합니다. \n", "* **Resource config**: 이 섹션은 훈련 데이터를 실행하기 위한 인스턴스 유형과 훈련을 위해 사용되는 호스트의 갯수를 설정합니다. \"InstanceCount\"가 1보다 크면 훈련은 분산된 방식으로 진행될 수 있습니다. \n", "\n", "Apart from the above set of parameters, there are hyperparameters that are specific to the algorithm. These are:\n", "위의 파라미터 셋외에도 알고리즘에서 지정할 하이퍼파라미터들이 있습니다. 그것들은 다음과 같습니다.:\n", "\n", "* **num_layers**: 네트워크의 레이어(depth) 수. 이 샘플에서는 18개를 사용하지만 50, 152와 같은 다른 값들을 사용할 수 있습니다. \n", "* **num_training_samples**: 총 훈련 샘플의 숫자입니다. 현재 분할된 caltech 데이터넷은 15420으로 설정됩니다. \n", "* **num_classes**: 새 데이터셋의 출력 클래스 수입니다. Imagenet은 1000개의 출력 클래스로 훈련되었으나 미세조정을 위해 출력 클래스의 숫자를 변경할 수 있습니다. caltech의 경우 256개의 객체 카테고리와 +1개의 잡다한 클래스를 가지고 있으므로 257를 사용합니다. \n", "* **epochs**: 훈련 epochs 수\n", "* **learning_rate**: 훈련을 위한 Learning rate\n", "* **mini_batch_size**: 각 미니배치에서 사용할 훈련 샘플의 수. 분산 훈련일 경우, 배치당 훈련 샘플의 수는 N * mini_batch_size입니다. 여기서 N은 훈련이 실행되는 호스트 수입니다. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "훈련 파라미터를 설정한 후에, 훈련을 시작하고 훈련이 완료될까지 상태를 폴링하는데, 이 예제에서는 p2.xlarge 에서 epoch당 10에서 12분의 시간이 걸립니다. 네트워크는 일반적으로 10 epochs 후에 수렴합니다. 그러나 훈련 시간을 절약하기 위해 epoch을 2로 설정했지만, 좋은 모델을 생성하기에 충분하지 않을 수 있습니다. " ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "isConfigCell": true }, "outputs": [], "source": [ "# The algorithm supports multiple network depth (number of layers). They are 18, 34, 50, 101, 152 and 200\n", "# For this training, we will use 18 layers\n", "num_layers = 18\n", "# we need to specify the input image shape for the training data\n", "image_shape = \"3,224,224\"\n", "# we also need to specify the number of training samples in the training set\n", "# for caltech it is 15420\n", "num_training_samples = 15420\n", "# specify the number of output classes\n", "num_classes = 257\n", "# batch size for training\n", "mini_batch_size = 128\n", "# number of epochs\n", "epochs = 2\n", "# learning rate\n", "learning_rate = 0.01\n", "top_k=2\n", "# Since we are using transfer learning, we set use_pretrained_model to 1 so that weights can be \n", "# initialized with pre-trained weights\n", "use_pretrained_model = 1" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 훈련\n", "\n", "Amazon SageMaker의 CreateTrainingJob API를 사용하여 훈련을 실행합니다. " ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Training job name: DEMO-imageclassification-2019-11-04-14-45-13\n", "\n", "Input Data Location: {'S3DataType': 'S3Prefix', 'S3Uri': 's3://sagemaker-seongshj/image-classification-transfer-learning/train/', 'S3DataDistributionType': 'FullyReplicated'}\n", "CPU times: user 1.28 ms, sys: 5.55 ms, total: 6.83 ms\n", "Wall time: 6.42 ms\n" ] } ], "source": [ "%%time\n", "import time\n", "import boto3\n", "from time import gmtime, strftime\n", "\n", "\n", "s3 = boto3.client('s3')\n", "# create unique job name \n", "job_name_prefix = 'DEMO-imageclassification'\n", "timestamp = time.strftime('-%Y-%m-%d-%H-%M-%S', time.gmtime())\n", "job_name = job_name_prefix + timestamp\n", "training_params = \\\n", "{\n", " # specify the training docker image\n", " \"AlgorithmSpecification\": {\n", " \"TrainingImage\": training_image,\n", " \"TrainingInputMode\": \"File\"\n", " },\n", " \"RoleArn\": role,\n", " \"OutputDataConfig\": {\n", " \"S3OutputPath\": 's3://{}/{}/output'.format(bucket, job_name_prefix)\n", " },\n", " \"ResourceConfig\": {\n", " \"InstanceCount\": 1,\n", " \"InstanceType\": \"ml.p2.xlarge\",\n", " \"VolumeSizeInGB\": 50\n", " },\n", " \"TrainingJobName\": job_name,\n", " \"HyperParameters\": {\n", " \"image_shape\": image_shape,\n", " \"num_layers\": str(num_layers),\n", " \"num_training_samples\": str(num_training_samples),\n", " \"num_classes\": str(num_classes),\n", " \"mini_batch_size\": str(mini_batch_size),\n", " \"epochs\": str(epochs),\n", " \"learning_rate\": str(learning_rate),\n", " \"use_pretrained_model\": str(use_pretrained_model)\n", " },\n", " \"StoppingCondition\": {\n", " \"MaxRuntimeInSeconds\": 360000\n", " },\n", "#Training data should be inside a subdirectory called \"train\"\n", "#Validation data should be inside a subdirectory called \"validation\"\n", "#The algorithm currently only supports fullyreplicated model (where data is copied onto each machine)\n", " \"InputDataConfig\": [\n", " {\n", " \"ChannelName\": \"train\",\n", " \"DataSource\": {\n", " \"S3DataSource\": {\n", " \"S3DataType\": \"S3Prefix\",\n", " \"S3Uri\": s3_train,\n", " \"S3DataDistributionType\": \"FullyReplicated\"\n", " }\n", " },\n", " \"ContentType\": \"application/x-recordio\",\n", " \"CompressionType\": \"None\"\n", " },\n", " {\n", " \"ChannelName\": \"validation\",\n", " \"DataSource\": {\n", " \"S3DataSource\": {\n", " \"S3DataType\": \"S3Prefix\",\n", " \"S3Uri\": s3_validation,\n", " \"S3DataDistributionType\": \"FullyReplicated\"\n", " }\n", " },\n", " \"ContentType\": \"application/x-recordio\",\n", " \"CompressionType\": \"None\"\n", " }\n", " ]\n", "}\n", "print('Training job name: {}'.format(job_name))\n", "print('\\nInput Data Location: {}'.format(training_params['InputDataConfig'][0]['DataSource']['S3DataSource']))" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Training job current status: InProgress\n", "Training job ended with status: Completed\n" ] } ], "source": [ "# create the Amazon SageMaker training job\n", "sagemaker = boto3.client(service_name='sagemaker')\n", "sagemaker.create_training_job(**training_params)\n", "\n", "# confirm that the training job has started\n", "status = sagemaker.describe_training_job(TrainingJobName=job_name)['TrainingJobStatus']\n", "print('Training job current status: {}'.format(status))\n", "\n", "try:\n", " # wait for the job to finish and report the ending status\n", " sagemaker.get_waiter('training_job_completed_or_stopped').wait(TrainingJobName=job_name)\n", " training_info = sagemaker.describe_training_job(TrainingJobName=job_name)\n", " status = training_info['TrainingJobStatus']\n", " print(\"Training job ended with status: \" + status)\n", "except:\n", " print('Training failed to start')\n", " # if exception is raised, that means it has failed\n", " message = sagemaker.describe_training_job(TrainingJobName=job_name)['FailureReason']\n", " print('Training failed with the following error: {}'.format(message))" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Training job ended with status: Completed\n" ] } ], "source": [ "training_info = sagemaker.describe_training_job(TrainingJobName=job_name)\n", "status = training_info['TrainingJobStatus']\n", "print(\"Training job ended with status: \" + status)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "다음 메시지를 보게 되면, \n", "> `Training job ended with status: Completed`\n", "\n", "훈련이 성공적으로 완료되었고 출력 모델이 `training_params['OutputDataConfig']`에서 지정한 위치에 저장됨을 의미합니다.\n", "\n", "또한 SageMaker 콘솔을 사용하여 Training job의 정보와 상태를 볼 수 있습니다. \"Jobs\" 탭을 클릭하세요. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 모델 배포하기\n", "\n", "***\n", "\n", "훈련된 모델은 그 자체로는 아무것도 수행하지 않습니다. 이제 추론을 수행하기 위해 모델을 사용하려고 합니다. 이 예제에서는, 주어진 문서를 나타내기 위해 혼합된 토픽들을 예측하는 것을 의미합니다. \n", "\n", "이미지 분류는 현재 추론 입력으로 .jpg와 .png로 인코딩된 이미지 형식만을 지원합니다. 출력은 JSON 형식의 인코딩된 모든 클래스의 확률값이거나 배치변환을 위한 JSON Lines 형식입니다.\n", "\n", "이 섹션은 몇 단계를 포함하고 있습니다. \n", "1. [Create Model](#CreateModel) - 훈련된 결과물로 모델 생성 \n", "1. [Batch Transform](#BatchTransform) - 배치 추론 수행을 위한 transform job 생성\n", "1. [Host the model for realtime inference](#HostTheModel) - 추론 endpoint 생성과 실시간 추론 수행\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 모델 생성\n", "\n", "이제 훈련된 결과물로 SageMaker 모델을 생성합니다. 모델을 사용하여 Endpoint Configuration을 생성할 수 있습니다. " ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "model_name: DEMO-image-classification-model--2019-11-04-16-27-33\n", "model_data: s3://sagemaker-seongshj/DEMO-imageclassification/output/DEMO-imageclassification-2019-11-04-14-45-13/output/model.tar.gz\n", "ModelArn: arn:aws:sagemaker:us-east-1:415373942856:model/demo-image-classification-model--2019-11-04-16-27-33\n", "CPU times: user 157 ms, sys: 0 ns, total: 157 ms\n", "Wall time: 438 ms\n" ] } ], "source": [ "%%time\n", "import boto3\n", "from time import gmtime, strftime\n", "\n", "sage = boto3.Session().client(service_name='sagemaker') \n", "\n", "model_name=\"DEMO-image-classification-model-\" + time.strftime('-%Y-%m-%d-%H-%M-%S', time.gmtime())\n", "print(\"model_name: {}\".format(model_name))\n", "info = sage.describe_training_job(TrainingJobName=job_name)\n", "model_data = info['ModelArtifacts']['S3ModelArtifacts']\n", "print(\"model_data: {}\".format(model_data))\n", "\n", "hosting_image = get_image_uri(boto3.Session().region_name, 'image-classification')\n", "\n", "primary_container = {\n", " 'Image': hosting_image,\n", " 'ModelDataUrl': model_data,\n", "}\n", "\n", "create_model_response = sage.create_model(\n", " ModelName = model_name,\n", " ExecutionRoleArn = role,\n", " PrimaryContainer = primary_container)\n", "\n", "print(\"ModelArn: {}\".format(create_model_response['ModelArn']))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Batch transform\n", "\n", "이제 배치 예측을 수행하기 위해, 위에서 생성한 모델을 사용하여 SageMaker Batch Transform job을 생성합니다." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### 테스트 데이터 다운로드하기\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Download images under /008.bathtub\n", "!wget -r -np -nH --cut-dirs=2 -P /tmp/ -R \"index.html*\" http://www.vision.caltech.edu/Image_Datasets/Caltech256/images/008.bathtub/\n" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [], "source": [ "batch_input = 's3://{}/image-classification-transfer-learning/test/'.format(bucket)\n", "test_images = '/tmp/images/008.bathtub'\n", "\n", "!aws s3 cp $test_images $batch_input --recursive --quiet " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Batch transform job 생성하기" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Transform job name: image-classification-model-2019-11-04-16-33-08\n", "\n", "Input Data Location: s3://sagemaker-seongshj/image-classification-transfer-learning/validation/\n" ] } ], "source": [ "timestamp = time.strftime('-%Y-%m-%d-%H-%M-%S', time.gmtime())\n", "batch_job_name = \"image-classification-model\" + timestamp\n", "request = \\\n", "{\n", " \"TransformJobName\": batch_job_name,\n", " \"ModelName\": model_name,\n", " \"MaxConcurrentTransforms\": 16,\n", " \"MaxPayloadInMB\": 6,\n", " \"BatchStrategy\": \"SingleRecord\",\n", " \"TransformOutput\": {\n", " \"S3OutputPath\": 's3://{}/{}/output'.format(bucket, batch_job_name)\n", " },\n", " \"TransformInput\": {\n", " \"DataSource\": {\n", " \"S3DataSource\": {\n", " \"S3DataType\": \"S3Prefix\",\n", " \"S3Uri\": batch_input\n", " }\n", " },\n", " \"ContentType\": \"application/x-image\",\n", " \"SplitType\": \"None\",\n", " \"CompressionType\": \"None\"\n", " },\n", " \"TransformResources\": {\n", " \"InstanceType\": \"ml.p2.xlarge\",\n", " \"InstanceCount\": 1\n", " }\n", "}\n", "\n", "print('Transform job name: {}'.format(batch_job_name))\n", "print('\\nInput Data Location: {}'.format(s3_validation))" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Created Transform job with name: image-classification-model-2019-11-04-16-33-08\n", "Transform job ended with status: Completed\n" ] } ], "source": [ "sagemaker = boto3.client('sagemaker')\n", "sagemaker.create_transform_job(**request)\n", "\n", "print(\"Created Transform job with name: \", batch_job_name)\n", "\n", "while(True):\n", " response = sagemaker.describe_transform_job(TransformJobName=batch_job_name)\n", " status = response['TransformJobStatus']\n", " if status == 'Completed':\n", " print(\"Transform job ended with status: \" + status)\n", " break\n", " if status == 'Failed':\n", " message = response['FailureReason']\n", " print('Transform failed with the following error: {}'.format(message))\n", " raise Exception('Transform job failed') \n", " time.sleep(30) " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Job이 완료된 후, 예측 결과를 검사하도록 하겠습니다. epoch를 2로 했기 때문에 좋은 모델을 훈련하기에 충분하지 않아서 정확도는 높지 않을 것입니다. " ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Sample inputs: ['image-classification-transfer-learning/test/008_0001.jpg', 'image-classification-transfer-learning/test/008_0002.jpg']\n", "Sample output: ['image-classification-model-2019-11-04-16-33-08/output/008_0001.jpg.out', 'image-classification-model-2019-11-04-16-33-08/output/008_0002.jpg.out']\n", "Result: label - bathtub, probability - 0.5867798924446106\n", "Result: label - birdbath, probability - 0.5136487483978271\n", "Result: label - diamond-ring, probability - 0.10593155771493912\n", "Result: label - bathtub, probability - 0.3399357497692108\n", "Result: label - bathtub, probability - 0.49366670846939087\n", "Result: label - washing-machine, probability - 0.21169473230838776\n", "Result: label - bathtub, probability - 0.8909815549850464\n", "Result: label - teapot, probability - 0.8619575500488281\n", "Result: label - bathtub, probability - 0.6042974591255188\n", "Result: label - bathtub, probability - 0.8098751306533813\n" ] }, { "data": { "text/plain": [ "[('bathtub', 0.5867798924446106),\n", " ('birdbath', 0.5136487483978271),\n", " ('diamond-ring', 0.10593155771493912),\n", " ('bathtub', 0.3399357497692108),\n", " ('bathtub', 0.49366670846939087),\n", " ('washing-machine', 0.21169473230838776),\n", " ('bathtub', 0.8909815549850464),\n", " ('teapot', 0.8619575500488281),\n", " ('bathtub', 0.6042974591255188),\n", " ('bathtub', 0.8098751306533813)]" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from urllib.parse import urlparse\n", "import json\n", "import numpy as np\n", "\n", "s3_client = boto3.client('s3')\n", "object_categories = ['ak47', 'american-flag', 'backpack', 'baseball-bat', 'baseball-glove', 'basketball-hoop', 'bat', 'bathtub', 'bear', 'beer-mug', 'billiards', 'binoculars', 'birdbath', 'blimp', 'bonsai-101', 'boom-box', 'bowling-ball', 'bowling-pin', 'boxing-glove', 'brain-101', 'breadmaker', 'buddha-101', 'bulldozer', 'butterfly', 'cactus', 'cake', 'calculator', 'camel', 'cannon', 'canoe', 'car-tire', 'cartman', 'cd', 'centipede', 'cereal-box', 'chandelier-101', 'chess-board', 'chimp', 'chopsticks', 'cockroach', 'coffee-mug', 'coffin', 'coin', 'comet', 'computer-keyboard', 'computer-monitor', 'computer-mouse', 'conch', 'cormorant', 'covered-wagon', 'cowboy-hat', 'crab-101', 'desk-globe', 'diamond-ring', 'dice', 'dog', 'dolphin-101', 'doorknob', 'drinking-straw', 'duck', 'dumb-bell', 'eiffel-tower', 'electric-guitar-101', 'elephant-101', 'elk', 'ewer-101', 'eyeglasses', 'fern', 'fighter-jet', 'fire-extinguisher', 'fire-hydrant', 'fire-truck', 'fireworks', 'flashlight', 'floppy-disk', 'football-helmet', 'french-horn', 'fried-egg', 'frisbee', 'frog', 'frying-pan', 'galaxy', 'gas-pump', 'giraffe', 'goat', 'golden-gate-bridge', 'goldfish', 'golf-ball', 'goose', 'gorilla', 'grand-piano-101', 'grapes', 'grasshopper', 'guitar-pick', 'hamburger', 'hammock', 'harmonica', 'harp', 'harpsichord', 'hawksbill-101', 'head-phones', 'helicopter-101', 'hibiscus', 'homer-simpson', 'horse', 'horseshoe-crab', 'hot-air-balloon', 'hot-dog', 'hot-tub', 'hourglass', 'house-fly', 'human-skeleton', 'hummingbird', 'ibis-101', 'ice-cream-cone', 'iguana', 'ipod', 'iris', 'jesus-christ', 'joy-stick', 'kangaroo-101', 'kayak', 'ketch-101', 'killer-whale', 'knife', 'ladder', 'laptop-101', 'lathe', 'leopards-101', 'license-plate', 'lightbulb', 'light-house', 'lightning', 'llama-101', 'mailbox', 'mandolin', 'mars', 'mattress', 'megaphone', 'menorah-101', 'microscope', 'microwave', 'minaret', 'minotaur', 'motorbikes-101', 'mountain-bike', 'mushroom', 'mussels', 'necktie', 'octopus', 'ostrich', 'owl', 'palm-pilot', 'palm-tree', 'paperclip', 'paper-shredder', 'pci-card', 'penguin', 'people', 'pez-dispenser', 'photocopier', 'picnic-table', 'playing-card', 'porcupine', 'pram', 'praying-mantis', 'pyramid', 'raccoon', 'radio-telescope', 'rainbow', 'refrigerator', 'revolver-101', 'rifle', 'rotary-phone', 'roulette-wheel', 'saddle', 'saturn', 'school-bus', 'scorpion-101', 'screwdriver', 'segway', 'self-propelled-lawn-mower', 'sextant', 'sheet-music', 'skateboard', 'skunk', 'skyscraper', 'smokestack', 'snail', 'snake', 'sneaker', 'snowmobile', 'soccer-ball', 'socks', 'soda-can', 'spaghetti', 'speed-boat', 'spider', 'spoon', 'stained-glass', 'starfish-101', 'steering-wheel', 'stirrups', 'sunflower-101', 'superman', 'sushi', 'swan', 'swiss-army-knife', 'sword', 'syringe', 'tambourine', 'teapot', 'teddy-bear', 'teepee', 'telephone-box', 'tennis-ball', 'tennis-court', 'tennis-racket', 'theodolite', 'toaster', 'tomato', 'tombstone', 'top-hat', 'touring-bike', 'tower-pisa', 'traffic-light', 'treadmill', 'triceratops', 'tricycle', 'trilobite-101', 'tripod', 't-shirt', 'tuning-fork', 'tweezer', 'umbrella-101', 'unicorn', 'vcr', 'video-projector', 'washing-machine', 'watch-101', 'waterfall', 'watermelon', 'welding-mask', 'wheelbarrow', 'windmill', 'wine-bottle', 'xylophone', 'yarmulke', 'yo-yo', 'zebra', 'airplanes-101', 'car-side-101', 'faces-easy-101', 'greyhound', 'tennis-shoes', 'toad', 'clutter']\n", "\n", "def list_objects(s3_client, bucket, prefix):\n", " response = s3_client.list_objects(Bucket=bucket, Prefix=prefix)\n", " objects = [content['Key'] for content in response['Contents']]\n", " return objects\n", "\n", "def get_label(s3_client, bucket, prefix):\n", " filename = prefix.split('/')[-1]\n", " s3_client.download_file(bucket, prefix, filename)\n", " with open(filename) as f:\n", " data = json.load(f)\n", " index = np.argmax(data['prediction'])\n", " probability = data['prediction'][index]\n", " print(\"Result: label - \" + object_categories[index] + \", probability - \" + str(probability))\n", " return object_categories[index], probability\n", "\n", "inputs = list_objects(s3_client, bucket, urlparse(batch_input).path.lstrip('/'))\n", "print(\"Sample inputs: \" + str(inputs[:2]))\n", "\n", "outputs = list_objects(s3_client, bucket, batch_job_name + \"/output\")\n", "print(\"Sample output: \" + str(outputs[:2]))\n", "\n", "# Check prediction result of the first 2 images\n", "[get_label(s3_client, bucket, prefix) for prefix in outputs[0:10]]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 실시간 추론\n", "\n", "이제 endpoint로 모델을 호스팅하고 실시간 추론을 수행합니다. \n", "\n", "이 섹션은 몇가지 단계를 포함합니다. \n", "\n", "1. [Endpoint Configuration 생성](#CreateEndpointConfiguration) - Endpoint를 정의하는 configuration 생성\n", "1. [Endpoint 생성](#CreateEndpoint) - inference endpoint를 생성하기 위해 configuration 사용\n", "1. [추론 수행](#PerformInference) - endpoint를 사용하여 일부 입력데이터의 추론 수행\n", "1. [정리](#CleanUp) - endpoint와 모델 삭제" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Endpoint Configuration 생성\n", "\n", "시작 시, A/B 테스팅의 목적과 같이 여러 모델을 호스팅할 수 있는 REST endpoint 구성을 지원합니다. 이것을 지원하기 위해서, 고객은 endpoint configuration을 생성하는데, 그 구성은 분할, 쉐도우 혹은 샘플링 여부와 관계없이 모델 간의 트래픽 분배 방법을 기술합니다. \n", "\n", "추가적으로, endpoint configuration은 모델 배포를 위해 요구되는 인스턴스 유형과 시작 시 autoscaling configuration을 설명합니다. " ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Endpoint configuration name: DEMO-imageclassification-epc--2019-11-04-16-58-09\n", "Endpoint configuration arn: arn:aws:sagemaker:us-east-1:415373942856:endpoint-config/demo-imageclassification-epc--2019-11-04-16-58-09\n" ] } ], "source": [ "from time import gmtime, strftime\n", "\n", "timestamp = time.strftime('-%Y-%m-%d-%H-%M-%S', time.gmtime())\n", "endpoint_config_name = job_name_prefix + '-epc-' + timestamp\n", "endpoint_config_response = sage.create_endpoint_config(\n", " EndpointConfigName = endpoint_config_name,\n", " ProductionVariants=[{\n", " 'InstanceType':'ml.m4.xlarge',\n", " 'InitialInstanceCount':1,\n", " 'ModelName':model_name,\n", " 'VariantName':'AllTraffic'}])\n", "\n", "print('Endpoint configuration name: {}'.format(endpoint_config_name))\n", "print('Endpoint configuration arn: {}'.format(endpoint_config_response['EndpointConfigArn']))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Endpoint 생성\n", "\n", "마지막으로, 고객은 위에서 정의한 이름과 configuration에서 지정한 모델을 서비스하는 endpoint를 생성합니다. \n", "최종 결과는 검증과 프로덕션 어플리케이션과 통합될 수 있는 endpoint입니다. 이 작업이 완료되기까지 9-11분이 걸립니다. " ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Endpoint name: DEMO-imageclassification-ep--2019-11-04-17-02-04\n", "EndpointArn = arn:aws:sagemaker:us-east-1:415373942856:endpoint/demo-imageclassification-ep--2019-11-04-17-02-04\n", "CPU times: user 25.6 ms, sys: 0 ns, total: 25.6 ms\n", "Wall time: 222 ms\n" ] } ], "source": [ "%%time\n", "import time\n", "\n", "timestamp = time.strftime('-%Y-%m-%d-%H-%M-%S', time.gmtime())\n", "endpoint_name = job_name_prefix + '-ep-' + timestamp\n", "print('Endpoint name: {}'.format(endpoint_name))\n", "\n", "endpoint_params = {\n", " 'EndpointName': endpoint_name,\n", " 'EndpointConfigName': endpoint_config_name,\n", "}\n", "endpoint_response = sagemaker.create_endpoint(**endpoint_params)\n", "print('EndpointArn = {}'.format(endpoint_response['EndpointArn']))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "마지막으로 endpoint가 생성이 될 수 있습니다. endpoint를 생성하는 데 다소 시간이 걸릴 수 있습니다..." ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "EndpointStatus = Creating\n", "Endpoint creation ended with EndpointStatus = InService\n" ] } ], "source": [ "# get the status of the endpoint\n", "response = sagemaker.describe_endpoint(EndpointName=endpoint_name)\n", "status = response['EndpointStatus']\n", "print('EndpointStatus = {}'.format(status))\n", "\n", "\n", "# wait until the status has changed\n", "sagemaker.get_waiter('endpoint_in_service').wait(EndpointName=endpoint_name)\n", "\n", "\n", "# print the status of the endpoint\n", "endpoint_response = sagemaker.describe_endpoint(EndpointName=endpoint_name)\n", "status = endpoint_response['EndpointStatus']\n", "print('Endpoint creation ended with EndpointStatus = {}'.format(status))\n", "\n", "if status != 'InService':\n", " raise Exception('Endpoint creation failed.')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "이 메시지를 보게 된다면,\n", "\n", "> `Endpoint creation ended with EndpointStatus = InService`\n", "\n", "축하드립니다! 이제 제대로 작동하는 추론 endpoint를 가지게 되었습니다. AWS SageMaker 콘솔의 \"Endpoints\" 탭에으로 이동하여 endpoint configuration과 상태를 확인할 수 있습니다. \n", "\n", "마지막으로 endpoint를 호출할 수 있는 runtime 객체를 만들 것입니다. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### 추론 수행\n", "마지막으로, 고객은 이제 모델을 검증할 수 있습니다. 이전 작업의 결과를 사용하여 클라이언트 라이브러리에서 endpoint를 얻을 수 있습니다. 그리고 그 endpoint를 사용하여 훈련된 모델로부터 분류를 생성할 수 있습니다. " ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [], "source": [ "import boto3\n", "runtime = boto3.Session().client(service_name='runtime.sagemaker') " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "##### 테스트 이미지를 다운로드하기" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "--2019-11-04 17:15:01-- http://www.vision.caltech.edu/Image_Datasets/Caltech256/images/008.bathtub/008_0007.jpg\n", "Resolving www.vision.caltech.edu (www.vision.caltech.edu)... 34.208.54.77\n", "Connecting to www.vision.caltech.edu (www.vision.caltech.edu)|34.208.54.77|:80... connected.\n", "HTTP request sent, awaiting response... 200 OK\n", "Length: 23750 (23K) [image/jpeg]\n", "Saving to: ‘/tmp/test.jpg’\n", "\n", "/tmp/test.jpg 100%[===================>] 23.19K --.-KB/s in 0.08s \n", "\n", "2019-11-04 17:15:01 (306 KB/s) - ‘/tmp/test.jpg’ saved [23750/23750]\n", "\n" ] }, { "data": { "image/jpeg": "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\n", "text/plain": [ "" ] }, "execution_count": 27, "metadata": {}, "output_type": "execute_result" } ], "source": [ "!wget -O /tmp/test.jpg http://www.vision.caltech.edu/Image_Datasets/Caltech256/images/008.bathtub/008_0007.jpg\n", "file_name = '/tmp/test.jpg'\n", "# test image\n", "from IPython.display import Image\n", "Image(file_name) " ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Result: label - bathtub, probability - 0.8909816145896912\n" ] } ], "source": [ "import json\n", "import numpy as np\n", "with open(file_name, 'rb') as f:\n", " payload = f.read()\n", " payload = bytearray(payload)\n", "response = runtime.invoke_endpoint(EndpointName=endpoint_name, \n", " ContentType='application/x-image', \n", " Body=payload)\n", "result = response['Body'].read()\n", "# result will be in json format and convert it to ndarray\n", "result = json.loads(result)\n", "# the result will output the probabilities for all classes\n", "# find the class with maximum probability and print the class index\n", "index = np.argmax(result)\n", "object_categories = ['ak47', 'american-flag', 'backpack', 'baseball-bat', 'baseball-glove', 'basketball-hoop', 'bat', 'bathtub', 'bear', 'beer-mug', 'billiards', 'binoculars', 'birdbath', 'blimp', 'bonsai-101', 'boom-box', 'bowling-ball', 'bowling-pin', 'boxing-glove', 'brain-101', 'breadmaker', 'buddha-101', 'bulldozer', 'butterfly', 'cactus', 'cake', 'calculator', 'camel', 'cannon', 'canoe', 'car-tire', 'cartman', 'cd', 'centipede', 'cereal-box', 'chandelier-101', 'chess-board', 'chimp', 'chopsticks', 'cockroach', 'coffee-mug', 'coffin', 'coin', 'comet', 'computer-keyboard', 'computer-monitor', 'computer-mouse', 'conch', 'cormorant', 'covered-wagon', 'cowboy-hat', 'crab-101', 'desk-globe', 'diamond-ring', 'dice', 'dog', 'dolphin-101', 'doorknob', 'drinking-straw', 'duck', 'dumb-bell', 'eiffel-tower', 'electric-guitar-101', 'elephant-101', 'elk', 'ewer-101', 'eyeglasses', 'fern', 'fighter-jet', 'fire-extinguisher', 'fire-hydrant', 'fire-truck', 'fireworks', 'flashlight', 'floppy-disk', 'football-helmet', 'french-horn', 'fried-egg', 'frisbee', 'frog', 'frying-pan', 'galaxy', 'gas-pump', 'giraffe', 'goat', 'golden-gate-bridge', 'goldfish', 'golf-ball', 'goose', 'gorilla', 'grand-piano-101', 'grapes', 'grasshopper', 'guitar-pick', 'hamburger', 'hammock', 'harmonica', 'harp', 'harpsichord', 'hawksbill-101', 'head-phones', 'helicopter-101', 'hibiscus', 'homer-simpson', 'horse', 'horseshoe-crab', 'hot-air-balloon', 'hot-dog', 'hot-tub', 'hourglass', 'house-fly', 'human-skeleton', 'hummingbird', 'ibis-101', 'ice-cream-cone', 'iguana', 'ipod', 'iris', 'jesus-christ', 'joy-stick', 'kangaroo-101', 'kayak', 'ketch-101', 'killer-whale', 'knife', 'ladder', 'laptop-101', 'lathe', 'leopards-101', 'license-plate', 'lightbulb', 'light-house', 'lightning', 'llama-101', 'mailbox', 'mandolin', 'mars', 'mattress', 'megaphone', 'menorah-101', 'microscope', 'microwave', 'minaret', 'minotaur', 'motorbikes-101', 'mountain-bike', 'mushroom', 'mussels', 'necktie', 'octopus', 'ostrich', 'owl', 'palm-pilot', 'palm-tree', 'paperclip', 'paper-shredder', 'pci-card', 'penguin', 'people', 'pez-dispenser', 'photocopier', 'picnic-table', 'playing-card', 'porcupine', 'pram', 'praying-mantis', 'pyramid', 'raccoon', 'radio-telescope', 'rainbow', 'refrigerator', 'revolver-101', 'rifle', 'rotary-phone', 'roulette-wheel', 'saddle', 'saturn', 'school-bus', 'scorpion-101', 'screwdriver', 'segway', 'self-propelled-lawn-mower', 'sextant', 'sheet-music', 'skateboard', 'skunk', 'skyscraper', 'smokestack', 'snail', 'snake', 'sneaker', 'snowmobile', 'soccer-ball', 'socks', 'soda-can', 'spaghetti', 'speed-boat', 'spider', 'spoon', 'stained-glass', 'starfish-101', 'steering-wheel', 'stirrups', 'sunflower-101', 'superman', 'sushi', 'swan', 'swiss-army-knife', 'sword', 'syringe', 'tambourine', 'teapot', 'teddy-bear', 'teepee', 'telephone-box', 'tennis-ball', 'tennis-court', 'tennis-racket', 'theodolite', 'toaster', 'tomato', 'tombstone', 'top-hat', 'touring-bike', 'tower-pisa', 'traffic-light', 'treadmill', 'triceratops', 'tricycle', 'trilobite-101', 'tripod', 't-shirt', 'tuning-fork', 'tweezer', 'umbrella-101', 'unicorn', 'vcr', 'video-projector', 'washing-machine', 'watch-101', 'waterfall', 'watermelon', 'welding-mask', 'wheelbarrow', 'windmill', 'wine-bottle', 'xylophone', 'yarmulke', 'yo-yo', 'zebra', 'airplanes-101', 'car-side-101', 'faces-easy-101', 'greyhound', 'tennis-shoes', 'toad', 'clutter']\n", "print(\"Result: label - \" + object_categories[index] + \", probability - \" + str(result[index]))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 정리\n", "\n", "endpoint 작업이 완료되면 이를 삭제하는데, 뒷단의 인스턴스들도 해제가 됩니다. endpoint를 삭제하기 위해 다음 셀을 실행하시기 바랍니다. " ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'ResponseMetadata': {'RequestId': '543c291e-8aa9-483c-be47-906fd5504e30',\n", " 'HTTPStatusCode': 200,\n", " 'HTTPHeaders': {'x-amzn-requestid': '543c291e-8aa9-483c-be47-906fd5504e30',\n", " 'content-type': 'application/x-amz-json-1.1',\n", " 'content-length': '0',\n", " 'date': 'Mon, 04 Nov 2019 17:19:29 GMT'},\n", " 'RetryAttempts': 0}}" ] }, "execution_count": 29, "metadata": {}, "output_type": "execute_result" } ], "source": [ "sage.delete_endpoint(EndpointName=endpoint_name)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true, "jupyter": { "outputs_hidden": true } }, "outputs": [], "source": [] } ], "metadata": { "celltoolbar": "Tags", "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.5" } }, "nbformat": 4, "nbformat_minor": 4 }