itsLu commited on
Commit
e5067f3
·
1 Parent(s): 16c08a4

initial deployment

Browse files
Dockerfile ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.10-slim
2
+
3
+ # HuggingFace Spaces requires non-root user with UID 1000
4
+ RUN useradd -m -u 1000 user
5
+ WORKDIR /home/user/app
6
+
7
+ # Install dependencies first (layer caching)
8
+ COPY --chown=user requirements.txt .
9
+ RUN pip install --no-cache-dir -r requirements.txt
10
+
11
+ # Copy application code
12
+ COPY --chown=user app.py .
13
+
14
+ # Copy model files (large layer — copy last to keep build cache useful)
15
+ COPY --chown=user saved_models/ ./saved_models/
16
+
17
+ USER user
18
+
19
+ EXPOSE 7860
20
+
21
+ ENV MODEL_DIR=./saved_models/mentalbert_v3flat
22
+ ENV TOKENIZER_DIR=./saved_models/mentalbert_v3flat/tokenizer
23
+
24
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
README.md CHANGED
@@ -1,12 +1,40 @@
1
  ---
2
- title: Vibecheck Api
3
- emoji: 🔥
4
- colorFrom: blue
5
- colorTo: blue
6
  sdk: docker
 
7
  pinned: false
8
  license: mit
9
- short_description: VibeCheck NLP's API
10
  ---
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: VibeCheck MentalBERT API
3
+ emoji: 🧠
4
+ colorFrom: indigo
5
+ colorTo: purple
6
  sdk: docker
7
+ app_port: 7860
8
  pinned: false
9
  license: mit
 
10
  ---
11
 
