Skip to content
Anirudh Rao
← All work

Serving the LSTM behind an authenticated API

Fake News Detection API.

Puts the fake-news LSTM behind a FastAPI service: training-identical preprocessing, API-key auth, an explicit “Uncertain” band instead of forced guesses, and Docker builds that pull versioned model releases.

A trained model in a notebook isn't a product. This project takes the LSTM from the Fake News Detector and puts it behind a small, production-shaped API: authenticated, containerized, observable, and honest about when it doesn't know.

The problem

The original project ended where most ML coursework ends: a model with good test accuracy and no way for anything else to use it. Serving it means solving a different set of problems. The exact preprocessing used in training has to run again at request time. The model file is too large to live comfortably in git. Someone has to be allowed to call it, and nobody else should be. And a classifier forced to pick “real” or “fake” for every input will confidently mislabel the articles it's least sure about.

Architecture

How it works

Same preprocessing as training

Train/serve skew is the most common way a served model quietly gets worse. The API rebuilds the training pipeline exactly: lowercase, tokenize with NLTK, lemmatize with WordNet, and drop stop words and tokens of two characters or fewer. It then uses the tokenizer fitted during training (shipped as a pickle) rather than fitting a new one, so every word maps to the same index the model learned.

python · 4 lines
def preprocess(text):
    tokens = nltk.word_tokenize(text.lower())
    tokens = [lemmatizer.lemmatize(token) for token in tokens if token not in stop_words and len(token) > 2]
    return ' '.join(tokens)

An explicit “Uncertain” band

Instead of thresholding at 0.5, the API only commits to a label when the score is past 0.7 on either side. Anything in between comes back as “Uncertain”, with a confidence that reflects how far the score sits from the midpoint. For a misinformation tool, a visible “I don't know” is more useful than a coin flip presented as an answer.

python · 9 lines
if prediction > PREDICTION_THRESHOLD:
    result = "Real News"
    confidence = round(prediction * 100, 2)
elif prediction < (1 - PREDICTION_THRESHOLD):
    result = "Fake News"
    confidence = round((1 - prediction) * 100, 2)
else:
    result = "Uncertain"
    confidence = round(abs(0.5 - prediction) * 200, 2)

Access control

Predictions require an X-API-Key header, checked by a FastAPI dependency against a key read from the environment, so the secret never lives in the code. A separate /health endpoint stays open for uptime checks.

Model artifacts as releases

The .keras file isn't committed to the repo. Each trained model is published as a GitHub Release asset (an initial release, then retrains v1.0.1 and v1.0.2 over the following days), and the Docker build downloads a specific version. That keeps the repository small and makes every image traceable to the exact model it serves.

python · 3 lines
RUN mkdir -p /app/fake_news_detector && \
    wget -O /app/fake_news_detector/fake_news_detection_model.keras \
    https://github.com/anirudhrao20/Portfolio-ML-Models/releases/download/Model-Retrain-v1.0.1/fake_news_detection_model_1.0.1.keras

What I'd change next

  • Make the model version a build argument. The Dockerfile pins v1.0.1 even though v1.0.2 shipped a day later; a build arg would make upgrades a one-line change and rollbacks just as easy.
  • Slim the image. The requirements still include pandas, matplotlib, and seaborn from the training notebook, which serving never uses.
  • Lock CORS down to known origins instead of allowing every origin.
  • Cap input length and add tests for the preprocessing path, since that's where train/serve skew would creep in.
esc
↑↓ to move · ↵ to selectk=16