Text Classification
Scikit-learn
scikit-learn
skops
intent-classification
selective-classification
error-prediction
banking
advisory
PolyAI/banking77
Instructions to use ITheEqualizer/banking77-intent-error-predictor with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Scikit-learn
How to use ITheEqualizer/banking77-intent-error-predictor with Scikit-learn:
# ⚠️ Model filename not specified in config.json
- Notebooks
- Google Colab
- Kaggle
| from __future__ import annotations | |
| import hashlib | |
| import json | |
| import math | |
| import unicodedata | |
| from pathlib import Path | |
| from typing import Any, cast | |
| import numpy as np | |
| import skops.io as sio | |
| def sha256_file(path: Path) -> str: | |
| digest = hashlib.sha256() | |
| with path.open("rb") as stream: | |
| for chunk in iter(lambda: stream.read(1 << 20), b""): | |
| digest.update(chunk) | |
| return digest.hexdigest() | |
| def normalize_text(value: str, maximum_characters: int) -> str: | |
| if not isinstance(value, str): | |
| raise TypeError("input must be a string") | |
| if len(value) > maximum_characters: | |
| raise ValueError(f"input must not exceed {maximum_characters} characters") | |
| normalized = " ".join(unicodedata.normalize("NFKC", value).casefold().split()) | |
| if not normalized: | |
| raise ValueError("input must not be empty") | |
| return normalized | |
| def risk_features(probabilities: np.ndarray, texts: list[str]) -> np.ndarray: | |
| clipped = np.clip(probabilities, 1e-12, 1.0) | |
| ordered = np.sort(clipped, axis=1) | |
| top1 = ordered[:, -1] | |
| top2 = ordered[:, -2] | |
| entropy = -(clipped * np.log(clipped)).sum(axis=1) / math.log(clipped.shape[1]) | |
| shapes = np.asarray( | |
| [ | |
| [ | |
| min(len(text), 512) / 512, | |
| min(len(text.split()), 100) / 100, | |
| min(sum(ch.isdigit() for ch in text), 20) / 20, | |
| float("?" in text), | |
| float( | |
| any( | |
| token in text.split() | |
| for token in ("not", "no", "never", "wrong") | |
| ) | |
| ), | |
| ] | |
| for text in texts | |
| ], | |
| dtype=np.float64, | |
| ) | |
| predicted_one_hot = np.zeros_like(clipped) | |
| predicted_one_hot[np.arange(len(clipped)), np.argmax(clipped, axis=1)] = 1.0 | |
| return np.column_stack( | |
| [clipped, predicted_one_hot, top1, top2, top1 - top2, entropy, shapes] | |
| ) | |
| def _load_checked(path: Path, expected_hash: str) -> dict[str, Any]: | |
| if sha256_file(path) != expected_hash: | |
| raise RuntimeError(f"artifact checksum mismatch: {path.name}") | |
| untrusted = sio.get_untrusted_types(file=path) | |
| if untrusted: | |
| raise RuntimeError(f"artifact requires untrusted skops types: {untrusted}") | |
| return cast(dict[str, Any], sio.load(path, trusted=[])) | |
| def predict_text( | |
| text: str, | |
| *, | |
| primary_path: Path = Path("primary_baseline.skops"), | |
| risk_path: Path = Path("model.skops"), | |
| config_path: Path = Path("config.json"), | |
| ) -> dict[str, Any]: | |
| config = cast(dict[str, Any], json.loads(config_path.read_text(encoding="utf-8"))) | |
| if config.get("schema_version") != "1.0": | |
| raise RuntimeError("unsupported configuration schema") | |
| normalized = normalize_text(text, int(config["input"]["maximum_characters"])) | |
| primary = _load_checked(primary_path, str(config["primary_artifact_sha256"])) | |
| candidate = _load_checked(risk_path, str(config["artifact_sha256"])) | |
| labels = cast(list[str], config["labels"]) | |
| if labels != primary["labels"] or labels != candidate["labels"]: | |
| raise RuntimeError("label mapping mismatch") | |
| probabilities = np.asarray( | |
| primary["classifier"].predict_proba( | |
| primary["features"].transform([normalized]) | |
| ), | |
| dtype=np.float64, | |
| ) | |
| if probabilities.shape != (1, len(labels)) or not np.isfinite(probabilities).all(): | |
| raise RuntimeError("primary model returned invalid probabilities") | |
| predicted = int(probabilities[0].argmax()) | |
| error_risk = float( | |
| candidate["classifier"].predict_proba( | |
| risk_features(probabilities, [normalized]) | |
| )[0, 1] | |
| ) | |
| threshold = float(config["review_at_or_above_error_risk"]) | |
| return { | |
| "intent": labels[predicted], | |
| "intent_score": float(probabilities[0, predicted]), | |
| "error_risk": error_risk, | |
| "review": error_risk >= threshold, | |
| "review_threshold": threshold, | |
| "advisory_only": True, | |
| "score_semantics": config["score_semantics"], | |
| } | |
| def main() -> None: | |
| import argparse | |
| parser = argparse.ArgumentParser(description="Route one banking-support query") | |
| parser.add_argument("text") | |
| parser.add_argument("--primary", type=Path, default=Path("primary_baseline.skops")) | |
| parser.add_argument("--risk-model", type=Path, default=Path("model.skops")) | |
| parser.add_argument("--config", type=Path, default=Path("config.json")) | |
| args = parser.parse_args() | |
| result = predict_text( | |
| args.text, | |
| primary_path=args.primary, | |
| risk_path=args.risk_model, | |
| config_path=args.config, | |
| ) | |
| print(json.dumps(result, indent=2, sort_keys=True)) | |
| if __name__ == "__main__": | |
| main() | |