Initial working version of TF model

This commit is contained in:
Mathew Salvaris 2018-03-21 11:44:11 +00:00
Родитель 790750dea5
Коммит d2dbf59063
9 изменённых файлов: 4543 добавлений и 37 удалений

1805
00_BuildImage.ipynb Normal file

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

421
01_TestLocally.ipynb Normal file

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

1171
02_DeployOnAKS.ipynb Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

324
03_TestWebApp.ipynb Normal file

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

359
04_SpeedTestWebApp.ipynb Normal file

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

42
LICENSE
Просмотреть файл

@ -1,21 +1,21 @@
MIT License
Copyright (c) Microsoft Corporation. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE
MIT License
Copyright (c) Microsoft Corporation. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE

Просмотреть файл

@ -1,20 +1,22 @@
# Introduction
TODO: Give a short introduction of your project. Let this section explain the objectives or the motivation behind this project.
### Authors: Mathew Salvaris and Ilia Karmanov
# Getting Started
TODO: Guide users through getting your code up and running on their own system. In this section you can talk about:
1. Installation process
2. Software dependencies
3. Latest releases
4. API references
# Deploy ML on ACS
Deploying machine learning models can often be tricky due to their numerous dependencies, deep learning models often even more so. One of the ways to overcome this is to use Docker containers. Unfortunately, it is rarely straight-forward. In this tutorial, we will demonstrate how to deploy a pre-trained deep learning model using Azure Container Services, which allows us to orchestrate a number of containers using DC/OS. By using Azure Container Services, we can ensure that it is performant, scalable and flexible enough to accommodate any deep learning framework.
The Docker image we will be deploying can be found [here](https://hub.docker.com/r/masalvar/cntkresnet/). It contains a simple Flask web application with Nginx web server. The deep learning framework we will use is the Microsoft Cognitive Toolkit (CNTK) and we will be using a pre-trained model; specifically the ResNet 152 model.
# Build and Test
TODO: Describe and show how to build your code and run the tests.
Azure Container Services enables you to configure, construct and manage a cluster of virtual machines pre-configured to run containerized applications. Once the cluster is set up you can use a number of open-source scheduling and orchestration tools, such as Kubernetes and DC/OS. This is ideal for machine learning application since we can use Docker containers which enable us to have ultimate flexibility in the libraries we use and allows us to easily scale up based on demand. While always ensuring that our application remains performant. You can create an ACS through the Azure portal but in this tutorial we will be constructing it using the Azure CLI.
# Contribute
TODO: Explain how other users and developers can contribute to make your code better.
The application will be a simple image classification service, where we will submit an image and get back what class the image belongs to. We have split the process into five sections.
* [Create Docker image of our application](00_BuildImage.ipynb)
* [Test the application locally](01_TestLocally.ipynb)
* [Create an ACS cluster and deploy our web app](02_DeployOnACS.ipynb)
* [Test our web app](03_TestWebApp.ipynb)
* [Load Test our web app](04_SpeedTestWebApp.ipynb)
Each section is accompanied by a Jupyter notebook which contains step-by-step instructions on how to create, deploy and test a web application.
If you already have a Docker image that you would like to deploy you can skip the first two notebooks.
# Contributing
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
If you want to learn more about creating good readme files then refer the following [guidelines](https://www.visualstudio.com/en-us/docs/git/create-a-readme). You can also seek inspiration from the below readme files:
- [ASP.NET Core](https://github.com/aspnet/Home)
- [Visual Studio Code](https://github.com/Microsoft/vscode)
- [Chakra Core](https://github.com/Microsoft/ChakraCore)

332
TFModel.ipynb Normal file
Просмотреть файл

@ -0,0 +1,332 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"from PIL import Image\n",
"import numpy as np\n",
"import tensorflow as tf\n",
"from tensorflow.contrib.slim.nets import resnet_v1"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"--2018-03-19 13:53:02-- https://upload.wikimedia.org/wikipedia/commons/thumb/6/68/Lynx_lynx_poing.jpg/220px-Lynx_lynx_poing.jpg\n",
"Resolving upload.wikimedia.org (upload.wikimedia.org)... 208.80.153.240, 2620:0:860:ed1a::2:b\n",
"Connecting to upload.wikimedia.org (upload.wikimedia.org)|208.80.153.240|:443... connected.\n",
"HTTP request sent, awaiting response... 200 OK\n",
"Length: 27183 (27K) [image/jpeg]\n",
"Saving to: 220px-Lynx_lynx_poing.jpg.1\n",
"\n",
"220px-Lynx_lynx_poi 100%[===================>] 26.55K --.-KB/s in 0.01s \n",
"\n",
"2018-03-19 13:53:02 (2.44 MB/s) - 220px-Lynx_lynx_poing.jpg.1 saved [27183/27183]\n",
"\n"
]
}
],
"source": [
"!wget https://upload.wikimedia.org/wikipedia/commons/thumb/6/68/Lynx_lynx_poing.jpg/220px-Lynx_lynx_poing.jpg"
]
},
{
"cell_type": "code",
"execution_count": 40,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"im = Image.open(\"220px-Lynx_lynx_poing.jpg\").resize((224,224))\n",
"im = np.array(im)\n",
"im = np.expand_dims(im, 0)"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"--2018-03-19 13:53:11-- http://download.tensorflow.org/models/resnet_v1_152_2016_08_28.tar.gz\n",
"Resolving download.tensorflow.org (download.tensorflow.org)... 172.217.9.16, 2607:f8b0:4000:812::2010\n",
"Connecting to download.tensorflow.org (download.tensorflow.org)|172.217.9.16|:80... connected.\n",
"HTTP request sent, awaiting response... 200 OK\n",
"Length: 224342140 (214M) [application/x-tar]\n",
"Saving to: resnet_v1_152_2016_08_28.tar.gz.1\n",
"\n",
"resnet_v1_152_2016_ 100%[===================>] 213.95M 106MB/s in 2.0s \n",
"\n",
"2018-03-19 13:53:14 (106 MB/s) - resnet_v1_152_2016_08_28.tar.gz.1 saved [224342140/224342140]\n",
"\n"
]
}
],
"source": [
"!wget http://download.tensorflow.org/models/resnet_v1_152_2016_08_28.tar.gz"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"resnet_v1_152.ckpt\r\n"
]
}
],
"source": [
"!tar xvf resnet_v1_152_2016_08_28.tar.gz"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"# Placeholders\n",
"input_tensor = tf.placeholder(tf.float32, shape=(None,224,224,3), name='input_image')"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"# Load the model\n",
"sess = tf.Session()\n",
"arg_scope = resnet_v1.resnet_arg_scope()\n",
"with tf.contrib.slim.arg_scope(arg_scope):\n",
" logits, _ = resnet_v1.resnet_v1_152(input_tensor, num_classes=1000, is_training=False)"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"probabilities = tf.nn.softmax(logits)"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"INFO:tensorflow:Restoring parameters from resnet_v1_152.ckpt\n"
]
}
],
"source": [
"checkpoint_file = 'resnet_v1_152.ckpt'\n",
"saver = tf.train.Saver()\n",
"saver.restore(sess, checkpoint_file)"
]
},
{
"cell_type": "code",
"execution_count": 41,
"metadata": {},
"outputs": [
{
"ename": "ValueError",
"evalue": "Cannot feed value of shape (224, 224, 3) for Tensor 'input_image:0', which has shape '(?, 224, 224, 3)'",
"output_type": "error",
"traceback": [
"\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[1;31mValueError\u001b[0m Traceback (most recent call last)",
"\u001b[1;32m<ipython-input-41-31b809ab445e>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m()\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mpred\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mpred_proba\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0msess\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mrun\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m[\u001b[0m\u001b[0mlogits\u001b[0m\u001b[1;33m,\u001b[0m\u001b[0mprobabilities\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mfeed_dict\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;33m{\u001b[0m\u001b[0minput_tensor\u001b[0m\u001b[1;33m:\u001b[0m \u001b[0mim\u001b[0m\u001b[1;33m}\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[1;32m/anaconda/envs/py35/lib/python3.5/site-packages/tensorflow/python/client/session.py\u001b[0m in \u001b[0;36mrun\u001b[1;34m(self, fetches, feed_dict, options, run_metadata)\u001b[0m\n\u001b[0;32m 887\u001b[0m \u001b[1;32mtry\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 888\u001b[0m result = self._run(None, fetches, feed_dict, options_ptr,\n\u001b[1;32m--> 889\u001b[1;33m run_metadata_ptr)\n\u001b[0m\u001b[0;32m 890\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0mrun_metadata\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 891\u001b[0m \u001b[0mproto_data\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mtf_session\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mTF_GetBuffer\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mrun_metadata_ptr\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
"\u001b[1;32m/anaconda/envs/py35/lib/python3.5/site-packages/tensorflow/python/client/session.py\u001b[0m in \u001b[0;36m_run\u001b[1;34m(self, handle, fetches, feed_dict, options, run_metadata)\u001b[0m\n\u001b[0;32m 1094\u001b[0m \u001b[1;34m'Cannot feed value of shape %r for Tensor %r, '\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 1095\u001b[0m \u001b[1;34m'which has shape %r'\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m-> 1096\u001b[1;33m % (np_val.shape, subfeed_t.name, str(subfeed_t.get_shape())))\n\u001b[0m\u001b[0;32m 1097\u001b[0m \u001b[1;32mif\u001b[0m \u001b[1;32mnot\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mgraph\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mis_feedable\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0msubfeed_t\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 1098\u001b[0m \u001b[1;32mraise\u001b[0m \u001b[0mValueError\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'Tensor %s may not be fed.'\u001b[0m \u001b[1;33m%\u001b[0m \u001b[0msubfeed_t\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
"\u001b[1;31mValueError\u001b[0m: Cannot feed value of shape (224, 224, 3) for Tensor 'input_image:0', which has shape '(?, 224, 224, 3)'"
]
}
],
"source": [
"pred, pred_proba = sess.run([logits,probabilities], feed_dict={input_tensor: im})"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(1, 1, 1, 1000)"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"pred_proba.shape"
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([282, 285, 287])"
]
},
"execution_count": 18,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"np.argsort(pred.squeeze())[-3:]"
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([282, 285, 287])"
]
},
"execution_count": 17,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"np.argsort(pred_proba.squeeze())[-3:]"
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"def create_label_lookup():\n",
" with open('synset.txt', 'r') as f:\n",
" label_list = [l.rstrip() for l in f]\n",
" def _label_lookup(*label_locks):\n",
" return [label_list[l] for l in label_locks]\n",
" return _label_lookup"
]
},
{
"cell_type": "code",
"execution_count": 23,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"label_lookup = create_label_lookup()"
]
},
{
"cell_type": "code",
"execution_count": 38,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"labels=label_lookup(*np.flip(np.argsort(pred.squeeze()), 0)[:3])"
]
},
{
"cell_type": "code",
"execution_count": 43,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'n02123159 tiger cat': 11.609917,\n",
" 'n02124075 Egyptian cat': 11.695245,\n",
" 'n02127052 lynx, catamount': 18.750937}"
]
},
"execution_count": 43,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"dict(zip(labels, np.flip(np.sort(pred.squeeze()), 0)[:3]))"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3.5",
"language": "python",
"name": "python3"
},
"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.5.2"
}
},
"nbformat": 4,
"nbformat_minor": 2
}

