A Natural Language Processing (NLP) and Machine Learning project that classifies SMS messages as Ham (Not Spam) or Spam using TF-IDF vectorization and Naive Bayes.
The project is implemented in spam_classifier.ipynb and follows the complete workflow from data understanding and exploratory data analysis to text preprocessing, vectorization, model training, and evaluation.
The objective of this project is to build a machine learning model that can identify whether an SMS message is:
- Ham (Not Spam) β a normal message
- Spam β an unwanted or promotional message
The project uses the SMS Spam Collection dataset from the following source:
https://raw.githubusercontent.com/justmarkham/pycon-2016-tutorial/master/data/sms.tsv
The dataset initially contains:
- 5572 messages
- 2 columns
labelmessage
The original class distribution is:
| Label | Count | Percentage |
|---|---|---|
| Ham | 4825 | 86.59% |
| Spam | 747 | 13.41% |
A local copy of the dataset is saved as:
dataset/spam.csv
- Python
- Pandas
- NumPy
- Matplotlib
- Seaborn
- Scikit-learn
- WordCloud
- Jupyter Notebook
The project dependencies are listed in requirements.txt.
SMS Dataset
β
Data Understanding
β
Exploratory Data Analysis
β
Remove Duplicates
β
Text Preprocessing
β
Check & Remove Empty Messages
β
TF-IDF Vectorization
β
Train-Test Split
β
Multinomial Naive Bayes
β
Model Evaluation
The first phase explores the basic structure of the dataset.
The notebook checks:
- Dataset shape
- Column names
- Data types
- Missing values
- Duplicate rows
- Class distribution
The dataset contains 403 duplicate rows.
The labels are converted from:
ham
spam
to numerical values using LabelEncoder:
ham β 0
spam β 1
Several aspects of the SMS data are analyzed before training the model.
The project visualizes the distribution of Ham and Spam messages.
The dataset is imbalanced, with Ham messages making up the majority of the data.
The following feature is created:
df['message_length'] = df['message'].apply(len)This represents the total number of characters in an SMS.
The following feature is also created:
df['word_count'] = df['message'].apply(lambda x: len(x.split()))This represents the number of words in each message.
The project extracts words from Ham and Spam messages and identifies the most frequently occurring words.
It also performs a second analysis after removing English stopwords and some SMS-specific terms.
Separate word clouds are generated for:
- Spam messages
- Ham messages
A correlation heatmap is used to examine relationships among the numerical features created during EDA.
Duplicate rows are removed using:
df = df.drop_duplicates()The dataset changes from:
5572 rows
β
5169 rows
After duplicate removal, the class distribution becomes:
| Label | Count |
|---|---|
| Ham | 4516 |
| Spam | 653 |
A custom clean_text() function is used to prepare SMS messages for machine learning.
The preprocessing steps are:
- Lowercasing
- URL removal
- Email address removal
- Number removal
- Punctuation removal
- Whitespace normalization
- Tokenization
- Stopword removal
- Removal of words with length β€ 2
- Joining the processed tokens back into a string
Example conceptually:
Raw SMS
β
Lowercase
β
Remove URLs, emails, numbers and punctuation
β
Tokenize
β
Remove stopwords
β
Remove very short words
β
Cleaned SMS
The cleaned messages are stored in:
df['cleaned_message']After preprocessing, the notebook checks whether any messages became empty:
empty_messages = df[df['cleaned_message'].str.strip() == '']There are 36 empty messages after cleaning.
These rows are removed because an empty text representation provides no text features for the model.
After removing them:
5169 rows
β
5133 rows
Final class distribution:
| Label | Count |
|---|---|
| Ham | 4480 |
| Spam | 653 |
The index is then reset.
Machine learning algorithms cannot directly process SMS text, so the cleaned messages are converted into numerical features.
The project uses:
from sklearn.feature_extraction.text import TfidfVectorizer
tfidf = TfidfVectorizer(max_features=5000)The vectorizer is fitted and transformed using:
X = tfidf.fit_transform(df['cleaned_message']).toarray()The final feature matrix has the shape:
X.shape = (5133, 5000)
This means:
- 5133 rows β SMS messages
- 5000 columns β TF-IDF features
The target variable is:
y = df['label']with:
y.shape = (5133,)
So the data passed to the machine learning model consists of:
X β TF-IDF features
y β Spam/Ham labels
The dataset is split into training and testing sets using:
train_test_split(
X,
y,
test_size=0.2,
random_state=42,
stratify=y
)| Dataset | Messages |
|---|---|
| Training | 4106 |
| Testing | 1027 |
stratify=y is used to maintain a similar spam/ham distribution in both sets.
The observed spam percentages are:
Training: 12.71%
Testing: 12.76%
Three Naive Bayes algorithms were implemented and evaluated for the SMS classification task:
GaussianNB()MultinomialNB()BernoulliNB()
All three algorithms were trained using the TF-IDF feature representation and evaluated on the test dataset.
After comparing their performance, Multinomial Naive Bayes achieved good accuracy with high precision compared with the other two algorithms.
Therefore, MultinomialNB was selected as the final model for this project.
from sklearn.naive_bayes import GaussianNB, MultinomialNB, BernoulliNB
gnb = GaussianNB()
mnb = MultinomialNB()
bnb = BernoulliNB()The selected final model is:
mnb = MultinomialNB()
mnb.fit(X_train, y_train)
y_pred = mnb.predict(X_test)SMS Messages
β
Text Preprocessing
β
TF-IDF Vectorization
β
βββββββββββββββββββββββββββββββ
β GaussianNB() β
β MultinomialNB() β β
β BernoulliNB() β
βββββββββββββββββββββββββββββββ
β
Performance Comparison
β
MultinomialNB Selected π
The current notebook evaluates the model using:
- Accuracy
- Confusion Matrix
- Precision
The final Multinomial Naive Bayes results are:
97.08%
100%
[[896 0]
[ 30 101]]
Because the labels are encoded as:
0 β Ham
1 β Spam
the confusion matrix can be interpreted as:
| Predicted Ham | Predicted Spam | |
|---|---|---|
| Actual Ham | 896 | 0 |
| Actual Spam | 30 | 101 |
This means:
- 896 Ham messages were correctly classified as Ham.
- 0 Ham messages were classified as Spam.
- 101 Spam messages were correctly classified as Spam.
- 30 Spam messages were classified as Ham.
The model therefore achieved perfect precision for the Spam class on this test set, while some spam messages were still missed.
spam-classifier/
β
βββ dataset/
β βββ spam.csv
β
βββ spam_classifier.ipynb
βββ requirements.txt
βββ .gitignore
βββ README.md
A Python virtual environment is also used locally:
venv/
The virtual environment is excluded from version control through .gitignore.
git clone <your-repository-url>
cd spam-classifierpython -m venv venvvenv\Scripts\activatevenv\Scripts\Activate.ps1pip install -r requirements.txtThe notebook also contains a WordCloud installation cell:
pip install wordcloudIf WordCloud is not already installed in your environment, install it separately.
Start Jupyter Notebook:
jupyter notebookOpen:
spam_classifier.ipynb
Then run the notebook cells from top to bottom.
The notebook automatically downloads the dataset and creates:
dataset/spam.csv
The current project contains one active machine learning model:
Cleaned SMS
β
TF-IDF
β
Multinomial Naive Bayes
β
Spam / Ham
Although GaussianNB and BernoulliNB are initialized in the notebook, their training and prediction code is currently commented out. Therefore, they are not part of the active final model.