Creating a simple machine learning demo with GradioML

Deploy a model in a functional user interface with just a few lines of code

David Yoo
Towards Data Science

--

Image by Author

Deploying a machine learning model is sometimes an overlooked aspect of data science. Many analytics professionals focus a majority of their attention on theory and application to build models that can solve problems and satisfy objectives. This is completely fine at either the research or prototyping stage of the project. But in order to share this work with either stakeholders or collaborators of other occupation, it would be nice to develop some sort of an application for these users and viewers.

However, the task of deploying these models is not always so simple. There are many additional skills and tools to master to get this job done. Traditionally, experts such as machine learning engineers and DevOps specialists collaborate with data scientists to put these models into production.

Instead of going full-out on this deployment effort, there are times when a simple user interface demo may get the job done of communicating the contents of the machine learning model. While searching for a tool that can help build this, I stumbled upon GradioML, an open source tool that fits this description.

Gradio is a package that allows users to create simple web apps with just a few lines of code. It is essentially used for the same purpose as Streamlight and Flask but is much simpler to utilize. Many types of web interface tools can be selected including sketchpad, text boxes, file upload buttons, webcam, etc. Using these tools to receive various types of data as input, machine learning tasks such as classification and regression can easily be demoed. (for reference, see the official website for the tool and its Github page)

In this walkthrough, we will quickly create a cat/dog image classification model and deploy a simple Gradio demo where we can upload new images for class prediction. The model will be a simple Keras convolutional neural network (CNN) that would be trained on images of cats and dogs as features and their class names as labels.

For the full Jupyter Notebook with code, please refer to this link

Essentials

First we pip install Gradio

pip install gradio

Next we import our dependencies. Some tools we need would be for numerical computing (Numpy), visualization/plotting (Matplotlib), image data wrangling (cv2), deep learning (Tensorflow, Keras), and of course, building web interfaces (Gradio)

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import os
import cv2
from sklearn.model_selection import train_test_split
from sklearn import metrics
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Dropout, Activation, Flatten, Conv2D, MaxPooling2D
import gradio
import gradio as gr

Explore and organize data

We then take a look at our data. The cat/dog images are from a dataset source posted in Kaggle. We will check out the first image

DATADIR = os.path.abspath(os.getcwd()) + '/PetImages'path = os.path.join(DATADIR, category)    #path to cats or dogs dir
first_img_path = os.listdir(path)[0]
img_array = cv2.imread(os.path.join(path, first_img_path), cv2.IMREAD_GRAYSCALE)
plt.imshow(img_array, cmap = "gray")
plt.show()
#show image shape
print('The image shape is {}'.format(img_array.shape))
Image by Author

Because this exercise will be for a simple CNN model, we will resize all images to shape (100, 100) and change their colors to grayscale.

Features and labels will also be separated and added to lists X and y respectively

#create create array of data
data = []
def create_data():
for category in CATEGORIES:
path = os.path.join(DATADIR, category)
class_num = CATEGORIES.index(category)
for img in os.listdir(path):
try:
img_array = cv2.imread(os.path.join(path, img), cv2.IMREAD_GRAYSCALE)
new_array = cv2.resize(img_array, (IMG_SIZE, IMG_SIZE))
data.append([new_array, class_num])
except Exception as e:
pass
create_data()
#randomly shuffle the images
random.shuffle(data)
#separate features and labels
X = []
y = []
for features, label in data:
X.append(features)
y.append(label)
#neural network takes in a numpy array as the features and labels so convert from list to array and change shape
X = np.array(X).reshape(-1, IMG_SIZE, IMG_SIZE, 1)
y = np.array(y)

an example of a resized feature image is shown below

#show first feature image X
first_feature = X[0]
plt.imshow(first_feature, cmap = 'gray')
print('The image shape is {}'.format(first_feature.shape))
Image by Author

Finally, we normalize the images to ensure that each pixels have similar data distribution (allows faster convergence while training the convolutional neural network model)

#normalize images
X = X/255.0

Modeling

Here we separate our training (70%) and test (30%) data. There would be a total of 17462 training and 7484 test data.

#separate training and test data
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
print('the shape of training features is {}'.format(X_train.shape))
print('the shape of training labels is {}'.format(y_train.shape))
print('the shape of test features is {}'.format(X_test.shape))
print('the shape of test labels is {}'.format(y_test.shape))
Image by Author

We now create our Convolutional Neural Network (CNN) model. It will be composed of multiple convolutional layers, max pooling layers, and dropout layers. All activation functions in the convolutional layers will be relu while the activation function for the output dense layer will be softmax.

