doris-sylvie's picture
Update app.py
f08f861 verified
Raw
History Blame Contribute Delete
17.3 kB
import gradio as gr
import torch
import numpy as np
from transformers import AutoTokenizer, AutoModelForSequenceClassification
# ============================================================
# CONFIGURATION
# ============================================================
MODEL_ID = "doris-sylvie/afro-xlmr-malagasy-sentiment"
device = "cuda" if torch.cuda.is_available() else "cpu"
ID_TO_LABEL = {0: "negatif", 1: "positif"}
ID_TO_EMOJI = {0: "😔", 1: "😊"}
ID_TO_COLOR = {0: "#ef4444", 1: "#22c55e"}
# ============================================================
# CHARGEMENT DU MODÈLE
# ============================================================
print(f"Chargement du modèle {MODEL_ID}...")
tokenizer = AutoTokenizer.from_pretrained(MODEL_ID)
model = AutoModelForSequenceClassification.from_pretrained(MODEL_ID).to(device)
model.eval()
print("Modèle chargé.")
# ============================================================
# FONCTION D'ANALYSE
# ============================================================
def analyser_sentiment(texte):
if not texte or not texte.strip():
return (
gr.update(value="—", visible=True),
gr.update(value="—", visible=True),
gr.update(value=0, visible=True),
gr.update(value=0, visible=True),
)
inputs = tokenizer(
texte,
return_tensors="pt",
truncation=True,
max_length=256,
padding=True
).to(device)
with torch.no_grad():
outputs = model(**inputs)
probs = torch.softmax(outputs.logits, dim=-1)[0]
pred_id = torch.argmax(probs).item()
score_pos = round(probs[1].item() * 100, 1)
score_neg = round(probs[0].item() * 100, 1)
label = ID_TO_LABEL[pred_id]
emoji = ID_TO_EMOJI[pred_id]
resultat = f"{emoji} Sentiment **{label.upper()}** — confiance {max(score_pos, score_neg):.1f}%"
return (
gr.update(value=resultat),
gr.update(value=f"Positif : {score_pos}%"),
gr.update(value=score_pos),
gr.update(value=score_neg),
)
# ============================================================
# EXEMPLES
# ============================================================
EXEMPLES = [
["Tsara be ny andro anio, faly aho satria nahita ny namako."],
["Ratsy ny vaovao avy any an-tany, manahirana ny fo."],
["Manao ahoana ianao androany?"],
["Dimy ao amin'ny faritra Diana."],
["Sambatra ny olona izay mahatoky, fa ho tanteraka ny tenin'Andriamanitra."],
["Kivy aho satria tsy nisy nanampy ahy tamin'ny andro sarotra."],
]
# ============================================================
# INTERFACE GRADIO
# ============================================================
CSS = """
.container { max-width: 820px; margin: 0 auto; }
.titre-principal {
font-family: 'Georgia', serif;
font-size: 2rem;
font-weight: 700;
color: #1e293b;
text-align: center;
margin-bottom: 0.25rem;
}
.sous-titre {
text-align: center;
color: #64748b;
font-size: 0.95rem;
margin-bottom: 1.5rem;
}
.badge-modele {
display: inline-block;
background: #f1f5f9;
border: 1px solid #e2e8f0;
border-radius: 999px;
padding: 2px 12px;
font-size: 0.75rem;
color: #475569;
font-family: monospace;
}
.card-resultat {
background: #f8fafc;
border: 1px solid #e2e8f0;
border-radius: 12px;
padding: 1.25rem;
margin-top: 0.5rem;
}
.label-score {
font-size: 0.8rem;
color: #94a3b8;
margin-bottom: 2px;
}
.section-label {
font-size: 0.75rem;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
color: #94a3b8;
margin-bottom: 6px;
}
footer { display: none !important; }
"""
with gr.Blocks(css=CSS, theme=gr.themes.Base()) as demo:
# ── En-tête
gr.HTML("""
<div class="container" style="padding-top:1.5rem; padding-bottom:0.5rem;">
<p class="titre-principal">DIAGuard — Analyse de Sentiment</p>
<p class="sous-titre">
Classifiez le sentiment d'un texte en Malagasy officiel<br>
<span class="badge-modele">doris-sylvie/afro-xlmr-malagasy-sentiment</span>
</p>
</div>
""")
with gr.Column(elem_classes="container"):
# ── Zone de saisie
gr.HTML('<p class="section-label">Texte à analyser</p>')
texte_input = gr.Textbox(
placeholder="Écrivez un texte en Malagasy officiel…",
lines=3,
max_lines=6,
show_label=False,
container=False
)
btn = gr.Button(
"Analyser le sentiment",
variant="primary",
size="lg"
)
# ── Résultat principal
gr.HTML('<p class="section-label" style="margin-top:1rem;">Résultat</p>')
with gr.Group(elem_classes="card-resultat"):
resultat_md = gr.Markdown(value="*En attente d'un texte…*")
with gr.Row():
with gr.Column():
gr.HTML('<p class="label-score">😊 Score positif</p>')
score_pos_bar = gr.Slider(
minimum=0, maximum=100,
value=0, interactive=False,
show_label=False
)
with gr.Column():
gr.HTML('<p class="label-score">😔 Score négatif</p>')
score_neg_bar = gr.Slider(
minimum=0, maximum=100,
value=0, interactive=False,
show_label=False
)
detail_md = gr.Markdown(value="")
# ── Exemples
gr.HTML('<p class="section-label" style="margin-top:1.25rem;">Exemples</p>')
gr.Examples(
examples=EXEMPLES,
inputs=[texte_input],
label=None,
examples_per_page=6,
)
# ── Note méthodologique
gr.HTML("""
<div style="
margin-top: 1.5rem;
padding: 1rem 1.25rem;
background: #fefce8;
border: 1px solid #fde68a;
border-radius: 10px;
font-size: 0.82rem;
color: #78350f;
line-height: 1.6;
">
<strong>Note méthodologique</strong><br>
Ce modèle est basé sur <strong>Afro-XLM-R</strong> (Davlan/afro-xlmr-base),
fine-tuné sur le <strong>VMSC</strong> (Vaovao Malagasy Sentiment Corpus —
4 536 phrases, presse Malagasy).
Accuracy : <strong>84.75%</strong> | F1 : <strong>0.8476</strong>
(évaluation sur 505 exemples, epoch 3/5).<br><br>
Dans le pipeline DIAGuard, ce modèle reçoit du texte déjà traduit
en Malagasy officiel par ByT5 depuis le Betsileo.
</div>
""")
# ── Événements
btn.click(
fn=analyser_sentiment,
inputs=[texte_input],
outputs=[resultat_md, detail_md, score_pos_bar, score_neg_bar]
)
texte_input.submit(
fn=analyser_sentiment,
inputs=[texte_input],
outputs=[resultat_md, detail_md, score_pos_bar, score_neg_bar]
)
if __name__ == "__main__":
demo.launch()
# import gradio as gr
# import torch
# from transformers import (
# AutoTokenizer,
# AutoModelForSequenceClassification,
# AutoModelForSeq2SeqLM,
# )
# # ============================================================
# # CONFIGURATION
# # ============================================================
# SENTIMENT_MODEL_ID = "doris-sylvie/afro-xlmr-malagasy-sentiment"
# TRANSLATOR_MODEL_ID = "doris-sylvie/byt5-betsileo-malagasy"
# DIRECTION_BETS_TO_MG = "translate Betsileo to Malagasy"
# device = "cuda" if torch.cuda.is_available() else "cpu"
# ID_TO_LABEL = {0: "negatif", 1: "positif"}
# ID_TO_EMOJI = {0: "😔", 1: "😊"}
# # ============================================================
# # CHARGEMENT DES MODÈLES
# # ============================================================
# print(f"Chargement du traducteur ByT5 ({TRANSLATOR_MODEL_ID})...")
# translator_tokenizer = AutoTokenizer.from_pretrained(TRANSLATOR_MODEL_ID)
# translator = AutoModelForSeq2SeqLM.from_pretrained(TRANSLATOR_MODEL_ID).to(device)
# translator.eval()
# print("Traducteur ByT5 chargé.")
# print(f"Chargement du classificateur ({SENTIMENT_MODEL_ID})...")
# sentiment_tokenizer = AutoTokenizer.from_pretrained(SENTIMENT_MODEL_ID)
# sentiment_model = AutoModelForSequenceClassification.from_pretrained(
# SENTIMENT_MODEL_ID
# ).to(device)
# sentiment_model.eval()
# print("Classificateur chargé.")
# # ============================================================
# # TRADUCTION ByT5 (Betsileo → Malagasy officiel)
# # ============================================================
# def traduire_betsileo_vers_malagasy(texte: str) -> str:
# if not texte or not texte.strip():
# return texte
# inputs = translator_tokenizer(
# f"{DIRECTION_BETS_TO_MG}: {texte}",
# return_tensors="pt",
# truncation=True,
# max_length=512,
# ).to(device)
# with torch.no_grad():
# outputs = translator.generate(
# **inputs,
# max_length=256,
# num_beams=4,
# no_repeat_ngram_size=3,
# early_stopping=True,
# )
# return translator_tokenizer.decode(outputs[0], skip_special_tokens=True)
# # ============================================================
# # CLASSIFICATION DE SENTIMENT (Malagasy officiel)
# # ============================================================
# def classer_sentiment(texte_mg: str):
# inputs = sentiment_tokenizer(
# texte_mg,
# return_tensors="pt",
# truncation=True,
# max_length=256,
# padding=True,
# ).to(device)
# with torch.no_grad():
# outputs = sentiment_model(**inputs)
# probs = torch.softmax(outputs.logits, dim=-1)[0]
# pred_id = torch.argmax(probs).item()
# score_pos = round(probs[1].item() * 100, 1)
# score_neg = round(probs[0].item() * 100, 1)
# label = ID_TO_LABEL[pred_id]
# emoji = ID_TO_EMOJI[pred_id]
# confiance = max(score_pos, score_neg)
# resultat = (
# f"{emoji} Sentiment **{label.upper()}** — confiance {confiance:.1f}%"
# )
# detail = f"Positif : {score_pos}% · Négatif : {score_neg}%"
# return resultat, detail, score_pos, score_neg
# # ============================================================
# # PIPELINE COMPLET
# # ============================================================
# def analyser_sentiment(texte: str):
# vide = (
# gr.update(value="—"),
# gr.update(value="*En attente d'un texte…*"),
# gr.update(value=""),
# gr.update(value=0),
# gr.update(value=0),
# )
# if not texte or not texte.strip():
# return vide
# # 1. Betsileo → Malagasy officiel (ByT5)
# texte_mg = traduire_betsileo_vers_malagasy(texte)
# # 2. Classification sur le Malagasy officiel
# resultat, detail, score_pos, score_neg = classer_sentiment(texte_mg)
# return (
# gr.update(value=texte_mg),
# gr.update(value=resultat),
# gr.update(value=detail),
# gr.update(value=score_pos),
# gr.update(value=score_neg),
# )
# # ============================================================
# # EXEMPLES (Betsileo)
# # ============================================================
# EXEMPLES = [
# ["Akory anao io? Tsara be ny andro anio."],
# ["Ratsy ny vaovao, manahirana ny fo."],
# ["Kivy aho satria tsy nisy nanampy ahy."],
# ["Sambatra ny olona izay mahatoky."],
# ["Manao ahoana ianao androany?"],
# ["Faly aho satria nahita ny namako."],
# ]
# # ============================================================
# # INTERFACE GRADIO
# # ============================================================
# CSS = """
# .container { max-width: 820px; margin: 0 auto; }
# .titre-principal {
# font-family: 'Georgia', serif;
# font-size: 2rem;
# font-weight: 700;
# color: #1e293b;
# text-align: center;
# margin-bottom: 0.25rem;
# }
# .sous-titre {
# text-align: center;
# color: #64748b;
# font-size: 0.95rem;
# margin-bottom: 1.5rem;
# }
# .badge-modele {
# display: inline-block;
# background: #f1f5f9;
# border: 1px solid #e2e8f0;
# border-radius: 999px;
# padding: 2px 12px;
# font-size: 0.75rem;
# color: #475569;
# font-family: monospace;
# margin: 0 2px;
# }
# .card-resultat {
# background: #f8fafc;
# border: 1px solid #e2e8f0;
# border-radius: 12px;
# padding: 1.25rem;
# margin-top: 0.5rem;
# }
# .label-score {
# font-size: 0.8rem;
# color: #94a3b8;
# margin-bottom: 2px;
# }
# .section-label {
# font-size: 0.75rem;
# font-weight: 600;
# letter-spacing: 0.08em;
# text-transform: uppercase;
# color: #94a3b8;
# margin-bottom: 6px;
# }
# footer { display: none !important; }
# """
# with gr.Blocks(css=CSS, theme=gr.themes.Base()) as demo:
# gr.HTML("""
# <div class="container" style="padding-top:1.5rem; padding-bottom:0.5rem;">
# <p class="titre-principal">DIAGuard — Analyse de Sentiment</p>
# <p class="sous-titre">
# Pipeline Betsileo → Malagasy officiel → Sentiment<br>
# <span class="badge-modele">doris-sylvie/byt5-betsileo-malagasy</span>
# <span class="badge-modele">doris-sylvie/afro-xlmr-malagasy-sentiment</span>
# </p>
# </div>
# """)
# with gr.Column(elem_classes="container"):
# gr.HTML('<p class="section-label">Texte en Betsileo</p>')
# texte_input = gr.Textbox(
# placeholder="Écrivez un texte en dialecte Betsileo…",
# lines=3,
# max_lines=6,
# show_label=False,
# container=False,
# )
# btn = gr.Button(
# "Traduire & analyser le sentiment",
# variant="primary",
# size="lg",
# )
# gr.HTML(
# '<p class="section-label" style="margin-top:1rem;">'
# "Traduction Malagasy officiel (ByT5)</p>"
# )
# traduction_md = gr.Textbox(
# value="—",
# lines=2,
# max_lines=4,
# interactive=False,
# show_label=False,
# container=False,
# )
# gr.HTML(
# '<p class="section-label" style="margin-top:1rem;">Résultat</p>'
# )
# with gr.Group(elem_classes="card-resultat"):
# resultat_md = gr.Markdown(value="*En attente d'un texte…*")
# with gr.Row():
# with gr.Column():
# gr.HTML('<p class="label-score">😊 Score positif</p>')
# score_pos_bar = gr.Slider(
# minimum=0,
# maximum=100,
# value=0,
# interactive=False,
# show_label=False,
# )
# with gr.Column():
# gr.HTML('<p class="label-score">😔 Score négatif</p>')
# score_neg_bar = gr.Slider(
# minimum=0,
# maximum=100,
# value=0,
# interactive=False,
# show_label=False,
# )
# detail_md = gr.Markdown(value="")
# gr.HTML(
# '<p class="section-label" style="margin-top:1.25rem;">Exemples</p>'
# )
# gr.Examples(
# examples=EXEMPLES,
# inputs=[texte_input],
# label=None,
# examples_per_page=6,
# )
# gr.HTML("""
# <div style="
# margin-top: 1.5rem;
# padding: 1rem 1.25rem;
# background: #fefce8;
# border: 1px solid #fde68a;
# border-radius: 10px;
# font-size: 0.82rem;
# color: #78350f;
# line-height: 1.6;
# ">
# <strong>Pipeline DIAGuard</strong><br>
# 1. <strong>ByT5</strong> (<code>byt5-betsileo-malagasy</code>) traduit
# le texte Betsileo en Malagasy officiel.<br>
# 2. <strong>Afro-XLM-R</strong> (<code>afro-xlmr-malagasy-sentiment</code>)
# classifie le sentiment sur cette traduction
# (Accuracy 84.75% | F1 0.8476 sur VMSC).<br><br>
# Le classificateur n'a pas été entraîné sur le Betsileo :
# la traduction ByT5 est l'étape indispensable du pipeline.
# </div>
# """)
# outputs = [
# traduction_md,
# resultat_md,
# detail_md,
# score_pos_bar,
# score_neg_bar,
# ]
# btn.click(fn=analyser_sentiment, inputs=[texte_input], outputs=outputs)
# texte_input.submit(
# fn=analyser_sentiment, inputs=[texte_input], outputs=outputs
# )
# if __name__ == "__main__":
# demo.launch()