In most ML portfolios you get a confusion matrix, and that's it. That wasn't enough for me. I was looking for a model you can get your hands dirty on. So I set up my IMDB sentiment classifier as a web app that runs in the browser without a server and lays open how it works.
It reaches a hit rate of around 88% at telling positive from negative reviews. The statistics are the boring part. Far more interesting is how it arrives at its verdict, and at which points it fails.
A bag of word-pairs
I'm sticking with the old-school approach, which is what makes the model a good teacher. The way through the pipeline:
- Clean the data (tokenise, lemmatise, strip punctuation and capitalisation).
- Build bigrams. "not good" becomes a single token not_good.
- For each of these pairs, pull the vector from Word2Vec (skip-gram, 200 dimensions). Those are trained on 50,000 reviews, so similar contexts yield similar vectors.
- Average all the bigram vectors to get a 200-number summary of the text.
- A tiny 2-layer network classifies that as positive or negative.
No recurrence, no attention. You simply draw a line through the averaged word-pairs. In the demo you can follow every step live, and each bigram is coloured according to its leaning.
Scrubbing the labels
Before training, I cleaned the data with a noise-robust pass. Using an Isolation Forest for anomaly detection and TextBlob polarity as a cross-check, I sorted out about 1,500 of the 50,000 reviews whose label didn't match the text. That's 3% — a small but honest gain for the model's stability. Proves once again that data quality often buys you more than complexity.
What works
Bigrams have their charm, for instance with local negation. Since "not good" is stored as not_good, and thus distinct from "good", the model learns that it's negative. Type "Not good." into the demo and it hits the bullseye. With bag-of-words alone, that would have gone nowhere.
And where it struggles
This is where it gets entertaining — the moment meaning stretches beyond two adjacent words:
- "This film is not bad." — to us that's mild praise, to the model it's negative. It can't reassemble the averaged bigrams that way.
- "I wouldn't say it was bad." — negative as well, because the "not" is too far from "bad" to ever land in a pair.
- Flip "boring but ultimately rewarding" around and the verdict tips flatly in the wrong direction. That "but ultimately X" means X wins is foreign to the model.
It's not a bug, but the logical consequence of giving up word order.
The lesson
Transformers tore this wall down. With attention, a "not" can reach across a whole sentence and reinterpret "a bad film". The path from this bag-of-embeddings (circa 2013) to a modern LLM is essentially the shift from averaging to attention.
My model is not state of the art, and it isn't meant to be. But its mistakes are legible. They give the best intuition for why I design my models today the way I do.
Try it yourself
Test your own review in the live demo, watch the word-pairs light up and try to break the thing.
Code: Movie_Sentiment_Analysis