From 963beed65693bd646780f14a0736d3043d89e669 Mon Sep 17 00:00:00 2001 From: vishal Date: Thu, 19 Sep 2019 02:32:20 +0000 Subject: [PATCH 1/7] Pytorch example --- examples/alexnet/cortex.yaml | 7 +++++++ examples/alexnet/image.py | 33 +++++++++++++++++++++++++++++++ examples/alexnet/requirements.txt | 2 ++ examples/alexnet/sample.json | 3 +++ 4 files changed, 45 insertions(+) create mode 100644 examples/alexnet/cortex.yaml create mode 100644 examples/alexnet/image.py create mode 100644 examples/alexnet/requirements.txt create mode 100644 examples/alexnet/sample.json diff --git a/examples/alexnet/cortex.yaml b/examples/alexnet/cortex.yaml new file mode 100644 index 0000000000..d2eb19e509 --- /dev/null +++ b/examples/alexnet/cortex.yaml @@ -0,0 +1,7 @@ +- kind: deployment + name: image + +- kind: api + name: classifier + model: s3://data-vishal/alexnet.onnx + request_handler: image.py diff --git a/examples/alexnet/image.py b/examples/alexnet/image.py new file mode 100644 index 0000000000..ed92f37427 --- /dev/null +++ b/examples/alexnet/image.py @@ -0,0 +1,33 @@ +import requests +import numpy as np +import base64 +from PIL import Image +from io import BytesIO +from torchvision import transforms + +labels = requests.get( + "https://storage.googleapis.com/download.tensorflow.org/data/ImageNetLabels.txt" +).text.split("\n")[1:] + + +# https://github.com/pytorch/examples/blob/447974f6337543d4de6b888e244a964d3c9b71f6/imagenet/main.py#L198-L199 +normalize = transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) +preprocess = transforms.Compose( + [transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), normalize] +) + + +def pre_inference(sample, metadata): + if "url" in sample: + image = requests.get(sample["url"]).content + elif "base64" in sample: + image = base64.b64decode(sample["base64"]) + + img_pil = Image.open(BytesIO(image)) + img_tensor = preprocess(img_pil) + img_tensor.unsqueeze_(0) + return img_tensor.numpy() + + +def post_inference(prediction, metadata): + return labels[np.argmax(np.array(prediction).squeeze())] diff --git a/examples/alexnet/requirements.txt b/examples/alexnet/requirements.txt new file mode 100644 index 0000000000..f0eabe59f4 --- /dev/null +++ b/examples/alexnet/requirements.txt @@ -0,0 +1,2 @@ +pillow +torchvision diff --git a/examples/alexnet/sample.json b/examples/alexnet/sample.json new file mode 100644 index 0000000000..0a3b3ff6ae --- /dev/null +++ b/examples/alexnet/sample.json @@ -0,0 +1,3 @@ +{ + "url": "https://bowwowinsurance.com.au/wp-content/uploads/2018/10/akita-700x700.jpg" +} \ No newline at end of file From 2a4f8fd1473547a9799b3b2bfb78e55c94a40f6c Mon Sep 17 00:00:00 2001 From: vishal Date: Fri, 20 Sep 2019 10:39:09 -0400 Subject: [PATCH 2/7] Move alexnet to image-classifier pytorch --- .../alexnet_cortex.ipynb | 173 ++++++++++++++++++ .../cortex.yaml | 2 +- .../image.py | 0 .../requirements.txt | 0 .../sample.json | 0 5 files changed, 174 insertions(+), 1 deletion(-) create mode 100644 examples/image-classifier-pytorch/alexnet_cortex.ipynb rename examples/{alexnet => image-classifier-pytorch}/cortex.yaml (57%) rename examples/{alexnet => image-classifier-pytorch}/image.py (100%) rename examples/{alexnet => image-classifier-pytorch}/requirements.txt (100%) rename examples/{alexnet => image-classifier-pytorch}/sample.json (100%) diff --git a/examples/image-classifier-pytorch/alexnet_cortex.ipynb b/examples/image-classifier-pytorch/alexnet_cortex.ipynb new file mode 100644 index 0000000000..2ae3b4b17a --- /dev/null +++ b/examples/image-classifier-pytorch/alexnet_cortex.ipynb @@ -0,0 +1,173 @@ +{ + "nbformat": 4, + "nbformat_minor": 0, + "metadata": { + "colab": { + "name": "alexnet_cortex.ipynb", + "provenance": [], + "collapsed_sections": [] + }, + "kernelspec": { + "name": "python3", + "display_name": "Python 3" + } + }, + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "_KePiywrVHG2", + "colab_type": "text" + }, + "source": [ + "# Export Alexnet from Torchvision Models\n", + "In this notebook we convert Alexnet to ONNX and upload it to S3 where it can be deployed by Cortex\n", + "\n", + "Based on: [PytorchOnnxExport](https://github.com/onnx/tutorials/blob/master/tutorials/PytorchOnnxExport.ipynb)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "dzphLNy5VswD", + "colab_type": "text" + }, + "source": [ + "## Install dependencies" + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "N69aGD72Is4t", + "colab_type": "code", + "colab": {} + }, + "source": [ + "!pip install torch==1.2.* torchvision==0.4.*" + ], + "execution_count": 0, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "2raEvUmojKhK", + "colab_type": "text" + }, + "source": [ + "## Download and Export Model\n", + "Download the pretrained Alexnet Model and export to ONNX model format:" + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "zKuFyRTlJUkd", + "colab_type": "code", + "colab": {} + }, + "source": [ + "import torch\n", + "import torch.onnx\n", + "import torchvision\n", + "\n", + "# Standard ImageNet input - 3 channels, 224x224,\n", + "# values don't matter since we only care about network structure.\n", + "dummy_input = torch.randn(1, 3, 224, 224)\n", + "\n", + "# We are going to use a Pretrained alexnet model\n", + "model = torchvision.models.alexnet(pretrained=True)\n", + "\n", + "# Export to ONNX\n", + "torch.onnx.export(model, dummy_input, \"alexnet.onnx\")" + ], + "execution_count": 0, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "4YvEPLmljaMT", + "colab_type": "text" + }, + "source": [ + "## Upload the model to AWS\n", + "Cortex loads models from AWS, so we need to upload the exported model.\n", + "\n", + "Set these variables to configure your AWS credentials and model upload path:" + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "y-SAhUH-Jlo_", + "colab_type": "code", + "cellView": "form", + "colab": {} + }, + "source": [ + "AWS_ACCESS_KEY_ID = \"\" #@param {type:\"string\"}\n", + "AWS_SECRET_ACCESS_KEY = \"\" #@param {type:\"string\"}\n", + "S3_UPLOAD_PATH = \"s3://my-bucket/alexnet.onnx\" #@param {type:\"string\"}\n", + "\n", + "import sys\n", + "import re\n", + "\n", + "if AWS_ACCESS_KEY_ID == \"\":\n", + " print(\"\\033[91m{}\\033[00m\".format(\"ERROR: Please set AWS_ACCESS_KEY_ID\"), file=sys.stderr)\n", + "\n", + "elif AWS_SECRET_ACCESS_KEY == \"\":\n", + " print(\"\\033[91m{}\\033[00m\".format(\"ERROR: Please set AWS_SECRET_ACCESS_KEY\"), file=sys.stderr)\n", + "\n", + "else:\n", + " try:\n", + " bucket = re.search(\"s3://(.+?)/\", S3_UPLOAD_PATH).group(1)\n", + " key = re.search(\"s3://.+?/(.+)\", S3_UPLOAD_PATH).group(1)\n", + " except:\n", + " print(\"\\033[91m{}\\033[00m\".format(\"ERROR: Invalid s3 path (should be of the form s3://my-bucket/path/to/file)\"), file=sys.stderr)" + ], + "execution_count": 0, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "HmvoV7v96jip", + "colab_type": "text" + }, + "source": [ + "Upload the model to S3:" + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "--va3L2KNBHX", + "colab_type": "code", + "colab": {} + }, + "source": [ + "import boto3\n", + "\n", + "s3 = boto3.client(\"s3\", aws_access_key_id=AWS_ACCESS_KEY_ID, aws_secret_access_key=AWS_SECRET_ACCESS_KEY)\n", + "print(\"Uploading {} ...\".format(S3_UPLOAD_PATH), end = '')\n", + "s3.upload_file(\"alexnet.onnx\", bucket, key)\n", + "print(\" ✓\")" + ], + "execution_count": 0, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "acHZMDxqjnNQ", + "colab_type": "text" + }, + "source": [ + "\n", + "That's it! See the [example on GitHub](https://github.com/cortexlabs/cortex/tree/master/examples/alexnet) for how to deploy the model as an API." + ] + } + ] +} \ No newline at end of file diff --git a/examples/alexnet/cortex.yaml b/examples/image-classifier-pytorch/cortex.yaml similarity index 57% rename from examples/alexnet/cortex.yaml rename to examples/image-classifier-pytorch/cortex.yaml index d2eb19e509..1c18a9ef3a 100644 --- a/examples/alexnet/cortex.yaml +++ b/examples/image-classifier-pytorch/cortex.yaml @@ -3,5 +3,5 @@ - kind: api name: classifier - model: s3://data-vishal/alexnet.onnx + model: s3://cortex-examples/image-classifier-pytorch/alexnet.onnx request_handler: image.py diff --git a/examples/alexnet/image.py b/examples/image-classifier-pytorch/image.py similarity index 100% rename from examples/alexnet/image.py rename to examples/image-classifier-pytorch/image.py diff --git a/examples/alexnet/requirements.txt b/examples/image-classifier-pytorch/requirements.txt similarity index 100% rename from examples/alexnet/requirements.txt rename to examples/image-classifier-pytorch/requirements.txt diff --git a/examples/alexnet/sample.json b/examples/image-classifier-pytorch/sample.json similarity index 100% rename from examples/alexnet/sample.json rename to examples/image-classifier-pytorch/sample.json From 2159d56110e3a14464b40afed68f7166cd31bcfd Mon Sep 17 00:00:00 2001 From: vishal Date: Fri, 20 Sep 2019 10:45:32 -0400 Subject: [PATCH 3/7] Rename files --- .../{alexnet_cortex.ipynb => alexnet.ipynb} | 0 examples/image-classifier-pytorch/cortex.yaml | 2 +- examples/image-classifier-pytorch/{image.py => handler.py} | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename examples/image-classifier-pytorch/{alexnet_cortex.ipynb => alexnet.ipynb} (100%) rename examples/image-classifier-pytorch/{image.py => handler.py} (100%) diff --git a/examples/image-classifier-pytorch/alexnet_cortex.ipynb b/examples/image-classifier-pytorch/alexnet.ipynb similarity index 100% rename from examples/image-classifier-pytorch/alexnet_cortex.ipynb rename to examples/image-classifier-pytorch/alexnet.ipynb diff --git a/examples/image-classifier-pytorch/cortex.yaml b/examples/image-classifier-pytorch/cortex.yaml index 1c18a9ef3a..2f6b5a6897 100644 --- a/examples/image-classifier-pytorch/cortex.yaml +++ b/examples/image-classifier-pytorch/cortex.yaml @@ -4,4 +4,4 @@ - kind: api name: classifier model: s3://cortex-examples/image-classifier-pytorch/alexnet.onnx - request_handler: image.py + request_handler: handler.py diff --git a/examples/image-classifier-pytorch/image.py b/examples/image-classifier-pytorch/handler.py similarity index 100% rename from examples/image-classifier-pytorch/image.py rename to examples/image-classifier-pytorch/handler.py From 374a803d1653150a582baf5721c35e5421f1c539 Mon Sep 17 00:00:00 2001 From: vishal Date: Fri, 20 Sep 2019 14:51:07 -0400 Subject: [PATCH 4/7] Move alexnet to image-classifier --- examples/image-classifier-pytorch/cortex.yaml | 7 ------- examples/image-classifier-pytorch/requirements.txt | 2 -- examples/image-classifier-pytorch/sample.json | 3 --- .../alexnet.ipynb | 0 .../alexnet_handler.py} | 0 examples/image-classifier/cortex.yaml | 13 ++++++++++--- .../{handler.py => inception_handler.py} | 1 + examples/image-classifier/requirements.txt | 1 + 8 files changed, 12 insertions(+), 15 deletions(-) delete mode 100644 examples/image-classifier-pytorch/cortex.yaml delete mode 100644 examples/image-classifier-pytorch/requirements.txt delete mode 100644 examples/image-classifier-pytorch/sample.json rename examples/{image-classifier-pytorch => image-classifier}/alexnet.ipynb (100%) rename examples/{image-classifier-pytorch/handler.py => image-classifier/alexnet_handler.py} (100%) rename examples/image-classifier/{handler.py => inception_handler.py} (99%) diff --git a/examples/image-classifier-pytorch/cortex.yaml b/examples/image-classifier-pytorch/cortex.yaml deleted file mode 100644 index 2f6b5a6897..0000000000 --- a/examples/image-classifier-pytorch/cortex.yaml +++ /dev/null @@ -1,7 +0,0 @@ -- kind: deployment - name: image - -- kind: api - name: classifier - model: s3://cortex-examples/image-classifier-pytorch/alexnet.onnx - request_handler: handler.py diff --git a/examples/image-classifier-pytorch/requirements.txt b/examples/image-classifier-pytorch/requirements.txt deleted file mode 100644 index f0eabe59f4..0000000000 --- a/examples/image-classifier-pytorch/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -pillow -torchvision diff --git a/examples/image-classifier-pytorch/sample.json b/examples/image-classifier-pytorch/sample.json deleted file mode 100644 index 0a3b3ff6ae..0000000000 --- a/examples/image-classifier-pytorch/sample.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "url": "https://bowwowinsurance.com.au/wp-content/uploads/2018/10/akita-700x700.jpg" -} \ No newline at end of file diff --git a/examples/image-classifier-pytorch/alexnet.ipynb b/examples/image-classifier/alexnet.ipynb similarity index 100% rename from examples/image-classifier-pytorch/alexnet.ipynb rename to examples/image-classifier/alexnet.ipynb diff --git a/examples/image-classifier-pytorch/handler.py b/examples/image-classifier/alexnet_handler.py similarity index 100% rename from examples/image-classifier-pytorch/handler.py rename to examples/image-classifier/alexnet_handler.py diff --git a/examples/image-classifier/cortex.yaml b/examples/image-classifier/cortex.yaml index 02776818e5..3708c19992 100644 --- a/examples/image-classifier/cortex.yaml +++ b/examples/image-classifier/cortex.yaml @@ -2,8 +2,15 @@ name: image - kind: api - name: classifier - model: s3://cortex-examples/inception - request_handler: handler.py + name: classifier-inception + model: s3://cortex-examples/image-classifier/inception + request_handler: inception_handler.py tracker: model_type: classification + +- kind: api + name: classifier-alexnet + model: s3://cortex-examples/image-classifier/alexnet.onnx + request_handler: alexnet_handler.py + tracker: + model_type: classification \ No newline at end of file diff --git a/examples/image-classifier/handler.py b/examples/image-classifier/inception_handler.py similarity index 99% rename from examples/image-classifier/handler.py rename to examples/image-classifier/inception_handler.py index 6187ca53bc..9c553ee62d 100644 --- a/examples/image-classifier/handler.py +++ b/examples/image-classifier/inception_handler.py @@ -22,3 +22,4 @@ def pre_inference(sample, metadata): def post_inference(prediction, metadata): return labels[np.argmax(prediction["classes"])] + diff --git a/examples/image-classifier/requirements.txt b/examples/image-classifier/requirements.txt index 3868fb16b8..f0eabe59f4 100644 --- a/examples/image-classifier/requirements.txt +++ b/examples/image-classifier/requirements.txt @@ -1 +1,2 @@ pillow +torchvision From 7ed0b0b509138dd21a299f4de7d3f1d24eedc7f1 Mon Sep 17 00:00:00 2001 From: vishal Date: Fri, 20 Sep 2019 18:56:30 +0000 Subject: [PATCH 5/7] Fix linting --- examples/image-classifier/alexnet.ipynb | 2 +- examples/image-classifier/cortex.yaml | 2 +- examples/image-classifier/inception_handler.py | 2 -- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/examples/image-classifier/alexnet.ipynb b/examples/image-classifier/alexnet.ipynb index 2ae3b4b17a..0255c935a1 100644 --- a/examples/image-classifier/alexnet.ipynb +++ b/examples/image-classifier/alexnet.ipynb @@ -170,4 +170,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/examples/image-classifier/cortex.yaml b/examples/image-classifier/cortex.yaml index 3708c19992..4916a21c1e 100644 --- a/examples/image-classifier/cortex.yaml +++ b/examples/image-classifier/cortex.yaml @@ -13,4 +13,4 @@ model: s3://cortex-examples/image-classifier/alexnet.onnx request_handler: alexnet_handler.py tracker: - model_type: classification \ No newline at end of file + model_type: classification diff --git a/examples/image-classifier/inception_handler.py b/examples/image-classifier/inception_handler.py index 9c553ee62d..1b2185821f 100644 --- a/examples/image-classifier/inception_handler.py +++ b/examples/image-classifier/inception_handler.py @@ -4,7 +4,6 @@ from PIL import Image from io import BytesIO - labels = requests.get( "https://storage.googleapis.com/download.tensorflow.org/data/ImageNetLabels.txt" ).text.split("\n") @@ -22,4 +21,3 @@ def pre_inference(sample, metadata): def post_inference(prediction, metadata): return labels[np.argmax(prediction["classes"])] - From 4956c03e9f4b2568f74c43f7da4b58a3f0186eeb Mon Sep 17 00:00:00 2001 From: vishal Date: Fri, 20 Sep 2019 15:18:30 -0400 Subject: [PATCH 6/7] Change name of python notebook --- examples/image-classifier/alexnet.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/image-classifier/alexnet.ipynb b/examples/image-classifier/alexnet.ipynb index 0255c935a1..28fc8e08c2 100644 --- a/examples/image-classifier/alexnet.ipynb +++ b/examples/image-classifier/alexnet.ipynb @@ -3,7 +3,7 @@ "nbformat_minor": 0, "metadata": { "colab": { - "name": "alexnet_cortex.ipynb", + "name": "alexnet.ipynb", "provenance": [], "collapsed_sections": [] }, From eceaaaf9f4791a871e3472c91a36461a364e3134 Mon Sep 17 00:00:00 2001 From: vishal Date: Fri, 20 Sep 2019 15:24:18 -0400 Subject: [PATCH 7/7] Change upload path --- examples/image-classifier/alexnet.ipynb | 2 +- examples/image-classifier/inception.ipynb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/image-classifier/alexnet.ipynb b/examples/image-classifier/alexnet.ipynb index 28fc8e08c2..fac5354f15 100644 --- a/examples/image-classifier/alexnet.ipynb +++ b/examples/image-classifier/alexnet.ipynb @@ -109,7 +109,7 @@ "source": [ "AWS_ACCESS_KEY_ID = \"\" #@param {type:\"string\"}\n", "AWS_SECRET_ACCESS_KEY = \"\" #@param {type:\"string\"}\n", - "S3_UPLOAD_PATH = \"s3://my-bucket/alexnet.onnx\" #@param {type:\"string\"}\n", + "S3_UPLOAD_PATH = \"s3://my-bucket/image-classifier/alexnet.onnx\" #@param {type:\"string\"}\n", "\n", "import sys\n", "import re\n", diff --git a/examples/image-classifier/inception.ipynb b/examples/image-classifier/inception.ipynb index cc02d53ad9..b1fb0d0ff8 100644 --- a/examples/image-classifier/inception.ipynb +++ b/examples/image-classifier/inception.ipynb @@ -136,7 +136,7 @@ "source": [ "AWS_ACCESS_KEY_ID = \"\" #@param {type:\"string\"}\n", "AWS_SECRET_ACCESS_KEY = \"\" #@param {type:\"string\"}\n", - "S3_UPLOAD_PATH = \"s3://my-bucket/inception\" #@param {type:\"string\"}\n", + "S3_UPLOAD_PATH = \"s3://my-bucket/image-classifier/inception\" #@param {type:\"string\"}\n", "\n", "import sys\n", "import re\n",