Spaces:
Sleeping
Sleeping
File size: 2,645 Bytes
1159eb1 c23f0f7 1159eb1 c23f0f7 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 | from sklearn.pipeline import Pipeline
import pandas as pd
import numpy as np
from typing import Tuple
def probabilities_and_labels(model: Pipeline, X: pd.DataFrame) -> pd.DataFrame:
predictions = model.predict(X)
probabilities = model.predict_proba(X)
classes = model.named_steps["clf"].classes_
proba_df = pd.DataFrame(probabilities, columns=classes, index=X.index)
return predictions, proba_df
def predict_one(model: Pipeline, subject: str, body: str) -> tuple[str, pd.Series]:
X = pd.DataFrame([{
"subject": subject or "",
"body": body or "",
}])
y_pred, proba_df = probabilities_and_labels(model, X)
return y_pred[0], proba_df.iloc[0]
def explain_linear_top_features(
model: Pipeline,
X: pd.DataFrame,
class_label: str,
top_k: int = 10,
) -> list[str]:
"""
Returns top_k feature contributions for `class_label` on the first row of X.
Contributions are in logit units (not probability).
"""
if "text" not in model.named_steps:
return ["Feature explanations unavailable for this model."]
text = model.named_steps["text"]
clf = model.named_steps["clf"]
Xv = text.transform(X) # sparse (1, n_features)
feature_names = text.get_feature_names_out()
classes = list(clf.classes_)
class_idx = classes.index(class_label)
coef = clf.coef_ # (K, n_features) for multiclass, or (1, n_features) for binary
if coef.shape[0] == 1 and len(classes) == 2:
# binary special-case: sklearn stores only coef for classes_[1]
coef_c = coef[0] if class_idx == 1 else -coef[0]
else:
coef_c = coef[class_idx]
row = Xv[0]
idx = row.indices
vals = row.data
if idx.size == 0:
return ["No nonzero TF-IDF features (empty subject/body after preprocessing)."]
contrib = vals * coef_c[idx] # per-feature contribution for this class
order = np.argsort(np.abs(contrib))[::-1][:top_k]
reasons = []
for k in order:
feat = feature_names[idx[k]] # e.g. "subject__password" or "body__unsubscribe"
reasons.append(f"{feat}: {contrib[k]:+.3f}")
return reasons
def predict_one_with_reasons(model: Pipeline, subject: str, body: str, top_k: int = 10):
X = pd.DataFrame([{"subject": subject or "", "body": body or ""}])
proba = model.predict_proba(X)[0]
classes = list(model.named_steps["clf"].classes_)
probs = pd.Series(proba, index=classes)
label = probs.idxmax()
confidence = float(probs.max())
reasons = explain_linear_top_features(model, X, class_label=label, top_k=top_k)
return label, confidence, probs, reasons
|