Build a Spam Classifier with Python
Train a Naive Bayes text classifier on 5,574 real SMS messages.
Introduction
30 Second Summary
Every day, your phone filters out dozens of spam messages before you even see them. Behind that seemingly simple filter is a machine learning model trained on thousands of real messages.
In this project, you will build your own spam classifier from scratch using Python and scikit-learn. You will train a model on real SMS messages, evaluate its accuracy, and use it to classify new text interactively.
What You'll Build
A command-line spam classifier that loads 5,574 real SMS messages, trains a Naive Bayes model using a TF-IDF text vectorization pipeline, and lets you classify new messages interactively.
By the end of this project, you'll have:
- A Python virtual environment with pinned dependencies, following professional project structure.
- A trained ML pipeline that classifies text as spam or ham with over 95% accuracy.
- An interactive prediction mode that classifies any message you type in real time.
- Secret Mission: Compare two algorithms head-to-head and see which one catches spam with more confidence.
Are there any prerequisites?
You need software engineering experience in any language. No prior Python or machine learning knowledge is required.
Set Up Your Python Environment
Your Python environment is ready with pandas and scikit-learn installed. Now you need data to train your spam classifier.
Every machine learning model starts with a dataset. You will download a collection of real SMS messages, load them into Python, and explore their structure to understand what you are working with.
In this step, get ready to:
- Download the SMS Spam Collection dataset.
- Write a Python script that loads the data into a DataFrame.
- Explore the dataset and convert labels to numeric values.
Download the SMS Spam Collection dataset
The SMS Spam Collection is a public dataset from the UCI Machine Learning Repository. It contains 5,574 real SMS messages labeled as either "ham" (legitimate) or "spam".
- Download the dataset ZIP file by running this command in your terminal:
curl -L -o sms_spam.zip https://archive.ics.uci.edu/static/public/228/sms+spam+collection.zip
- Extract the ZIP file by running:
unzip sms_spam.zip
- Confirm the dataset file exists by running:
ls SMSSpamCollection
You should see SMSSpamCollection listed in the output.
What is this dataset?
Each line in the file has a label (ham or spam) followed by a tab character, then the raw SMS message text. There are 4,827 ham messages and 747 spam messages.
Command not found?
If curl or unzip is not available, download the ZIP manually from the UCI Machine Learning Repository page and extract it into your project folder.
Help me download or extract the SMS Spam Collection dataset into my project directory.
- Clean up the ZIP file and readme since you only need the dataset:
rm sms_spam.zip readme
Create the spam classifier script
With your data in place, you need a Python script to load it into a structured format. DataFrames give you a tabular view of your data with labeled columns.
- Create a new file called spam_classifier.py in your project directory by running:
touch spam_classifier.py
- Confirm the file was created by running:
ls
You should see spam_classifier.py and SMSSpamCollection in the output.
- Open spam_classifier.py in VS Code by clicking on it in the Explorer sidebar.
- Add the import and data-loading function by copying this code into spam_classifier.py:
import pandas as pd
def load_data(filepath):
"""Load the SMS Spam Collection dataset."""
df = pd.read_csv(
filepath,
sep="\t",
header=None,
names=["label", "message"],
)
# Convert text labels to numbers: ham = 0, spam = 1
df["label_num"] = df["label"].map({"ham": 0, "spam": 1})
return df
What does this code do?
- pd.read_csv() with sep="\t" reads the tab-separated file. The header=None parameter tells pandas there is no header row in the file.
- names=["label", "message"] assigns column names so you can reference them easily.
- df["label"].map({"ham": 0, "spam": 1}) converts the text labels into numbers that a machine learning model can work with.
- Add the exploration function and main block below load_data:
def explore_data(df):
"""Print dataset statistics."""
print(f"Dataset shape: {df.shape}")
print(f"\nLabel distribution:")
print(df["label"].value_counts())
print(f"\nSample ham message: {df[df['label'] == 'ham']['message'].iloc[0]}")
print(f"\nSample spam message: {df[df['label'] == 'spam']['message'].iloc[0]}")
def main():
df = load_data("SMSSpamCollection")
explore_data(df)
if __name__ == "__main__":
main()
What does this code do?
- explore_data() prints the dataset's shape, how many messages are in each class, and one example from each class so you can see what ham and spam look like.
- main() calls load_data and explore_data in sequence to run the full workflow.
- if __name__ == "__main__" ensures main() only runs when you execute the script directly, not when importing it.
Run the script and explore the data
- Save spam_classifier.py.
- Run the script by executing this command in your terminal:
python3 spam_classifier.py
You should see output showing the dataset shape, the label distribution (4,827 ham and 747 spam), and sample messages from each class.
What should I see?
The output shows Dataset shape: followed by the number of rows and three columns (label, message, and label_num).
The label distribution confirms the dataset is imbalanced. There are far more ham messages than spam. This is realistic because most messages people receive are legitimate.
Seeing a FileNotFoundError?
Make sure SMSSpamCollection is in the same directory as spam_classifier.py. Run ls to check that both files are listed in the same folder.
Help me fix the FileNotFoundError in my spam classifier script.
✔️ Awesome, I've got everything!
Great. Make sure you have saved spam_classifier.py before moving on.
ⓧ I'd like to double check the full code
import pandas as pd
def load_data(filepath):
"""Load the SMS Spam Collection dataset."""
df = pd.read_csv(
filepath,
sep="\t",
header=None,
names=["label", "message"],
)
df["label_num"] = df["label"].map({"ham": 0, "spam": 1})
return df
def explore_data(df):
"""Print dataset statistics."""
print(f"Dataset shape: {df.shape}")
print(f"\nLabel distribution:")
print(df["label"].value_counts())
print(f"\nSample ham message: {df[df['label'] == 'ham']['message'].iloc[0]}")
print(f"\nSample spam message: {df[df['label'] == 'spam']['message'].iloc[0]}")
def main():
df = load_data("SMSSpamCollection")
explore_data(df)
if __name__ == "__main__":
main()
Your data is loaded, explored, and ready for machine learning. Next up, you will build a scikit-learn Pipeline that transforms these raw messages into numerical features and trains a Naive Bayes classifier.
Load and Explore the Dataset
Your dataset is loaded and you know what you're working with: 5,574 SMS messages, split between 4,827 ham messages and 747 spam messages.
Now it's time to prepare the data for machine learning. You'll split it into training and test sets, then build a scikit-learn Pipeline that transforms raw text into spam predictions.
In this step, get ready to:
- Import scikit-learn components and split data into training and test sets.
- Build and train a three-stage ML pipeline.
- Print the pipeline structure to verify the full workflow.
Import scikit-learn and split the data
Before a model can learn patterns, you need to hold some data back for testing. Training on all the data means you have no way to measure how well the model performs on messages it has never seen.
- In spam_classifier.py, find the existing import pandas as pd line at the top of the file.
- Add the following scikit-learn imports directly below it:
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.feature_extraction.text import CountVectorizer, TfidfTransformer
from sklearn.naive_bayes import MultinomialNB
from sklearn.metrics import classification_report, accuracy_score
What do these imports do?
- train_test_split splits your data into separate training and test sets.
- Pipeline chains multiple processing steps into a single object.
- CountVectorizer converts text into word counts. TfidfTransformer normalizes those counts into weights that reflect word importance.
- MultinomialNB is the Naive Bayes classifier that learns from TF-IDF features.
- classification_report and accuracy_score measure model performance. You'll use these in the next step.
Now you'll add the train/test split logic to your main() function.
- In your main() function, find the explore_data(df) line.
- Add the following code directly below it:
X_train, X_test, y_train, y_test = train_test_split(
df["message"],
df["label_num"],
test_size=0.2,
random_state=42,
)
print(f"\nTraining samples: {len(X_train)}, Test samples: {len(X_test)}")
What does this code do?
train_test_split randomly shuffles your dataset and holds back 20% for testing (test_size=0.2). The remaining 80% is used for training.
Setting random_state=42 fixes the random seed so the split is identical every time you run the script. This makes your results reproducible.
- Save spam_classifier.py.
- Run the script in your terminal to confirm the split works:
python spam_classifier.py
- You should see the dataset statistics from before, followed by a new line: Training samples: 4459, Test samples: 1115.
Not seeing the training/test counts?
- Make sure the new code is indented inside main(), at the same indentation level as explore_data(df).
- Check that your virtual environment is still active. You should see (.venv) at the start of your terminal prompt.
- If you see ModuleNotFoundError: No module named 'sklearn', reactivate your virtual environment with source .venv/bin/activate and try again.
Help me debug my train/test split code
Build and train the ML pipeline
A Pipeline chains multiple processing steps into one object. Instead of manually calling each step in sequence, the pipeline handles the entire flow: tokenize text, weight the tokens, then classify.
Why use a Pipeline instead of separate steps?
Pipelines are the idiomatic pattern in scikit-learn for production ML systems. They encapsulate the entire workflow (preprocess, transform, classify) into a single object that you can fit, predict, and serialize.
This also prevents data leakage. The pipeline ensures that transformations learned from training data (like vocabulary) are never accidentally contaminated by test data.
- In spam_classifier.py, add the following function between the explore_data function and the main() function:
def build_and_train_pipeline(X_train, y_train):
"""Build and train the ML pipeline."""
# Create a three-stage pipeline: tokenize -> weight -> classify
pipeline = Pipeline([
("vectorizer", CountVectorizer()),
("tfidf", TfidfTransformer()),
("classifier", MultinomialNB()),
])
# Train the pipeline on the training data
pipeline.fit(X_train, y_train)
return pipeline
What does this pipeline do?
- CountVectorizer (named "vectorizer"): Tokenizes each message into individual words and counts how many times each word appears.
- TfidfTransformer (named "tfidf"): Normalizes word counts into TF-IDF weights. Common words like "the" get downweighted. Rare, distinctive words like "prize" get upweighted.
- MultinomialNB (named "classifier"): A Naive Bayes classifier that learns which word patterns are associated with spam vs ham.
Calling pipeline.fit(X_train, y_train) runs all three stages on the training data in sequence.
Now call this function from main() and print the pipeline structure.
- In your main() function, find the print(f"\nTraining samples: line you just added.
- Add the following code directly below it:
pipeline = build_and_train_pipeline(X_train, y_train)
print(f"\nPipeline structure: {pipeline}")
- Save spam_classifier.py and run it again:
python spam_classifier.py
- You should see the pipeline structure printed, showing all three stages: Pipeline(steps=[('vectorizer', CountVectorizer()), ('tfidf', TfidfTransformer()), ('classifier', MultinomialNB())]).
Seeing a NameError?
- If you see NameError: name 'train_test_split' is not defined, make sure the import lines are at the very top of your file, below import pandas as pd.
- If you see NameError: name 'build_and_train_pipeline' is not defined, check that the function is defined above main(), not inside it.
- If you see NameError: name 'X_train' is not defined, make sure the train_test_split call appears before the build_and_train_pipeline call inside main().
Help me fix my pipeline code
✔️ Awesome, I've got everything!
Great. Double check you've saved spam_classifier.py and confirm the pipeline structure prints when you run the script.
ⓧ I'd like to double check the full code
Here is the complete spam_classifier.py at this point:
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.feature_extraction.text import CountVectorizer, TfidfTransformer
from sklearn.naive_bayes import MultinomialNB
from sklearn.metrics import classification_report, accuracy_score
def load_data(filepath):
"""Load the SMS Spam Collection dataset."""
df = pd.read_csv(
filepath,
sep="\t",
header=None,
names=["label", "message"],
)
df["label_num"] = df["label"].map({"ham": 0, "spam": 1})
return df
def explore_data(df):
"""Print dataset statistics."""
print(f"Dataset shape: {df.shape}")
print(f"\nLabel distribution:")
print(df["label"].value_counts())
print(f"\nSample ham message: {df[df['label'] == 'ham']['message'].iloc[0]}")
print(f"\nSample spam message: {df[df['label'] == 'spam']['message'].iloc[0]}")
def build_and_train_pipeline(X_train, y_train):
"""Build and train the ML pipeline."""
pipeline = Pipeline([
("vectorizer", CountVectorizer()),
("tfidf", TfidfTransformer()),
("classifier", MultinomialNB()),
])
pipeline.fit(X_train, y_train)
return pipeline
def main():
df = load_data("SMSSpamCollection")
explore_data(df)
X_train, X_test, y_train, y_test = train_test_split(
df["message"],
df["label_num"],
test_size=0.2,
random_state=42,
)
print(f"\nTraining samples: {len(X_train)}, Test samples: {len(X_test)}")
pipeline = build_and_train_pipeline(X_train, y_train)
print(f"\nPipeline structure: {pipeline}")
if __name__ == "__main__":
main()
Your ML pipeline is trained and ready to make predictions. Next up, you'll evaluate how accurate it is and build an interactive prediction mode.
Build the ML Pipeline and Train
Your pipeline is built and trained on 4,459 SMS messages. But how good is it actually? A model that's 95% accurate sounds great until you realize it might be letting spam slip through or flagging legitimate messages.
In this step, you'll evaluate the model's performance using a classification report, then build an interactive mode that lets you test it with your own messages in real time.
In this step, get ready to:
- Evaluate your trained model with precision, recall, and F1-score metrics.
- Build an interactive prediction loop that classifies any text you type.
- Run the full classifier end-to-end and test it with real messages.
Evaluate model performance
Accuracy alone doesn't tell the full story for a spam classifier. You need to know how many spam messages it catches (recall) and how often it's correct when it says something is spam (precision).
- In spam_classifier.py, find the end of the build_and_train_pipeline function.
- Add the following evaluate_model function below it:
def evaluate_model(pipeline, X_test, y_test):
"""Evaluate the trained pipeline."""
y_pred = pipeline.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
print(f"\nAccuracy: {accuracy:.4f}")
print(f"\nClassification Report:")
print(classification_report(y_test, y_pred, target_names=["ham", "spam"]))
return accuracy
What does this code do?
This function runs the trained pipeline against messages it has never seen before (the test set) and measures how well it performs.
- pipeline.predict(X_test) classifies every message in the test set.
- accuracy_score calculates the overall percentage of correct predictions.
- classification_report breaks down performance per class with precision, recall, and F1-score.
What are precision, recall, and F1-score?
Precision answers: "Of all messages flagged as spam, how many actually were spam?" High precision means fewer false alarms.
Recall answers: "Of all actual spam messages, how many did the model catch?" High recall means less spam slipping through.
F1-score is the balanced average of both. It gives you a single number to compare models.
- Save spam_classifier.py.
- You won't see a change yet. The next substep wires this function into main() so you can run it.
Seeing a red underline on accuracy_score or classification_report?
Both functions are already imported at the top of your file from the previous step. Check that your import line still reads from sklearn.metrics import classification_report, accuracy_score.
Make sure the function is defined at the top level (no extra indentation) and placed below build_and_train_pipeline.
Why isn't my evaluate_model function recognized?
Add interactive prediction
A model that only works on pre-loaded test data isn't very useful in practice. You'll add a loop that lets you type any message and instantly see whether the model thinks it's spam or ham.
- Below the evaluate_model function, add the following interactive_predict function:
def interactive_predict(pipeline):
"""Run interactive prediction loop."""
print("\n--- Interactive Spam Classifier ---")
print("Type a message to classify (or 'quit' to exit):\n")
while True:
message = input("> ")
if message.lower() == "quit":
break
prediction = pipeline.predict([message])[0]
label = "SPAM" if prediction == 1 else "HAM"
print(f" Prediction: {label}\n")
What does this code do?
- The while True loop keeps asking for input until the user types quit.
- pipeline.predict([message])[0] wraps the input in a list (scikit-learn expects an array of documents) and grabs the first prediction.
- The prediction is 1 for spam or 0 for ham, matching the mapping you created earlier.
- Save spam_classifier.py.
Indentation error in the while loop?
Make sure every line inside while True: is indented by 8 spaces (two levels). The if message.lower() and prediction = lines should be at the same indent level.
Help me fix my indentation in interactive_predict.
Wire up main() and run the classifier
Both functions are defined but not called yet. You need to add them to main() so the script runs evaluation and prediction after training.
- In your main() function, find this line:
print(f"\nPipeline structure: {pipeline}")
- Add these two lines directly below it:
evaluate_model(pipeline, X_test, y_test)
interactive_predict(pipeline)
What does this do?
This completes the ML workflow: load data, train a model, evaluate its performance, then let the user interact with it. The order matters because evaluate_model needs the trained pipeline, and interactive_predict runs last since it blocks on user input.
- Save spam_classifier.py.
- Run the full script by running this command in your terminal:
python spam_classifier.py
You should see the dataset statistics, training info, accuracy score, and a full classification report. Then the interactive prompt appears.
- Try typing a message like Congratulations! You've won a free iPhone. Click here now! and press Enter.
- Try a normal message like Hey, are we still meeting for lunch tomorrow? and press Enter.
- Type quit to exit the interactive mode.
Seeing a FileNotFoundError?
Make sure your terminal is in the same folder as SMSSpamCollection and spam_classifier.py. Run ls to confirm both files are listed.
Also confirm your virtual environment is still active. You should see (.venv) at the start of your terminal prompt.
Help me fix my spam classifier script.
✔️ Awesome, I've got everything!
Great. Double check you've saved your file and the interactive classifier is working.
ⓧ I'd like to double check the full code
Here's the complete spam_classifier.py at this point:
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.feature_extraction.text import CountVectorizer, TfidfTransformer
from sklearn.naive_bayes import MultinomialNB
from sklearn.metrics import classification_report, accuracy_score
def load_data(filepath):
"""Load the SMS Spam Collection dataset."""
df = pd.read_csv(
filepath,
sep="\t",
header=None,
names=["label", "message"],
)
df["label_num"] = df["label"].map({"ham": 0, "spam": 1})
return df
def explore_data(df):
"""Print dataset statistics."""
print(f"Dataset shape: {df.shape}")
print(f"\nLabel distribution:")
print(df["label"].value_counts())
print(f"\nSample ham message: {df[df['label'] == 'ham']['message'].iloc[0]}")
print(f"\nSample spam message: {df[df['label'] == 'spam']['message'].iloc[0]}")
def build_and_train_pipeline(X_train, y_train):
"""Build and train the ML pipeline."""
pipeline = Pipeline([
("vectorizer", CountVectorizer()),
("tfidf", TfidfTransformer()),
("classifier", MultinomialNB()),
])
pipeline.fit(X_train, y_train)
return pipeline
def evaluate_model(pipeline, X_test, y_test):
"""Evaluate the trained pipeline."""
y_pred = pipeline.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
print(f"\nAccuracy: {accuracy:.4f}")
print(f"\nClassification Report:")
print(classification_report(y_test, y_pred, target_names=["ham", "spam"]))
return accuracy
def interactive_predict(pipeline):
"""Run interactive prediction loop."""
print("\n--- Interactive Spam Classifier ---")
print("Type a message to classify (or 'quit' to exit):\n")
while True:
message = input("> ")
if message.lower() == "quit":
break
prediction = pipeline.predict([message])[0]
label = "SPAM" if prediction == 1 else "HAM"
print(f" Prediction: {label}\n")
def main():
df = load_data("SMSSpamCollection")
explore_data(df)
X_train, X_test, y_train, y_test = train_test_split(
df["message"],
df["label_num"],
test_size=0.2,
random_state=42,
)
print(f"\nTraining samples: {len(X_train)}, Test samples: {len(X_test)}")
pipeline = build_and_train_pipeline(X_train, y_train)
print(f"\nPipeline structure: {pipeline}")
evaluate_model(pipeline, X_test, y_test)
interactive_predict(pipeline)
if __name__ == "__main__":
main()
Your spam classifier is fully functional. It loads real SMS data, trains a model, measures its performance, and classifies new messages on demand. Next up, you'll take on the Secret Mission and pit a second algorithm against Naive Bayes to see which one wins.
Evaluate and Predict
Your pipeline is trained, but a trained model is only useful if it performs well on messages it has never seen before. The test set you held back earlier is the proving ground.
In this step, you'll run the full classifier, read the evaluation metrics to understand exactly where the model excels, and then test it yourself with custom messages in the interactive prediction loop.
In this step, get ready to:
- Run the classifier and check the accuracy score.
- Interpret the classification report (precision, recall, F1-score).
- Test the interactive prediction mode with your own messages.
Run the classifier and check accuracy
The evaluate_model function in your script uses accuracy_score to compare the pipeline's predictions against the true labels on the held-out test set.
- Run the spam classifier by entering this command in your terminal:
python spam_classifier.py
The script loads the dataset, explores it, trains the pipeline, and then prints the accuracy score. You should see an accuracy value above 0.95 (95%).
What does accuracy tell you?
Accuracy is the percentage of test messages the model classified correctly. An accuracy above 0.95 means the model gets more than 95 out of every 100 messages right.
However, accuracy alone can be misleading when classes are imbalanced. Your dataset has far more ham (4,827) than spam (747), so a model that always guesses "ham" would still get 86% accuracy without detecting any spam at all.
Interpret the classification report
Right below the accuracy line, the script prints a full classification report. This table gives you a more detailed picture of how the model performs on each class separately.
- Look at the classification report in your terminal output.
- Find the two rows labeled ham and spam with their precision, recall, and f1-score columns.
What do precision, recall, and F1-score mean?
- Precision: of all messages the model labeled as spam, what percentage were actually spam? High precision means fewer false alarms.
- Recall: of all messages that truly are spam, what percentage did the model catch? High recall means fewer missed spam messages.
- F1-score: the harmonic mean of precision and recall. It balances both into a single number, so you can compare performance at a glance.
- Support: the number of actual messages in that class within the test set.
What should I see in my report?
For the ham class, you should see precision, recall, and F1 all near 0.99. The model is excellent at identifying legitimate messages.
For the spam class, recall may be slightly lower (around 0.90-0.95). This means a few real spam messages slip through undetected, which is a common trade-off for Naive Bayes text classifiers.
Test the interactive prediction mode
After the classification report prints, the script automatically enters the interactive prediction loop. This is where you can type any message and see whether the model classifies it as spam or ham.
- At the > prompt, type a ham-like message and press Enter:
- Try: Hey, are we still meeting for lunch tomorrow? and confirm the prediction says HAM.
- Now type a spam-like message:
- Try: CONGRATULATIONS! You've won a free iPhone! Click here to claim your prize NOW and confirm the prediction says SPAM.
- Type quit to exit the interactive loop.
Why does the model get these right?
The TF-IDF vectorizer learned which words are strong spam signals during training. Words like "free", "won", "prize", and "claim" appear frequently in spam but rarely in ham. The model weights these words heavily when making predictions.
Predictions seem wrong?
- Make sure you typed a full sentence, not just one word. Single words may not carry enough signal for a confident prediction.
- Very short messages (1-2 words) can produce surprising results because the model has limited features to work with.
- Try messages that closely resemble real spam or real texts to see the model at its best.
Still confused about the predictions? Why is my spam classifier predicting this message incorrectly?
Your spam classifier is fully working. You've trained a model, measured its performance with real evaluation metrics, and tested it on custom input. Next up in the Secret Mission, you'll swap in a different algorithm and see how it compares head-to-head.
Secret mission
Algorithm Showdown
Your Naive Bayes model works well, but is it the best option? In this secret mission, you'll swap in Logistic Regression as a challenger and pit the two algorithms against each other. You'll compare accuracy, precision, recall, and confidence scores side-by-side to see which model wins the spam-fighting crown.
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely locally, so there are no ongoing costs. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.
Resources you used:
- Project folder on your Desktop (contains spam_classifier.py, SMSSpamCollection, and the .venv virtual environment).
- Python 3.13 installation (installed from python.org).
Keep everything running
No action needed. Choose this if you plan to keep experimenting with the spam classifier or want to use Python 3.13 for future projects.
- Your project folder stays on your Desktop with all files intact.
- Python 3.13 remains installed and available system-wide.
- The .venv virtual environment and its packages (pandas, scikit-learn) stay inside your project folder.
Pause - I'll come back to this later
Deactivate the virtual environment so it's no longer active in your terminal session. Your files stay on disk for later use.
- In your terminal, deactivate the virtual environment by running:
deactivate
- Close the project in VS Code by clicking File, then Close Folder.
- When you return, reactivate the environment by running source .venv/bin/activate from inside your project folder.
Delete - I don't want to use this again
Remove all project resources and optionally uninstall Python 3.13 from your system.
- Deactivate the virtual environment first (if still active) by running:
deactivate
- Navigate to your Desktop and delete your project folder by running:
cd ~/Desktop
rm -rf spam-classifier
What if my folder has a different name?
Replace spam-classifier with whatever you named your project folder in Step 1. You can check with ls ~/Desktop to find it.
- (Optional) To uninstall Python 3.13 entirely, remove the three components the python.org installer added to your system:
sudo rm -rf /Library/Frameworks/Python.framework/Versions/3.13
sudo rm -rf /Applications/Python\ 3.13/
ls /usr/local/bin/python3.13*
- The ls command shows any remaining symlinks. Remove each one listed with sudo rm followed by the file path.
- Do not remove /usr/bin/python3. That is Apple's system Python and is controlled by macOS.
Keep Python if you plan to learn more
Python 3.13 is useful for many projects beyond this one. If you plan to continue learning data science or machine learning, keep it installed.
That's a Wrap!
That's a Wrap!
Nice work! You just built a working machine learning spam classifier from scratch using Python and scikit-learn.
You've learned how to:
- Built and trained an ML text classification pipeline using scikit-learn's Pipeline pattern to preprocess, vectorize, and classify SMS messages.
- Evaluated model performance using precision, recall, and F1-score to understand how well your classifier distinguishes spam from ham.
- Created an interactive prediction system that classifies arbitrary text input in real time.
- Secret Mission: Compared Naive Bayes vs Logistic Regression performance head-to-head with confidence scores.
Ready to quiz yourself?