Portfolio / Projects / sms-spam-detection

NLP · Deep learning · Academic project

How do you filter unwanted SMS without blocking real messages?

I compared two ways of reading an SMS: a neural network that follows word order (BiLSTM) and a classic method that weighs important words (TF-IDF + Logistic Regression). Both are evaluated on messages they have never seen.

98.32%AccuracyBiLSTM, test
0.930Spam F1BiLSTM, test
4Real messages blockedout of 678
0.915Spam F1TF-IDF + log. reg.

Tools & technologies

Pythonscikit-learnTF-IDFBiLSTM / Keras

01 / Context & question

A spam filter can fail in two ways, and they do not cost the same.

Every incoming SMS must be classified as ham (a legitimate message) or spam (promotions, fake prizes, premium-rate numbers).

A filter can let a spam through: annoying. It can also block a real message, such as an appointment or a login code: much worse for the user. Everything that follows is therefore built to measure these two errors separately, not just an overall percentage of correct answers.

The project question

On this set of messages, does a model that follows word order really beat a much simpler classic method?

Before choosing a model, I first needed to know what these messages look like.

02 / Exploratory data analysis (EDA)

Exploration reveals three things that shape everything after: imbalance, duplicates and a recognisable spam style.

Exploratory data analysis (EDA) means looking at the data before modelling: how many messages, what share of spam, which anomalies, which visible differences between the two classes. It is what drives the right preparation and evaluation choices.

What I checked
  1. counted messages per class and looked for missing values;
  2. looked for duplicated messages;
  3. compared the length of ham and spam messages;
  4. listed the most frequent words in each class.
Raw messages5,572

4,825 ham and 747 spam, no missing values.

Exact duplicates415

Removed before any split.

Unique messages5,157

4,516 ham and 641 spam.

Ham and spam distribution
Only 12% of messages are spam: high accuracy alone will not be enough to judge the model.
Message length by class
Spam is clearly longer: median 148 characters versus 53 for real messages.
Most frequent words in spam
In spam: free, txt, stop, claim, prize, cash, and links.
Most frequent words in real messages
In real messages: conversational language (ok, know, like, come, love).
What these findings change

The imbalance means tracking spam precision and recall rather than accuracy. Duplicates must be removed before splitting, otherwise the same message could be learned and then “recognised” at test time. Links and sales vocabulary show that cleaning must not erase these clues.

These observations directly guided how the text was prepared.

03 / Text preparation

Deliberately light cleaning, to keep the clues the filter needs.

Transformations applied to every SMS
  1. lowercase (“FREE” and “free” become the same word);
  2. each link replaced by the word urltoken: the exact address changes from one spam to another, but the presence of a link is a signal;
  3. punctuation and repeated spaces removed;
  4. labels encoded: ham = 0, spam = 1.

I did not remove short words or numbers: in SMS, “txt”, “150p” or a short code are precisely typical of spam, as the exploration showed.

Once the messages were prepared, the evaluation had to be organised before training anything.

04 / Training, validation and test split

Three separate sets of messages, each with a precise role.

Training · 3,609models learn
Validation · 774tuning and threshold
Test · 774final evaluation, once

The BiLSTM vocabulary and the TF-IDF weights are learned only from the 3,609 training messages. Otherwise, words seen only in the test set would influence the model, and the result would be too optimistic.

With this frame in place, I could train two very different approaches under the same conditions.

05 / Two models, two ways of reading an SMS

A model that follows word order, against a method that weighs important words.

A · BiLSTM

textword IDsembeddingBiLSTMspam probability

B · TF-IDF + Logistic Regression

textTF-IDF weightslogistic regressionspam probability
How I trained the BiLSTM
  1. each SMS is brought to 33 words, the length of 95% of training messages (shorter ones padded, longer ones cut);
  2. class weights give more importance to errors on spam, the minority class;
  3. training stops when the validation error stops decreasing (early stopping), before the model memorises the training set.
BiLSTM training and validation loss
Training error keeps falling, but validation error levels off from the second epoch: the signal to stop.

Both models output a probability; what remained was deciding when a message is declared spam.

06 / Choosing the decision threshold

The threshold sets the trade-off between spam let through and real messages blocked.

I tested thresholds on the validation set and kept the one with the best spam F1: 0.83 for the BiLSTM and 0.56 for TF-IDF + Logistic Regression. The BiLSTM’s high threshold suggests very clear-cut probabilities on obvious spam: it can demand strong certainty before blocking.

Fixed before the test.

Both thresholds were frozen before any evaluation on the test set, so the final result is not tuned on the messages used to measure it.

With models and thresholds fixed, the test set could finally be used.

07 / Test results

The BiLSTM wins, but only by a short margin.

The test set contains 774 messages: 678 real messages and 96 spam.

ModelAccuracySpam precisionSpam recallSpam F1
BiLSTM98.32%95.60%90.62%0.930
TF-IDF + log. regression97.93%93.48%89.58%0.915
Comparison of both models on the test set
The two approaches are close on every metric.
BiLSTM confusion matrix
BiLSTM: 4 real messages blocked, 9 spam let through.
TF-IDF + Logistic Regression confusion matrix
TF-IDF: 6 real messages blocked, 10 spam let through.

These numbers are close; the real question becomes which one to choose, and why.

08 / Interpretation & real-world value

The best score does not always mean the best production choice.

The BiLSTM blocks two fewer real messages and stops one more spam: it is the best classifier in this experiment. But TF-IDF stays very close, and even gets slightly higher areas under the ROC and precision-recall curves, meaning it ranks messages just as well when the threshold varies.

In practiceChoose according to context

The BiLSTM if every blocked real message matters; TF-IDF if speed, easy updates and explainability come first, since the words driving each decision can be read directly.

What the errors showRecall is the point to improve

With 13 errors out of 774 messages, the BiLSTM is rarely wrong. But 9 of the 96 spam still get through, almost one in ten: that is where more recent data or a richer model would help most.

It remains to be clear about what this experiment proves, and what it does not.

Decision: the BiLSTM is the best tested classifier, but the gap is small enough that TF-IDF + Logistic Regression remains a credible choice when simplicity, speed and explainability matter more.

09 / Limits & lessons

A solid result on this set of messages, to be confirmed on recent SMS.

  • An old, English-only set of messages (corpus): spam vocabulary has changed since.
  • Training and test come from the same source: no validation on another dataset.
  • No speed or memory measurement, which would matter for deployment.
What I learned

Comparing a complex model with a simple method, on the same split and with a threshold chosen on validation, teaches more than an isolated score. Here the BiLSTM wins, but the small gap shows that extra complexity is only worthwhile when the gain justifies the operational cost.

Going further

I would extend the study by testing a recent SMS dataset, trying a transformer-type model and setting the threshold from the real cost of each error. The notebook details every step and its outputs.

Next project

Glass Fragment Classification: classify a small chemical dataset without overstating model performance.