92
testing_utilities.py Normal file
Просмотреть файл

@ -0,0 +1,92 @@
import base64
import urllib
import requests
import json
from PIL import Image, ImageOps
from io import BytesIO
import toolz
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
def read_image_from(url):
return toolz.pipe(url,
urllib.request.urlopen,
lambda x: x.read(),
BytesIO)
def to_rgb(img_bytes):
return Image.open(img_bytes).convert('RGB')
@toolz.curry
def resize(img_file, new_size=(100, 100)):
return ImageOps.fit(img_file, new_size, Image.ANTIALIAS)
def to_base64(img):
imgio = BytesIO()
img.save(imgio, 'PNG')
imgio.seek(0)
dataimg = base64.b64encode(imgio.read())
return dataimg.decode('utf-8')
def to_img(img_url):
return toolz.pipe(img_url,
read_image_from,
to_rgb,
resize(new_size=(224,224)))
def img_url_to_json(url):
img_data = toolz.pipe(url,
to_img,
to_base64)
return json.dumps({'input':'[\"{0}\"]'.format(img_data)})
def _plot_image(ax, img):
ax.imshow(to_img(img))
ax.tick_params(axis='both',
which='both',
bottom='off',
top='off',
left='off',
right='off',
labelleft='off',
labelbottom='off')
return ax
def _plot_prediction_bar(ax, r):
perf = list(c[1] for c in r.json()['result'][0][0])
ax.barh(range(3, 0, -1), perf, align='center', color='#55DD55')
ax.tick_params(axis='both',
which='both',
bottom='off',
top='off',
left='off',
right='off',
labelbottom='off')
tick_labels = reversed(list(' '.join(c[0].split()[1:]).split(',')[0] for c in r.json()['result'][0][0]))
ax.yaxis.set_ticks([1,2,3])
ax.yaxis.set_ticklabels(tick_labels, position=(0.5,0), minor=False, horizontalalignment='center')
def plot_predictions(images, classification_results):
if len(images)!=6:
raise Exception('This method is only designed for 6 images')
gs = gridspec.GridSpec(2, 3)
fig = plt.figure(figsize=(12, 9))
gs.update(hspace=0.1, wspace=0.001)
for gg,r, img in zip(gs, classification_results, images):
gg2 = gridspec.GridSpecFromSubplotSpec(4, 10, subplot_spec=gg)
ax = fig.add_subplot(gg2[0:3, :])
_plot_image(ax, img)
ax = fig.add_subplot(gg2[3, 1:9])
_plot_prediction_bar(ax, r)