Spaces:
Sleeping
Sleeping
| 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 | |