| import os |
| import re |
| from contextlib import asynccontextmanager |
| from typing import Literal |
|
|
| import torch |
| from fastapi import FastAPI |
| from pydantic import BaseModel, Field |
| from transformers import AutoConfig, AutoModelForSequenceClassification, AutoTokenizer |
|
|
|
|
| MODEL_NAME = os.getenv("MODEL_NAME", "distilbert-base-multilingual-cased") |
| MODEL_WEIGHTS_PATH = os.getenv("MODEL_WEIGHTS_PATH", "depression_model.pt") |
| TOKENIZER_PATH = os.getenv("TOKENIZER_PATH", "tokenizer") |
|
|
| _device: torch.device | None = None |
| _tokenizer = None |
| _model = None |
|
|
|
|
| def clean_text(text: str) -> str: |
| text = str(text).lower() |
| text = re.sub(r"http\S+", "", text) |
| text = re.sub(r"@\w+", "", text) |
| text = re.sub(r"[^\w\s]", "", text) |
| text = re.sub(r"\s+", " ", text) |
| return text.strip() |
|
|
|
|
| def _load_model() -> None: |
| global _device, _tokenizer, _model |
|
|
| _device = torch.device("cuda" if torch.cuda.is_available() else "cpu") |
|
|
| tokenizer_source = TOKENIZER_PATH if os.path.isdir(TOKENIZER_PATH) else MODEL_NAME |
| _tokenizer = AutoTokenizer.from_pretrained(tokenizer_source) |
|
|
| |
| |
| config = AutoConfig.from_pretrained(MODEL_NAME, num_labels=2) |
| _model = AutoModelForSequenceClassification.from_config(config) |
|
|
| state_dict = torch.load(MODEL_WEIGHTS_PATH, map_location="cpu") |
| _model.load_state_dict(state_dict) |
| _model.to(_device) |
| _model.eval() |
|
|
|
|
| @asynccontextmanager |
| async def lifespan(_: FastAPI): |
| _load_model() |
| yield |
|
|
|
|
| app = FastAPI(title="Depression Text Classifier", version="1.0.0", lifespan=lifespan) |
|
|
|
|
| class PredictRequest(BaseModel): |
| text: str = Field(..., min_length=1, description="Input text to classify") |
|
|
|
|
| class PredictResponse(BaseModel): |
| label: Literal["Depressed", "Non-Depressed"] |
| class_id: int |
|
|
|
|
| @app.get("/health") |
| def health() -> dict: |
| return {"status": "ok"} |
|
|
|
|
| @app.post("/predict", response_model=PredictResponse) |
| def predict(req: PredictRequest) -> PredictResponse: |
| if _model is None or _tokenizer is None or _device is None: |
| _load_model() |
|
|
| text = clean_text(req.text) |
| inputs = _tokenizer( |
| text, |
| return_tensors="pt", |
| truncation=True, |
| padding=True, |
| max_length=128, |
| ) |
| inputs = {k: v.to(_device) for k, v in inputs.items()} |
|
|
| with torch.no_grad(): |
| outputs = _model(**inputs) |
| pred = int(torch.argmax(outputs.logits, dim=1).item()) |
|
|
| label = "Depressed" if pred == 1 else "Non-Depressed" |
| return PredictResponse(label=label, class_id=pred) |
|
|