How to Deploy an NLP Model with FastAPI

 If you're working with Natural Language Processing, knowing how to deploy a model is one of the most important skills you'll need to have.

Model deployment is the process of integrating your model into an existing production environment. The model will receive input and predict an output for decision-making for a specific use case.

“Only when a model is fully integrated with the business systems, we can extract real value from its predictions”. — Christopher Samiullah

There are different ways you can deploy your NLP model into production, like using Flask, Django, Bottle or other frameworks. But in today’s article, you will learn how to build and deploy your NLP model with FastAPI.

In this article, you will learn:

  • How to build a NLP model that classifies IMDB movie reviews into different sentiments.
  • What is FastAPI and how to install it.
  • How to deploy your model with FastAPI.
  • How to use your deployed NLP model in any Python application.

So let’s get started.🚀

How to Build an NLP Model

First, we need to build our NLP model. We are going to use the IMDB Movie dataset to build a simple model that can classify if a movie review is positive or negative. Here are the steps you should follow to do that.

Import the Important packages

First, we need to import some Python packages to load the data, clean the data, create a machine learning model (classifier), and save the model for deployment.

# import important modules
import numpy as np
import pandas as pd
# sklearn modules
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.naive_bayes import MultinomialNB # classifier 
from sklearn.metrics import (
    accuracy_score,
    classification_report,
    plot_confusion_matrix,
)
from sklearn.feature_extraction.text import TfidfVectorizer, CountVectorizer
# text preprocessing modules
from string import punctuation 
# text preprocessing modules
from nltk.tokenize import word_tokenize
import nltk
from nltk.corpus import stopwords
from nltk.stem import WordNetLemmatizer 
import re #regular expression
# Download dependency
for dependency in (
    "brown",
    "names",
    "wordnet",
    "averaged_perceptron_tagger",
    "universal_tagset",
):
    nltk.download(dependency)
    
