Instructions to use itsLu/mentalbert-v6-hierarchical with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use itsLu/mentalbert-v6-hierarchical with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-classification", model="itsLu/mentalbert-v6-hierarchical")# Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("itsLu/mentalbert-v6-hierarchical", dtype="auto") - Notebooks
- Google Colab
- Kaggle
| """ | |
| HuggingFace Inference Endpoints handler for the V6 Hierarchical cascade. | |
| Loads all 5 stages plus Platt calibrators (Stage 1A, Stage 3) and the 3-seed | |
| Stage 2 ensemble. Routes a single string through the cascade. | |
| Supports two operating points selected via `data["mode"]`: | |
| - "balanced" (default): F1-optimal balance with Sui-miss / Sui-FP penalty. | |
| - "safety": stricter Suicidal recall (val Sui->Dep <= 70, calibrated for drift). | |
| """ | |
| import os, json, glob, joblib | |
| import numpy as np | |
| import torch | |
| import torch.nn.functional as F | |
| from transformers import ( | |
| AutoTokenizer, | |
| RobertaForSequenceClassification, | |
| BertTokenizerFast, BertForSequenceClassification, | |
| LongformerTokenizerFast, LongformerForSequenceClassification, | |
| ) | |
| def _apply_platt(calibrator, p0, p1, eps=1e-7): | |
| """Apply Platt scaling to a single binary (p0, p1) probability.""" | |
| p0 = float(np.clip(p0, eps, 1 - eps)) | |
| p1 = float(np.clip(p1, eps, 1 - eps)) | |
| logit = np.log(p1) - np.log(p0) | |
| cal_p1 = float(calibrator.predict_proba(np.array([[logit]]))[:, 1][0]) | |
| return [1.0 - cal_p1, cal_p1] | |
| class EndpointHandler: | |
| def __init__(self, path=""): | |
| with open(os.path.join(path, "config.json"), "r") as f: | |
| self.cfg = json.load(f) | |
| self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu") | |
| self.s2_classes = self.cfg["stage2_class_order"] | |
| # ββ Stage 0: Cardiff RoBERTa ββ | |
| s0_path = os.path.join(path, "stage0") | |
| self.tok0 = AutoTokenizer.from_pretrained(s0_path) | |
| self.m0 = RobertaForSequenceClassification.from_pretrained(s0_path).to(self.device).eval() | |
| # ββ Stage 1A: MentalBERT + Platt calibrator ββ | |
| s1a_path = os.path.join(path, "stage1a") | |
| self.tok1a = BertTokenizerFast.from_pretrained(s1a_path) | |
| self.m1a = BertForSequenceClassification.from_pretrained(s1a_path).to(self.device).eval() | |
| cal_s1a = os.path.join(s1a_path, "platt_calibrator.joblib") | |
| self.platt_s1a = joblib.load(cal_s1a) if os.path.exists(cal_s1a) else None | |
| # ββ Stage 1B: MentalBERT ββ | |
| s1b_path = os.path.join(path, "stage1b") | |
| self.tok1b = BertTokenizerFast.from_pretrained(s1b_path) | |
| self.m1b = BertForSequenceClassification.from_pretrained(s1b_path).to(self.device).eval() | |
| # ββ Stage 2: 3-seed MentalBERT ensemble ββ | |
| seed_dirs = sorted(glob.glob(os.path.join(path, "stage2", "seed_*"))) | |
| if not seed_dirs: | |
| # Backwards compat: single stage2 folder | |
| seed_dirs = [os.path.join(path, "stage2")] | |
| self.tok2_list = [BertTokenizerFast.from_pretrained(d) for d in seed_dirs] | |
| self.m2_list = [BertForSequenceClassification.from_pretrained(d).to(self.device).eval() | |
| for d in seed_dirs] | |
| self.n_s2_models = len(self.m2_list) | |
| # ββ Stage 3: Longformer + Platt calibrator ββ | |
| s3_path = os.path.join(path, "stage3") | |
| self.tok3 = LongformerTokenizerFast.from_pretrained(s3_path) | |
| self.m3 = LongformerForSequenceClassification.from_pretrained(s3_path).to(self.device).eval() | |
| cal_s3 = os.path.join(s3_path, "platt_calibrator.joblib") | |
| self.platt_s3 = joblib.load(cal_s3) if os.path.exists(cal_s3) else None | |
| st = self.cfg["stages"] | |
| self.ml0 = st["stage0"]["max_len"] | |
| self.ml1a = st["stage1a"]["max_len"] | |
| self.ml1b = st["stage1b"]["max_len"] | |
| self.ml2 = st["stage2"]["max_len"] | |
| self.ml3 = st["stage3"]["max_len"] | |
| thr = self.cfg["thresholds"] | |
| self.t0 = float(thr["stage0"]) | |
| self.balanced = {"stage1a": float(thr["balanced"]["stage1a"]), | |
| "stage3": float(thr["balanced"]["stage3"])} | |
| self.safety = {"stage1a": float(thr["safety"]["stage1a"]), | |
| "stage3": float(thr["safety"]["stage3"])} | |
| self.default_mode = thr.get("default_mode", "balanced") | |
| def _probs_bert(self, m, tok, text, max_len): | |
| enc = tok(text, max_length=max_len, padding="max_length", | |
| truncation=True, return_tensors="pt").to(self.device) | |
| out = m(input_ids=enc["input_ids"], attention_mask=enc["attention_mask"]) | |
| return F.softmax(out.logits, dim=-1)[0].cpu().tolist() | |
| def _probs_s2_ensemble(self, text): | |
| """Average softmax probs across all stage-2 seed models.""" | |
| acc = None | |
| for m, tok in zip(self.m2_list, self.tok2_list): | |
| enc = tok(text, max_length=self.ml2, padding="max_length", | |
| truncation=True, return_tensors="pt").to(self.device) | |
| out = m(input_ids=enc["input_ids"], attention_mask=enc["attention_mask"]) | |
| p = F.softmax(out.logits, dim=-1)[0].cpu().numpy() | |
| acc = p if acc is None else acc + p | |
| return (acc / self.n_s2_models).tolist() | |
| def _probs_lf(self, m, tok, text, max_len): | |
| enc = tok(text, max_length=max_len, padding="max_length", | |
| truncation=True, return_tensors="pt").to(self.device) | |
| gmask = torch.zeros_like(enc["attention_mask"]) | |
| gmask[0, 0] = 1 | |
| out = m(input_ids=enc["input_ids"], | |
| attention_mask=enc["attention_mask"], | |
| global_attention_mask=gmask) | |
| return F.softmax(out.logits, dim=-1)[0].cpu().tolist() | |
| def __call__(self, data): | |
| if isinstance(data, str): | |
| text = data; mode = self.default_mode | |
| else: | |
| text = data.get("inputs", "") | |
| if isinstance(text, list): | |
| text = text[0] if len(text) > 0 else "" | |
| mode = data.get("mode", self.default_mode) | |
| if mode not in ("balanced", "safety"): | |
| mode = self.default_mode | |
| thr = self.safety if mode == "safety" else self.balanced | |
| t1a = thr["stage1a"]; t3 = thr["stage3"] | |
| stage_probs = {} | |
| # Stage 0: DA gate | |
| p0 = self._probs_bert(self.m0, self.tok0, text, self.ml0) | |
| stage_probs["stage0"] = p0 | |
| if p0[1] >= self.t0: | |
| return {"label": "Directed Aggression", "exit_stage": "stage0", | |
| "mode": mode, "stage_probs": stage_probs} | |
| # Stage 1A: Sui gate (Platt-calibrated) | |
| p1a_raw = self._probs_bert(self.m1a, self.tok1a, text, self.ml1a) | |
| p1a = _apply_platt(self.platt_s1a, p1a_raw[0], p1a_raw[1]) if self.platt_s1a else p1a_raw | |
| stage_probs["stage1a"] = p1a | |
| stage_probs["stage1a_raw"] = p1a_raw | |
| if p1a[1] >= t1a: | |
| return {"label": "Suicidal", "exit_stage": "stage1a", | |
| "mode": mode, "stage_probs": stage_probs} | |
| # Stage 1B: Normal vs Distress (argmax) | |
| p1b = self._probs_bert(self.m1b, self.tok1b, text, self.ml1b) | |
| stage_probs["stage1b"] = p1b | |
| if p1b[0] > p1b[1]: | |
| return {"label": "Normal", "exit_stage": "stage1b", | |
| "mode": mode, "stage_probs": stage_probs} | |
| # Stage 2: 5-class ensemble argmax | |
| p2 = self._probs_s2_ensemble(text) | |
| stage_probs["stage2"] = p2 | |
| s2_idx = int(max(range(len(p2)), key=lambda i: p2[i])) | |
| s2_label = self.s2_classes[s2_idx] | |
| if s2_label != "Depression": | |
| return {"label": s2_label, "exit_stage": "stage2", | |
| "mode": mode, "stage_probs": stage_probs} | |
| # Stage 3: Dep vs Sui (Platt-calibrated) | |
| p3_raw = self._probs_lf(self.m3, self.tok3, text, self.ml3) | |
| p3 = _apply_platt(self.platt_s3, p3_raw[0], p3_raw[1]) if self.platt_s3 else p3_raw | |
| stage_probs["stage3"] = p3 | |
| stage_probs["stage3_raw"] = p3_raw | |
| final = "Suicidal" if p3[1] >= t3 else "Depression" | |
| return {"label": final, "exit_stage": "stage3", | |
| "mode": mode, "stage_probs": stage_probs} | |