Instructions to use itsLu/mentalbert-v5-hierarchical-v3 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use itsLu/mentalbert-v5-hierarchical-v3 with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-classification", model="itsLu/mentalbert-v5-hierarchical-v3")# Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("itsLu/mentalbert-v5-hierarchical-v3", device_map="auto") - Notebooks
- Google Colab
- Kaggle
File size: 7,914 Bytes
e6aaa60 f1ec9bb 2596f8c f1ec9bb e6aaa60 f1ec9bb 2596f8c f1ec9bb 2596f8c f1ec9bb 2596f8c e6aaa60 2596f8c e6aaa60 f1ec9bb e6aaa60 f1ec9bb e6aaa60 f1ec9bb e6aaa60 f1ec9bb e6aaa60 2596f8c e6aaa60 2596f8c f1ec9bb 2596f8c e6aaa60 2596f8c e6aaa60 f1ec9bb e6aaa60 2596f8c e6aaa60 f1ec9bb e6aaa60 f1ec9bb e6aaa60 f1ec9bb e6aaa60 2596f8c e6aaa60 f1ec9bb e6aaa60 f1ec9bb e6aaa60 f1ec9bb e6aaa60 | 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 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 | """
HuggingFace Inference Endpoints handler for the V5 Hierarchical V3-aug 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")
@torch.no_grad()
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()
@torch.no_grad()
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()
@torch.no_grad()
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}
|