#create model
model = Sequential()
model.add(Conv2D(64, (3,3), activation = 'relu'))
model.add(MaxPooling2D(2,2))
model.add(Dropout(0.1))
model.add(Conv2D(64, (3,3), activation = 'relu'))
model.add(MaxPooling2D(2,2))
model.add(Dropout(0.2))
model.add(Conv2D(64, (3,3), activation = 'relu'))
model.add(MaxPooling2D(2,2))
model.add(Dropout(0.2))
model.add(Flatten())model.add(Dense(128, input_shape = X.shape[1:], activation = 'relu'))#output layer
model.add(Dense(2, activation = 'softmax'))

compile the model with the following parameters:

#compile the model
model.compile(loss="sparse_categorical_crossentropy",
optimizer="adam",
metrics=['accuracy'])

train the model with 5 epochs and validation split of 0.1

#fit model
history = model.fit(X_train, y_train, epochs=5, validation_split=0.1)
Training the model with 5 epochs: Image by Author
#show learning curves
#mean training loss and accuracy measured over each epoch
#mean validation loss and accuracy measured at the end of each epoch
pd.DataFrame(history.history).plot(figsize=(8,5))
plt.grid(True)
plt.gca().set_ylim(0,1) # set the vertical range to [0-1]
plt.show()
Learning Curve; Training vs. Validation: Image by Author

The model does not seem to overfit with the training data as there is not a sizable gap between training and validation accuracy/loss. The validation accuracy is slightly over the training accuracy which may happen when using dropout layers.

We predict classes of the test data and compare how accurate we were

#use predict_classes() to find the class with the highest probability
y_pred = model.predict_classes(X_test)
print("Performance Summary of Sequential Neural Network on test data:")#show classification report
print(metrics.classification_report(y_test, y_pred))
#show confusion matrix
print(metrics.confusion_matrix(y_test, y_pred))
Classification Report and Confusion Matrix: Image by Author

While not amazing, at an overall accuracy of 83% on the test data, this is a fairly decent model

We can quickly scan for the first 5 correct and incorrect classifications

#show first 5 correctly identified test images with predicted labels and probabilities
fig, ax = plt.subplots(1,5,figsize=(20,20))
class_names = ["Dog", "Cat"]for i, correct_idx in enumerate(correct_indices[:5]):
ax[i].imshow(X_test[correct_idx].reshape(100,100),cmap='gray')
ax[i].set_title("{} with probabililty of {}%".format(class_names[y_pred[correct_idx]], int(max(y_proba[correct_idx]
First 5 correctly identified images: Image by Author
#show first 5 incorrectly identified test images with predicted labels and probabilities
fig, ax = plt.subplots(1,5,figsize=(20,20))
for i, incorrect_idx in enumerate(incorrect_indices[:5]):
ax[i].imshow(X_test[incorrect_idx].reshape(100,100),cmap='gray')
ax[i].set_title("{} with probabililty of {}%".format(class_names[y_pred[incorrect_idx]], int(max(y_proba[incorrect_idx])*100)))
First 5 incorrectly identified images: Image by Author

It is interesting to note that the model has correctly identified some images that are difficult for even humans to see. However, it has also incorrectly identified some obvious cat and dog pictures.

Model demo using Gradio

Finally, we are ready use Gradio to create a demo of our model. Once again, the purpose is to use the model to predict cat or dog class on the new uploaded input image

#create a function to make predictions
#return a dictionary of labels and probabilities
def cat_or_dog(img):
img = img.reshape(1, 100, 100, 1)
prediction = model.predict(img).tolist()[0]
class_names = ["Dog", "Cat"]
return {class_names[i]: prediction[i] for i in range(2)}
#set the user uploaded image as the input array
#match same shape as the input shape in the model
im = gradio.inputs.Image(shape=(100, 100), image_mode='L', invert_colors=False, source="upload")
#setup the interface
iface = gr.Interface(
fn = cat_or_dog,
inputs = im,
outputs = gradio.outputs.Label(),
)
iface.launch(share=True)

The following is happening in this code:

  1. we first create a function called cat_or_dog() which takes an image array as an input, utilizes the model to make predictions, and returns a dictionary with each class name as the key and its respective probability as the value (e.g. {Dog: 0.6, Cat: 0.4})
  2. Set up the input upload: transform the input image to match the input shape which the model was trained on, which is (100, 100) in our case. We also set the source to “upload” so that we may upload our own images into Gradio
  3. Compile the interface; utilize the cat_or_dog() function that we created above, set input as the input uploader, and allow Gradio to return the class and their probabilities as the output

The result is shown in the gif below:

Gradio demo for image classification: Image by Author

Users can easily drop or upload images from their local and click ‘submit’ to show output of the model classification. The class (cat or dog) with the higher probability will be shown as the final prediction.

Conclusion

In this exercise, an image classification model using CNN was built and deployed as a simple demo using a tool called GradioML. However, Gradio can be used for so much more tasks such as regression/numerical prediction and natural language processing. With various interface options that are appropriate for these tasks, Gradio is a tool that can serve as a quick and efficient solution for presenting a quick machine learning demo.

--

--