Spaces:
Sleeping
Sleeping
File size: 5,959 Bytes
c77e919 | 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 | from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from transformers import AutoTokenizer, AutoModelForSequenceClassification
import torch
import numpy as np
import os
app = FastAPI(title="PGOS Emotion Agent API", version="1.0.0")
# ββ μ€μ βββββββββββββββββββββββββββββββββββββββββββ
HF_USERNAME = "nuguri01"
HF_TOKEN = os.environ.get("HF_TOKEN", "") # Spaces SecretμΌλ‘ μ£Όμ
MAJOR_CLASSES = ["λΆλ
Έκ³", "μ¬νκ³", "λΆμκ³", "μμ²κ³", "λΉν©κ³", "κΈ°μ¨κ³"]
MINOR_STRUCTURE = {
"λΆλ
Έκ³": {"labels": ["λΆλ
Έ", "μ’μ ", "μ§μ¦"], "repo": "pgos-emotion-stage2-anger"},
"μ¬νκ³": {"labels": ["μ¬ν", "μ°μΈ", "νν"], "repo": "pgos-emotion-stage2-sadness"},
"λΆμκ³": {"labels": ["λΆμ", "κ±±μ "], "repo": "pgos-emotion-stage2-anxiety"},
"μμ²κ³": {"labels": ["μμ²", "μ΅μΈ", "μ§ν¬"], "repo": "pgos-emotion-stage2-hurt"},
"λΉν©κ³": {"labels": ["μμΉμ¬", "μ£μ±
κ°", "λΉν©"], "repo": "pgos-emotion-stage2-embarrass"},
"κΈ°μ¨κ³": {"labels": ["κΈ°μ¨", "μλ"], "repo": "pgos-emotion-stage2-joy"},
}
PGOS_CLASSES = [
"λΆλ
Έ", "μ’μ ", "μ§μ¦", "μ¬ν", "μ°μΈ", "νν",
"λΆμ", "κ±±μ ", "μμ²", "μ΅μΈ", "μ§ν¬",
"μμΉμ¬", "μ£μ±
κ°", "λΉν©", "κΈ°μ¨", "μλ"
]
label2id = {l: i for i, l in enumerate(PGOS_CLASSES)}
device = "cuda" if torch.cuda.is_available() else "cpu"
# ββ λͺ¨λΈ λ‘λ (μλ² μμ μ ν λ²λ§) ββββββββββββββ
print("π₯ λͺ¨λΈ λ‘λ© μ€...")
tokenizer = AutoTokenizer.from_pretrained(
f"{HF_USERNAME}/pgos-emotion-stage1-major",
token=HF_TOKEN
)
model_major = AutoModelForSequenceClassification.from_pretrained(
f"{HF_USERNAME}/pgos-emotion-stage1-major",
token=HF_TOKEN
).to(device)
model_major.eval()
minor_models = {}
for major_label, config in MINOR_STRUCTURE.items():
model_sub = AutoModelForSequenceClassification.from_pretrained(
f"{HF_USERNAME}/{config['repo']}",
token=HF_TOKEN,
num_labels=len(config["labels"])
).to(device)
model_sub.eval()
minor_models[major_label] = model_sub
print("β
μ 체 λͺ¨λΈ λ‘λ μλ£")
# ββ μμ²/μλ΅ μ€ν€λ§ βββββββββββββββββββββββββββββββ
class EmotionRequest(BaseModel):
text: str
top_k: int = 3 # λ°νν κ°μ μ (κΈ°λ³Έ 3κ°)
class EmotionResult(BaseModel):
emotion: str
score: float
rank: int
class EmotionResponse(BaseModel):
text: str
primary_emotion: str
primary_score: float
top_emotions: list[EmotionResult]
pgos_message: str # λ΄λ΄μμκ² λλ €μ€ λ©νΈ
# ββ κ°μ λΆλ₯ ν¨μ βββββββββββββββββββββββββββββββββ
def predict_emotions(text: str, top_k: int = 3):
# 1λ¨κ³: λλΆλ₯
enc = tokenizer(text, max_length=128, padding=True,
truncation=True, return_tensors="pt").to(device)
with torch.no_grad():
major_probs = torch.softmax(
model_major(**enc).logits, dim=-1
).cpu().numpy()[0]
# 2λ¨κ³: μλΆλ₯ + νλ₯ κ²°ν©
final_probs = np.zeros(len(PGOS_CLASSES))
for major_idx, major_label in enumerate(MAJOR_CLASSES):
config = MINOR_STRUCTURE[major_label]
minor_labels = config["labels"]
model_sub = minor_models[major_label]
with torch.no_grad():
sub_probs = torch.softmax(
model_sub(**enc).logits, dim=-1
).cpu().numpy()[0]
for minor_idx, minor_label in enumerate(minor_labels):
final_probs[label2id[minor_label]] = (
major_probs[major_idx] * sub_probs[minor_idx]
)
# Top-K μΆμΆ
top_k_ids = np.argsort(final_probs)[-top_k:][::-1]
top_emotions = [
EmotionResult(
emotion=PGOS_CLASSES[idx],
score=round(float(final_probs[idx]), 4),
rank=rank + 1
)
for rank, idx in enumerate(top_k_ids)
]
# PGOS λ©νΈ μμ±
primary = top_emotions[0].emotion
subs = [e.emotion for e in top_emotions[1:] if e.score > 0.05]
sub_str = f", {'κ³Ό '.join(subs)} κ°μ λ ν¨κ» λκ»΄μ§λ κ² κ°μμ" if subs else ""
message = f"'{primary}' κ°μ μ΄ μ£Όλ‘ λκ»΄μ§λ©°{sub_str}. μ§κΈ μ΄ κ°μ μ μ²μ²ν λ€μ¬λ€λ³ΌκΉμ?"
return top_emotions, primary, float(final_probs[top_k_ids[0]]), message
# ββ API μλν¬μΈνΈ βββββββββββββββββββββββββββββββββ
@app.get("/")
def root():
return {
"service": "PGOS Emotion Agent API",
"version": "1.0.0",
"description": "κ³μΈ΅μ 2λ¨κ³ κ°μ λΆλ₯κΈ° (16ν΄λμ€, Top-3 Hit Rate 0.870)",
"endpoints": {
"POST /predict": "κ°μ λΆμ",
"GET /health": "μλ² μν νμΈ"
}
}
@app.get("/health")
def health():
return {"status": "ok", "device": device, "models_loaded": len(minor_models) + 1}
@app.post("/predict", response_model=EmotionResponse)
def predict(req: EmotionRequest):
if not req.text.strip():
raise HTTPException(status_code=400, detail="ν
μ€νΈλ₯Ό μ
λ ₯ν΄μ£ΌμΈμ.")
if len(req.text) > 500:
raise HTTPException(status_code=400, detail="ν
μ€νΈλ 500μ μ΄νλ‘ μ
λ ₯ν΄μ£ΌμΈμ.")
top_k = max(1, min(req.top_k, 5)) # 1~5 μ¬μ΄λ‘ μ ν
top_emotions, primary, primary_score, message = predict_emotions(req.text, top_k)
return EmotionResponse(
text=req.text,
primary_emotion=primary,
primary_score=primary_score,
top_emotions=top_emotions,
pgos_message=message
)
|