proxy3d's picture
Upload folder using huggingface_hub
851d18f verified
Raw History Blame Contribute Delete
6.08 kB
from __future__ import annotations
import json
from pathlib import Path
import gradio as gr
import pandas as pd
import torch
from huggingface_hub import hf_hub_download
from transformers import AutoModelForSequenceClassification, AutoTokenizer
MODEL_ID = "proxy3d/multi-motions-28"
MAX_LENGTH = 64
DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
tokenizer = AutoTokenizer.from_pretrained(MODEL_ID, use_fast=True)
model = AutoModelForSequenceClassification.from_pretrained(MODEL_ID)
model.to(DEVICE)
model.eval()
# Prefer the thresholds published with the model. Fall back to 0.5 if unavailable.
try:
thresholds_path = hf_hub_download(
repo_id=MODEL_ID,
filename="thresholds.json",
repo_type="model",
)
with open(thresholds_path, "r", encoding="utf-8") as f:
THRESHOLDS = json.load(f)
except Exception:
THRESHOLDS = {}
ID2LABEL = {
int(k): v for k, v in model.config.id2label.items()
}
LABELS = [ID2LABEL[i] for i in range(len(ID2LABEL))]
def _predict_scores(text: str) -> list[tuple[str, float]]:
text = (text or "").strip()
if not text:
return []
encoded = tokenizer(
text,
return_tensors="pt",
truncation=True,
max_length=MAX_LENGTH,
)
encoded = {k: v.to(DEVICE) for k, v in encoded.items()}
with torch.inference_mode():
logits = model(**encoded).logits[0]
probs = torch.sigmoid(logits).detach().cpu().float().tolist()
return [(ID2LABEL[i], float(probs[i])) for i in range(len(probs))]
def predict(text: str):
scores = _predict_scores(text)
if not scores:
empty = pd.DataFrame(columns=["emotion", "score", "threshold", "active"])
return (
"Введите текст / Enter text.",
empty,
empty,
)
rows = []
active = []
for label, score in scores:
threshold = float(THRESHOLDS.get(label, 0.5))
is_active = score >= threshold
rows.append(
{
"emotion": label,
"score": score,
"threshold": threshold,
"active": "✓" if is_active else "",
}
)
if is_active:
active.append((label, score))
rows.sort(key=lambda x: x["score"], reverse=True)
top_df = pd.DataFrame(rows[:10])
all_df = pd.DataFrame(rows)
if active:
active.sort(key=lambda x: x[1], reverse=True)
active_text = ", ".join(f"**{label}** ({score:.3f})" for label, score in active)
summary = f"### Активные эмоции / Active labels\n{active_text}"
else:
top_label, top_score = rows[0]["emotion"], rows[0]["score"]
summary = (
"### Активные эмоции / Active labels\n"
"Ни один класс не превысил свой tuned threshold. "
f"Максимальный score: **{top_label}** ({top_score:.3f})."
)
return summary, top_df, all_df
EXAMPLES = [
["Я боюсь опоздать на рейс."],
["Мне наконец ответили — какое облегчение."],
["Спасибо, это действительно очень помогло."],
["Я совсем не понимаю, почему это произошло."],
["I'm worried I'll miss my flight."],
["Thank you so much, this really helped me."],
["I can't believe this actually happened!"],
["I'm disappointed, but I understand the decision."],
]
with gr.Blocks(title="Multi-Motions 28 — EN/RU Emotion Classifier") as demo:
gr.Markdown(
"""
# Multi-Motions 28
**English + Russian · 28 GoEmotions classes · multi-label emotion classification**
Введите английский или русский текст. Модель возвращает полный вектор confidence scores,
а также labels, прошедшие индивидуальные tuned thresholds.
**Model:** [proxy3d/multi-motions-28](https://huggingface.co/proxy3d/multi-motions-28)
**Author:** Ilya Zelenskiy (proxy3d) / Илья Зеленский (proxy3d)
**Telegram:** [t.me/greenruff](https://t.me/greenruff)
**Communication Styles LLM:**
[Article](https://iproxy3d.github.io/communication-styles-llm/) ·
[GitHub](https://github.com/iproxy3d/communication-styles-llm)
"""
)
text = gr.Textbox(
label="Text / Текст",
placeholder="Введите сообщение на русском или английском...",
lines=4,
)
run = gr.Button("Analyze / Анализировать", variant="primary")
summary = gr.Markdown()
gr.Markdown("### Top-10 scores")
top_table = gr.Dataframe(
headers=["emotion", "score", "threshold", "active"],
datatype=["str", "number", "number", "str"],
interactive=False,
)
with gr.Accordion("All 28 scores / Все 28 классов", open=False):
all_table = gr.Dataframe(
headers=["emotion", "score", "threshold", "active"],
datatype=["str", "number", "number", "str"],
interactive=False,
)
gr.Examples(
examples=EXAMPLES,
inputs=text,
label="Examples / Примеры",
)
gr.Markdown(
"""
---
### Notes
- Scores are sigmoid outputs for all 28 GoEmotions labels.
- This is a **multi-label** classifier: more than one emotion may be active.
- The `active` column uses the per-label thresholds published with the model.
- Native-Russian transfer was additionally evaluated on CEDR and SemEval-2025 RU.
For implementation details, benchmarks and usage examples, see the
[model page](https://huggingface.co/proxy3d/multi-motions-28).
"""
)
run.click(
fn=predict,
inputs=text,
outputs=[summary, top_table, all_table],
)
text.submit(
fn=predict,
inputs=text,
outputs=[summary, top_table, all_table],
)
if __name__ == "__main__":
demo.launch()