12
+ # VibeCheck MentalBERT API
13
+
14
+ FastAPI backend serving a fine-tuned MentalBERT model for mental health text classification.
15
+
16
+ ## Endpoint
17
+
18
+ **POST** `/classify`
19
+
20
+ Request body:
21
+ ```json
22
+ { "text": "I have been feeling really overwhelmed lately" }
23
+ ```
24
+
25
+ Response:
26
+ ```json
27
+ { "classification": "stress", "confidence": 0.8731 }
28
+ ```
29
+
30
+ ## Classes
31
+ `anxiety` · `bipolar` · `depression` · `normal` · `personality_disorder` · `stress` · `suicidal`
32
+
33
+ ## Health check
34
+ **GET** `/` → `{ "status": "ok", "model_loaded": true }`
35
+
36
+ ## Deployment note
37
+ When pushing to this Space, use git-lfs for model files:
38
+ ```bash
39
+ git lfs track "*.safetensors" "*.pt" "*.joblib"
40
+ ```
app.py ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from contextlib import asynccontextmanager
2
+ from fastapi import FastAPI, HTTPException
3
+ from fastapi.middleware.cors import CORSMiddleware
4
+ from pydantic import BaseModel
5
+ import torch
6
+ import torch.nn.functional as F
7
+ from transformers import AutoTokenizer, BertForSequenceClassification
8
+ import joblib
9
+ import os
10
+
11
+ _BASE = os.path.dirname(os.path.abspath(__file__))
12
+ MODEL_DIR = os.getenv(
13
+ "MODEL_DIR",
14
+ os.path.join(_BASE, "..", "saved_models", "mentalbert_v3flat")
15
+ )
16
+ TOKENIZER_DIR = os.getenv(
17
+ "TOKENIZER_DIR",
18
+ os.path.join(MODEL_DIR, "tokenizer")
19
+ )
20
+ CHECKPOINT_PATH = os.getenv(
21
+ "CHECKPOINT_PATH",
22
+ os.path.join(_BASE, "..", "saved_models", "mentalbert_v3flat_best.pt")
23
+ )
24
+ LABEL_ENCODER_PATH = os.path.join(MODEL_DIR, "label_encoder.joblib")
25
+ BASE_MODEL_NAME = "mental/mental-bert-base-uncased"
26
+ N_CLASSES = 7
27
+ MAX_LEN = 128
28
+ DEVICE = torch.device("cpu")
29
+
30
+ LABEL_MAP: dict[str, str] = {
31
+ "Anxiety": "anxiety",
32
+ "Bipolar": "bipolar",
33
+ "Depression": "depression",
34
+ "Normal": "normal",
35
+ "Personality Disorder": "personality_disorder",
36
+ "Stress": "stress",
37
+ "Suicidal": "suicidal",
38
+ }
39
+
40
+ model_state: dict = {}
41
+
42
+
43
+ @asynccontextmanager
44
+ async def lifespan(app: FastAPI):
45
+ tokenizer = AutoTokenizer.from_pretrained(TOKENIZER_DIR)
46
+
47
+ # Load architecture from HuggingFace base, then overwrite weights from .pt checkpoint
48
+ model = BertForSequenceClassification.from_pretrained(
49
+ BASE_MODEL_NAME, num_labels=N_CLASSES, ignore_mismatched_sizes=True
50
+ )
51
+ checkpoint = torch.load(CHECKPOINT_PATH, map_location=DEVICE)
52
+ # .pt may be a raw state_dict or wrapped under a key
53
+ state_dict = checkpoint.get("model_state_dict", checkpoint)
54
+ model.load_state_dict(state_dict)
55
+
56
+ model.to(DEVICE)
57
+ model.eval()
58
+ label_encoder = joblib.load(LABEL_ENCODER_PATH)
59
+ model_state.update({"tokenizer": tokenizer, "model": model, "label_encoder": label_encoder})
60
+ print("MentalBERT model loaded successfully.")
61
+ yield
62
+ model_state.clear()
63
+
64
+
65
+ app = FastAPI(title="VibeCheck API", version="1.0.0", lifespan=lifespan)
66
+
67
+ app.add_middleware(
68
+ CORSMiddleware,
69
+ allow_origins=["*"],
70
+ allow_credentials=False,
71
+ allow_methods=["POST", "GET"],
72
+ allow_headers=["Content-Type"],
73
+ )
74
+
75
+
76
+ class ClassifyRequest(BaseModel):
77
+ text: str
78
+
79
+
80
+ class ClassifyResponse(BaseModel):
81
+ classification: str
82
+ confidence: float
83
+
84
+
85
+ @app.get("/")
86
+ def health():
87
+ return {"status": "ok", "model_loaded": bool(model_state)}
88
+
89
+
90
+ @app.post("/classify", response_model=ClassifyResponse)
91
+ def classify(req: ClassifyRequest):
92
+ text = req.text.strip()
93
+ if not text:
94
+ raise HTTPException(status_code=422, detail="text must not be empty")
95
+
96
+ tokenizer = model_state["tokenizer"]
97
+ model = model_state["model"]
98
+ label_encoder = model_state["label_encoder"]
99
+
100
+ inputs = tokenizer(
101
+ text,
102
+ return_tensors="pt",
103
+ truncation=True,
104
+ padding="max_length",
105
+ max_length=MAX_LEN,
106
+ )
107
+ inputs = {k: v.to(DEVICE) for k, v in inputs.items()}
108
+
109
+ with torch.no_grad():
110
+ logits = model(**inputs).logits
111
+
112
+ probs = F.softmax(logits, dim=-1)
113
+ confidence = float(probs.max().item())
114
+ pred_idx = int(torch.argmax(probs, dim=-1).item())
115
+ raw_label: str = label_encoder.inverse_transform([pred_idx])[0]
116
+
117
+ return ClassifyResponse(
118
+ classification=LABEL_MAP.get(raw_label, "normal"),
119
+ confidence=round(confidence, 4),
120
+ )
requirements.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ --extra-index-url https://download.pytorch.org/whl/cpu
2
+ torch==2.3.1+cpu
3
+ transformers==4.43.4
4
+ fastapi==0.111.0
5
+ uvicorn[standard]==0.30.1
6
+ scikit-learn==1.5.1
7
+ joblib==1.4.2
8
+ safetensors==0.4.3
9
+ pydantic==2.8.2
saved_models/mentalbert_v3flat/config.json ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "model_name": "mental/mental-bert-base-uncased",
3
+ "dataset": "mental_health_v3.csv (mentaldistress excluded)",
4
+ "architecture": "flat 7-class",
5
+ "max_len": 128,
6
+ "n_classes": 7,
7
+ "classes": [
8
+ "Anxiety",
9
+ "Bipolar",
10
+ "Depression",
11
+ "Normal",
12
+ "Personality Disorder",
13
+ "Stress",
14
+ "Suicidal"
15
+ ],
16
+ "test_acc": 0.8197624586751561,
17
+ "test_f1_macro": 0.7646452685254392,
18
+ "test_f1_weighted": 0.8193583261268111,
19
+ "macro_auc": 0.9730642236990662,
20
+ "dep_sui_bleed": 1224
21
+ }
saved_models/mentalbert_v3flat/label_encoder.joblib ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e0fd16abb836f540e7b52e71747fc16cc1be299c8024e51284d9cd1b99c226e6
3
+ size 558
saved_models/mentalbert_v3flat/model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a1357b170b0d3335f475148307e3348a327ed5979c38447dbd049c4411e26a58
3
+ size 437974004
saved_models/mentalbert_v3flat/tokenizer/tokenizer.json ADDED
The diff for this file is too large to render. See raw diff
 
saved_models/mentalbert_v3flat/tokenizer/tokenizer_config.json ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "backend": "tokenizers",
3
+ "cls_token": "[CLS]",
4
+ "do_lower_case": true,
5
+ "is_local": false,
6
+ "mask_token": "[MASK]",
7
+ "model_max_length": 512,
8
+ "pad_token": "[PAD]",
9
+ "sep_token": "[SEP]",
10
+ "strip_accents": null,
11
+ "tokenize_chinese_chars": true,
12
+ "tokenizer_class": "BertTokenizer",
13
+ "unk_token": "[UNK]"
14
+ }
saved_models/mentalbert_v3flat_best.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:55997f0777beac09dd99b9100e5bbcf1a03de40b40ef02e9a8efef62ecd57869
3
+ size 438035662