Simplify cleaning transformers and shorten module names

Move cleaning transformers into classifiers/cleaning.py (dropping the
separate cleaning package) and implement them as plain classes rather
than BaseEstimator/TransformerMixin subclasses, since Pipeline only
needs fit/transform via duck typing. Also rename feature_classifier.py
and bag_of_words.py to features.py and bow.py for brevity.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Chris Proctor
2026-06-08 10:02:41 -04:00
parent 5f6f171369
commit bbe8054910
6 changed files with 6 additions and 10 deletions

46
classifiers/bow.py Normal file
View File

@@ -0,0 +1,46 @@
from collections import Counter
import numpy as np
from sklearn.feature_extraction import DictVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline
from classifiers.cleaning import LowercaseTransformer, PunctuationRemover
class FeatureExtractor:
def fit(self, X, y=None):
return self
def transform(self, X):
return [self.extract_features(msg) for msg in X]
def extract_features(self, message):
return dict(Counter(message.split()))
class BagOfWordsClassifier:
def fit(self, X, y):
self._pipeline = Pipeline([
("lowercase", LowercaseTransformer()),
("punctuation", PunctuationRemover()),
("features", FeatureExtractor()),
("vectorizer", DictVectorizer()),
("classifier", LogisticRegression(max_iter=1000)),
])
y_binary = (np.array(y) == "spam").astype(int)
self._pipeline.fit(X, y_binary)
return self
def predict(self, X):
y_binary = self._pipeline.predict(X)
return np.where(y_binary == 1, "spam", "ham")
def feature_weights(self, top_n=10):
vectorizer = self._pipeline.named_steps["vectorizer"]
classifier = self._pipeline.named_steps["classifier"]
names = vectorizer.get_feature_names_out()
weights = classifier.coef_[0]
pairs = sorted(zip(names, weights), key=lambda x: x[1])
half = top_n // 2
return pairs[-half:][::-1] + pairs[:half]