Коммит
04cb164650
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -1,311 +1,144 @@
|
||||||
{
|
{
|
||||||
"cells": [
|
"cells": [
|
||||||
{
|
{
|
||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": "# Develop Model Driver"
|
||||||
"# Develop Model Driver"
|
},
|
||||||
]
|
{
|
||||||
},
|
"cell_type": "markdown",
|
||||||
{
|
"metadata": {},
|
||||||
"cell_type": "markdown",
|
"source": "In this notebook, we will develop the API that will call our model. This module initializes the model, transforms the input so that it is in the appropriate format and defines the scoring method that will produce the predictions. The API will expect the input to be in JSON format. Once a request is received, the API will convert the json encoded request body into the image format. There are two main functions in the API. The first function loads the model and returns a scoring function. The second function process the images and uses the first function to score them."
|
||||||
"metadata": {},
|
},
|
||||||
"source": [
|
{
|
||||||
"In this notebook, we will develop the API that will call our model. This module initializes the model, transforms the input so that it is in the appropriate format and defines the scoring method that will produce the predictions. The API will expect the input to be in JSON format. Once a request is received, the API will convert the json encoded request body into the image format. There are two main functions in the API. The first function loads the model and returns a scoring function. The second function process the images and uses the first function to score them."
|
"cell_type": "code",
|
||||||
]
|
"execution_count": 1,
|
||||||
},
|
"metadata": {},
|
||||||
{
|
"outputs": [],
|
||||||
"cell_type": "code",
|
"source": "import logging\nfrom testing_utilities import img_url_to_json\nfrom pprint import pprint"
|
||||||
"execution_count": 1,
|
},
|
||||||
"metadata": {},
|
{
|
||||||
"outputs": [],
|
"cell_type": "code",
|
||||||
"source": [
|
"execution_count": 2,
|
||||||
"import logging\n",
|
"metadata": {},
|
||||||
"from testing_utilities import img_url_to_json\n",
|
"outputs": [],
|
||||||
"from pprint import pprint"
|
"source": "logging.basicConfig(level=logging.DEBUG)"
|
||||||
]
|
},
|
||||||
},
|
{
|
||||||
{
|
"cell_type": "markdown",
|
||||||
"cell_type": "code",
|
"metadata": {},
|
||||||
"execution_count": 2,
|
"source": "We use the writefile magic to write the contents of the below cell to driver.py which includes the driver methods."
|
||||||
"metadata": {},
|
},
|
||||||
"outputs": [],
|
{
|
||||||
"source": [
|
"cell_type": "code",
|
||||||
"logging.basicConfig(level=logging.DEBUG)"
|
"execution_count": 3,
|
||||||
]
|
"metadata": {
|
||||||
},
|
"lines_to_end_of_cell_marker": 2
|
||||||
{
|
},
|
||||||
"cell_type": "markdown",
|
"outputs": [
|
||||||
"metadata": {},
|
{
|
||||||
"source": [
|
"name": "stdout",
|
||||||
"We use the writefile magic to write the contents of the below cell to driver.py which includes the driver methods."
|
"output_type": "stream",
|
||||||
]
|
"text": "Overwriting driver.py\n"
|
||||||
},
|
}
|
||||||
{
|
],
|
||||||
"cell_type": "code",
|
"source": "%%writefile driver.py \nimport base64\nimport json\nimport logging\nimport os\nimport timeit as t\nfrom io import BytesIO\n\nimport PIL\nimport numpy as np\nimport torch\nimport torch.nn as nn\nimport torchvision\nfrom PIL import Image\nfrom torchvision import models, transforms\n\n\n\n_LABEL_FILE = os.getenv(\"LABEL_FILE\", \"synset.txt\")\n_NUMBER_RESULTS = 3\n\n\ndef _create_label_lookup(label_path):\n with open(label_path, \"r\") as f:\n label_list = [l.rstrip() for l in f]\n\n def _label_lookup(*label_locks):\n return [label_list[l] for l in label_locks]\n\n return _label_lookup\n\n\ndef _load_model():\n # Load the model\n model = models.resnet152(pretrained=True)\n model = model.cuda()\n softmax = nn.Softmax(dim=1).cuda()\n model = model.eval()\n\n preprocess_input = transforms.Compose(\n [\n torchvision.transforms.Resize((224, 224), interpolation=PIL.Image.BICUBIC),\n transforms.ToTensor(),\n transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]),\n ]\n )\n\n def predict_for(image):\n image = preprocess_input(image)\n with torch.no_grad():\n image = image.unsqueeze(0)\n image_gpu = image.type(torch.float).cuda()\n outputs = model(image_gpu)\n pred_proba = softmax(outputs)\n return pred_proba.cpu().numpy().squeeze()\n\n return predict_for\n\n\ndef _base64img_to_pil_image(base64_img_string):\n if base64_img_string.startswith(\"b'\"):\n base64_img_string = base64_img_string[2:-1]\n base64Img = base64_img_string.encode(\"utf-8\")\n\n # Preprocess the input data\n decoded_img = base64.b64decode(base64Img)\n img_buffer = BytesIO(decoded_img)\n\n # Load image with PIL (RGB)\n pil_img = Image.open(img_buffer).convert(\"RGB\")\n return pil_img\n\n\ndef create_scoring_func(label_path=_LABEL_FILE):\n logger = logging.getLogger(\"model_driver\")\n\n start = t.default_timer()\n labels_for = _create_label_lookup(label_path)\n predict_for = _load_model()\n end = t.default_timer()\n\n loadTimeMsg = \"Model loading time: {0} ms\".format(round((end - start) * 1000, 2))\n logger.info(loadTimeMsg)\n\n def call_model(image, number_results=_NUMBER_RESULTS):\n pred_proba = predict_for(image).squeeze()\n selected_results = np.flip(np.argsort(pred_proba), 0)[:number_results]\n labels = labels_for(*selected_results)\n return list(zip(labels, pred_proba[selected_results].astype(np.float64)))\n\n return call_model\n\n\ndef get_model_api():\n logger = logging.getLogger(\"model_driver\")\n scoring_func = create_scoring_func()\n\n def process_and_score(images_dict, number_results=_NUMBER_RESULTS):\n start = t.default_timer()\n\n results = {}\n for key, base64_img_string in images_dict.items():\n rgb_image = _base64img_to_pil_image(base64_img_string)\n results[key] = scoring_func(rgb_image, number_results=number_results)\n\n end = t.default_timer()\n\n logger.info(\"Predictions: {0}\".format(results))\n logger.info(\"Predictions took {0} ms\".format(round((end - start) * 1000, 2)))\n return (results, \"Computed in {0} ms\".format(round((end - start) * 1000, 2)))\n\n return process_and_score\n\n\ndef version():\n return torch.__version__"
|
||||||
"execution_count": 3,
|
},
|
||||||
"metadata": {
|
{
|
||||||
"lines_to_end_of_cell_marker": 2
|
"cell_type": "markdown",
|
||||||
},
|
"metadata": {},
|
||||||
"outputs": [
|
"source": "Let's test the module."
|
||||||
{
|
},
|
||||||
"name": "stdout",
|
{
|
||||||
"output_type": "stream",
|
"cell_type": "markdown",
|
||||||
"text": [
|
"metadata": {},
|
||||||
"Overwriting driver.py\n"
|
"source": "We run the file driver.py which will bring everything into the context of the notebook."
|
||||||
]
|
},
|
||||||
}
|
{
|
||||||
],
|
"cell_type": "code",
|
||||||
"source": [
|
"execution_count": 4,
|
||||||
"%%writefile driver.py \n",
|
"metadata": {},
|
||||||
"import base64\n",
|
"outputs": [],
|
||||||
"import json\n",
|
"source": "%run driver.py"
|
||||||
"import logging\n",
|
},
|
||||||
"import os\n",
|
{
|
||||||
"import timeit as t\n",
|
"cell_type": "markdown",
|
||||||
"from io import BytesIO\n",
|
"metadata": {},
|
||||||
"\n",
|
"source": "We will use the same Lynx image we used ealier to check that our driver works as expected."
|
||||||
"import PIL\n",
|
},
|
||||||
"import numpy as np\n",
|
{
|
||||||
"import torch\n",
|
"cell_type": "code",
|
||||||
"import torch.nn as nn\n",
|
"execution_count": 5,
|
||||||
"import torchvision\n",
|
"metadata": {},
|
||||||
"from PIL import Image\n",
|
"outputs": [],
|
||||||
"from torchvision import models, transforms\n",
|
"source": "IMAGEURL = \"https://upload.wikimedia.org/wikipedia/commons/thumb/6/68/Lynx_lynx_poing.jpg/220px-Lynx_lynx_poing.jpg\""
|
||||||
"\n",
|
},
|
||||||
"\n",
|
{
|
||||||
"\n",
|
"cell_type": "code",
|
||||||
"_LABEL_FILE = os.getenv(\"LABEL_FILE\", \"synset.txt\")\n",
|
"execution_count": 6,
|
||||||
"_NUMBER_RESULTS = 3\n",
|
"metadata": {},
|
||||||
"\n",
|
"outputs": [
|
||||||
"\n",
|
{
|
||||||
"def _create_label_lookup(label_path):\n",
|
"name": "stderr",
|
||||||
" with open(label_path, \"r\") as f:\n",
|
"output_type": "stream",
|
||||||
" label_list = [l.rstrip() for l in f]\n",
|
"text": "INFO:model_driver:Model loading time: 3972.62 ms\n"
|
||||||
"\n",
|
}
|
||||||
" def _label_lookup(*label_locks):\n",
|
],
|
||||||
" return [label_list[l] for l in label_locks]\n",
|
"source": "predict_for = get_model_api()"
|
||||||
"\n",
|
},
|
||||||
" return _label_lookup\n",
|
{
|
||||||
"\n",
|
"cell_type": "code",
|
||||||
"\n",
|
"execution_count": 7,
|
||||||
"def _load_model():\n",
|
"metadata": {},
|
||||||
" # Load the model\n",
|
"outputs": [
|
||||||
" model = models.resnet152(pretrained=True)\n",
|
{
|
||||||
" model = model.cuda()\n",
|
"name": "stderr",
|
||||||
" softmax = nn.Softmax(dim=1).cuda()\n",
|
"output_type": "stream",
|
||||||
" model = model.eval()\n",
|
"text": "DEBUG:PIL.PngImagePlugin:STREAM b'IHDR' 16 13\nDEBUG:PIL.PngImagePlugin:STREAM b'iCCP' 41 292\nDEBUG:PIL.PngImagePlugin:iCCP profile name b'ICC Profile'\nDEBUG:PIL.PngImagePlugin:Compression method 0\nDEBUG:PIL.PngImagePlugin:STREAM b'IDAT' 345 65536\nINFO:model_driver:Predictions: {'image': [('n02127052 lynx, catamount', 0.9965722560882568), ('n02128757 snow leopard, ounce, Panthera uncia', 0.0013256857637315989), ('n02128385 leopard, Panthera pardus', 0.0009192737634293735)]}\nINFO:model_driver:Predictions took 84.51 ms\n"
|
||||||
"\n",
|
}
|
||||||
" preprocess_input = transforms.Compose(\n",
|
],
|
||||||
" [\n",
|
"source": "jsonimg = img_url_to_json(IMAGEURL)\njson_load_img = json.loads(jsonimg)\nbody = json_load_img[\"input\"]\nresp = predict_for(body)"
|
||||||
" torchvision.transforms.Resize((224, 224), interpolation=PIL.Image.BICUBIC),\n",
|
},
|
||||||
" transforms.ToTensor(),\n",
|
{
|
||||||
" transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]),\n",
|
"cell_type": "code",
|
||||||
" ]\n",
|
"execution_count": 8,
|
||||||
" )\n",
|
"metadata": {},
|
||||||
"\n",
|
"outputs": [
|
||||||
" def predict_for(image):\n",
|
{
|
||||||
" image = preprocess_input(image)\n",
|
"name": "stdout",
|
||||||
" with torch.no_grad():\n",
|
"output_type": "stream",
|
||||||
" image = image.unsqueeze(0)\n",
|
"text": "{'image': [('n02127052 lynx, catamount', 0.9965722560882568),\n ('n02128757 snow leopard, ounce, Panthera uncia',\n 0.0013256857637315989),\n ('n02128385 leopard, Panthera pardus', 0.0009192737634293735)]}\n"
|
||||||
" image_gpu = image.type(torch.float).cuda()\n",
|
}
|
||||||
" outputs = model(image_gpu)\n",
|
],
|
||||||
" pred_proba = softmax(outputs)\n",
|
"source": "pprint(resp[0])"
|
||||||
" return pred_proba.cpu().numpy().squeeze()\n",
|
},
|
||||||
"\n",
|
{
|
||||||
" return predict_for\n",
|
"cell_type": "markdown",
|
||||||
"\n",
|
"metadata": {},
|
||||||
"\n",
|
"source": "Next, we can move on to [building our docker image](02_BuildImage.ipynb)."
|
||||||
"def _base64img_to_pil_image(base64_img_string):\n",
|
}
|
||||||
" if base64_img_string.startswith(\"b'\"):\n",
|
],
|
||||||
" base64_img_string = base64_img_string[2:-1]\n",
|
"metadata": {
|
||||||
" base64Img = base64_img_string.encode(\"utf-8\")\n",
|
"jupytext_format_version": "1.3",
|
||||||
"\n",
|
"jupytext_formats": "py:light",
|
||||||
" # Preprocess the input data\n",
|
"kernelspec": {
|
||||||
" decoded_img = base64.b64decode(base64Img)\n",
|
"display_name": "Python 3",
|
||||||
" img_buffer = BytesIO(decoded_img)\n",
|
"language": "python",
|
||||||
"\n",
|
"name": "python3"
|
||||||
" # Load image with PIL (RGB)\n",
|
},
|
||||||
" pil_img = Image.open(img_buffer).convert(\"RGB\")\n",
|
"language_info": {
|
||||||
" return pil_img\n",
|
"codemirror_mode": {
|
||||||
"\n",
|
"name": "ipython",
|
||||||
"\n",
|
"version": 3
|
||||||
"def create_scoring_func(label_path=_LABEL_FILE):\n",
|
},
|
||||||
" logger = logging.getLogger(\"model_driver\")\n",
|
"file_extension": ".py",
|
||||||
"\n",
|
"mimetype": "text/x-python",
|
||||||
" start = t.default_timer()\n",
|
"name": "python",
|
||||||
" labels_for = _create_label_lookup(label_path)\n",
|
"nbconvert_exporter": "python",
|
||||||
" predict_for = _load_model()\n",
|
"pygments_lexer": "ipython3",
|
||||||
" end = t.default_timer()\n",
|
"version": "3.6.6"
|
||||||
"\n",
|
}
|
||||||
" loadTimeMsg = \"Model loading time: {0} ms\".format(round((end - start) * 1000, 2))\n",
|
},
|
||||||
" logger.info(loadTimeMsg)\n",
|
"nbformat": 4,
|
||||||
"\n",
|
"nbformat_minor": 2
|
||||||
" def call_model(image, number_results=_NUMBER_RESULTS):\n",
|
|
||||||
" pred_proba = predict_for(image).squeeze()\n",
|
|
||||||
" selected_results = np.flip(np.argsort(pred_proba), 0)[:number_results]\n",
|
|
||||||
" labels = labels_for(*selected_results)\n",
|
|
||||||
" return list(zip(labels, pred_proba[selected_results].astype(np.float64)))\n",
|
|
||||||
"\n",
|
|
||||||
" return call_model\n",
|
|
||||||
"\n",
|
|
||||||
"\n",
|
|
||||||
"def get_model_api():\n",
|
|
||||||
" logger = logging.getLogger(\"model_driver\")\n",
|
|
||||||
" scoring_func = create_scoring_func()\n",
|
|
||||||
"\n",
|
|
||||||
" def process_and_score(images_dict, number_results=_NUMBER_RESULTS):\n",
|
|
||||||
" start = t.default_timer()\n",
|
|
||||||
"\n",
|
|
||||||
" results = {}\n",
|
|
||||||
" for key, base64_img_string in images_dict.items():\n",
|
|
||||||
" rgb_image = _base64img_to_pil_image(base64_img_string)\n",
|
|
||||||
" results[key] = scoring_func(rgb_image, number_results=number_results)\n",
|
|
||||||
"\n",
|
|
||||||
" end = t.default_timer()\n",
|
|
||||||
"\n",
|
|
||||||
" logger.info(\"Predictions: {0}\".format(results))\n",
|
|
||||||
" logger.info(\"Predictions took {0} ms\".format(round((end - start) * 1000, 2)))\n",
|
|
||||||
" return (results, \"Computed in {0} ms\".format(round((end - start) * 1000, 2)))\n",
|
|
||||||
"\n",
|
|
||||||
" return process_and_score\n",
|
|
||||||
"\n",
|
|
||||||
"\n",
|
|
||||||
"def version():\n",
|
|
||||||
" return torch.__version__"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"metadata": {},
|
|
||||||
"source": [
|
|
||||||
"Let's test the module."
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"metadata": {},
|
|
||||||
"source": [
|
|
||||||
"We run the file driver.py which will bring everything into the context of the notebook."
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": 4,
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [],
|
|
||||||
"source": [
|
|
||||||
"%run driver.py"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"metadata": {},
|
|
||||||
"source": [
|
|
||||||
"We will use the same Lynx image we used ealier to check that our driver works as expected."
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": 5,
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [],
|
|
||||||
"source": [
|
|
||||||
"IMAGEURL = \"https://upload.wikimedia.org/wikipedia/commons/thumb/6/68/Lynx_lynx_poing.jpg/220px-Lynx_lynx_poing.jpg\""
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": 6,
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "stderr",
|
|
||||||
"output_type": "stream",
|
|
||||||
"text": [
|
|
||||||
"INFO:model_driver:Model loading time: 4070.21 ms\n"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"source": [
|
|
||||||
"predict_for = get_model_api()"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": 7,
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "stderr",
|
|
||||||
"output_type": "stream",
|
|
||||||
"text": [
|
|
||||||
"DEBUG:PIL.PngImagePlugin:STREAM b'IHDR' 16 13\n",
|
|
||||||
"DEBUG:PIL.PngImagePlugin:STREAM b'iCCP' 41 292\n",
|
|
||||||
"DEBUG:PIL.PngImagePlugin:iCCP profile name b'ICC Profile'\n",
|
|
||||||
"DEBUG:PIL.PngImagePlugin:Compression method 0\n",
|
|
||||||
"DEBUG:PIL.PngImagePlugin:STREAM b'IDAT' 345 65536\n",
|
|
||||||
"INFO:model_driver:Predictions: {'image': [('n02127052 lynx, catamount', 0.9965722560882568), ('n02128757 snow leopard, ounce, Panthera uncia', 0.0013256857637315989), ('n02128385 leopard, Panthera pardus', 0.0009192737634293735)]}\n",
|
|
||||||
"INFO:model_driver:Predictions took 92.28 ms\n"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"source": [
|
|
||||||
"jsonimg = img_url_to_json(IMAGEURL)\n",
|
|
||||||
"json_load_img = json.loads(jsonimg)\n",
|
|
||||||
"body = json_load_img[\"input\"]\n",
|
|
||||||
"resp = predict_for(body)"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": 8,
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "stdout",
|
|
||||||
"output_type": "stream",
|
|
||||||
"text": [
|
|
||||||
"{'image': [('n02127052 lynx, catamount', 0.9965722560882568),\n",
|
|
||||||
" ('n02128757 snow leopard, ounce, Panthera uncia',\n",
|
|
||||||
" 0.0013256857637315989),\n",
|
|
||||||
" ('n02128385 leopard, Panthera pardus', 0.0009192737634293735)]}\n"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"source": [
|
|
||||||
"pprint(resp[0])"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"metadata": {},
|
|
||||||
"source": [
|
|
||||||
"Next, we can move on to [building our docker image](02_BuildImage.ipynb)."
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"metadata": {
|
|
||||||
"jupytext_format_version": "1.3",
|
|
||||||
"jupytext_formats": "py:light",
|
|
||||||
"kernelspec": {
|
|
||||||
"display_name": "Python [conda env:AKSDeploymentPytorch]",
|
|
||||||
"language": "python",
|
|
||||||
"name": "conda-env-AKSDeploymentPytorch-py"
|
|
||||||
},
|
|
||||||
"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.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nbformat": 4,
|
|
||||||
"nbformat_minor": 2
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,540 +1,292 @@
|
||||||
{
|
{
|
||||||
"cells": [
|
"cells": [
|
||||||
{
|
{
|
||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": "# Build Docker image "
|
||||||
"# Build Docker image "
|
},
|
||||||
]
|
{
|
||||||
},
|
"cell_type": "markdown",
|
||||||
{
|
"metadata": {},
|
||||||
"cell_type": "markdown",
|
"source": "In this notebook, we will build the docker container that contains the ResNet152 model, Flask web application, model driver and all dependencies.\nMake sure you have logged in using docker login."
|
||||||
"metadata": {},
|
},
|
||||||
"source": [
|
{
|
||||||
"In this notebook, we will build the docker container that contains the ResNet152 model, Flask web application, model driver and all dependencies.\n",
|
"cell_type": "code",
|
||||||
"Make sure you have logged in using docker login."
|
"execution_count": 7,
|
||||||
]
|
"metadata": {},
|
||||||
},
|
"outputs": [],
|
||||||
{
|
"source": "import os\nfrom os import path\nimport json\nimport shutil\nfrom dotenv import set_key, get_key, find_dotenv\nfrom pathlib import Path"
|
||||||
"cell_type": "code",
|
},
|
||||||
"execution_count": 1,
|
{
|
||||||
"metadata": {},
|
"cell_type": "markdown",
|
||||||
"outputs": [],
|
"metadata": {},
|
||||||
"source": [
|
"source": "We are going to use a .env file to store all our information"
|
||||||
"import os\n",
|
},
|
||||||
"from os import path\n",
|
{
|
||||||
"import json\n",
|
"cell_type": "code",
|
||||||
"import shutil\n",
|
"execution_count": 8,
|
||||||
"from dotenv import set_key, get_key, find_dotenv\n",
|
"metadata": {},
|
||||||
"from pathlib import Path"
|
"outputs": [],
|
||||||
]
|
"source": "env_path = find_dotenv()\nif env_path=='':\n Path('.env').touch()\n env_path = find_dotenv()"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": "We will be using the following Docker information to push the image to docker hub. "
|
||||||
"We are going to use a .env file to store all our information"
|
},
|
||||||
]
|
{
|
||||||
},
|
"cell_type": "code",
|
||||||
{
|
"execution_count": 9,
|
||||||
"cell_type": "code",
|
"metadata": {},
|
||||||
"execution_count": null,
|
"outputs": [
|
||||||
"metadata": {},
|
{
|
||||||
"outputs": [],
|
"data": {
|
||||||
"source": [
|
"text/plain": "(True, 'image_repo', 'pytorch-gpu')"
|
||||||
"env_path = find_dotenv()\n",
|
},
|
||||||
"if env_path=='':\n",
|
"execution_count": 9,
|
||||||
" Path('.env').touch()\n",
|
"metadata": {},
|
||||||
" env_path = find_dotenv()"
|
"output_type": "execute_result"
|
||||||
]
|
}
|
||||||
},
|
],
|
||||||
{
|
"source": "set_key(env_path, \"docker_login\", \"<YOUR_DOCKER_LOGIN>\") # Replace YOUR_DOCKER_LOGIN with your dockerhub login\nset_key(env_path, \"image_repo\", \"pytorch-gpu\")"
|
||||||
"cell_type": "markdown",
|
},
|
||||||
"metadata": {},
|
{
|
||||||
"source": [
|
"cell_type": "code",
|
||||||
"We will be using the following Docker information to push the image to docker hub. "
|
"execution_count": 10,
|
||||||
]
|
"metadata": {
|
||||||
},
|
"tags": [
|
||||||
{
|
"stripout"
|
||||||
"cell_type": "code",
|
]
|
||||||
"execution_count": null,
|
},
|
||||||
"metadata": {},
|
"outputs": [],
|
||||||
"outputs": [],
|
"source": "!cat .env"
|
||||||
"source": [
|
},
|
||||||
"set_key(env_path, \"docker_login\", \"YOUR_DOCKER_LOGIN\") # Replace YOUR_DOCKER_LOGIN with your dockerhub login\n",
|
{
|
||||||
"set_key(env_path, \"image_repo\", \"pytorch-gpu\")"
|
"cell_type": "code",
|
||||||
]
|
"execution_count": 11,
|
||||||
},
|
"metadata": {},
|
||||||
{
|
"outputs": [],
|
||||||
"cell_type": "code",
|
"source": "os.makedirs(\"flaskwebapp\", exist_ok=True)\nos.makedirs(os.path.join(\"flaskwebapp\", \"nginx\"), exist_ok=True)\nos.makedirs(os.path.join(\"flaskwebapp\", \"etc\"), exist_ok=True)"
|
||||||
"execution_count": null,
|
},
|
||||||
"metadata": {},
|
{
|
||||||
"outputs": [],
|
"cell_type": "code",
|
||||||
"source": [
|
"execution_count": 12,
|
||||||
"!cat .env"
|
"metadata": {},
|
||||||
]
|
"outputs": [
|
||||||
},
|
{
|
||||||
{
|
"data": {
|
||||||
"cell_type": "code",
|
"text/plain": "['synset.txt',\n 'etc',\n 'driver.py',\n 'dockerfile',\n 'kill_supervisor.py',\n 'requirements.txt',\n 'app.py',\n 'gunicorn_logging.conf',\n 'wsgi.py',\n 'nginx']"
|
||||||
"execution_count": 7,
|
},
|
||||||
"metadata": {},
|
"execution_count": 12,
|
||||||
"outputs": [],
|
"metadata": {},
|
||||||
"source": [
|
"output_type": "execute_result"
|
||||||
"os.makedirs(\"flaskwebapp\", exist_ok=True)\n",
|
}
|
||||||
"os.makedirs(os.path.join(\"flaskwebapp\", \"nginx\"), exist_ok=True)\n",
|
],
|
||||||
"os.makedirs(os.path.join(\"flaskwebapp\", \"etc\"), exist_ok=True)"
|
"source": "shutil.copy(\"synset.txt\", \"flaskwebapp\")\nshutil.copy(\"driver.py\", \"flaskwebapp\")\nos.listdir(\"flaskwebapp\")"
|
||||||
]
|
},
|
||||||
},
|
{
|
||||||
{
|
"cell_type": "markdown",
|
||||||
"cell_type": "code",
|
"metadata": {},
|
||||||
"execution_count": 8,
|
"source": "Below, we create the module for the Flask web application."
|
||||||
"metadata": {},
|
},
|
||||||
"outputs": [
|
{
|
||||||
{
|
"cell_type": "code",
|
||||||
"data": {
|
"execution_count": 13,
|
||||||
"text/plain": [
|
"metadata": {},
|
||||||
"['synset.txt', 'etc', 'driver.py', 'nginx']"
|
"outputs": [
|
||||||
]
|
{
|
||||||
},
|
"name": "stdout",
|
||||||
"execution_count": 8,
|
"output_type": "stream",
|
||||||
"metadata": {},
|
"text": "Overwriting flaskwebapp/app.py\n"
|
||||||
"output_type": "execute_result"
|
}
|
||||||
}
|
],
|
||||||
],
|
"source": "%%writefile flaskwebapp/app.py\n\nfrom flask import Flask, request, Response\nimport logging\nimport json\nimport driver\n\napp = Flask(__name__)\npredict_for = driver.get_model_api()\n \n@app.route(\"/score\", methods = ['POST'])\ndef scoreRRS():\n \"\"\" Endpoint for scoring\n \"\"\"\n if request.headers['Content-Type'] != 'application/json':\n return Response(json.dumps({}), status= 415, mimetype ='application/json')\n request_input = request.json['input']\n response = predict_for(request_input)\n return json.dumps({'result': response})\n\n\n@app.route(\"/\")\ndef healthy():\n return \"Healthy\"\n\n# PyTorch Version\n@app.route('/version', methods = ['GET'])\ndef version_request():\n return driver.version()\n\nif __name__ == \"__main__\":\n app.run(host='0.0.0.0', port=5000)"
|
||||||
"source": [
|
},
|
||||||
"shutil.copy(\"synset.txt\", \"flaskwebapp\")\n",
|
{
|
||||||
"shutil.copy(\"driver.py\", \"flaskwebapp\")\n",
|
"cell_type": "code",
|
||||||
"os.listdir(\"flaskwebapp\")"
|
"execution_count": 14,
|
||||||
]
|
"metadata": {},
|
||||||
},
|
"outputs": [
|
||||||
{
|
{
|
||||||
"cell_type": "markdown",
|
"name": "stdout",
|
||||||
"metadata": {},
|
"output_type": "stream",
|
||||||
"source": [
|
"text": "Overwriting flaskwebapp/wsgi.py\n"
|
||||||
"Below, we create the module for the Flask web application."
|
}
|
||||||
]
|
],
|
||||||
},
|
"source": "%%writefile flaskwebapp/wsgi.py\nfrom app import app as application\n\ndef create():\n print(\"Initialising\")\n application.run(host='127.0.0.1', port=5000)"
|
||||||
{
|
},
|
||||||
"cell_type": "code",
|
{
|
||||||
"execution_count": 9,
|
"cell_type": "markdown",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [
|
"source": "Here, we write the configuration for the Nginx which creates a proxy between ports **80** and **5000**."
|
||||||
{
|
},
|
||||||
"name": "stdout",
|
{
|
||||||
"output_type": "stream",
|
"cell_type": "code",
|
||||||
"text": [
|
"execution_count": 15,
|
||||||
"Writing flaskwebapp/app.py\n"
|
"metadata": {},
|
||||||
]
|
"outputs": [
|
||||||
}
|
{
|
||||||
],
|
"name": "stdout",
|
||||||
"source": [
|
"output_type": "stream",
|
||||||
"%%writefile flaskwebapp/app.py\n",
|
"text": "Overwriting flaskwebapp/nginx/app\n"
|
||||||
"\n",
|
}
|
||||||
"from flask import Flask, request, Response\n",
|
],
|
||||||
"import logging\n",
|
"source": "%%writefile flaskwebapp/nginx/app\nserver {\n listen 80;\n server_name _;\n \n location / {\n include proxy_params;\n proxy_pass http://127.0.0.1:5000;\n proxy_connect_timeout 5000s;\n proxy_read_timeout 5000s;\n }\n}"
|
||||||
"import json\n",
|
},
|
||||||
"import driver\n",
|
{
|
||||||
"\n",
|
"cell_type": "code",
|
||||||
"app = Flask(__name__)\n",
|
"execution_count": 16,
|
||||||
"predict_for = driver.get_model_api()\n",
|
"metadata": {},
|
||||||
" \n",
|
"outputs": [
|
||||||
"@app.route(\"/score\", methods = ['POST'])\n",
|
{
|
||||||
"def scoreRRS():\n",
|
"name": "stdout",
|
||||||
" \"\"\" Endpoint for scoring\n",
|
"output_type": "stream",
|
||||||
" \"\"\"\n",
|
"text": "Overwriting flaskwebapp/gunicorn_logging.conf\n"
|
||||||
" if request.headers['Content-Type'] != 'application/json':\n",
|
}
|
||||||
" return Response(json.dumps({}), status= 415, mimetype ='application/json')\n",
|
],
|
||||||
" request_input = request.json['input']\n",
|
"source": "%%writefile flaskwebapp/gunicorn_logging.conf\n\n[loggers]\nkeys=root, gunicorn.error\n\n[handlers]\nkeys=console\n\n[formatters]\nkeys=json\n\n[logger_root]\nlevel=INFO\nhandlers=console\n\n[logger_gunicorn.error]\nlevel=ERROR\nhandlers=console\npropagate=0\nqualname=gunicorn.error\n\n[handler_console]\nclass=StreamHandler\nformatter=json\nargs=(sys.stdout, )\n\n[formatter_json]\nclass=jsonlogging.JSONFormatter"
|
||||||
" response = predict_for(request_input)\n",
|
},
|
||||||
" return json.dumps({'result': response})\n",
|
{
|
||||||
"\n",
|
"cell_type": "code",
|
||||||
"\n",
|
"execution_count": 17,
|
||||||
"@app.route(\"/\")\n",
|
"metadata": {},
|
||||||
"def healthy():\n",
|
"outputs": [
|
||||||
" return \"Healthy\"\n",
|
{
|
||||||
"\n",
|
"name": "stdout",
|
||||||
"# PyTorch Version\n",
|
"output_type": "stream",
|
||||||
"@app.route('/version', methods = ['GET'])\n",
|
"text": "Overwriting flaskwebapp/kill_supervisor.py\n"
|
||||||
"def version_request():\n",
|
}
|
||||||
" return driver.version()\n",
|
],
|
||||||
"\n",
|
"source": "%%writefile flaskwebapp/kill_supervisor.py\nimport sys\nimport os\nimport signal\n\ndef write_stdout(s):\n sys.stdout.write(s)\n sys.stdout.flush()\n\n# this function is modified from the code and knowledge found here: http://supervisord.org/events.html#example-event-listener-implementation\ndef main():\n while 1:\n write_stdout('READY\\n')\n # wait for the event on stdin that supervisord will send\n line = sys.stdin.readline()\n write_stdout('Killing supervisor with this event: ' + line);\n try:\n # supervisord writes its pid to its file from which we read it here, see supervisord.conf\n pidfile = open('/tmp/supervisord.pid','r')\n pid = int(pidfile.readline());\n os.kill(pid, signal.SIGQUIT)\n except Exception as e:\n write_stdout('Could not kill supervisor: ' + e.strerror + '\\n')\n write_stdout('RESULT 2\\nOK')\n\nmain()"
|
||||||
"if __name__ == \"__main__\":\n",
|
},
|
||||||
" app.run(host='0.0.0.0', port=5000)"
|
{
|
||||||
]
|
"cell_type": "code",
|
||||||
},
|
"execution_count": 18,
|
||||||
{
|
"metadata": {},
|
||||||
"cell_type": "code",
|
"outputs": [
|
||||||
"execution_count": 10,
|
{
|
||||||
"metadata": {},
|
"name": "stdout",
|
||||||
"outputs": [
|
"output_type": "stream",
|
||||||
{
|
"text": "Overwriting flaskwebapp/etc/supervisord.conf\n"
|
||||||
"name": "stdout",
|
}
|
||||||
"output_type": "stream",
|
],
|
||||||
"text": [
|
"source": "%%writefile flaskwebapp/etc/supervisord.conf \n[supervisord]\nlogfile=/tmp/supervisord.log ; (main log file;default $CWD/supervisord.log)\nlogfile_maxbytes=50MB ; (max main logfile bytes b4 rotation;default 50MB)\nlogfile_backups=10 ; (num of main logfile rotation backups;default 10)\nloglevel=info ; (log level;default info; others: debug,warn,trace)\npidfile=/tmp/supervisord.pid ; (supervisord pidfile;default supervisord.pid)\nnodaemon=true ; (start in foreground if true;default false)\nminfds=1024 ; (min. avail startup file descriptors;default 1024)\nminprocs=200 ; (min. avail process descriptors;default 200)\n\n[program:gunicorn]\ncommand=bash -c \"gunicorn --workers 1 -m 007 --timeout 100000 --capture-output --error-logfile - --log-level debug --log-config gunicorn_logging.conf \\\"wsgi:create()\\\"\"\ndirectory=/code\nredirect_stderr=true\nstdout_logfile =/dev/stdout\nstdout_logfile_maxbytes=0\nstartretries=2\nstartsecs=20\n\n[program:nginx]\ncommand=/usr/sbin/nginx -g \"daemon off;\"\nstartretries=2\nstartsecs=5\npriority=3\n\n[eventlistener:program_exit]\ncommand=python kill_supervisor.py\ndirectory=/code\nevents=PROCESS_STATE_FATAL\npriority=2"
|
||||||
"Writing flaskwebapp/wsgi.py\n"
|
},
|
||||||
]
|
{
|
||||||
}
|
"cell_type": "markdown",
|
||||||
],
|
"metadata": {},
|
||||||
"source": [
|
"source": "We create a custom image based on the CUDA 9 image from NVIDIA and install all the necessary dependencies. This is in order to try and keep the size of the image as small as possible."
|
||||||
"%%writefile flaskwebapp/wsgi.py\n",
|
},
|
||||||
"from app import app as application\n",
|
{
|
||||||
"\n",
|
"cell_type": "code",
|
||||||
"def create():\n",
|
"execution_count": 19,
|
||||||
" print(\"Initialising\")\n",
|
"metadata": {},
|
||||||
" application.run(host='127.0.0.1', port=5000)"
|
"outputs": [
|
||||||
]
|
{
|
||||||
},
|
"name": "stdout",
|
||||||
{
|
"output_type": "stream",
|
||||||
"cell_type": "markdown",
|
"text": "Overwriting flaskwebapp/requirements.txt\n"
|
||||||
"metadata": {},
|
}
|
||||||
"source": [
|
],
|
||||||
"Here, we write the configuration for the Nginx which creates a proxy between ports **80** and **5000**."
|
"source": "%%writefile flaskwebapp/requirements.txt\nPillow==5.0.0\nclick==6.7\nconfigparser==3.5.0\nFlask==0.12.2\ngunicorn==19.6.0\njson-logging-py==0.2\nMarkupSafe==1.0\nolefile==0.44\nrequests==2.12.3"
|
||||||
]
|
},
|
||||||
},
|
{
|
||||||
{
|
"cell_type": "code",
|
||||||
"cell_type": "code",
|
"execution_count": 52,
|
||||||
"execution_count": 11,
|
"metadata": {},
|
||||||
"metadata": {},
|
"outputs": [
|
||||||
"outputs": [
|
{
|
||||||
{
|
"name": "stdout",
|
||||||
"name": "stdout",
|
"output_type": "stream",
|
||||||
"output_type": "stream",
|
"text": "Overwriting flaskwebapp/dockerfile\n"
|
||||||
"text": [
|
}
|
||||||
"Writing flaskwebapp/nginx/app\n"
|
],
|
||||||
]
|
"source": "%%writefile flaskwebapp/dockerfile\n\nFROM nvidia/cuda:9.0-cudnn7-devel-ubuntu16.04\n\nRUN echo \"deb http://developer.download.nvidia.com/compute/machine-learning/repos/ubuntu1604/x86_64 /\" > /etc/apt/sources.list.d/nvidia-ml.list\n\nRUN mkdir /code\nWORKDIR /code\nADD . /code/\nADD etc /etc\n\nRUN apt-get update && apt-get install -y --no-install-recommends \\\n build-essential \\\n ca-certificates \\\n cmake \\\n curl \\\n git \\\n nginx \\\n supervisor \\\n wget && \\\n rm -rf /var/lib/apt/lists/*\n\n\nENV PYTHON_VERSION=3.6\nRUN curl -o ~/miniconda.sh -O https://repo.anaconda.com/miniconda/Miniconda3-4.5.11-Linux-x86_64.sh && \\\n chmod +x ~/miniconda.sh && \\\n ~/miniconda.sh -b -p /opt/conda && \\\n rm ~/miniconda.sh && \\\n /opt/conda/bin/conda create -y --name py$PYTHON_VERSION python=$PYTHON_VERSION numpy scipy pandas scikit-learn && \\\n /opt/conda/bin/conda clean -ya && \\\n ln -s /opt/conda/etc/profile.d/conda.sh /etc/profile.d/conda.sh && \\ \n echo \". /opt/conda/etc/profile.d/conda.sh\" >> ~/.bashrc && \\ \n echo \"conda activate py$PYTHON_VERSION\" >> ~/.bashrc\n\nENV PATH /opt/conda/envs/py$PYTHON_VERSION/bin:$PATH\nENV LD_LIBRARY_PATH /opt/conda/envs/py$PYTHON_VERSION/lib:/usr/local/cuda/lib64/:$LD_LIBRARY_PATH\nENV PYTHONPATH /code/:$PYTHONPATH\n\nRUN rm /etc/nginx/sites-enabled/default && \\\n cp /code/nginx/app /etc/nginx/sites-available/ && \\\n ln -s /etc/nginx/sites-available/app /etc/nginx/sites-enabled/ && \\\n /opt/conda/bin/conda install --name py$PYTHON_VERSION -c pytorch pytorch==0.4.1 && \\\n pip install --upgrade pip && \\\n pip install torchvision==0.2.1 && \\\n pip install -r /code/requirements.txt && \\ \n /opt/conda/bin/conda clean -yt\n\nEXPOSE 80\nCMD [\"supervisord\", \"-c\", \"/code/etc/supervisord.conf\"]"
|
||||||
}
|
},
|
||||||
],
|
{
|
||||||
"source": [
|
"cell_type": "markdown",
|
||||||
"%%writefile flaskwebapp/nginx/app\n",
|
"metadata": {},
|
||||||
"server {\n",
|
"source": "The image name below refers to our dockerhub account. If you wish to push the image to your account make sure you change the docker login."
|
||||||
" listen 80;\n",
|
},
|
||||||
" server_name _;\n",
|
{
|
||||||
" \n",
|
"cell_type": "code",
|
||||||
" location / {\n",
|
"execution_count": 53,
|
||||||
" include proxy_params;\n",
|
"metadata": {},
|
||||||
" proxy_pass http://127.0.0.1:5000;\n",
|
"outputs": [],
|
||||||
" proxy_connect_timeout 5000s;\n",
|
"source": "image_name = get_key(env_path, 'docker_login') + '/' +get_key(env_path, 'image_repo') \napplication_path = 'flaskwebapp'\ndocker_file_location = path.join(application_path, 'dockerfile')"
|
||||||
" proxy_read_timeout 5000s;\n",
|
},
|
||||||
" }\n",
|
{
|
||||||
"}"
|
"cell_type": "markdown",
|
||||||
]
|
"metadata": {},
|
||||||
},
|
"source": "Next, we build our docker image. The output of this cell is cleared from this notebook as it is quite long due to all the installations required to build the image. However, you should make sure you see 'Successfully built' and 'Successfully tagged' messages in the last line of the output when you run the cell. "
|
||||||
{
|
},
|
||||||
"cell_type": "code",
|
{
|
||||||
"execution_count": 12,
|
"cell_type": "code",
|
||||||
"metadata": {},
|
"execution_count": 54,
|
||||||
"outputs": [
|
"metadata": {
|
||||||
{
|
"scrolled": false,
|
||||||
"name": "stdout",
|
"tags": [
|
||||||
"output_type": "stream",
|
"stripout"
|
||||||
"text": [
|
]
|
||||||
"Writing flaskwebapp/gunicorn_logging.conf\n"
|
},
|
||||||
]
|
"outputs": [],
|
||||||
}
|
"source": "!docker build -t $image_name -f $docker_file_location $application_path"
|
||||||
],
|
},
|
||||||
"source": [
|
{
|
||||||
"%%writefile flaskwebapp/gunicorn_logging.conf\n",
|
"cell_type": "markdown",
|
||||||
"\n",
|
"metadata": {},
|
||||||
"[loggers]\n",
|
"source": "Below we will push the image created to our dockerhub registry. Make sure you have already logged in to the appropriate dockerhub account using the docker login command. If you haven't loged in to the approrpiate dockerhub account you will get an error."
|
||||||
"keys=root, gunicorn.error\n",
|
},
|
||||||
"\n",
|
{
|
||||||
"[handlers]\n",
|
"cell_type": "code",
|
||||||
"keys=console\n",
|
"execution_count": 50,
|
||||||
"\n",
|
"metadata": {
|
||||||
"[formatters]\n",
|
"tags": [
|
||||||
"keys=json\n",
|
"stripout"
|
||||||
"\n",
|
]
|
||||||
"[logger_root]\n",
|
},
|
||||||
"level=INFO\n",
|
"outputs": [],
|
||||||
"handlers=console\n",
|
"source": "!docker push $image_name"
|
||||||
"\n",
|
},
|
||||||
"[logger_gunicorn.error]\n",
|
{
|
||||||
"level=ERROR\n",
|
"cell_type": "code",
|
||||||
"handlers=console\n",
|
"execution_count": 51,
|
||||||
"propagate=0\n",
|
"metadata": {
|
||||||
"qualname=gunicorn.error\n",
|
"tags": [
|
||||||
"\n",
|
"stripout"
|
||||||
"[handler_console]\n",
|
]
|
||||||
"class=StreamHandler\n",
|
},
|
||||||
"formatter=json\n",
|
"outputs": [],
|
||||||
"args=(sys.stdout, )\n",
|
"source": "print('Docker image name {}'.format(image_name))"
|
||||||
"\n",
|
},
|
||||||
"[formatter_json]\n",
|
{
|
||||||
"class=jsonlogging.JSONFormatter"
|
"cell_type": "markdown",
|
||||||
]
|
"metadata": {},
|
||||||
},
|
"source": "We can now [test our image locally](03_TestLocally.ipynb)."
|
||||||
{
|
}
|
||||||
"cell_type": "code",
|
],
|
||||||
"execution_count": 13,
|
"metadata": {
|
||||||
"metadata": {},
|
"celltoolbar": "Tags",
|
||||||
"outputs": [
|
"jupytext_format_version": "1.3",
|
||||||
{
|
"jupytext_formats": "py:light",
|
||||||
"name": "stdout",
|
"kernelspec": {
|
||||||
"output_type": "stream",
|
"display_name": "Python 3",
|
||||||
"text": [
|
"language": "python",
|
||||||
"Writing flaskwebapp/kill_supervisor.py\n"
|
"name": "python3"
|
||||||
]
|
},
|
||||||
}
|
"language_info": {
|
||||||
],
|
"codemirror_mode": {
|
||||||
"source": [
|
"name": "ipython",
|
||||||
"%%writefile flaskwebapp/kill_supervisor.py\n",
|
"version": 3
|
||||||
"import sys\n",
|
},
|
||||||
"import os\n",
|
"file_extension": ".py",
|
||||||
"import signal\n",
|
"mimetype": "text/x-python",
|
||||||
"\n",
|
"name": "python",
|
||||||
"def write_stdout(s):\n",
|
"nbconvert_exporter": "python",
|
||||||
" sys.stdout.write(s)\n",
|
"pygments_lexer": "ipython3",
|
||||||
" sys.stdout.flush()\n",
|
"version": "3.6.6"
|
||||||
"\n",
|
}
|
||||||
"# this function is modified from the code and knowledge found here: http://supervisord.org/events.html#example-event-listener-implementation\n",
|
},
|
||||||
"def main():\n",
|
"nbformat": 4,
|
||||||
" while 1:\n",
|
"nbformat_minor": 2
|
||||||
" write_stdout('READY\\n')\n",
|
|
||||||
" # wait for the event on stdin that supervisord will send\n",
|
|
||||||
" line = sys.stdin.readline()\n",
|
|
||||||
" write_stdout('Killing supervisor with this event: ' + line);\n",
|
|
||||||
" try:\n",
|
|
||||||
" # supervisord writes its pid to its file from which we read it here, see supervisord.conf\n",
|
|
||||||
" pidfile = open('/tmp/supervisord.pid','r')\n",
|
|
||||||
" pid = int(pidfile.readline());\n",
|
|
||||||
" os.kill(pid, signal.SIGQUIT)\n",
|
|
||||||
" except Exception as e:\n",
|
|
||||||
" write_stdout('Could not kill supervisor: ' + e.strerror + '\\n')\n",
|
|
||||||
" write_stdout('RESULT 2\\nOK')\n",
|
|
||||||
"\n",
|
|
||||||
"main()"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": 14,
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "stdout",
|
|
||||||
"output_type": "stream",
|
|
||||||
"text": [
|
|
||||||
"Writing flaskwebapp/etc/supervisord.conf\n"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"source": [
|
|
||||||
"%%writefile flaskwebapp/etc/supervisord.conf \n",
|
|
||||||
"[supervisord]\n",
|
|
||||||
"logfile=/tmp/supervisord.log ; (main log file;default $CWD/supervisord.log)\n",
|
|
||||||
"logfile_maxbytes=50MB ; (max main logfile bytes b4 rotation;default 50MB)\n",
|
|
||||||
"logfile_backups=10 ; (num of main logfile rotation backups;default 10)\n",
|
|
||||||
"loglevel=info ; (log level;default info; others: debug,warn,trace)\n",
|
|
||||||
"pidfile=/tmp/supervisord.pid ; (supervisord pidfile;default supervisord.pid)\n",
|
|
||||||
"nodaemon=true ; (start in foreground if true;default false)\n",
|
|
||||||
"minfds=1024 ; (min. avail startup file descriptors;default 1024)\n",
|
|
||||||
"minprocs=200 ; (min. avail process descriptors;default 200)\n",
|
|
||||||
"\n",
|
|
||||||
"[program:gunicorn]\n",
|
|
||||||
"command=bash -c \"gunicorn --workers 1 -m 007 --timeout 100000 --capture-output --error-logfile - --log-level debug --log-config gunicorn_logging.conf \\\"wsgi:create()\\\"\"\n",
|
|
||||||
"directory=/code\n",
|
|
||||||
"redirect_stderr=true\n",
|
|
||||||
"stdout_logfile =/dev/stdout\n",
|
|
||||||
"stdout_logfile_maxbytes=0\n",
|
|
||||||
"startretries=2\n",
|
|
||||||
"startsecs=20\n",
|
|
||||||
"\n",
|
|
||||||
"[program:nginx]\n",
|
|
||||||
"command=/usr/sbin/nginx -g \"daemon off;\"\n",
|
|
||||||
"startretries=2\n",
|
|
||||||
"startsecs=5\n",
|
|
||||||
"priority=3\n",
|
|
||||||
"\n",
|
|
||||||
"[eventlistener:program_exit]\n",
|
|
||||||
"command=python kill_supervisor.py\n",
|
|
||||||
"directory=/code\n",
|
|
||||||
"events=PROCESS_STATE_FATAL\n",
|
|
||||||
"priority=2"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"metadata": {},
|
|
||||||
"source": [
|
|
||||||
"We create a custom image based on the CUDA 9 image from NVIDIA and install all the necessary dependencies. This is in order to try and keep the size of the image as small as possible."
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": 15,
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "stdout",
|
|
||||||
"output_type": "stream",
|
|
||||||
"text": [
|
|
||||||
"Writing flaskwebapp/requirements.txt\n"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"source": [
|
|
||||||
"%%writefile flaskwebapp/requirements.txt\n",
|
|
||||||
"Pillow==5.0.0\n",
|
|
||||||
"click==6.7\n",
|
|
||||||
"configparser==3.5.0\n",
|
|
||||||
"Flask==0.12.2\n",
|
|
||||||
"gunicorn==19.6.0\n",
|
|
||||||
"json-logging-py==0.2\n",
|
|
||||||
"MarkupSafe==1.0\n",
|
|
||||||
"olefile==0.44\n",
|
|
||||||
"requests==2.12.3"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": 16,
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "stdout",
|
|
||||||
"output_type": "stream",
|
|
||||||
"text": [
|
|
||||||
"Writing flaskwebapp/dockerfile\n"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"source": [
|
|
||||||
"%%writefile flaskwebapp/dockerfile\n",
|
|
||||||
"\n",
|
|
||||||
"FROM nvidia/cuda:9.0-cudnn7-devel-ubuntu16.04\n",
|
|
||||||
"\n",
|
|
||||||
"RUN echo \"deb http://developer.download.nvidia.com/compute/machine-learning/repos/ubuntu1604/x86_64 /\" > /etc/apt/sources.list.d/nvidia-ml.list\n",
|
|
||||||
"\n",
|
|
||||||
"RUN mkdir /code\n",
|
|
||||||
"WORKDIR /code\n",
|
|
||||||
"ADD . /code/\n",
|
|
||||||
"ADD etc /etc\n",
|
|
||||||
"\n",
|
|
||||||
"RUN apt-get update && apt-get install -y --no-install-recommends \\\n",
|
|
||||||
" build-essential \\\n",
|
|
||||||
" ca-certificates \\\n",
|
|
||||||
" cmake \\\n",
|
|
||||||
" curl \\\n",
|
|
||||||
" git \\\n",
|
|
||||||
" nginx \\\n",
|
|
||||||
" supervisor \\\n",
|
|
||||||
" wget && \\\n",
|
|
||||||
" rm -rf /var/lib/apt/lists/*\n",
|
|
||||||
"\n",
|
|
||||||
"\n",
|
|
||||||
"ENV PYTHON_VERSION=3.6\n",
|
|
||||||
"RUN curl -o ~/miniconda.sh -O https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh && \\\n",
|
|
||||||
" chmod +x ~/miniconda.sh && \\\n",
|
|
||||||
" ~/miniconda.sh -b -p /opt/conda && \\\n",
|
|
||||||
" rm ~/miniconda.sh && \\\n",
|
|
||||||
" /opt/conda/bin/conda create -y --name py$PYTHON_VERSION python=$PYTHON_VERSION numpy scipy pandas scikit-learn && \\\n",
|
|
||||||
" /opt/conda/bin/conda clean -ya\n",
|
|
||||||
"ENV PATH /opt/conda/envs/py$PYTHON_VERSION/bin:$PATH\n",
|
|
||||||
"ENV LD_LIBRARY_PATH /opt/conda/envs/py$PYTHON_VERSION/lib:/usr/local/cuda/lib64/:$LD_LIBRARY_PATH\n",
|
|
||||||
"ENV PYTHONPATH /code/:$PYTHONPATH\n",
|
|
||||||
"\n",
|
|
||||||
" \n",
|
|
||||||
"RUN rm /etc/nginx/sites-enabled/default && \\\n",
|
|
||||||
" cp /code/nginx/app /etc/nginx/sites-available/ && \\\n",
|
|
||||||
" ln -s /etc/nginx/sites-available/app /etc/nginx/sites-enabled/ && \\\n",
|
|
||||||
" /opt/conda/bin/conda install -c pytorch pytorch==0.4.1 && \\\n",
|
|
||||||
" pip install --upgrade pip && \\\n",
|
|
||||||
" pip install torchvision==0.2.1 && \\\n",
|
|
||||||
" pip install -r /code/requirements.txt && \\ \n",
|
|
||||||
" /opt/conda/bin/conda clean -yt\n",
|
|
||||||
"\n",
|
|
||||||
"EXPOSE 80\n",
|
|
||||||
"CMD [\"supervisord\", \"-c\", \"/code/etc/supervisord.conf\"]"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"metadata": {},
|
|
||||||
"source": [
|
|
||||||
"The image name below refers to our dockerhub account. If you wish to push the image to your account make sure you change the docker login."
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": 17,
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [],
|
|
||||||
"source": [
|
|
||||||
"image_name = get_key(env_path, 'docker_login') + '/' +get_key(env_path, 'image_repo') \n",
|
|
||||||
"application_path = 'flaskwebapp'\n",
|
|
||||||
"docker_file_location = path.join(application_path, 'dockerfile')"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"metadata": {},
|
|
||||||
"source": [
|
|
||||||
"Next, we build our docker image. The output of this cell is cleared from this notebook as it is quite long due to all the installations required to build the image. However, you should make sure you see 'Successfully built' and 'Successfully tagged' messages in the last line of the output when you run the cell. "
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": null,
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [],
|
|
||||||
"source": [
|
|
||||||
"!docker build -t $image_name -f $docker_file_location $application_path"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"metadata": {},
|
|
||||||
"source": [
|
|
||||||
"Below we will push the image created to our dockerhub registry. Make sure you have already logged in to the appropriate dockerhub account using the docker login command. If you haven't loged in to the approrpiate dockerhub account you will get an error."
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": null,
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [],
|
|
||||||
"source": [
|
|
||||||
"!docker push $image_name"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": null,
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [],
|
|
||||||
"source": [
|
|
||||||
"print('Docker image name {}'.format(image_name))"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"metadata": {},
|
|
||||||
"source": [
|
|
||||||
"We can now [test our image locally](03_TestLocally.ipynb)."
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"metadata": {
|
|
||||||
"jupytext_format_version": "1.3",
|
|
||||||
"jupytext_formats": "py:light",
|
|
||||||
"kernelspec": {
|
|
||||||
"display_name": "Python [conda env:AKSDeploymentPytorch]",
|
|
||||||
"language": "python",
|
|
||||||
"name": "conda-env-AKSDeploymentPytorch-py"
|
|
||||||
},
|
|
||||||
"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.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nbformat": 4,
|
|
||||||
"nbformat_minor": 2
|
|
||||||
}
|
}
|
||||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -1,128 +1,100 @@
|
||||||
{
|
{
|
||||||
"cells": [
|
"cells": [
|
||||||
{
|
{
|
||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": "# Tear it all down\nOnce you are done with your cluster you can use the following two commands to destroy it all."
|
||||||
"# Tear it all down\n",
|
},
|
||||||
"Once you are done with your cluster you can use the following two commands to destroy it all."
|
{
|
||||||
]
|
"cell_type": "code",
|
||||||
},
|
"execution_count": 4,
|
||||||
{
|
"metadata": {},
|
||||||
"cell_type": "code",
|
"outputs": [],
|
||||||
"execution_count": 1,
|
"source": "from dotenv import get_key, find_dotenv"
|
||||||
"metadata": {},
|
},
|
||||||
"outputs": [],
|
{
|
||||||
"source": [
|
"cell_type": "code",
|
||||||
"from dotenv import get_key, find_dotenv"
|
"execution_count": 5,
|
||||||
]
|
"metadata": {},
|
||||||
},
|
"outputs": [],
|
||||||
{
|
"source": "env_path = find_dotenv(raise_error_if_not_found=True)"
|
||||||
"cell_type": "code",
|
},
|
||||||
"execution_count": 2,
|
{
|
||||||
"metadata": {},
|
"cell_type": "markdown",
|
||||||
"outputs": [],
|
"metadata": {},
|
||||||
"source": [
|
"source": "Once you are done with your cluster you can use the following two commands to destroy it all. First, delete the application."
|
||||||
"env_path = find_dotenv(raise_error_if_not_found=True)"
|
},
|
||||||
]
|
{
|
||||||
},
|
"cell_type": "code",
|
||||||
{
|
"execution_count": 6,
|
||||||
"cell_type": "markdown",
|
"metadata": {},
|
||||||
"metadata": {},
|
"outputs": [
|
||||||
"source": [
|
{
|
||||||
"Once you are done with your cluster you can use the following two commands to destroy it all. First, delete the application."
|
"name": "stdout",
|
||||||
]
|
"output_type": "stream",
|
||||||
},
|
"text": "deployment.apps \"azure-dl\" deleted\nservice \"azure-dl\" deleted\n"
|
||||||
{
|
}
|
||||||
"cell_type": "code",
|
],
|
||||||
"execution_count": 3,
|
"source": "!kubectl delete -f az-dl.json"
|
||||||
"metadata": {},
|
},
|
||||||
"outputs": [
|
{
|
||||||
{
|
"cell_type": "markdown",
|
||||||
"name": "stdout",
|
"metadata": {},
|
||||||
"output_type": "stream",
|
"source": "Next, you delete the AKS cluster. This step may take a few minutes."
|
||||||
"text": [
|
},
|
||||||
"deployment.apps \"azure-dl\" deleted\n",
|
{
|
||||||
"service \"azure-dl\" deleted\n"
|
"cell_type": "code",
|
||||||
]
|
"execution_count": 7,
|
||||||
}
|
"metadata": {},
|
||||||
],
|
"outputs": [
|
||||||
"source": [
|
{
|
||||||
"!kubectl delete -f az-dl.json"
|
"name": "stdout",
|
||||||
]
|
"output_type": "stream",
|
||||||
},
|
"text": "\u001b[K\u001b[0minished .."
|
||||||
{
|
}
|
||||||
"cell_type": "markdown",
|
],
|
||||||
"metadata": {},
|
"source": "!az aks delete -n {get_key(env_path, 'aks_name')} \\\n -g {get_key(env_path, 'resource_group')} \\\n -y"
|
||||||
"source": [
|
},
|
||||||
"Next, you delete the AKS cluster. This step may take a few minutes."
|
{
|
||||||
]
|
"cell_type": "markdown",
|
||||||
},
|
"metadata": {},
|
||||||
{
|
"source": "Finally, you should delete the resource group. This also deletes the AKS cluster and can be used instead of the above command if the resource group is only used for this purpose."
|
||||||
"cell_type": "code",
|
},
|
||||||
"execution_count": 4,
|
{
|
||||||
"metadata": {},
|
"cell_type": "code",
|
||||||
"outputs": [
|
"execution_count": 8,
|
||||||
{
|
"metadata": {},
|
||||||
"name": "stdout",
|
"outputs": [
|
||||||
"output_type": "stream",
|
{
|
||||||
"text": [
|
"name": "stdout",
|
||||||
"\u001b[K\u001b[0minished .."
|
"output_type": "stream",
|
||||||
]
|
"text": "\u001b[K\u001b[0minished .."
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"source": [
|
"source": "!az group delete --name {get_key(env_path, 'resource_group')} -y"
|
||||||
"!az aks delete -n {get_key(env_path, 'aks_name')} \\\n",
|
}
|
||||||
" -g {get_key(env_path, 'resource_group')} \\\n",
|
],
|
||||||
" -y"
|
"metadata": {
|
||||||
]
|
"jupytext_format_version": "1.3",
|
||||||
},
|
"jupytext_formats": "py:light",
|
||||||
{
|
"kernelspec": {
|
||||||
"cell_type": "markdown",
|
"display_name": "Python 3",
|
||||||
"metadata": {},
|
"language": "python",
|
||||||
"source": [
|
"name": "python3"
|
||||||
"Finally, you should delete the resource group. This also deletes the AKS cluster and can be used instead of the above command if the resource group is only used for this purpose."
|
},
|
||||||
]
|
"language_info": {
|
||||||
},
|
"codemirror_mode": {
|
||||||
{
|
"name": "ipython",
|
||||||
"cell_type": "code",
|
"version": 3
|
||||||
"execution_count": 5,
|
},
|
||||||
"metadata": {},
|
"file_extension": ".py",
|
||||||
"outputs": [
|
"mimetype": "text/x-python",
|
||||||
{
|
"name": "python",
|
||||||
"name": "stdout",
|
"nbconvert_exporter": "python",
|
||||||
"output_type": "stream",
|
"pygments_lexer": "ipython3",
|
||||||
"text": [
|
"version": "3.6.6"
|
||||||
"\u001b[K\u001b[0minished .."
|
}
|
||||||
]
|
},
|
||||||
}
|
"nbformat": 4,
|
||||||
],
|
"nbformat_minor": 2
|
||||||
"source": [
|
|
||||||
"!az group delete --name {get_key(env_path, 'resource_group')} -y"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"metadata": {
|
|
||||||
"jupytext_format_version": "1.3",
|
|
||||||
"jupytext_formats": "py:light",
|
|
||||||
"kernelspec": {
|
|
||||||
"display_name": "Python [conda env:AKSDeploymentPytorch]",
|
|
||||||
"language": "python",
|
|
||||||
"name": "conda-env-AKSDeploymentPytorch-py"
|
|
||||||
},
|
|
||||||
"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.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nbformat": 4,
|
|
||||||
"nbformat_minor": 2
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ dependencies:
|
||||||
- pytorch
|
- pytorch
|
||||||
- pip:
|
- pip:
|
||||||
- papermill==0.14.1
|
- papermill==0.14.1
|
||||||
- python-dotenv==0.9.0
|
- https://github.com/theskumar/python-dotenv/archive/master.zip
|
||||||
- Pillow==5.2.0
|
- Pillow==5.2.0
|
||||||
- wget==3.2
|
- wget==3.2
|
||||||
- matplotlib==2.2.2
|
- matplotlib==2.2.2
|
||||||
|
|
Загрузка…
Ссылка в новой задаче