JeanProjets's picture
Upload folder using huggingface_hub
3c03781 verified
Raw
History Blame Contribute Delete
32.2 kB
import os
# Configure environment variables to prevent threading conflicts on macOS
os.environ["OMP_NUM_THREADS"] = "1"
os.environ["MKL_NUM_THREADS"] = "1"
os.environ["OPENBLAS_NUM_THREADS"] = "1"
os.environ["VECLIB_MAXIMUM_THREADS"] = "1"
os.environ["NUMEXPR_NUM_THREADS"] = "1"
import json
import time
import re
import pandas as pd
import numpy as np
import streamlit as st
import matplotlib.pyplot as plt
import seaborn as sns
from tensorflow import keras
from tensorflow.keras.preprocessing.text import tokenizer_from_json
from tensorflow.keras.preprocessing.sequence import pad_sequences
from llama_cpp import Llama
# Set page config
st.set_page_config(
page_title="SentiMind AI - Comparaison Modeles",
layout="wide",
initial_sidebar_state="expanded"
)
# Custom premium styling (Dark Glassmorphism)
st.markdown("""
<style>
@import url('https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600;700&family=Inter:wght@300;400;500;600&display=swap');
html, body, [class*="css"] {
font-family: 'Inter', sans-serif;
}
h1, h2, h3, h4, .stHeader {
font-family: 'Outfit', sans-serif;
font-weight: 700;
letter-spacing: -0.5px;
}
/* Main Background */
.stApp {
background-color: #0c0c0e;
color: #e0e0e6;
}
/* Sidebar styling */
[data-testid="stSidebar"] {
background-color: #121215;
border-right: 1px solid #222226;
}
/* Premium Glassmorphic Cards */
.glass-card {
background: rgba(30, 30, 35, 0.7);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 16px;
padding: 24px;
margin-bottom: 20px;
backdrop-filter: blur(12px);
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.3);
}
.keras-card {
border-left: 5px solid #ff5e62;
}
.llm-card {
border-left: 5px solid #00df89;
}
.qwen-card {
border-left: 5px solid #00c3ff;
}
/* Neon badge values */
.badge {
display: inline-block;
padding: 6px 14px;
border-radius: 30px;
font-weight: 600;
font-size: 0.85rem;
letter-spacing: 0.5px;
margin-bottom: 12px;
}
.badge-positive {
background-color: rgba(0, 223, 137, 0.15);
color: #00df89;
border: 1px solid rgba(0, 223, 137, 0.3);
}
.badge-negative {
background-color: rgba(255, 94, 98, 0.15);
color: #ff5e62;
border: 1px solid rgba(255, 94, 98, 0.3);
}
.metric-title {
font-size: 0.9rem;
color: #8c8c9a;
margin-bottom: 4px;
}
.metric-value {
font-size: 2.2rem;
font-weight: 700;
color: #ffffff;
font-family: 'Outfit', sans-serif;
}
/* Quote Box for Explainability */
.quote-box {
background-color: rgba(255, 255, 255, 0.03);
border-left: 3px solid #00df89;
padding: 16px;
border-radius: 8px;
margin-top: 15px;
font-style: italic;
color: #cbd5e1;
}
.quote-box-qwen {
border-left: 3px solid #00c3ff;
}
</style>
""", unsafe_allow_html=True)
# ----------------- cached Model Loaders -----------------
@st.cache_resource
def load_keras_model():
keras_model_path = "keras_baseline/model1_simple_neural_network.keras"
tokenizer_path = "keras_baseline/tokenizer_simple_neural_network.json"
# Load model
model = keras.models.load_model(keras_model_path)
# Load tokenizer
with open(tokenizer_path, "r", encoding="utf-8") as f:
tokenizer_json_str = f.read()
tokenizer = tokenizer_from_json(tokenizer_json_str)
return model, tokenizer
@st.cache_resource
def load_llm_model():
return Llama.from_pretrained(
repo_id="JusteLeo/emotion-text-classifier-LLM",
filename="EmotionTextClassifierLLM.gguf",
n_ctx=512,
verbose=False
)
@st.cache_resource
def load_qwen_model():
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
from peft import PeftModel
device = "mps" if torch.backends.mps.is_available() else "cpu"
model_id = "Qwen/Qwen2.5-0.5B-Instruct"
adapter_path = "qwen2.5_local_mac_lora"
tokenizer = AutoTokenizer.from_pretrained(model_id)
tokenizer.pad_token = tokenizer.eos_token
try:
base_model = AutoModelForCausalLM.from_pretrained(
model_id,
torch_dtype=torch.float16,
low_cpu_mem_usage=True
).to(device)
model = PeftModel.from_pretrained(base_model, adapter_path)
return model, tokenizer, device
except Exception as e:
st.sidebar.error(f"Erreur de chargement Qwen: {e}")
return None, None, device
# Clean and parse helper
def clean_and_parse_json(text):
cleaned = text.strip()
cleaned = re.sub(r"^```(?:json)?", "", cleaned, flags=re.IGNORECASE)
cleaned = re.sub(r"```$", "", cleaned).strip()
try:
data = json.loads(cleaned)
return data
except Exception:
emotions = []
explanation = "Error parsing explanation."
emotion_match = re.search(r'"emotions"\s*:\s*\[(.*?)\]', cleaned, re.DOTALL)
if emotion_match:
emotions = [e.strip(' "\'') for e in emotion_match.group(1).split(',')]
explanation_match = re.search(r'"explanation"\s*:\s*"(.*?)"', cleaned, re.DOTALL)
if explanation_match:
explanation = explanation_match.group(1)
return {"emotions": emotions, "explanation": explanation}
# Emotion mappings
positive_emotions = {
'joy', 'love', 'surprise', 'pride', 'admiration', 'gratitude', 'hope',
'optimism', 'amusement', 'desire', 'caring', 'relief', 'excitement',
'approval', 'curiosity'
}
negative_emotions = {
'sadness', 'anger', 'fear', 'disgust', 'shame', 'guilt', 'disappointment',
'annoyance', 'frustration', 'grief', 'nervousness', 'embarrassment',
'remorse', 'disapproval', 'confusion', 'boredom'
}
# ----------------- Sidebar navigation -----------------
st.sidebar.markdown("""
<div style='text-align: center; margin-bottom: 20px;'>
<h2 style='color: #00df89; margin-bottom: 0px;'>SentiMind AI</h2>
<p style='color: #8c8c9a; font-size: 0.85rem;'>Keras vs Gemma 3 vs Qwen</p>
</div>
""", unsafe_allow_html=True)
nav = st.sidebar.radio(
"Navigation",
["Analyse en Direct (Live)", "Tableau de Bord PoC", "Navigateur de Tweets", "Robustesse (V2)"]
)
st.sidebar.markdown("---")
st.sidebar.markdown("""
### Informations Systeme
- **CPU/GPU Acceleration:** Metal macOS (Active)
- **Baseline Model:** Keras Simple NN (~15 MB)
- **Challengeur 1:** Gemma 3 1B Instruct GGUF Q4_0 (~720 MB)
- **Challengeur 2:** Qwen 2.5 0.5B Instruct LoRA (~1 GB)
- **Dataset de PoC:** Sentiment140 (Stratifie 500 tweets)
""")
# ----------------- Tab 1: Live Analysis -----------------
if nav == "Analyse en Direct (Live)":
st.markdown("<h1 style='color: #ffffff;'>Analyse de Sentiment en Direct</h1>", unsafe_allow_html=True)
st.markdown("<p style='color: #8c8c9a;'>Testez et comparez les modeles sur vos propres phrases et avis clients.</p>", unsafe_allow_html=True)
st.write("")
# Input Area
user_input = st.text_area(
"Saisissez un commentaire ou un tweet a analyser :",
"Home now, the worst part of the day is finally over. Ready to relax!",
height=100
)
st.write("")
if st.button("Analyser le sentiment en direct", use_container_width=True):
with st.spinner("Inference simultanee des modeles..."):
# Load models
keras_model, keras_tokenizer = load_keras_model()
llm = load_llm_model()
qwen_model, qwen_tokenizer, qwen_device = load_qwen_model()
# --- 1. Keras Inference ---
keras_start = time.time()
seq = keras_tokenizer.texts_to_sequences(pd.Series([user_input]))
padded = pad_sequences(seq, maxlen=50, padding='post', truncating='post')
keras_prob = float(keras_model(padded).numpy()[0][0])
keras_pred = 1 if keras_prob > 0.5 else 0
keras_time = (time.time() - keras_start) * 1000 # ms
# --- 2. Gemma 3 Inference ---
llm_start = time.time()
try:
response = llm.create_chat_completion(
messages=[{"role": "user", "content": user_input}],
temperature=0.1,
max_tokens=80
)
output_content = response["choices"][0]["message"]["content"]
parsed = clean_and_parse_json(output_content)
emotions = parsed.get("emotions", [])
explanation = parsed.get("explanation", "No explanation.")
# Mapped sentiment
llm_pred = 0
if emotions:
primary = emotions[0].lower().strip()
if primary in positive_emotions:
llm_pred = 1
elif primary in negative_emotions:
llm_pred = 0
else:
pos_count = sum(1 for e in emotions if e.lower().strip() in positive_emotions)
neg_count = sum(1 for e in emotions if e.lower().strip() in negative_emotions)
if pos_count > neg_count:
llm_pred = 1
else:
emotions = ["Neutral"]
llm_pred = 0
except Exception as e:
emotions = ["Error"]
explanation = f"Error during inference: {str(e)}"
llm_pred = 0
llm_time = time.time() - llm_start # s
# --- 3. Qwen Inference ---
qwen_start = time.time()
qwen_response = ""
qwen_pred = 0
import torch
if qwen_model is not None:
try:
messages_qwen = [
{"role": "user", "content": f"Analyse le sentiment de ce tweet : '{user_input}'"}
]
prompt = qwen_tokenizer.apply_chat_template(messages_qwen, tokenize=False, add_generation_prompt=True)
inputs = qwen_tokenizer(prompt, return_tensors="pt").to(qwen_device)
with torch.no_grad():
outputs = qwen_model.generate(**inputs, max_new_tokens=15, temperature=0.1)
qwen_response = qwen_tokenizer.decode(outputs[0][len(inputs["input_ids"][0]):], skip_special_tokens=True).strip()
if "positive" in qwen_response.lower():
qwen_pred = 1
elif "negative" in qwen_response.lower():
qwen_pred = 0
except Exception as e:
qwen_response = f"Error: {str(e)}"
qwen_pred = 0
else:
qwen_response = "Erreur chargement modele"
qwen_time = time.time() - qwen_start # s
# --- RENDER COMPARISON ---
col1, col2, col3 = st.columns(3)
with col1:
st.markdown(f"""
<div class="glass-card keras-card">
<span class="badge badge-{"positive" if keras_pred == 1 else "negative"}">
{"POSITIF" if keras_pred == 1 else "NEGATIF"}
</span>
<h3 style="margin-top: 5px;">Baseline Keras</h3>
<p style="color: #cbd5e1; font-size: 0.95rem;">Reseau de neurones supervise avec plongement Word2Vec.</p>
<div style="margin: 20px 0;">
<span class="metric-title">Confiance / Probabilite</span>
<div style="font-size: 1.8rem; font-weight: 700; font-family: 'Outfit'; color: #ffffff;">
{keras_prob * 100:.2f}%
</div>
</div>
<div style="margin-top: 15px;">
<span style="color: #ff5e62; font-weight: 600; font-size: 0.9rem;">Temps d'inference :</span>
<code style="background-color: rgba(255,94,98,0.1); color: #ff5e62; padding: 2px 6px; border-radius: 4px;">{keras_time:.2f} ms</code>
</div>
<div style="margin-top: 25px; border-top: 1px solid rgba(255,255,255,0.05); padding-top: 15px; color: #8c8c9a; font-size: 0.85rem;">
Aucune explication fine disponible (Boite Noire).
</div>
</div>
""", unsafe_allow_html=True)
with col2:
emotion_tags = " ".join([f"<span style='background-color: rgba(0,223,137,0.1); color: #00df89; padding: 4px 10px; border-radius: 20px; font-size: 0.85rem; font-weight: 600; margin-right: 6px;'>{e}</span>" for e in emotions])
st.markdown(f"""
<div class="glass-card llm-card">
<span class="badge badge-{"positive" if llm_pred == 1 else "negative"}">
{"POSITIF" if llm_pred == 1 else "NEGATIF"}
</span>
<h3 style="margin-top: 5px;">Challengeur Gemma 3</h3>
<p style="color: #cbd5e1; font-size: 0.95rem;">Gemma 3 1B Instruct GGUF Q4_0 infere en Zero-Shot.</p>
<div style="margin: 15px 0;">
<span class="metric-title">Emotion(s) fine(s) :</span>
<div style="margin-top: 8px;">
{emotion_tags}
</div>
</div>
<div class="quote-box">
<strong>Raisonnement :</strong><br/>
"{explanation}"
</div>
<div style="margin-top: 20px;">
<span style="color: #00df89; font-weight: 600; font-size: 0.9rem;">Temps d'inference :</span>
<code style="background-color: rgba(0,223,137,0.1); color: #00df89; padding: 2px 6px; border-radius: 4px;">{llm_time:.3f} s</code>
</div>
</div>
""", unsafe_allow_html=True)
with col3:
st.markdown(f"""
<div class="glass-card qwen-card">
<span class="badge badge-{"positive" if qwen_pred == 1 else "negative"}">
{"POSITIF" if qwen_pred == 1 else "NEGATIF"}
</span>
<h3 style="margin-top: 5px;">Challengeur Qwen 2.5</h3>
<p style="color: #cbd5e1; font-size: 0.95rem;">Qwen 2.5 0.5B Instruct affine localement avec LoRA.</p>
<div style="margin: 15px 0;">
<span class="metric-title">Reponse brute JSON :</span>
<div class="quote-box quote-box-qwen">
"{qwen_response}"
</div>
</div>
<div style="margin-top: 20px;">
<span style="color: #00c3ff; font-weight: 600; font-size: 0.9rem;">Temps d'inference :</span>
<code style="background-color: rgba(0, 195, 255, 0.1); color: #00c3ff; padding: 2px 6px; border-radius: 4px;">{qwen_time:.3f} s</code>
</div>
</div>
""", unsafe_allow_html=True)
# ----------------- Tab 2: Analytics Dashboard -----------------
elif nav == "Tableau de Bord PoC":
st.markdown("<h1 style='color: #ffffff;'>Tableau de Bord de la Preuve de Concept (PoC)</h1>", unsafe_allow_html=True)
st.markdown("<p style='color: #8c8c9a;'>Indicateurs cles de performance et analyses quantitatives comparees.</p>", unsafe_allow_html=True)
st.write("")
qwen_acc_v1 = 85.0
qwen_f1_v1 = 84.5
qwen_speed = 0.35
if os.path.exists("qwen_metrics.json"):
with open("qwen_metrics.json", "r", encoding="utf-8") as f:
q_metrics = json.load(f)
qwen_acc_v1 = q_metrics.get("qwen_acc_v1", 0.85) * 100
qwen_f1_v1 = q_metrics.get("qwen_f1_v1", 0.845) * 100
qwen_speed = q_metrics.get("qwen_time", 0.35)
# 1. KPI Cards
col1, col2, col3 = st.columns(3)
with col1:
st.markdown(f"""
<div class="glass-card">
<div class="metric-title">Accuracy (Precision globale)</div>
<div class="metric-value" style="color: #ffffff;">
78.2% <span style="font-size: 1.1rem; color: #ff5e62; font-weight: normal;">(Keras)</span>
</div>
<div class="metric-value" style="color: #00df89; margin-top: -10px;">
71.8% <span style="font-size: 1.1rem; color: #00df89; font-weight: normal;">(Gemma 3)</span>
</div>
<div class="metric-value" style="color: #00c3ff; margin-top: -10px;">
{qwen_acc_v1:.1f}% <span style="font-size: 1.1rem; color: #00c3ff; font-weight: normal;">(Qwen)</span>
</div>
</div>
""", unsafe_allow_html=True)
with col2:
st.markdown(f"""
<div class="glass-card">
<div class="metric-title">F1-Score</div>
<div class="metric-value" style="color: #ffffff;">
77.6% <span style="font-size: 1.1rem; color: #ff5e62; font-weight: normal;">(Keras)</span>
</div>
<div class="metric-value" style="color: #00df89; margin-top: -10px;">
67.9% <span style="font-size: 1.1rem; color: #00df89; font-weight: normal;">(Gemma 3)</span>
</div>
<div class="metric-value" style="color: #00c3ff; margin-top: -10px;">
{qwen_f1_v1:.1f}% <span style="font-size: 1.1rem; color: #00c3ff; font-weight: normal;">(Qwen)</span>
</div>
</div>
""", unsafe_allow_html=True)
with col3:
st.markdown(f"""
<div class="glass-card">
<div class="metric-title">Vitesse d'Inference moyenne</div>
<div class="metric-value" style="color: #ff5e62;">
0.13 ms <span style="font-size: 1.1rem; color: #ff5e62; font-weight: normal;">/ tweet</span>
</div>
<div class="metric-value" style="color: #00df89; margin-top: -10px;">
0.56 s <span style="font-size: 1.1rem; color: #00df89; font-weight: normal;">/ tweet</span>
</div>
<div class="metric-value" style="color: #00c3ff; margin-top: -10px;">
{qwen_speed:.2f} s <span style="font-size: 1.1rem; color: #00c3ff; font-weight: normal;">/ tweet</span>
</div>
</div>
""", unsafe_allow_html=True)
# 2. Section: Justesse de Prediction
st.markdown("<h2 style='color: #ffffff;'>Justesse de Prediction (Accuracy & Erreurs)</h2>", unsafe_allow_html=True)
col_acc1, col_acc2 = st.columns(2)
with col_acc1:
if os.path.exists("assets/accuracy_comparison.png"):
st.image("assets/accuracy_comparison.png", caption="Comparaison de la Precision globale", use_container_width=True)
else:
st.warning("Graphique accuracy_comparison.png manquant dans assets.")
with col_acc2:
if os.path.exists("assets/confusion_matrices.png"):
st.image("assets/confusion_matrices.png", caption="Matrices de confusion comparees (Keras vs. Gemma 3)", use_container_width=True)
else:
st.warning("Matrice de confusion manquante dans assets.")
# 3. Section: Vitesse d'Inference
st.markdown("<h2 style='color: #ffffff;'>Vitesse d'Inference</h2>", unsafe_allow_html=True)
col_speed1, col_speed2, col_speed3 = st.columns([1, 2, 1])
with col_speed2:
if os.path.exists("assets/speed_comparison.png"):
st.image("assets/speed_comparison.png", caption="Comparaison des temps d'inference (secondes par tweet)", use_container_width=True)
else:
st.warning("Graphique speed_comparison.png manquant dans assets.")
# 3. Qualitative table
st.markdown("<h2 style='color: #ffffff;'>Exemples Qualitatifs & Explicabilite</h2>", unsafe_allow_html=True)
if os.path.exists("assets/qualitative_table.md"):
with open("assets/qualitative_table.md", "r", encoding="utf-8") as f:
table_content = f.read()
st.markdown(table_content, unsafe_allow_html=True)
else:
st.warning("Tableau d'explicabilite qualitatif manquant dans assets.")
# ----------------- Tab 3: Tweet Browser -----------------
elif nav == "Navigateur de Tweets":
st.markdown("<h1 style='color: #ffffff;'>Navigateur de Tweets du Benchmark</h1>", unsafe_allow_html=True)
st.markdown("<p style='color: #8c8c9a;'>Explorez l'echantillon complet des tweets evalues.</p>", unsafe_allow_html=True)
st.write("")
if not os.path.exists("data/benchmark_results.csv"):
st.error("Le fichier benchmark_results.csv n'existe pas encore. Veuillez d'abord executer l'inference complete.")
else:
df_bench = pd.read_csv("data/benchmark_results.csv")
# Filters row
col_f1, col_f2, col_f3 = st.columns(3)
with col_f1:
search_query = st.text_input("Filtrer par mot-cle (Texte) :", "")
with col_f2:
filter_sentiment = st.selectbox(
"Vrai Sentiment :",
["Tous", "Positif", "Negatif"]
)
with col_f3:
filter_match = st.selectbox(
"Filtrer par Accord :",
["Tous les tweets", "Accord des modeles", "Desaccord des modeles"]
)
# Apply filters
filtered_df = df_bench.copy()
if search_query:
filtered_df = filtered_df[filtered_df['text'].str.contains(search_query, case=False, na=False)]
if filter_sentiment == "Positif":
filtered_df = filtered_df[filtered_df['true_sentiment'] == 1]
elif filter_sentiment == "Negatif":
filtered_df = filtered_df[filtered_df['true_sentiment'] == 0]
if filter_match == "Accord des modeles":
filtered_df = filtered_df[filtered_df['keras_pred'] == filtered_df['llm_pred']]
elif filter_match == "Desaccord des modeles":
filtered_df = filtered_df[filtered_df['keras_pred'] != filtered_df['llm_pred']]
st.markdown(f"<p style='color: #cbd5e1; font-weight: 600;'>Nombre de tweets trouves : <span style='color: #00df89;'>{len(filtered_df)}</span> sur 500</p>", unsafe_allow_html=True)
for idx, row in filtered_df.head(20).iterrows():
true_lbl = "POSITIF" if row['true_sentiment'] == 1 else "NEGATIF"
keras_lbl = "POSITIF" if row['keras_pred'] == 1 else "NEGATIF"
llm_lbl = "POSITIF" if row['llm_pred'] == 1 else "NEGATIF"
if 'qwen_pred' in row:
qwen_lbl = "POSITIF" if row['qwen_pred'] == 1 else "NEGATIF"
qwen_display = f"<span style=\"font-weight: 600; color: {'#00df89' if row['qwen_pred'] == 1 else '#ff5e62'}\">{qwen_lbl}</span>"
else:
qwen_display = "<span style=\"font-weight: 600; color: #00c3ff;\">Non calcule</span>"
match_color = "rgba(0, 223, 137, 0.08)" if row['keras_pred'] == row['llm_pred'] else "rgba(255, 94, 98, 0.08)"
match_border = "rgba(0, 223, 137, 0.2)" if row['keras_pred'] == row['llm_pred'] else "rgba(255, 94, 98, 0.2)"
st.markdown(f"""
<div style="background-color: {match_color}; border: 1px solid {match_border}; border-radius: 12px; padding: 18px; margin-bottom: 15px;">
<p style="font-size: 1.05rem; font-weight: 500; color: #ffffff; margin-bottom: 12px;">"{row['text']}"</p>
<div style="display: flex; gap: 15px; flex-wrap: wrap; font-size: 0.85rem;">
<div><span style="color: #8c8c9a;">Vrai Label :</span> <span style="font-weight: 600; color: {'#00df89' if row['true_sentiment'] == 1 else '#ff5e62'}">{true_lbl}</span></div>
<div><span style="color: #8c8c9a;">Keras :</span> <span style="font-weight: 600; color: {'#00df89' if row['keras_pred'] == 1 else '#ff5e62'}">{keras_lbl} ({row.get('keras_prob', 0):.2f})</span></div>
<div><span style="color: #8c8c9a;">Gemma 3 :</span> <span style="font-weight: 600; color: {'#00df89' if row['llm_pred'] == 1 else '#ff5e62'}">{llm_lbl}</span></div>
<div><span style="color: #8c8c9a;">Qwen :</span> {qwen_display}</div>
<div><span style="color: #8c8c9a;">Emotions LLM :</span> <code style="background-color: rgba(255,255,255,0.05); padding: 2px 6px; border-radius: 4px; color: #ffffff;">{row.get('llm_emotions', '')}</code></div>
</div>
<div style="margin-top: 10px; padding-top: 8px; border-top: 1px solid rgba(255,255,255,0.05); font-style: italic; color: #cbd5e1; font-size: 0.9rem;">
<strong>Explication Gemma 3 :</strong> "{row.get('llm_explanation', '')}"
</div>
</div>
""", unsafe_allow_html=True)
if len(filtered_df) > 20:
st.info("Affichage des 20 premiers tweets filtres. Veuillez affiner vos filtres.")
# ----------------- Tab 4: Robustness (V2) -----------------
elif nav == "Robustesse (V2)":
st.markdown("<h1 style='color: #ffffff;'>Test de Robustesse face au Data Drift (V2)</h1>", unsafe_allow_html=True)
st.markdown("<p style='color: #8c8c9a;'>Evaluation comparative sur un jeu de donnees neutre compose de tweets modernes.</p>", unsafe_allow_html=True)
st.write("")
keras_human = 0.45
llm_human = 0.55
qwen_human = 0.88
qwen_acc_v2 = 92.5
conflict_count = 150
if os.path.exists("human_metrics.json"):
with open("human_metrics.json", "r", encoding="utf-8") as f:
metrics = json.load(f)
keras_human = metrics.get("keras_human_agreement", 0.45)
llm_human = metrics.get("llm_human_agreement", 0.55)
conflict_count = metrics.get("conflict_count", 150)
if os.path.exists("qwen_metrics.json"):
with open("qwen_metrics.json", "r", encoding="utf-8") as f:
q_metrics = json.load(f)
qwen_human = q_metrics.get("qwen_human", 0.88)
qwen_acc_v2 = q_metrics.get("qwen_acc_v2", 0.925) * 100
col1, col2, col3 = st.columns(3)
with col1:
st.markdown(f"""
<div class="glass-card">
<div class="metric-title">Accuracy Out-of-Distribution (V2)</div>
<div class="metric-value" style="color: #ff5e62;">
87.0% <span style="font-size: 1.1rem; color: #ff5e62; font-weight: normal;">(Keras)</span>
</div>
<div class="metric-value" style="color: #00df89; margin-top: -10px;">
91.0% <span style="font-size: 1.1rem; color: #00df89; font-weight: normal;">(Gemma 3)</span>
</div>
<div class="metric-value" style="color: #00c3ff; margin-top: -10px;">
{qwen_acc_v2:.1f}% <span style="font-size: 1.1rem; color: #00c3ff; font-weight: normal;">(Qwen)</span>
</div>
</div>
""", unsafe_allow_html=True)
with col2:
st.markdown(f"""
<div class="glass-card">
<div class="metric-title">Accord avec le Jugement Humain</div>
<div class="metric-value" style="color: #ff5e62;">
{keras_human * 100:.1f}% <span style="font-size: 1.1rem; color: #ff5e62; font-weight: normal;">(Keras)</span>
</div>
<div class="metric-value" style="color: #00df89; margin-top: -10px;">
{llm_human * 100:.1f}% <span style="font-size: 1.1rem; color: #00df89; font-weight: normal;">(Gemma 3)</span>
</div>
<div class="metric-value" style="color: #00c3ff; margin-top: -10px;">
{qwen_human * 100:.1f}% <span style="font-size: 1.1rem; color: #00c3ff; font-weight: normal;">(Qwen)</span>
</div>
</div>
""", unsafe_allow_html=True)
with col3:
st.markdown(f"""
<div class="glass-card">
<div class="metric-title">Desaccords identifies (V1)</div>
<div class="metric-value" style="color: #ffffff;">
{conflict_count} <span style="font-size: 1.1rem; color: #8c8c9a; font-weight: normal;">tweets</span>
</div>
<div style="margin-top: 30px; font-size: 0.85rem; color: #8c8c9a;">
soit <strong>30.0%</strong> de divergences sur l'echantillon de 500 tweets originaux.
</div>
</div>
""", unsafe_allow_html=True)
st.markdown("<h2 style='color: #ffffff;'>Comparaison des performances V1 vs. V2</h2>", unsafe_allow_html=True)
col_c1, col_c2 = st.columns([1, 4])
with col_c2:
if os.path.exists("assets/modern_accuracy_comparison.png"):
st.image("assets/modern_accuracy_comparison.png", caption="Sensibilite des modeles face au changement de distribution (Data Drift)", use_container_width=True)
else:
st.warning("Graphique de comparaison V2 manquant dans assets.")
st.markdown("<h2 style='color: #ffffff;'>Echantillon des Desaccords Evalues (Gold Standard)</h2>", unsafe_allow_html=True)
if os.path.exists("data/human_ground_truth.csv"):
df_human = pd.read_csv("data/human_ground_truth.csv")
for idx, row in df_human.head(10).iterrows():
keras_lbl = "POSITIF" if row['keras_pred'] == 1 else "NEGATIF"
llm_lbl = "POSITIF" if row['llm_pred'] == 1 else "NEGATIF"
human_lbl = "POSITIF" if row['human_label'] == 1 else "NEGATIF"
if 'qwen_pred' in row:
qwen_lbl = "POSITIF" if row['qwen_pred'] == 1 else "NEGATIF"
qwen_display = f"<span style=\"font-weight: 600; color: {'#00df89' if row['qwen_pred'] == 1 else '#ff5e62'}\">{qwen_lbl}</span>"
else:
qwen_display = "<span style=\"font-weight: 600; color: #00c3ff;\">Non calcule</span>"
st.markdown(f"""
<div style="background-color: rgba(255, 255, 255, 0.03); border: 1px solid rgba(255,255,255,0.05); border-radius: 12px; padding: 18px; margin-bottom: 12px;">
<p style="font-size: 1.05rem; font-weight: 500; color: #ffffff; margin-bottom: 10px;">"{row['text']}"</p>
<div style="display: flex; gap: 20px; flex-wrap: wrap; font-size: 0.85rem;">
<div><span style="color: #8c8c9a;">Baseline Keras :</span> <span style="font-weight: 600; color: {'#00df89' if row['keras_pred'] == 1 else '#ff5e62'}">{keras_lbl}</span></div>
<div><span style="color: #8c8c9a;">Gemma 3 :</span> <span style="font-weight: 600; color: {'#00df89' if row['llm_pred'] == 1 else '#ff5e62'}">{llm_lbl}</span></div>
<div><span style="color: #8c8c9a;">Qwen :</span> {qwen_display}</div>
<div><span style="color: #8c8c9a;">Jugement Humain :</span> <span style="background-color: rgba(0, 223, 137, 0.15) if row['human_label'] == 1 else rgba(255, 94, 98, 0.15); color: {'#00df89' if row['human_label'] == 1 else '#ff5e62'}; font-weight: bold; padding: 2px 8px; border-radius: 10px;">{human_lbl}</span></div>
</div>
</div>
""", unsafe_allow_html=True)