Creating a simple machine learning demo with GradioML | by David Yoo | Towards Data Science

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
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 npimport pandas as pdimport matplotlib.pyplot as pltimport osimport cv2from sklearn.model_selection import train_test_splitfrom sklearn import metricsimport tensorflow as tffrom tensorflow import kerasfrom tensorflow.keras.models import Sequentialfrom tensorflow.keras.layers import Dense, Dropout, Activation, Flatten, Conv2D, MaxPooling2Dimport gradioimport gradio as gr
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 dirfirst_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 shapeprint('The image shape is {}'.format(img_array.shape))
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 datadata = []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:                passcreate_data()#randomly shuffle the imagesrandom.shuffle(data)#separate features and labelsX = []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 shapeX = 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 Xfirst_feature = X[0]plt.imshow(first_feature, cmap = 'gray')print('The image shape is {}'.format(first_feature.shape))
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 imagesX = X/255.0
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 dataX_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))
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 modelmodel = 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 layermodel.add(Dense(2, activation = 'softmax'))
compile the model with the following parameters:
#compile the modelmodel.compile(loss="sparse_categorical_crossentropy",             optimizer="adam",             metrics=['accuracy'])
train the model with 5 epochs and validation split of 0.1
#fit modelhistory = model.fit(X_train, y_train, epochs=5, validation_split=0.1)
#show learning curves#mean training loss and accuracy measured over each epoch#mean validation loss and accuracy measured at the end of each epochpd.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()
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 probabilityy_pred = model.predict_classes(X_test)print("Performance Summary of Sequential Neural Network on test data:")#show classification reportprint(metrics.classification_report(y_test, y_pred))#show confusion matrixprint(metrics.confusion_matrix(y_test, y_pred))
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 probabilitiesfig, 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]
#show first 5 incorrectly identified test images with predicted labels and probabilitiesfig, 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)))
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.
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 probabilitiesdef 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 modelim = gradio.inputs.Image(shape=(100, 100), image_mode='L', invert_colors=False, source="upload")#setup the interfaceiface = gr.Interface(    fn = cat_or_dog,     inputs = im,     outputs = gradio.outputs.Label(),)iface.launch(share=True)
The following is happening in this code:
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})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 GradioCompile 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
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})
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
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:
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.
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.