import warnings
warnings.filterwarnings("ignore")
# seeding
np.random.seed(123

Load the dataset from the data folder:

# load data
data = pd.read_csv("../data/labeledTrainData.tsv", sep='\t')

And then show a sample of the dataset:

# show top five rows of data
data.head()

Our dataset has 3 columns:

  • Id — This is the id of the review
  • Sentiment — either positive (1) or negative (0)
  • Review — comment about the movie

Next, let's check the shape of the dataset:

# check the shape of the data
data.shape

(25000, 3)

The dataset has 25,000 reviews.

Now we need to check if the dataset has any missing values:

# check missing values in data
data.isnull().sum()

id 0
sentiment 0
review 0
dtype: int64

The output shows that our dataset does not have any missing values.

How to Evaluate Class Distribution

We can use the value_counts() method from the Pandas package to evaluate the class distribution from our dataset.

# evalute news sentiment distribution
data.sentiment.value_counts()

1 12500
0 12500
Name: sentiment, dtype: int64

In this dataset, we have an equal number of positive and negative reviews.

How to Process the Data

After analyzing the dataset, the next step is to preprocess the dataset into the right format before creating our machine learning model.

The reviews in this dataset contain a lot of unnecessary words and characters that we don’t need when creating a machine learning model.

We will clean the messages by removing stopwords, numbers, and punctuation. Then we will convert each word into its base form by using the lemmatization process in the NLTK package.

The text_cleaning() function will handle all necessary steps to clean our dataset.

stop_words =  stopwords.words('english')
def text_cleaning(text, remove_stop_words=True, lemmatize_words=True):
    # Clean the text, with the option to remove stop_words and to lemmatize word
    # Clean the text
    text = re.sub(r"[^A-Za-z0-9]", " ", text)
    text = re.sub(r"\'s", " ", text)
    text =  re.sub(r'http\S+',' link ', text)
    text = re.sub(r'\b\d+(?:\.\d+)?\s+', '', text) # remove numbers
        
    # Remove punctuation from text
    text = ''.join([c for c in text if c not in punctuation])
    
    # Optionally, remove stop words
    if remove_stop_words:
        text = text.split()
        text = [w for w in text if not w in stop_words]
        text = " ".join(text)
    
    # Optionally, shorten words to their stems
    if lemmatize_words:
        text = text.split()
        lemmatizer = WordNetLemmatizer() 
        lemmatized_words = [lemmatizer.lemmatize(word) for word in text]
        text = " ".join(lemmatized_words)
    
    # Return a list of words
    return(text)

Now we can clean our dataset by using the text_cleaning() function:

#clean the review
data["cleaned_review"] = data["review"].apply(text_cleaning)

Then split data into feature and target variables like this:

#split features and target from  data 
X = data["cleaned_review"]
y = data.sentiment.values

Our feature for training is the cleaned_review variable and the target is the sentiment variable.

We then split our dataset into train and test data. The test size is 15% of the entire dataset.

# split data into train and validate
X_train, X_valid, y_train, y_valid = train_test_split(
    X,
    y,
    test_size=0.15,
    random_state=42,
    shuffle=True,
    stratify=y,
)

How to Create an NLP Model

We will train the Multinomial Naive Bayes algorithm to classify if a review is positive or negative. This is one of the most common algorithms used for text classification.

But before training the model, we need to transform our cleaned reviews into numerical values so that the model can understand the data.

In this case, we will use the TfidfVectorizer method from scikit-learn. TfidfVectorizer will help us to convert a collection of text documents to a matrix of TF-IDF features.

To apply this series of steps (pre-processing and training), we will use a Pipeline class from scikit-learn that sequentially applies a list of transforms and a final estimator.

# Create a classifier in pipeline
sentiment_classifier = Pipeline(steps=[
                               ('pre_processing',TfidfVectorizer(lowercase=False)),
                                 ('naive_bayes',MultinomialNB())
                                 ])

Then we train our classifier like this:

# train the sentiment classifier 
sentiment_classifier.fit(X_train,y_train)

We then create a prediction from the validation set:

# test model performance on valid data 
y_preds = sentiment_classifier.predict(X_valid)

The model’s performance will be evaluated by using the accuracy_score evaluation metric. We use accuracy_score because we have an equal number of classes in the sentiment variable.

accuracy_score(y_valid,y_preds)

0.8629333333333333

The accuracy of our model is around 86.29% which is good performance.

How to Save the Model Pipeline

We can save the model pipeline in the model’s directory by using the joblib Python package.

#save model 
import joblib 
joblib.dump(sentiment_classifier, '../models/sentiment_model_pipeline.pkl')

Now that we've built our NLP model, let's learn how to use FastAPI.

What is FastAPI?

FastAPI is a fast and modern Python web framework for building different APIs. It provides higher performance, it's easier to code, and it comes up with automatic and interactive documentation.

FastAPI is built upon two major Python libraries — Starlette (for web handling) and Pydantic (for data handling and validation). FastAPI is very fast compared to Flask because it brings asynchronous function handlers to the table.

If you want to know more about FastAPI, I recommend you read this article by Sebastián Ramírez.

In this article, we will try to use some of the features FastAPI has to serve our NLP model.

How to Install FastAPI

Firstly, make sure you install the latest version of FastAPI (with pip):

pip install fastapi

You will also need an ASGI server for production such as uvicorn.

pip install uvicorn

How to Deploy an NLP Model with FastAPI

In this section, we are going to deploy our trained NLP model as a REST API with FastAPI. We'll save the code for our API in a Python file called main.py. This file will be responsible for running our FastAPI app.

Import packages

The first step is to import packages that will help us build the FastAPI app and run the NLP model.

# text preprocessing modules
from string import punctuation 
# text preprocessing modules
from nltk.tokenize import word_tokenize
import nltk
from nltk.corpus import stopwords
from nltk.stem import WordNetLemmatizer
import re  # regular expression
import os
from os.path import dirname, join, realpath
import joblib
import uvicorn
from fastapi import FastAPI

How to Initialize a FastAPI App Instance

We can use the following code to initialize the FastAPI app:

app = FastAPI(
    title="Sentiment Model API",
    description="A simple API that use NLP model to predict the sentiment of the movie's reviews",
    version="0.1",
)

As you can see, we have customized the configuration of our FastAPI application by including the:

  • Title of the API
  • Description of the API.
  • Version of the API.

How to Load the NLP Model

To load the NLP model, we'll use the joblib.load() method and add the path to the model directory. The name of the NLP model is sentiment_model_pipeline.pkl:

# load the sentiment model
with open(
    join(dirname(realpath(__file__)), "models/sentiment_model_pipeline.pkl"), "rb"
) as f:
    model = joblib.load(f)

How to Define a Function to Clean the Data

We will use the same function called text_cleaning() from Part 1 that cleans the review data by removing stopwords, numbers, and punctuation. Finally, we'll convert each word into its base form by using the lemmatization process in the NLTK package.

def text_cleaning(text, remove_stop_words=True, lemmatize_words=True):
    # Clean the text, with the option to remove stop_words and to lemmatize word
    # Clean the text
    text = re.sub(r"[^A-Za-z0-9]", " ", text)
    text = re.sub(r"\'s", " ", text)
    text = re.sub(r"http\S+", " link ", text)
    text = re.sub(r"\b\d+(?:\.\d+)?\s+", "", text)  # remove numbers
    # Remove punctuation from text
    text = "".join([c for c in text if c not in punctuation])
    # Optionally, remove stop words
    if remove_stop_words:
        # load stopwords
        stop_words = stopwords.words("english")
        text = text.split()
        text = [w for w in text if not w in stop_words]
        text = " ".join(text)
    # Optionally, shorten words to their stems
    if lemmatize_words:
        text = text.split()
        lemmatizer = WordNetLemmatizer()
        lemmatized_words = [lemmatizer.lemmatize(word) for word in text]
        text = " ".join(lemmatized_words)
    # Return a list of words
    return text

How to Create a Prediction Endpoint

The next step is to add our prediction endpoint called “/predict-review” with the GET request method.

@app.get("/predict-review")
An API endpoint is the point of entry in a communication channel when two systems are interacting. It refers to touchpoints of the communication between an API and a server.

Then we define a prediction function for this endpoint. The name of the function is predict_sentiment() with a review parameter.

The predict_sentiment() function will do the following tasks:

  • Receive the movie review.
  • Clean the movie review by using the text_cleaning() function.
  • Make a prediction by using our NLP model.
  • Save the prediction result in the output variable (either 0 or 1).
  • Save the probability of the prediction in the probas variable and format it into 2 decimal places.
  • Finally, return prediction and probability results.
@app.get("/predict-review")
def predict_sentiment(review: str):
    """
    A simple function that receive a review content and predict the sentiment of the content.
    :param review:
    :return: prediction, probabilities
    """
    # clean the review
    cleaned_review = text_cleaning(review)
    
    # perform prediction
    prediction = model.predict([cleaned_review])
    output = int(prediction[0])
    probas = model.predict_proba([cleaned_review])
    output_probability = "{:.2f}".format(float(probas[:, output]))
    
    # output dictionary
    sentiments = {0: "Negative", 1: "Positive"}
    
    # show results
    result = {"prediction": sentiments[output], "Probability": output_probability}
    return result

Here are all blocks of codes in the main.py file:

# text preprocessing modules
from string import punctuation
# text preprocessing modules
from nltk.tokenize import word_tokenize
import nltk
from nltk.corpus import stopwords
from nltk.stem import WordNetLemmatizer
import re  # regular expression
import os
from os.path import dirname, join, realpath
import joblib
import uvicorn
from fastapi import FastAPI 

app = FastAPI(
    title="Sentiment Model API",
    description="A simple API that use NLP model to predict the sentiment of the movie's reviews",
    version="0.1",
)

# load the sentiment model
with open(
    join(dirname(realpath(__file__)), "models/sentiment_model_pipeline.pkl"), "rb"
) as f:
    model = joblib.load(f)


# cleaning the data
def text_cleaning(text, remove_stop_words=True, lemmatize_words=True):
    # Clean the text, with the option to remove stop_words and to lemmatize word
    # Clean the text
    text = re.sub(r"[^A-Za-z0-9]", " ", text)
    text = re.sub(r"\'s", " ", text)
    text = re.sub(r"http\S+", " link ", text)
    text = re.sub(r"\b\d+(?:\.\d+)?\s+", "", text)  # remove numbers
    
    # Remove punctuation from text
    text = "".join([c for c in text if c not in punctuation])
    
    # Optionally, remove stop words
    if remove_stop_words:
        # load stopwords
        stop_words = stopwords.words("english")
        text = text.split()
        text = [w for w in text if not w in stop_words]
        text = " ".join(text)
        
    # Optionally, shorten words to their stems
    if lemmatize_words:
        text = text.split()
        lemmatizer = WordNetLemmatizer()
        lemmatized_words = [lemmatizer.lemmatize(word) for word in text]
        text = " ".join(lemmatized_words)
        
    # Return a list of words
    return text

@app.get("/predict-review")
def predict_sentiment(review: str):
    """
    A simple function that receive a review content and predict the sentiment of the content.
    :param review:
    :return: prediction, probabilities
    """
    # clean the review
    cleaned_review = text_cleaning(review)
    
    # perform prediction
    prediction = model.predict([cleaned_review])
    output = int(prediction[0])
    probas = model.predict_proba([cleaned_review])
    output_probability = "{:.2f}".format(float(probas[:, output]))
    
    # output dictionary
    sentiments = {0: "Negative", 1: "Positive"}
    
    # show results
    result = {"prediction": sentiments[output], "Probability": output_probability}
    return result

How to Run the API

The following command will help us run the FastAPI app we have created.

uvicorn main:app --reload

Here are the settings we have defined for uvicorn to run our FastAPI app.

  • main: the file main.py that has the FastAPI app.
  • app: the object created inside of main.py with the line app = FastAPI().
  • — reload : Enables the server to automatically restart whenever we make changes in the code.

FastAPI provides an Automatic Interactive API documentation page. To access it navigate to http://127.0.0.1:8000/docs in your browser and then you will see the documentation page created automatically by FastAPI.

The documentation page shows the name of our API, the description, and its version. It also shows a list of available routes in the API that you can interact with.

To make a prediction, first click the “predict-review” route and then click on the button “Try it out”. This allows you to fill the review parameter and directly interact with the API.

Fill the review field by adding a movie review of your choice. I added the following movie review about Zack Snyder’s Justice League movie released in 2021.

“I loved the movie from the beginning to the end. Just like Ray fisher said, I was hoping that the movie doesn’t end. The begging scene was mind blowing, liked that scene very much. Unlike ‘the Justice League’ the movie show every hero is best at their own thing, make us love every character. Thanks, Zack and the whole team.”

Then click the execute button to make a prediction and get the result.

Finally, the result from the API shows that our NLP model predicts the review provided has a Positive sentiment with the probability of 0.70:

How to Use an NLP Model in Any Python Application

To use our NLP API in any Python application, we need to install the requests Python package. This package will help us send HTTP requests to the FastAPI app we have developed.

To install the requests package, run the following command:

pip install requests

Then create a simple Python file called python_app.py. This file will be responsible for sending our HTTP requests.

We first import the requests package:

import requests as r

Add a movie review about the Godzilla vs Kong (2021) movie:

# add review
review = "This movie was exactly what I wanted in a Godzilla vs Kong movie. It's big loud, brash and dumb, in the best ways possible. It also has a heart in a the form of Jia (Kaylee Hottle) and a superbly expressionful Kong. The scenes of him in the hollow world are especially impactful and beautifully shot/animated. Kong really is the emotional core of the film (with Godzilla more of an indifferent force of nature), and is done so well he may even convert a few members of Team Godzilla."

Then add the review in a key parameter to pass to the HTTP request:

keys = {"review": review}

Finally, we send a request to our API to make a prediction of the review:

prediction = r.get("http://127.0.0.1:8000/predict-review/", params=keys)

Then we can see the prediction results:

results = prediction.json()

print(results["prediction"])
print(results["Probability"])

This will show the prediction and its probability.
Here are the results:

Positive
0.54

Wrapping Up

Congratulations 👏👏, you have made it to the end of this article. I hope you have learned something new and now know how to deploy your NLP model with FastAPI.

If you want to learn more about FastAPI, I recommend taking this full FastAPI course created by Bitfumes.

You can download the project source code used in this article here.

If you learned something new or enjoyed reading this article, please share it so that others can see it. Until then, see you in the next article!.

You can also find me on Twitter @Davis_McDavid

Comments

Popular posts from this blog

Flutter for Single-Page Scrollable Websites with Navigator 2.0

A Data Science Portfolio is More Valuable than a Resume

Better File Storage in Oracle Cloud