Demystifying AI/ML Microservice with TensorFlow
Join the DZone community and get the full member experience.
Join For FreeOverview
This tutorial will walk you through how to build and deploy a sample microservice application using Red Hat OpenShift Container Platform. The scope of this service is to perform prediction of handwritten digit from 0 to 9 by accepting image pixel array as a parameter. This sample application also provides a HTML file that offer you a canvas to draw your number and convert it into an image pixel array. The intention of this article is to give you a high level idea, the same concept can be taken to the next level for addressing many other complex use cases.
Image recognition is one of the major capabilities of deep learning. In this article, we will be identifying our own hand written digit. However, in order to accurately predict what digit is it, learning has to be performed, so that it understands the different characteristics of each digit as well as the subtle variations in writing the same digit. Thus, we need to train the model with a dataset of labelled handwritten digit. This is where MNIST dataset comes handy.

MNIST dataset is comprised of 60,000 small 28x28 square pixel gray scale images and 10,000 test images. These are handwritten single digit images from 0 to 9. Instead of downloading it manually, we can download it using Tensorflow Keras API
We will be performing following steps in this exercise
- Build and train the model using MNIST dataset
- Saving the learned model in persistent storage
- Build and launch microservice for prediction using the provided dockerfile
- Generate image pixel array of your handwritten digit using the provided HTML file
- Perform prediction using microservice by providing image pixel array as a parameter to this service.
You can find the dockerfile and python code by clicking the following git repository.
https://github.com/mafzal786/tensorflow-microservice.git
Model Training
Below is the python code that performs building, compiling, training, and saving the model based on MNIST dataset. This code can also be executed in Jupyter Notebook. It is important to save this model in a persistent storage once the model training is successfully completed so that it can be used by the microservice application for prediction. Also make sure that saved model file should be accessible by the container running the microservice.
import tensorflow as tf
print('Tensorflow version:' + tf.__version__)
import numpy as np
from tensorflow.keras.datasets import mnist
(X_train, y_train), (X_test, y_test) = mnist.load_data()
input_shape = [28, 28, 1]
X_train = tf.reshape(X_train, [X_train.shape[0]] + input_shape)
X_test = tf.reshape(X_test, [X_test.shape[0]] + input_shape)
X_train = tf.cast(X_train, dtype=tf.float32)
X_test = tf.cast(X_test, dtype=tf.float32)
X_train /= 255
X_test /= 255
print(X_train.shape)
print(X_test.shape)
from sklearn.preprocessing import OneHotEncoder
# Reshape so labels can be onehot encoded.
y_train = tf.reshape(y_train, [-1, 1])
y_test = tf.reshape(y_test, [-1, 1])
# Set sparse to False in order to return an array.
encoder = OneHotEncoder(sparse=False)
y_train = tf.convert_to_tensor(encoder.fit_transform(y_train))
y_test = tf.convert_to_tensor(encoder.fit_transform(y_test))
model = tf.keras.Sequential([
tf.keras.layers.Conv2D(32, (3, 3), activation='relu', input_shape=input_shape),
tf.keras.layers.Conv2D(64, (3, 3), activation='relu'),
tf.keras.layers.MaxPooling2D(pool_size=(2, 2)),
tf.keras.layers.Dropout(0.25) ,
tf.keras.layers.Flatten(),
tf.keras.layers.Dense(128, activation='relu'),
tf.keras.layers.Dropout(0.5) ,
tf.keras.layers.Dense(10, activation='softmax')
])
epoch=10
batchsize=32
loss_fn = tf.keras.losses.CategoricalCrossentropy(from_logits=True)
optimizer = tf.keras.optimizers.Adam()
model.compile(optimizer=optimizer, loss=loss_fn, metrics=['accuracy'])
# Train our model!
h = model.fit(x=X_train, y=y_train, epochs=epoch, validation_data=(X_test, y_test), batch_size=batchsize)
score = model.evaluate(X_test, y_test, verbose=0)
print('Test loss:', score[0])
print('Test accuracy:', score[1])
# save your model
model.save('model.h5')
Prediction Microservice With Flask
Following instructions will help you with building the microservice using Red Hat OpenShift Container Platform. For the sake of simplicity, it’s a single source file and written in python. This source file is copied inside the container image via Dockerfile during the container image build process, which will be discussed later in this article. The sole purposes of this microservice to is to predict the handwritten digit using the already learned model. It simply takes the image pixel array as a parameter and predict it.
xxxxxxxxxx
import os
import numpy as np
from flask import Flask
from flask import request
from tensorflow.keras.models import load_model
app = Flask(__name__)
route("/") .
def index():
var_html = "<h1>Tensorflow handwritten digit prediction microservice. </h1><br> Usage: <url>/predict?pixelarray=0,0,0,0,0,0,0.03137254901960784,0.5764705882352941,1,1,1,1,1,1,0.17254901960784313,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.011764705882352941,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0.984313725490196,0.07450980392156863,0,0,0,0,0,0,0,0,0,0,1,1,1,0.6196078431372549,0,0,0,0,0,0,0,0,0,0,0,0.8980392156862745,1,1,1,1,0,0,0,0,0,0,0,0,0.48627450980392156,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0.8470588235294118,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.0784313725490196,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.07058823529411765,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.7098039215686275,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.7568627450980392,1,0.7843137254901961,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0.3176470588235294,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.4470588235294118,1,1,1,0,0,0,0,0,0,0,0,0,0.8980392156862745,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0.03137254901960784,0,0,0,0,0,0,0,0,0,0,0,0,0,0.06274509803921569,0.07058823529411765,0.07058823529411765,0.803921568627451,1,1,0.8941176470588236,0.07058823529411765,0.06666666666666667,0,0,0,0.0392156862745098,1,1,1,0.5098039215686274,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.9254901960784314,1,0.6901960784313725,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.5254901960784314,1,0.07450980392156863,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0.9254901960784314,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.3803921568627451,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0.24705882352941178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.34509803921568627,1,0,0,0,0,0,0.9803921568627451,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,1,0.3686274509803922,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0.9882352941176471,1,1,1,1,0.6784313725490196,0.08235294117647059,0,0,0,0,0,0.4235294117647059,1,1,1,1,1,1,1,0.3333333333333333,0,0,0,0,0,0,0,0,0,0.10588235294117647,0.8117647058823529,1,1,1,1,1,1,1,1,1,1,1,1,0.21568627450980393,0.12941176470588237,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"
return var_html
route("/predict") .
def predict():
pixelarray = request.args.get('pixelarray')
arr_digit=pixelarray.split(',')
arr32_digit = np.array(arr_digit,dtype='float32')
model = load_model("model.h5")
predict_img = arr32_digit.reshape(1,28,28,1)
prediction = model.predict(predict_img)
response = "<h1>Your handwritten digit is: {}</h1>".format(prediction.argmax())
return response
if __name__ == "__main__":
port = int(os.environ.get("PORT", 8080))
app.run(host='0.0.0.0', port=port)
Dockerfile
Below is the dockerfile that will be used to initiate the image build and launch the container in OpenShift platform. requirements.txt file contains the pre-requisite packages for this microservice to work. requirements.txt file contains the following.
xxxxxxxxxx
Flask==0.12.1
tensorflow==2.3.0
scikit-learn==0.22.1
xxxxxxxxxx
# ARG ubuntu_version=16.04
#FROM ubuntu:${ubuntu_version}
#Use ubuntu 16:04 as your base image
FROM ubuntu:16.04
#Any label to recognise this image.
#install the below packages on the ubuntu image
RUN apt-get update -y && apt-get install -y gnupg2 wget openjdk-8-jre python3-pip python3-dev \
&& cd /usr/local/bin \
&& ln -s /usr/bin/python3 python \
&& pip3 install --upgrade pip setuptools
COPY ./requirements.txt app/requirements.txt
WORKDIR /app
# install flask, tensorflow, scikit-learn, pyspark
RUN pip3 install -r requirements.txt
EXPOSE 8080
COPY . /app
# if execute the trianing inside this same container
# RUN python training.py
ENTRYPOINT [ "python3" ]
CMD [ "app.py" ]
Launch the Microservice
Now login to your Red Hat OpenShift cluster. Click Developer and then click Add to create an application. Click “From Dockerfile”. This will import your dockerfile from your git repo and initiate the image build and deploy the container.
Supply the Git Repo URL. The dockerfile for this project is located at https://github.com/mafzal786/tensorflow-microservice.git. Also give name to your application. Click create.
This will create the build config and build will start. To view the logs for your build, click “Builds” under Administrator tab and click the build as shown below. Once the build is completed, container image is pushed to your configured image registry.
After the build is completed and image is pushed to the registry, OpenShift will launch the container.
OpenShift will create a route for this service to be exposed by giving an externally reachable hostname. Service end point will be available in Networking →Routes tab. Clicking on the Location as shown below will launch the microservice in the browser.
The below figure shows when the microservice is launched in the browser.
Or run the following CLI as below
xxxxxxxxxx
oc get routes/ms-handwritten-digit-prediction-git
xxxxxxxxxx
curl http://`oc get routes/ms-handwritten-digit-prediction-git — template=’{{.spec.host}}’`
Canvas for Writing Digits
Below html file offers you a canvas to draw digit. Copy below code and save it as html on your computer and then launch it in the browser. Click “Convert” button to convert the image into image pixel array. Then click “Copy Image Pixel Array in Clipboard” button to copy the pixel array in your clipboard for providing it to the microservice as a parameter for prediction.
xxxxxxxxxx
<html>
<head>
</head>
<body onload="prepareSimpleCanvas();">
<div class="row" style="padding-bottom: 5px">
<div class="col-md-1"></div>
<div class="col-md-2">
<label for="comment">Draw you Number:</label><br>
<div class="form-group" id="canvasSimpleDiv"></div>
<div>
<button class="btn btn-default btn-xs" id="clearCanvas">Clear</button>
<button class="btn btn-default btn-xs btn-primary" id="convertToArray">Convert</button>
</div>
</div>
<div class="col-md-8">
<div class="form-group"><br>
<label for="comment">Digit Image Pixel Array:</label><br>
<textarea readonly id="array" class="form-control" rows="5" style="width:500px;"></textarea>
<br>
<button class="btn btn-default btn-xs" onclick="copyToClipboard('array')">Copy Image Pixel Array in Clipboard</button>
</div>
</div>
<div class="col-md-1"></div>
</div>
<code>Note: If you cannot draw anything on canvas, make change in the code (for example, add space) and rerun it!</code>
</body>
</html>
Draw Your Number
Perform Prediction Using the Microservice
Now pass this image pixel array to the microservice as a parameter as show below.
xxxxxxxxxx
# curl http://`oc get routes/ms-handwritten-digit-prediction-git — template=’{{.spec.host}}’`/predict?pixelarray=0,0,0,0,0,0,0.03137254901960784,0.5764705882352941,1,1,1,1,1,1,0.17254901960784313,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.011764705882352941,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0.984313725490196,0.07450980392156863,0,0,0,0,0,0,0,0,0,0,1,1,1,0.6196078431372549,0,0,0,0,0,0,0,0,0,0,0,0.8980392156862745,1,1,1,1,0,0,0,0,0,0,0,0,0.48627450980392156,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0.8470588235294118,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.0784313725490196,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.07058823529411765,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.7098039215686275,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.7568627450980392,1,0.7843137254901961,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0.3176470588235294,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.4470588235294118,1,1,1,0,0,0,0,0,0,0,0,0,0.8980392156862745,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0.03137254901960784,0,0,0,0,0,0,0,0,0,0,0,0,0,0.06274509803921569,0.07058823529411765,0.07058823529411765,0.803921568627451,1,1,0.8941176470588236,0.07058823529411765,0.06666666666666667,0,0,0,0.0392156862745098,1,1,1,0.5098039215686274,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.9254901960784314,1,0.6901960784313725,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.5254901960784314,1,0.07450980392156863,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0.9254901960784314,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.3803921568627451,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0.24705882352941178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.34509803921568627,1,0,0,0,0,0,0.9803921568627451,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,1,0.3686274509803922,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0.9882352941176471,1,1,1,1,0.6784313725490196,0.08235294117647059,0,0,0,0,0,0.4235294117647059,1,1,1,1,1,1,1,0.3333333333333333,0,0,0,0,0,0,0,0,0,0.10588235294117647,0.8117647058823529,1,1,1,1,1,1,1,1,1,1,1,1,0.21568627450980393,0.12941176470588237,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
<h1>Your handwritten digit is: 3</h1>
You can run the same in the browser as follows.
Published at DZone with permission of Muhammad Afzal. See the original article here.
Opinions expressed by DZone contributors are their own.
Trending
-
How to LINQ Between Java and SQL With JPAStreamer
-
Extending Java APIs: Add Missing Features Without the Hassle
-
Auditing Tools for Kubernetes
-
The SPACE Framework for Developer Productivity
Comments