case 03 2026 · Shipped · live on Hugging Face Spaces
Review Sentiment Analyzer
That number taught me more than a 99% ever would have.
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.
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
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
- 01
Baselines before transformers. You can't appreciate what attention buys you until you've watched a linear model fail at negation.
- 02
Error analysis is the curriculum. The confusion matrix was the start of the work, not the end.
- 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