Text Classification
Scikit-learn
English
finance
wealth-management
alternative-investments
private-equity
insurance
annuities
structured-products
data-operations
reconciliation
tabular-text
skops
Eval Results (legacy)
Instructions to use Torres1980/alts-transaction-classifier with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Scikit-learn
How to use Torres1980/alts-transaction-classifier with Scikit-learn:
# ⚠️ Model filename not specified in config.json
- Notebooks
- Google Colab
- Kaggle
Download train.py from Torres1980/alts-transaction-classifier: direct link, hf CLI and curl.
- Browser
- Download file 4.69 kB
-
https://huggingface.co/Torres1980/alts-transaction-classifier/resolve/main/train.py
- Command line
-
hf download hf://Torres1980/alts-transaction-classifier/train.py
-
curl -L -o train.py https://huggingface.co/Torres1980/alts-transaction-classifier/resolve/main/train.py
4.69 kB
| """ | |
| Train the alternatives & insurance transaction classifier. | |
| Model: word (1-2 gram) + character (2-5 gram) TF-IDF features into a multinomial | |
| logistic regression. Character n-grams handle abbreviations ("DIST ROC", "MGMT FEE", | |
| "PREM PMT") and typos; word n-grams carry phrase meaning. Predicted probabilities | |
| drive a confidence threshold: items above it are classified straight through, items | |
| below it go to a person. | |
| Two rounds are trained to show the review loop: | |
| v1 - training data only | |
| v2 - training data + 180 items labeled by reviewers from v1's exception queue | |
| The published model.skops is v2. | |
| Run: python data/generate.py && python train.py | |
| """ | |
| import json | |
| import zipfile | |
| from pathlib import Path | |
| import numpy as np | |
| import pandas as pd | |
| import skops.io as sio | |
| from sklearn.feature_extraction.text import TfidfVectorizer | |
| from sklearn.linear_model import LogisticRegression | |
| from sklearn.metrics import accuracy_score, classification_report, f1_score | |
| from sklearn.pipeline import FeatureUnion, Pipeline | |
| ROOT = Path(__file__).parent | |
| SEED = 42 | |
| TARGET_PRECISION = 0.99 | |
| def build_pipeline(): | |
| features = FeatureUnion([ | |
| ("word", TfidfVectorizer(analyzer="word", ngram_range=(1, 2), lowercase=True, sublinear_tf=True, | |
| token_pattern=r"(?u)\b[a-zA-Z][a-zA-Z&/]+\b", min_df=2)), | |
| ("char", TfidfVectorizer(analyzer="char_wb", ngram_range=(2, 5), lowercase=True, sublinear_tf=True, | |
| min_df=3, max_features=30000)), | |
| ]) | |
| return Pipeline([("features", features), | |
| ("clf", LogisticRegression(C=8.0, max_iter=3000, random_state=SEED))]) | |
| def straight_through(proba, y_true, classes, target=TARGET_PRECISION): | |
| """Lowest confidence threshold whose auto-classified items meet the target precision.""" | |
| conf, pred = proba.max(axis=1), classes[proba.argmax(axis=1)] | |
| for t in np.round(np.arange(0.30, 1.0, 0.01), 2): | |
| m = conf >= t | |
| if m.sum() and (pred[m] == y_true[m]).mean() >= target: | |
| return {"threshold": float(t), "auto_rate": round(float(m.mean()), 4), | |
| "auto_precision": round(float((pred[m] == y_true[m]).mean()), 4), | |
| "routed_to_review": round(float(1 - m.mean()), 4)} | |
| return None | |
| def evaluate(model, test): | |
| classes = model.classes_ | |
| proba = model.predict_proba(test.text) | |
| pred = classes[proba.argmax(axis=1)] | |
| y = test.label.to_numpy() | |
| out = {"accuracy": round(accuracy_score(y, pred), 4), | |
| "macro_f1": round(f1_score(y, pred, average="macro"), 4), | |
| "straight_through": straight_through(proba, y, classes), "by_condition": {}} | |
| for c in ("unseen_entities", "unseen_phrasing"): | |
| m = (test.condition == c).to_numpy() | |
| out["by_condition"][c] = {"n": int(m.sum()), "accuracy": round(accuracy_score(y[m], pred[m]), 4), | |
| "macro_f1": round(f1_score(y[m], pred[m], average="macro"), 4), | |
| "straight_through": straight_through(proba[m], y[m], classes)} | |
| return out, pred, y | |
| if __name__ == "__main__": | |
| train = pd.read_csv(ROOT / "data" / "train.csv") | |
| test = pd.read_csv(ROOT / "data" / "test.csv") | |
| feedback = pd.read_csv(ROOT / "data" / "review_feedback.csv") | |
| v1 = build_pipeline().fit(train.text, train.label) | |
| m1, _, _ = evaluate(v1, test) | |
| v2 = build_pipeline().fit(pd.concat([train.text, feedback.text]), pd.concat([train.label, feedback.label])) | |
| m2, pred, y = evaluate(v2, test) | |
| report = classification_report(y, pred, digits=4, output_dict=True) | |
| wrong = test[pred != y].assign(predicted=pred[pred != y]) | |
| metrics = { | |
| "train_size": len(train), "review_feedback_size": len(feedback), "test_size": len(test), | |
| "num_labels": len(v2.classes_), "target_precision": TARGET_PRECISION, | |
| "v1_train_only": m1, "v2_with_review_feedback": m2, | |
| "v2_per_label_f1": {k: round(v["f1-score"], 4) for k, v in report.items() if k in set(v2.classes_)}, | |
| "v2_sample_errors": wrong[["text", "label", "predicted"]].head(12).to_dict("records"), | |
| } | |
| # Drop vocabulary-pruning artifacts; they are only for introspection and bloat the file. | |
| for _, vec in v2.named_steps["features"].transformer_list: | |
| if hasattr(vec, "stop_words_"): | |
| del vec.stop_words_ | |
| sio.dump(v2, ROOT / "model.skops", compression=zipfile.ZIP_DEFLATED, compresslevel=9) | |
| (ROOT / "metrics.json").write_text(json.dumps(metrics, indent=2)) | |
| print(classification_report(y, pred, digits=4)) | |
| for name in ("v1_train_only", "v2_with_review_feedback"): | |
| print(name, json.dumps(metrics[name], indent=1)) | |