Hosting a Keyword Extraction Model with Flask and FastAPI
Tags: fastapi, flask, keywordextraction, python
Published: 2023-11-06 09:09:36


# Table of Contents

1.  [Hosting a Keyword Extraction Model with Flask and FastAPI Api Model
    1.  [Introduction](#org3c2aad7)
    2.  [Overview](#org6dc88b7)
    3.  [Yet Another Keyword Extractor](#org77af195)
        1.  [Main Features](#orge063476)
        2.  [Heuristic Approach](#org7938f0f)
    4.  [Our ML model](#org06246b4)
        1.  [Installation](#org30734d2)
        2.  [Our model](#org4b47f83)
    5.  [Flask API](#org41dea0c)
        1.  [Installation](#orgf3a9edb)
        2.  [Serve with `/keywords` endpoint](#org00611e1)
        3.  [Host the server with port 5001 `app.run(host="0.0.0.0", port=5001, debug=True)`](#org296aa12)
    6.  [Testing with curl](#orga148ab8)
        1.  [Demo](#orge5cb5b9)
    7.  [FastAPI](#org28b9a8e)
        1.  [Key steps](#orgab2ee37)
        2.  [Code](#org703e8cf)
        3.  [Host](#org7a860ed)
        4.  [Documentation](#org4f0a0a7)
    8.  [Testing with Postman](#orgde0bb2c)
        1.  [Demo](#orgc4cc9b6)
    9.  [Complete example](#orgec2ec93)
    10. [Final thoughts](#org7b20184)


<a id="orge8cdeb8"></a>

# Hosting a Keyword Extraction Model with Flask and FastAPI     API Model

  

![img](https://i1.wp.com/cdn-images-1.medium.com/max/1600/1*jBjJw7jq71pw4iZkqyp2Uw.jpeg?resize=382%2C374&ssl=1)


<a id="org3c2aad7"></a>

## Introduction

When you noticed learning how to build a machine learning model is not enough, you are graduating from the Data Science bootcamp.

Here is an introduction of ML model serving with Restful API endpoint.


<a id="org6dc88b7"></a>

## Overview

In this post, we will be covering the following topics

-   Yet Another Keyword Extractor
-   Our ML model
-   Flask API
-   Testing with curl
-   FastAPI
-   Testing with Postman

At the end of this post, we will have an API endpoint (from `localhost`) to **extract 20 keywords from a text paragraph** with a simple POST requests.


<a id="org77af195"></a>

## Yet Another Keyword Extractor

YAKE (Yet Another Keyword Extractor) is a **light-weight unsupervised automatic keyword extraction method**. It rests on **text statistical features** extracted from single document to select the most important keywords.

It is quite useful to extract details from a text paragraph or use it as an alternatives to labeled data (When you don't have any).


<a id="orge063476"></a>

### Main Features

-   Unsupervised approach
-   Single-Document
-   Corpus-Independent
-   Domain and Language Independent


<a id="org7938f0f"></a>

### Heuristic Approach

As opposed to other keyword extraction algorithms like tf-idf, one of the strong selling points of YAKE is its ability to extract keywords within a **single document**. It can be easily applied to single paragraph or document, without the existence of a corpus, dictionary or other external collections.

Here are some components of the method outlined in the paper.

-   Text preprocessing
-   Feature extraction
-   ****Individual terms score****
-   Candidate keywords list generation
-   Data deduplication
-   Ranking

Some interesting ideas during the term scoring process.

-   **Casing**
    
    Reflects the casing aspect of a word.

-   **Word Positions**
    
    Values more on the words occurring at the beginning of the documents, on the assumption that relevant keywords often concentrate more at the beginning.

-   **Word Relatedness to Context**
    
    Computes the number of different terms that occur to the **left and right side** of the candidate word. The more the ****number of different terms co-occur with the candidate word**** (on both sides), the more the meaningness it is likely to be. Similar idea applies to different sentences as well.

  

For more details, you can check out the paper in the reference page.

Reference: [Github](https://github.com/LIAAD/yake) and [Paper](https://repositorio.inesctec.pt/bitstream/123456789/7623/1/P-00N-NF5.pdf)


<a id="org06246b4"></a>

## Our ML model

As the main purpose of this post is to demonstrate how to **deploy a model with API endpoints**, I will use a simple wrapper of `yake.KeywordExtrator` function to act as our machine learning model.


<a id="org30734d2"></a>

### Installation

    pip install yake


<a id="org4b47f83"></a>
```python
### Our model
import yake
def ExtractKeywords(text):
    """ Extract 20 keywords from the input text """

    language = "en"
    max_ngram_size = 2
    deduplication_thresold = 0.3
    deduplication_algo = "seqm"
    window_size = 1
    num_of_keywords = 20

    custom_kw_extractor = yake.KeywordExtractor(
        lan=language,
        n=max_ngram_size,
        dedupLim=deduplication_thresold,
        dedupFunc=deduplication_algo,
        windowsSize=window_size,
        top=num_of_keywords,
        features=None,
    )
    kws = custom_kw_extractor.extract_keywords(text)
    keywords = [x[0] for x in kws]  # kws is in tuple format, extract the text part

    return keywords
    
```

<a id="org41dea0c"></a>

## Flask API

Having a ML model ready is only half the job done. A model is useful only when someone is able to use it.

Now we are going to serve our model with a **Restful API endpoint** using **Flask**. The package uses a simple decorator format for you to define an endpoint, e.g. `@app.route('/keywords', methods = ['POST', 'GET'])`.

Here we specify our endpoint to accept both `GET` and `POST` requests. The GET request will print a curl statement, and the POST request will extract the keywords.


<a id="orgf3a9edb"></a>

### Installation

`pip install flask`


<a id="org00611e1"></a>

### Serve with `/keywords` endpoint
```python
    from flask import Flask, request
import yake

app = Flask(__name__)

def ExtractKeywords(text):
    """ Extract 20 keywords from the input text """

    language = "en"
    max_ngram_size = 2
    deduplication_thresold = 0.3
    deduplication_algo = "seqm"
    window_size = 1
    num_of_keywords = 20

    custom_kw_extractor = yake.KeywordExtractor(
        lan=language,
        n=max_ngram_size,
        dedupLim=deduplication_thresold,
        dedupFunc=deduplication_algo,
        windowsSize=window_size,
        top=num_of_keywords,
        features=None,
    )
    kws = custom_kw_extractor.extract_keywords(text)
    keywords = [x[0] for x in kws]  # kws is in tuple format, extract the text part

    return keywords

@app.route('/keywords', methods = ['POST', 'GET'])
def keywords():
    if request.method == "POST":
        json_data = request.json
        text = json_data["text"]
        kws = ExtractKeywords(text)

        # return a dictionary
        response = {"keyowrds": kws}
        return response

    elif request.method == "GET":
        response = """
            Extract 20 keywords from a long text. Try with curl command. <br/><br/><br/>

            curl -X POST http://127.0.0.1:5001/keywords -H 'Content-Type: application/json' \
            -d '{"text": "Logistic regression is a statistical model that in its basic form uses a logistic function to model a binary dependent variable, although many more complex extensions exist. In regression analysis, logistic regression[1] (or logit regression) is estimating the parameters of a logistic model (a form of binary regression). Mathematically, a binary logistic model has a dependent variable with two possible values, such as pass/fail which is represented by an indicator variable, where the two values are labeled 0 and 1. In the logistic model, the log-odds (the logarithm of the odds) for the value labeled 1 is a linear combination of one or more independent variables (predictors); the independent variables can each be a binary variable (two classes, coded by an indicator variable) or a continuous variable (any real value). The corresponding probability of the value labeled 1 can vary between 0 (certainly the value 0) and 1 (certainly the value 1), hence the labeling; the function that converts log-odds to probability is the logistic function, hence the name. The unit of measurement for the log-odds scale is called a logit, from logistic unit, hence the alternative names. Analogous models with a different sigmoid function instead of the logistic function can also be used, such as the probit model; the defining characteristic of the logistic model is that increasing one of the independent variables multiplicatively scales the odds of the given outcome at a constant rate, with each independent variable having its own parameter; for a binary dependent variable this generalizes the odds ratio."}'
            """
        return response

    else:
        return "Not supported"

if __name__ == '__main__':
    app.run(host="0.0.0.0", port=5001, debug=True)
```

<a id="org296aa12"></a>

### Host the server with port 5001 `app.run(host="0.0.0.0", port=5001, debug=True)`

    `python main.py`

Reference - [Flask](https://flask.palletsprojects.com/en/2.0.x/)


<a id="orga148ab8"></a>

## Testing with curl

Let's use a paragraph from wikipedia of the `Logistic Regression` page as an input of our curl command and pass it as an argument `text` (Double quote removed) to the model.
```bash
curl -X POST http://127.0.0.1:5001/keywords -H 'Content-Type: application/json' \
      -d '{"text": "Logistic regression is a statistical model that in its basic form uses a logistic function to model a binary dependent variable, although many more complex extensions exist. In regression analysis, logistic regression[1] (or logit regression) is estimating the parameters of a logistic model (a form of binary regression). Mathematically, a binary logistic model has a dependent variable with two possible values, such as pass/fail which is represented by an indicator variable, where the two values are labeled 0 and 1. In the logistic model, the log-odds (the logarithm of the odds) for the value labeled 1 is a linear combination of one or more independent variables (predictors); the independent variables can each be a binary variable (two classes, coded by an indicator variable) or a continuous variable (any real value). The corresponding probability of the value labeled 1 can vary between 0 (certainly the value 0) and 1 (certainly the value 1), hence the labeling; the function that converts log-odds to probability is the logistic function, hence the name. The unit of measurement for the log-odds scale is called a logit, from logistic unit, hence the alternative names. Analogous models with a different sigmoid function instead of the logistic function can also be used, such as the probit model; the defining characteristic of the logistic model is that increasing one of the independent variables multiplicatively scales the odds of the given outcome at a constant rate, with each independent variable having its own parameter; for a binary dependent variable this generalizes the odds ratio."}'
```

<a id="orge5cb5b9"></a>

### Demo

![img](https://storage.googleapis.com/billylkc-blog-image/images/posts/12-model-hosting/flask-q.gif)

**Results**
```
    {
      "keywords": [
        "logistic model",
        "variable",
        "regression",
        "binary dependent",
        "labeled",
        "form",
        "odds",
        "exist",
        "basic",
        "complex",
        "indicator",
        "probability",
        "log-odds scale",
        "sigmoid function",
        "converts log-odds",
        "Mathematically",
        "scales",
        "alternative",
        "defining",
        "constant"
      ]
    }
```
The result is actually quite good given its unsupervised nature. We can see some important keywords like **log-odds, sigmoid function and binary** in the result.


<a id="org28b9a8e"></a>

## FastAPI

Apart from Flask that we just introduced, there is another popular package to host API endpoints - FastAPI.   

**FastAPI** is a modern, fast and popular web framework for building APIs based on standard Python type hints. It is a high performant package, and it is on par with some popular framework written in ****NodeJS**** and ****Go****.

Let's try to host our keywords model again with FastAPI.


<a id="orgab2ee37"></a>

### Key steps

-   Both **Input** and **Output Object** inherit `pydantic.Basemodel` object
-   Use python **type hints** `str` (input) and `List[str]` (output) to define field types of the objects
-   Use Objects as input/output parameter `Response/Paragraph`

```python
# Input object with a text field
class Paragraph(BaseModel):
    text: str

# Output object with keywords as field
class Response(BaseModel):
    keywords: List[str]

@app.post("/keywords", response_model=Response)
def keywords_two(p: Paragraph):
    ...
    return Response(keywords=kw)
```  

<a id="org703e8cf"></a>

### Code
```python
from fastapi import FastAPI
from pydantic import BaseModel
from typing import List
import yake

# Input
class Paragraph(BaseModel):
    text: str

# Output
class Response(BaseModel):
    keywords: List[str]

app = FastAPI()

def ExtractKeywords(text):
    """ Extract 20 keywords from the input text """

    language = "en"
    max_ngram_size = 2
    deduplication_thresold = 0.3
    deduplication_algo = "seqm"
    windowSize = 1
    numOfKeywords = 20

    custom_kw_extractor = yake.KeywordExtractor(
        lan=language,
        n=max_ngram_size,
        dedupLim=deduplication_thresold,
        dedupFunc=deduplication_algo,
        windowsSize=windowSize,
        top=numOfKeywords,
        features=None,
    )
    kws = custom_kw_extractor.extract_keywords(text)
    keywords = [x[0] for x in kws]  # kws is in tuple format, extract the text part

    return keywords


  @app.post("/keywords", response_model=Response)
  def keywords(p: Paragraph):
      kw = ExtractKeywords(p.text)
      return Response(keywords=kw)    
```


<a id="org7a860ed"></a>

### Host

a) Install fastapi and uvicorn
``` bash
    pip install fastapi
    pip install uvicorn
```
  

b) Host FastAPI with uvicorn

   `uvicorn main:app --host 0.0.0.0 --port 5001 --reload --debug --workers 3`


<a id="org4f0a0a7"></a>

### Documentation

FastAPI creates a documentation page for you by default using the [Swagger UI](https://swagger.io/tools/swagger-ui/). You can open the documentation page with `http://localhost:5001/docs`. If you follow the schema definition, you can have a nice looking API documentation with some examples as well.

  

![img](https://storage.googleapis.com/billylkc-blog-image/images/posts/12-model-hosting/swagger.png "Auto generated Swagger API doc")

Reference - [FastAPI](https://fastapi.tiangolo.com/https://fastapi.tiangolo.com/) and [Declare Request Example](https://fastapi.tiangolo.com/tutorial/schema-extra-example/)


<a id="orgde0bb2c"></a>

## Testing with Postman


<a id="orgc4cc9b6"></a>

### Demo

![img](https://storage.googleapis.com/billylkc-blog-image/images/posts/12-model-hosting/fastapi-q.gif)


<a id="orgec2ec93"></a>

## Complete example

You can find the complete examples here - [Flask](https://github.com/billylkc/blogposts/blob/7_flask_api/main.py) and [FastAPI](https://github.com/billylkc/blogposts/blob/8_fastapi/main.py)


<a id="org7b20184"></a>

## Final thoughts

Here we introduced two different frameworks (**Flask** and **FastAPI**) to serve our keyword extraction model on our local machine. While Flask being more popular among web developers, and FastAPI being more performant, it is both pretty easy to use.

Hopefully you can see how easy it is for you to host the model using the frameworks. If you have any questions or feedback, feel free to leave a comment.

I also write on my own blog ([https://data-gulu.com](https://data-gulu.com)). You can find more articles about python and machine learning there.

Happy Coding!

  

Reference:

-   Photo by [Ilyuza Mingazova](<https://unsplash.com/@ilyuza?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText>) on [Unsplash](<https://unsplash.com/s/photos/serfing?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText>)
-   YAKE - [Github](https://github.com/LIAAD/yake) and [Paper](https://repositorio.inesctec.pt/bitstream/123456789/7623/1/P-00N-NF5.pdf)
-   Flask and FastAPI - [Here](https://flask.palletsprojects.com/en/2.0.x/) and [Here](https://fastapi.tiangolo.com/https://fastapi.tiangolo.com/)