Abhinandan Pandey , public record

case 03 2026 · Shipped · live on Hugging Face Spaces

Review Sentiment Analyzer

That number taught me more than a 99% ever would have.

Role

Data, model, evaluation, deployment

Stack

Python · scikit-learn · TF-IDF · Streamlit · Hugging Face Spaces

Year

2026

Links

01 , The problem

Sentiment classification is a solved problem , which is exactly why I picked it. When the destination is known, the whole grade is on the journey: data discipline, evaluation honesty, and shipping something usable at the end.

The rule I set myself: no transformers, no pretrained shortcuts. Earn the classical baseline first , because if you can't explain why the simple model gets a review wrong, borrowing a bigger model just hides the question.

02 , Research & framing

The corpus was 100K+ Amazon reviews, and the first honest work was nowhere near a model: deduplication, cleaning, and class balancing, because raw review data is overwhelmingly positive and a classifier can score high by learning nothing.

Feature extraction settled on TF-IDF capped at 20,000 features , large enough to capture phrases that carry sentiment, small enough to train in seconds and stay fully inspectable. Every feature is a word I can read.

03 , Architecture

The pipeline is deliberately boring: preprocess and balance, vectorize with TF-IDF, train logistic regression, then spend the majority of the time where it actually pays , in evaluation. Confusion matrix, precision, recall, F1, and manual error reading, not just an accuracy printout.

The final layer is a Streamlit app on Hugging Face Spaces that returns a prediction with its confidence score, because a classifier that won't tell you how sure it is invites people to over-trust it.

Pipeline , deliberately boring, evaluated seriously
100K+ reviews raw · skewed Preprocess clean · balance TF-IDF 20K features LogReg scikit-learn Evaluation confusion matrix · P/R/F1 · error reading App HF Spaces + confidence

04 , Engineering decisions

D1 , Logistic regression over a deep model

A linear model on TF-IDF features, trained with scikit-learn.

Because

It's interpretable end-to-end: I can rank the exact words that push a review positive or negative. For a learning project, seeing the mechanism was worth more than five accuracy points.

The trade

It cannot understand negation or sarcasm structurally. 'Not great' looks half-great to a bag of words. I knew this going in , see the challenges.

D2 , Balance the classes before training

Balanced the dataset instead of training on the raw skew.

Because

On skewed review data, accuracy is a lie , predict 'positive' always and you look brilliant. Balancing made every metric mean something.

The trade

The deployed model sees a different class distribution than the real world. In production I'd calibrate instead; for an evaluation-honesty exercise, balance was the right call.

D3 , Ship the confidence, not just the label

The app shows prediction plus confidence score on every input.

Because

A bare label teaches users the model is an oracle. A 51%-confident 'positive' teaches them it's a model.

The trade

Logistic regression confidence isn't perfectly calibrated. Directionally honest beat falsely precise.

D4 , Deploy anyway

Wrapped the model in Streamlit and put it on Hugging Face Spaces.

Because

A notebook that ends at a metric is homework. A URL anyone can try is a product decision, and deployment problems , dependencies, cold starts, input sanitization , are part of the learning.

The trade

Streamlit is not a production serving stack. For this project's job , being usable and inspectable , it was exactly enough.

05 , What fought back

Negation and sarcasm

The model's most instructive failures: 'not worth the money' scored positive on 'worth', and sarcastic praise fooled it completely. Bigrams helped; nothing linear fixes it fully. Reading these errors one by one is where I actually learned how the model thinks , and where its ceiling comes from.

The ambiguous middle

Three-star reviews are genuinely mixed , 'good product, terrible delivery' has no single true label. Wrestling with how to handle the middle taught me that label definition is a modeling decision, not a preprocessing detail.

Resisting the leaderboard itch

I could have swapped in a pretrained transformer, gained ten points, and understood less. Choosing to publish 78.33% and explain it was uncomfortable in exactly the way I think early projects should be.

06 , The numbers, honestly

100K+

reviews processed and balanced

20,000

TF-IDF features, every one inspectable

78.33%

accuracy , reported, not rounded up

0.78

F1 · precision 0.79 · recall 0.79

A fine-tuned transformer beats this handily. That was never the point. The point was earning a baseline I can explain down to individual features , and being comfortable publishing the real number.

07 , One honest snippet

The whole model, honestly
pipeline = Pipeline([
    ("tfidf", TfidfVectorizer(max_features=20_000,
                              ngram_range=(1, 2),
                              stop_words="english")),
    ("clf", LogisticRegression(max_iter=1000)),
])

pipeline.fit(X_train, y_train)
# accuracy: 0.7833 , and the confusion matrix is
# more interesting than the accuracy.

Twelve lines. The value wasn't the code , it was everything the evaluation forced me to understand about why it fails.

08 , What it taught me

  1. 01

    Baselines before transformers. You can't appreciate what attention buys you until you've watched a linear model fail at negation.

  2. 02

    Error analysis is the curriculum. The confusion matrix was the start of the work, not the end.

  3. 03

    Deployment is part of the assignment. The gap between 'works in my notebook' and 'works at a URL' is where engineering lives.

09 , Where it goes next

  • A DistilBERT branch trained on the same split , measure exactly what the extra complexity buys
  • Aspect-based sentiment: 'good product, terrible delivery' should produce two answers
  • An error-explorer page in the app, because the failures are the interesting part

Next case , /01

AI Prompt Optimizer →