Building a TensorFlow-based Image Classifier

Let's train an image classifier using Tensorflow and deploy it in the Qwak platform.

First, we will need to create an empty Qwak project using the qwak models init command. If you are not familiar with it, see Getting Started with Qwak.

Defining Dependencies

After creating the empty project, we specify the dependencies. In this tutorial, we will use pip. We remove the default conda.yml file and create the requirements.txt file. In the requirements.txt file, we put the following dependencies:

pandas
tensorflow
keras
imageio==2.9.0
numpy

📘

Note

We use 3.9.7. If you want a different Python version, you have to use conda or poetry dependency management.

Training the Model

We will train the model using the Fashion-MNIST dataset. We will also log the model parameters and the evaluation metrics in the process. We will see them later in Qwak UI.

We will also use the ImageInputAdapter to send the images directly to the endpoint without converting them into arrays of numbers in the client that uses the model.

First, we have to define the imports:

import pandas as pd
import numpy as np
import qwak
import tensorflow as tf
from qwak.model.base import QwakModelInterface
from qwak.model.experiment_tracking import log_metric, log_param
from qwak.model.adapters import ImageInputAdapter

Now, we can start implementing the build function.

Let's download the dataset and preprocess the data:

fashion_mnist = tf.keras.datasets.fashion_mnist
(train_images, train_labels), (test_images, test_labels) = fashion_mnist.load_data()

train_images = train_images / 255.0
test_images = test_images / 255.0

We must define the parameters of the hidden layer used in the model. We will also log them to track the experiment parameters:

layer_size = 128
activation_function = 'relu'

log_param({'layer_size': layer_size, 'activation_function': activation_function})

We have everything we need to train the model, so let's start training it:

model = tf.keras.Sequential([
    tf.keras.layers.Flatten(input_shape=(28, 28)),
    tf.keras.layers.Dense(layer_size, activation=activation_function),
    tf.keras.layers.Dense(10)
])

model.compile(
    optimizer='adam',
    loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
    metrics=['accuracy']
)

model.fit(train_images, train_labels, epochs=10)

After training, we will use the test set to evaluate the model's performance, and we will log the evaluation metric in the Qwak platform:

test_loss, test_acc = model.evaluate(test_images,  test_labels, verbose=2)

log_metric({'test_acc': test_acc, 'test_loss': test_loss})

We want to replace the model output with probabilities, so we should add one more layer to the trained model:

model = tf.keras.Sequential([model, tf.keras.layers.Softmax()])

Finally, we have to serialize the model to make it available during the inference. Qwak automatically serializes all fields of your QwakModelInterface implementation. Therefore, it is sufficient to store the model in the model field:

self.probability_model = model

Inference

To use the model, we must implement the predict function. However, in this case, we want to send the entire image to the model, not individual features of the input layer. Our model will convert the image into those features automatically.

To do so, we have to define an input adapter for the predict function:

@qwak.api(analytics=False, input_adapter=ImageInputAdapter())
    def predict(self, input_data) -> pd.DataFrame:

In the predict function, we have access to the array of pixel values.
We trained the model using grayscale images, so we must convert every input image to grayscale.

Let's define a helper function first:

def rgb2gray(rgb):
   return np.dot(rgb[...,:3], [0.2989, 0.5870, 0.1140])

Now, we can loop over the input images (or you can use the map function to apply the transformations to all pictures of the input array):

result = []

for image in input_data:
    gray = rgb2gray(image)
    gray = gray / 255.0

    prediction_input = (np.expand_dims(gray, 0))
    prediction = self.probability_model.predict(prediction_input)
    result.append(prediction[0])
        
return pd.DataFrame(result)

Using the Model

In this tutorial, we will show how to get a prediction from the model using curl.

First, we must get an access token from the authentication service:

curl --location ‘https://grpc.qwak.ai/api/v1/authentication/qwak-api-key’ --request POST --header ‘Content-Type: application/json’ --data ‘{“qwakApiKey”: “YOUR_API_KEY”}’

In the response, we receive the authentication token valid for 24 hours. We will include the token in every subsequent API call.

To access the model using REST API, we need two information: the name of the Qwak environment running the model and the model name. When we have it, we can build the URL like this:

https://models.ENVIRONMENT_NAME.qwak.ai/v1/MODEL_NAME/predict

Now, we can send the image to the model:

curl -v --location https://models.ENVIRONMENT_NAME.qwak.ai/v1/MODEL_NAME/predict --request POST --header 'Content-Type: image/jpeg' --data-binary @fashion_mnist_28_28.jpg --header 'Authorization: Bearer ACCESS_TOKEN

Our model returns an entire array of values indicating the category of the item in the picture:

[{“0”:0.0,“1":0.0,“2”:1.0,“3":0.0,“4”:0.0,“5":0.0,“6”:0.0,“7":0.0,“8”:0.0,“9":0.0}]

Of course, we could modify the predict function to get a category name instead. You can use it to run any post-processing code that makes sense in your use case.

Experiment Tracking in the Qwak UI

Because we have logged the model parameters and the evaluation metrics, we can see them in the model's Build view: