Phishing lives or dies on language, so this is where natural language processing earns its place in a security curriculum. The simplest useful representation is a bag of words: count how often each word appears, optionally weighted by TF-IDF (term frequency, inverse document frequency), which down-weights words so common they carry no signal ("the," "and") and up-weights words that are rare overall but concentrated in one class.
phishing_tfidf_pipeline.py
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline
pipeline = Pipeline([
("tfidf", TfidfVectorizer(stop_words="english", ngram_range=(1, 2),
min_df=2, max_features=5000)),
("clf", LogisticRegression(max_iter=2000, class_weight="balanced")),
])
pipeline.fit(email_texts_train, labels_train)
print(f"Test accuracy: {pipeline.score(email_texts_test, labels_test):.3f}")
ML concept: Why ngram_range=(1, 2)?
A pure bag-of-words model (unigrams only) sees "urgent" and "action" as two independent signals. Setting ngram_range=(1, 2) also counts adjacent word pairs (bigrams), so the model can separately learn that "urgent action" specifically, not just the two words in isolation, is a strong phishing signal. Bigrams roughly double the vocabulary size, so max_features and min_df matter more once they are enabled, to avoid an explosion of rare, overfit-prone bigrams.
inspect_top_words.py
import numpy as np
feature_names = np.array(pipeline.named_steps["tfidf"].get_feature_names_out())
coefficients = pipeline.named_steps["clf"].coef_[0]
top_spam_words = feature_names[np.argsort(coefficients)[-10:]]
print(top_spam_words)
Tool in focus: A Public Benchmark: the SMS Spam Collection
For text classification specifically, the SMS Spam Collection dataset (compiled by Tiago Almeida and Jose Hidalgo, and hosted on the UCI Machine Learning Repository) is the field's most widely used public benchmark: 5,574 real, hand-labelled English SMS messages, about 13 percent of them spam. It is small enough to train on a laptop in seconds and realistic enough that a model's performance on it transfers meaningfully to real deployment, which is why it appears in so many of the companion code repositories this course draws on.

