Add a section of how to link IE with CMake project (#99)
[platform/upstream/dldt.git] / inference-engine / ie_bridges / python / sample / jupyter_notebooks / classification_demo / classification_demo.ipynb
1 {
2  "cells": [
3   {
4    "cell_type": "markdown",
5    "metadata": {},
6    "source": [
7     "This notebook demonstrates the worklflow of a simple image classification task.\n",
8     "We will go through all the pipeline steps: downloading the model, generating the Intermediate Representation (IR) using the Model Optimizer, running inference in Python, and parsing and interpretating the output results.\n",
9     "\n",
10     "To demonstrate the scenario, we will use the pre-trained SquezeNet V1.1 Caffe\\* model. SqueezeNet is a pretty accurate and at the same time lightweight network. For more information about the model, please visit <a href=\"https://github.com/DeepScale/SqueezeNet/\">GitHub</a> page and refer to original <a href=\"https://arxiv.org/abs/1602.07360\">SqueezeNet paper</a>.\n",
11     "\n",
12     "Follow the steps to perform image classification with the SquezeNet V1.1 model:"
13    ]
14   },
15   {
16    "cell_type": "markdown",
17    "metadata": {},
18    "source": [
19     "**1. Download the model files:** "
20    ]
21   },
22   {
23    "cell_type": "code",
24    "execution_count": null,
25    "metadata": {},
26    "outputs": [],
27    "source": [
28     "%%bash\n",
29     "echo \"Downloading deploy.protxt ...\"\n",
30     "if [ -f deploy.prototxt ]; then \n",
31     "    echo \"deploy.protxt file already exists. Downloading skipped\"\n",
32     "else\n",
33     "    wget https://raw.githubusercontent.com/DeepScale/SqueezeNet/a47b6f13d30985279789d08053d37013d67d131b/SqueezeNet_v1.1/deploy.prototxt -q\n",
34     "    echo \"Finished!\"\n",
35     "fi"
36    ]
37   },
38   {
39    "cell_type": "code",
40    "execution_count": null,
41    "metadata": {},
42    "outputs": [],
43    "source": [
44     "%%bash\n",
45     "! echo \"Downloading squeezenet_v1.1.caffemodel ...\"\n",
46     "if [ -f squeezenet_v1.1.caffemodel ]; then\n",
47     "    echo \"squeezenet_v1.1.caffemodel file already exists. Download skipped\"\n",
48     "else\n",
49     "    wget https://github.com/DeepScale/SqueezeNet/raw/a47b6f13d30985279789d08053d37013d67d131b/SqueezeNet_v1.1/squeezenet_v1.1.caffemodel -q\n",
50     "    echo \"Finished!\"\n",
51     "fi"
52    ]
53   },
54   {
55    "cell_type": "markdown",
56    "metadata": {},
57    "source": [
58     "**Run the following command to see the model files:**"
59    ]
60   },
61   {
62    "cell_type": "code",
63    "execution_count": null,
64    "metadata": {},
65    "outputs": [],
66    "source": [
67     "!ls -la"
68    ]
69   },
70   {
71    "cell_type": "markdown",
72    "metadata": {},
73    "source": [
74     "* `deploy.prototxt` contains the network toplogy description in text format. \n",
75     "* `squeezenet_v1.1.caffemodel` contains weights for all network layers"
76    ]
77   },
78   {
79    "cell_type": "markdown",
80    "metadata": {},
81    "source": [
82     "**2. Optimize and convert the model from intial Caffe representation to the IR representation, which is required for scoring the model using Inference Engine. To convert and optimize the model, use the Model Optimizer command line tool.**\n",
83     "\n",
84     "To locate Model Optimizer scripts, specify the path to the Model Optimizer root directory in the `MO_ROOT` variable in the cell bellow and then run it (If you use the installed OpenVINO&trade; package, you can find the Model Optimizer in `<INSTALLATION_ROOT_DIR>/deployment_tools/model_optimizer`)."
85    ]
86   },
87   {
88    "cell_type": "code",
89    "execution_count": null,
90    "metadata": {},
91    "outputs": [],
92    "source": [
93     "%%bash\n",
94     "MO_ROOT=/localdisk/repos/model-optimizer-tensorflow/\n",
95     "echo $MO_ROOT\n",
96     "python3 $MO_ROOT/mo.py --input_model squeezenet_v1.1.caffemodel --input_proto deploy.prototxt"
97    ]
98   },
99   {
100    "cell_type": "markdown",
101    "metadata": {},
102    "source": [
103     "**3. Now, you have the SqueezeNet model converted to the IR, and you can infer it.**\n",
104     "\n",
105     "a. First, import required modules:"
106    ]
107   },
108   {
109    "cell_type": "code",
110    "execution_count": null,
111    "metadata": {},
112    "outputs": [],
113    "source": [
114     "from openvino.inference_engine import IENetwork, IEPlugin\n",
115     "import numpy as np\n",
116     "import cv2\n",
117     "import logging as log\n",
118     "from time import time\n",
119     "import sys\n",
120     "import glob\n",
121     "import os\n",
122     "from matplotlib import pyplot as plt\n",
123     "%matplotlib inline"
124    ]
125   },
126   {
127    "cell_type": "markdown",
128    "metadata": {},
129    "source": [
130     "b. Initialize required constants:"
131    ]
132   },
133   {
134    "cell_type": "code",
135    "execution_count": null,
136    "metadata": {},
137    "outputs": [],
138    "source": [
139     "# Configure logging format\n",
140     "log.basicConfig(format=\"[ %(levelname)s ] %(message)s\", level=log.INFO, stream=sys.stdout)\n",
141     "\n",
142     "# Path to IR model files\n",
143     "MODEL_XML = \"./squeezenet_v1.1.xml\"\n",
144     "MODEL_BIN = \"./squeezenet_v1.1.bin\"\n",
145     "\n",
146     "# Target device to run inference\n",
147     "TARGET_DEVICE = \"CPU\"\n",
148     "\n",
149     "# Folder with input images for the model\n",
150     "IMAGES_FOLDER = \"./images\"\n",
151     "\n",
152     "# File containing information about classes names \n",
153     "LABELS_FILE = \"./image_net_synset.txt\"\n",
154     "\n",
155     "# Number of top prediction results to parse\n",
156     "NTOP = 5\n",
157     "\n",
158     "# Required batch size - number of images which will be processed in parallel\n",
159     "BATCH = 4"
160    ]
161   },
162   {
163    "cell_type": "markdown",
164    "metadata": {},
165    "source": [
166     "c. Create a plugin instance for the specified target device  \n",
167     "d. Read the IR files and create an `IENEtwork` instance"
168    ]
169   },
170   {
171    "cell_type": "code",
172    "execution_count": null,
173    "metadata": {},
174    "outputs": [],
175    "source": [
176     "plugin = IEPlugin(TARGET_DEVICE)\n",
177     "net = IENetwork(model=MODEL_XML, weights=MODEL_BIN)"
178    ]
179   },
180   {
181    "cell_type": "markdown",
182    "metadata": {},
183    "source": [
184     "e. Set the network batch size to the constatns specified above. \n",
185     "\n",
186     "Batch size is an \"amount\" of input data that will be infered in parallel. In this cases it is a number of images, which will be classified in parallel. \n",
187     "\n",
188     "You can set the network batch size using one of the following options:\n",
189     "1. On the IR generation stage, run the Model Optimizer with `-b` command line option. For example, to generate the IR with batch size equal to 4, add `-b 4` to Model Optimizer command line options. By default, it takes the batch size from the original network in framework representation (usually, it is equal to 1, but in this case, the original Caffe model is provided with the batch size equal to 10). \n",
190     "2. Use Inference Engine after reading IR. We will use this option.\n",
191     "\n",
192     "To set the batch size with the Inference Engine:"
193    ]
194   },
195   {
196    "cell_type": "code",
197    "execution_count": null,
198    "metadata": {},
199    "outputs": [],
200    "source": [
201     "log.info(\"Current network batch size is {}, will be changed to {}\".format(net.batch_size, BATCH))\n",
202     "net.batch_size = BATCH"
203    ]
204   },
205   {
206    "cell_type": "markdown",
207    "metadata": {},
208    "source": [
209     "f. After setting batch size, you can get required information about network input layers.\n",
210     "To preprocess input images, you need to know input layer shape.\n",
211     "\n",
212     "`inputs` property of `IENetwork` returns the dicitonary with input layer names and `InputInfo` objects, which contain information about an input layer including its shape.\n",
213     "\n",
214     "SqueezeNet is a single-input toplogy, so to get the input layer name and its shape, you can get the first item from the `inputs` dictionary:"
215    ]
216   },
217   {
218    "cell_type": "code",
219    "execution_count": null,
220    "metadata": {},
221    "outputs": [],
222    "source": [
223     "input_layer = next(iter(net.inputs))\n",
224     "n,c,h,w = net.inputs[input_layer].shape\n",
225     "layout = net.inputs[input_layer].layout\n",
226     "log.info(\"Network input layer {} has shape {} and layout {}\".format(input_layer, (n,c,h,w), layout))"
227    ]
228   },
229   {
230    "cell_type": "markdown",
231    "metadata": {},
232    "source": [
233     "So what do the shape and layout mean?  \n",
234     "Layout will helps to interprete the shape dimsesnions meaning. \n",
235     "\n",
236     "`NCHW` input layer layout means:\n",
237     "* the fisrt dimension of an input data is a batch of **N** images processed in parallel \n",
238     "* the second dimension is a numnber of **C**hannels expected in the input images\n",
239     "* the third and the forth are a spatial dimensions - **H**eight and **W**idth of an input image\n",
240     "\n",
241     "Our shapes means that the network expects four 3-channel images running in parallel with size 227x227."
242    ]
243   },
244   {
245    "cell_type": "markdown",
246    "metadata": {},
247    "source": [
248     "g. Read and preprocess input images.\n",
249     "\n",
250     "For it, go to `IMAGES_FOLDER`, find all `.bmp` files, and take four images for inference:"
251    ]
252   },
253   {
254    "cell_type": "code",
255    "execution_count": null,
256    "metadata": {},
257    "outputs": [],
258    "source": [
259     "search_pattern = os.path.join(IMAGES_FOLDER, \"*.bmp\")\n",
260     "images = glob.glob(search_pattern)[:BATCH]\n",
261     "log.info(\"Input images:\\n {}\".format(\"\\n\".join(images)))"
262    ]
263   },
264   {
265    "cell_type": "markdown",
266    "metadata": {},
267    "source": [
268     "Now you can read and preprocess the image files and create an array with input blob data.\n",
269     "\n",
270     "For preprocessing, you must do the following:\n",
271     "1. Resize the images to fit the HxW input dimenstions.\n",
272     "2. Transpose the HWC layout.\n",
273     "\n",
274     "Transposing is tricky and not really obvious.\n",
275     "As you alredy saw above, the network has the `NCHW` layout, so each input image should be in `CHW` format. But by deafult, OpenCV\\* reads images in the `HWC` format. That is why you have to swap the axes using the `numpy.transpose()` function:"
276    ]
277   },
278   {
279    "cell_type": "code",
280    "execution_count": null,
281    "metadata": {},
282    "outputs": [],
283    "source": [
284     "input_data = np.ndarray(shape=(n, c, h, w))\n",
285     "orig_images = [] # Will be used to show image in notebook\n",
286     "for i, img in enumerate(images):\n",
287     "    image = cv2.imread(img)\n",
288     "    orig_images.append(image)\n",
289     "    if image.shape[:-1] != (h, w):\n",
290     "        log.warning(\"Image {} is resized from {} to {}\".format(img, image.shape[:-1], (h, w)))\n",
291     "        image = cv2.resize(image, (w, h))\n",
292     "    image = image.transpose((2, 0, 1))  # Change data layout from HWC to CHW\n",
293     "    input_data[i] = image"
294    ]
295   },
296   {
297    "cell_type": "markdown",
298    "metadata": {},
299    "source": [
300     "i. Infer the model model to classify input images:\n",
301     "\n",
302     "1. Load the `IENetwork` object to the plugin to create `ExectuableNEtwork` object.    \n",
303     "2. Start inference using the `infer()` function specifying dictionary with input layer name and prepared data as an argument for the function.     \n",
304     "3. Measure inference time in miliseconds and calculate throughput metric in frames-per-second (FPS)."
305    ]
306   },
307   {
308    "cell_type": "code",
309    "execution_count": null,
310    "metadata": {},
311    "outputs": [],
312    "source": [
313     "exec_net = plugin.load(net)\n",
314     "t0 = time()\n",
315     "res_map = exec_net.infer({input_layer: input_data})\n",
316     "inf_time = (time() - t0) * 1000 \n",
317     "fps = BATCH * inf_time \n",
318     "log.info(\"Inference time: {} ms.\".format(inf_time))\n",
319     "log.info(\"Throughput: {} fps.\".format(fps))"
320    ]
321   },
322   {
323    "cell_type": "markdown",
324    "metadata": {},
325    "source": [
326     "**4. After the inference, you need to parse and interpretate the inference results.**\n",
327     "\n",
328     "First, you need to see the shape of the network output layer. It can be done in similar way as for the inputs, but here you need to call `outputs` property of `IENetwork` object:"
329    ]
330   },
331   {
332    "cell_type": "code",
333    "execution_count": null,
334    "metadata": {},
335    "outputs": [],
336    "source": [
337     "output_layer = next(iter(net.outputs))\n",
338     "n,c,h,w = net.outputs[output_layer].shape\n",
339     "layout = net.outputs[output_layer].layout\n",
340     "log.info(\"Network output layer {} has shape {} and layout {}\".format(output_layer, (n,c,h,w), layout))"
341    ]
342   },
343   {
344    "cell_type": "markdown",
345    "metadata": {},
346    "source": [
347     "It is not a common case for classification netowrks to have output layer with *NCHW* layout. Usually, it is just *NC*. However, in this case, the last two dimensions are just a feature of the network and do not have much sense. Ignore them as you will remove  them on the final parsing stage. \n",
348     "\n",
349     "What are the first and second dimensions of the output layer?    \n",
350     "* The first dimension is a batch. We precoessed four images, and the prediction result for a particular image is stored in the first dimension of the output array. For example, prediction results for the third image is `res[2]` (since numeration starts from 0).\n",
351     "* The second dimension is an array with normalized probabilities (from 0 to 1) for each class. This network is trained using the <a href=\"http://image-net.org/index\">ImageNet</a> dataset with 1000 classes. Each `n`-th value in the output data for a certain image represent the probability of the image belonging to the `n`-th class. "
352    ]
353   },
354   {
355    "cell_type": "markdown",
356    "metadata": {},
357    "source": [
358     "To parse the output results:\n",
359     "\n",
360     "a. Read the `LABELS_FILE`, which maps the class ID to human-readable class names:"
361    ]
362   },
363   {
364    "cell_type": "code",
365    "execution_count": null,
366    "metadata": {},
367    "outputs": [],
368    "source": [
369     "with open(LABELS_FILE, 'r') as f:\n",
370     "    labels_map = [x.split(sep=' ', maxsplit=1)[-1].strip() for x in f]\n"
371    ]
372   },
373   {
374    "cell_type": "markdown",
375    "metadata": {},
376    "source": [
377     "b. Parse the output array with prediction results. The parsing algorith is the following:\n",
378     "0. Squeeze the last two \"extra\" dimensions of the output data.\n",
379     "1. Iterate over all batches.\n",
380     "2. Sort the probabilities vector descendingly to get `NTOP` classes with the highest probabilities (by default, the `numpy.argsort` sorts the data in the ascending order, but using the array slicing `[::-1]`, you can reverse the data order).\n",
381     "3. Map the `NTOP` probabilities to the corresponding labeles in `labeles_map`.\n",
382     "\n",
383     "For the vizualization, you also need to store top-1 class and probability."
384    ]
385   },
386   {
387    "cell_type": "code",
388    "execution_count": null,
389    "metadata": {},
390    "outputs": [],
391    "source": [
392     "top1_res = [] # will be used for the visualization\n",
393     "res = np.squeeze(res_map[output_layer])\n",
394     "log.info(\"Top {} results: \".format(NTOP))\n",
395     "for i, probs in enumerate(res):\n",
396     "    top_ind = np.argsort(probs)[-NTOP:][::-1]\n",
397     "    print(\"Image {}\".format(images[i]))\n",
398     "    top1_ind = top_ind[0]\n",
399     "    top1_res.append((labels_map[top1_ind], probs[top1_ind]))\n",
400     "    for id in top_ind:\n",
401     "        print(\"label: {}   probability: {:.2f}% \".format(labels_map[id], probs[id] * 100))\n",
402     "    print(\"\\n\")"
403    ]
404   },
405   {
406    "cell_type": "markdown",
407    "metadata": {},
408    "source": [
409     "The code above prints the results as plain text.   \n",
410     "You can also use OpenCV\\* to visualize the results using the `orig_images` and `top1_res` variables, which you created during images reading and results parsing:"
411    ]
412   },
413   {
414    "cell_type": "code",
415    "execution_count": null,
416    "metadata": {},
417    "outputs": [],
418    "source": [
419     "plt.clf()\n",
420     "for i, img in enumerate(orig_images):\n",
421     "    label_str = \"{}\".format(top1_res[i][0].split(',')[0])\n",
422     "    prob_str = \"{:.2f}%\".format(top1_res[i][1])\n",
423     "    cv2.putText(img, label_str, (5, 15), cv2.FONT_HERSHEY_COMPLEX, 0.6, (220,100,10), 1)\n",
424     "    cv2.putText(img, prob_str, (5, 35), cv2.FONT_HERSHEY_COMPLEX, 0.6, (220,100,10), 1)\n",
425     "    plt.figure()\n",
426     "    plt.axis(\"off\")\n",
427     "    \n",
428     "    # We have to convert colors, because matplotlib expects an image in RGB color format \n",
429     "    # but by default, the OpenCV read images in BRG format\n",
430     "    im_to_show = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n",
431     "    plt.imshow(im_to_show)"
432    ]
433   },
434   {
435    "cell_type": "code",
436    "execution_count": null,
437    "metadata": {},
438    "outputs": [],
439    "source": []
440   }
441  ],
442  "metadata": {
443   "kernelspec": {
444    "display_name": "Python 3",
445    "language": "python",
446    "name": "python3"
447   },
448   "language_info": {
449    "codemirror_mode": {
450     "name": "ipython",
451     "version": 3
452    },
453    "file_extension": ".py",
454    "mimetype": "text/x-python",
455    "name": "python",
456    "nbconvert_exporter": "python",
457    "pygments_lexer": "ipython3",
458    "version": "3.6.7"
459   }
460  },
461  "nbformat": 4,
462  "nbformat_minor": 2
463 }