JeanProjets's picture
Upload folder using huggingface_hub
6cde6d6 verified
Raw
History Blame Contribute Delete
11 kB
import os
import json
import time
import re
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.metrics import accuracy_score, f1_score, confusion_matrix
from llama_cpp import Llama
# Set style for premium charts
sns.set_theme(style="darkgrid")
plt.rcParams.update({
'grid.color': '#2c2c2c',
'axes.facecolor': '#121212',
'figure.facecolor': '#0e0e0e',
'text.color': '#e0e0e0',
'axes.labelcolor': '#e0e0e0',
'xtick.color': '#b0b0b0',
'ytick.color': '#b0b0b0',
'font.size': 11
})
print("=== [Process 2] Evaluating Gemma 3 LLM & Plotting Comparisons ===", flush=True)
# 1. Load intermediate results from Keras baseline
if not os.path.exists("data/keras_results.csv"):
raise FileNotFoundError("data/keras_results.csv not found! Please run evaluate/evaluate_keras.py first.")
df_sample = pd.read_csv("data/keras_results.csv")
print(f"Loaded {len(df_sample)} pre-evaluated tweets from Keras baseline.", flush=True)
# 2. Loading Gemma 3 GGUF Model
print("Loading local Gemma 3 GGUF model via Llama.from_pretrained...", flush=True)
llm = Llama.from_pretrained(
repo_id="JusteLeo/emotion-text-classifier-LLM",
filename="EmotionTextClassifierLLM.gguf",
n_ctx=512,
verbose=False
)
print("Gemma 3 GGUF loaded successfully.", flush=True)
# Emotion Mapping Definitions
positive_emotions = {
'joy', 'love', 'surprise', 'pride', 'admiration', 'gratitude', 'hope',
'optimism', 'amusement', 'desire', 'caring', 'relief', 'excitement',
'approval', 'caring', 'curiosity'
}
negative_emotions = {
'sadness', 'anger', 'fear', 'disgust', 'shame', 'guilt', 'disappointment',
'annoyance', 'frustration', 'grief', 'nervousness', 'embarrassment',
'remorse', 'disapproval', 'confusion', 'boredom'
}
def clean_and_parse_json(text):
"""Robustly cleans and parses JSON from the LLM output."""
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_str = emotion_match.group(1)
emotions = [e.strip(' "\'') for e in emotions_str.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}
llm_emotions = []
llm_explanations = []
llm_predictions = []
llm_times = []
# Run LLM inference loop
print("Starting Gemma 3 predictions loop (500 tweets)...", flush=True)
for idx, text in enumerate(df_sample['text']):
if (idx + 1) % 25 == 0 or idx == 0:
print(f"Processing tweet {idx+1}/{len(df_sample)}...", flush=True)
start_time = time.time()
try:
response = llm.create_chat_completion(
messages=[{"role": "user", "content": text}],
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.")
# Map primary emotion to binary sentiment
mapped_sentiment = 0 # Default negative
if emotions:
primary_emotion = emotions[0].lower().strip()
if primary_emotion in positive_emotions:
mapped_sentiment = 1
elif primary_emotion in negative_emotions:
mapped_sentiment = 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:
mapped_sentiment = 1
else:
emotions = ["Neutral"]
explanation = "Unable to classify fine emotions."
mapped_sentiment = 0
except Exception as e:
print(f"Error at index {idx}: {str(e)}", flush=True)
emotions = ["Error"]
explanation = f"Inference failed with error: {str(e)}"
mapped_sentiment = 0
end_time = time.time()
llm_emotions.append(", ".join(emotions))
llm_explanations.append(explanation)
llm_predictions.append(mapped_sentiment)
llm_times.append(end_time - start_time)
df_sample['llm_emotions'] = llm_emotions
df_sample['llm_explanation'] = llm_explanations
df_sample['llm_pred'] = llm_predictions
df_sample['llm_time'] = llm_times
# Calculate metrics
keras_accuracy = accuracy_score(df_sample['true_sentiment'], df_sample['keras_pred'])
keras_f1 = f1_score(df_sample['true_sentiment'], df_sample['keras_pred'])
keras_avg_time = df_sample['keras_time'].iloc[0]
llm_accuracy = accuracy_score(df_sample['true_sentiment'], df_sample['llm_pred'])
llm_f1 = f1_score(df_sample['true_sentiment'], df_sample['llm_pred'])
llm_total_time = sum(llm_times)
llm_avg_time = np.mean(llm_times)
print(f"\nBaseline Keras Accuracy: {keras_accuracy:.4f}", flush=True)
print(f"Baseline Keras F1-Score: {keras_f1:.4f}", flush=True)
print(f"Baseline Keras Avg Speed: {keras_avg_time*1000:.2f} ms / tweet", flush=True)
print(f"\nGemma 3 Accuracy: {llm_accuracy:.4f}", flush=True)
print(f"Gemma 3 F1-Score: {llm_f1:.4f}", flush=True)
print(f"Gemma 3 Avg Speed: {llm_avg_time:.4f} s / tweet", flush=True)
# Save final benchmark results
df_sample.to_csv("data/benchmark_results.csv", index=False)
print("\nFinal benchmark results saved to data/benchmark_results.csv", flush=True)
# 3. Generate Visualizations
print("\n--- Generating Premium Performance Visualizations ---", flush=True)
os.makedirs("assets", exist_ok=True)
# 3.1 Confusion Matrices
fig, axes = plt.subplots(1, 2, figsize=(14, 6))
# Keras Matrix
cm_keras = confusion_matrix(df_sample['true_sentiment'], df_sample['keras_pred'])
sns.heatmap(cm_keras, annot=True, fmt='d', cmap='Blues', cbar=False, ax=axes[0],
xticklabels=['Négatif', 'Positif'], yticklabels=['Négatif', 'Positif'],
annot_kws={"size": 14, "weight": "bold"})
axes[0].set_title("Matrice de Confusion : Baseline Keras", fontsize=14, weight='bold', pad=15)
axes[0].set_xlabel("Prédiction", fontsize=12)
axes[0].set_ylabel("Vrai Label", fontsize=12)
# Gemma Matrix
cm_llm = confusion_matrix(df_sample['true_sentiment'], df_sample['llm_pred'])
sns.heatmap(cm_llm, annot=True, fmt='d', cmap='Greens', cbar=False, ax=axes[1],
xticklabels=['Négatif', 'Positif'], yticklabels=['Négatif', 'Positif'],
annot_kws={"size": 14, "weight": "bold"})
axes[1].set_title("Matrice de Confusion : Challengeur Gemma 3 (LLM)", fontsize=14, weight='bold', pad=15)
axes[1].set_xlabel("Prédiction", fontsize=12)
axes[1].set_ylabel("Vrai Label", fontsize=12)
plt.tight_layout()
plt.savefig("assets/confusion_matrices.png", dpi=300, facecolor='#0e0e0e')
plt.close()
# 3.2 Accuracy Bar Chart
fig, ax = plt.subplots(figsize=(8, 6))
models = ['Keras (Baseline)', 'Gemma 3 (LLM)']
accuracies = [keras_accuracy * 100, llm_accuracy * 100]
speeds = [keras_avg_time, llm_avg_time]
color_acc = '#00df89'
bars = ax.bar(models, accuracies, width=0.5, color=color_acc, alpha=0.85, edgecolor='#00df89', linewidth=1.5)
ax.set_ylabel('Précision (Accuracy %)', color=color_acc, fontsize=12, weight='bold')
ax.set_ylim(0, 100)
ax.tick_params(axis='y', labelcolor=color_acc)
ax.set_xticklabels(models, fontsize=12, weight='bold')
for bar in bars:
height = bar.get_height()
ax.text(bar.get_x() + bar.get_width()/2., height + 2, f'{height:.1f}%', ha='center', va='bottom', color='#00df89', weight='bold', fontsize=12)
plt.title("Comparaison de la Justesse de Prédiction (Accuracy)", fontsize=14, weight='bold', pad=20, color='white')
plt.tight_layout()
plt.savefig("assets/accuracy_comparison.png", dpi=300, facecolor='#0e0e0e')
plt.close()
# 3.3 Speed Bar Chart
fig, ax = plt.subplots(figsize=(8, 6))
color_speed = '#ff5e62'
bars_time = ax.bar(models, speeds, width=0.5, color=color_speed, alpha=0.85, edgecolor='#ff5e62', linewidth=1.5)
ax.set_ylabel("Temps d'inférence moyen par tweet (s)", color=color_speed, fontsize=12, weight='bold')
ax.tick_params(axis='y', labelcolor=color_speed)
ax.set_xticklabels(models, fontsize=12, weight='bold')
for bar in bars_time:
height = bar.get_height()
time_text = f'{height*1000:.1f} ms' if height < 0.01 else f'{height:.3f} s'
ax.text(bar.get_x() + bar.get_width()/2., height + 0.02 * max(speeds), time_text, ha='center', va='bottom', color='#ff5e62', weight='bold', fontsize=12)
plt.title("Comparaison de la Vitesse d'Inférence", fontsize=14, weight='bold', pad=20, color='white')
plt.tight_layout()
plt.savefig("assets/speed_comparison.png", dpi=300, facecolor='#0e0e0e')
plt.close()
# 4. Construct Qualitative Explainability Table
print("\n--- Constructing Qualitative Explainability Table ---", flush=True)
ex_happy = df_sample[(df_sample['true_sentiment'] == 1) & (df_sample['llm_pred'] == 1)].head(2)
ex_sad = df_sample[(df_sample['true_sentiment'] == 0) & (df_sample['llm_pred'] == 0)].head(2)
ex_disagree = df_sample[df_sample['keras_pred'] != df_sample['llm_pred']].head(1)
if len(ex_disagree) == 0:
ex_disagree = df_sample.head(1)
qualitative_sample = pd.concat([ex_happy, ex_sad, ex_disagree]).head(5)
qual_markdown = """### Table 1: Analyse Qualitative de l'Explicabilité (Explainability) de Gemma 3
Ce tableau présente 5 exemples de tweets analysés lors de la preuve de concept, confrontant les prédictions binaires de la baseline Keras avec l'analyse d'émotion fine et l'explication formulée par le LLM Gemma 3.
| Tweet Original | Vrai Sentiment | Prédiction Keras (Probabilité) | Émotion Fine détectée (Gemma 3) | Explication Générée par Gemma 3 |
| :--- | :---: | :---: | :---: | :--- |
"""
for _, row in qualitative_sample.iterrows():
true_lbl = "😊 Positif" if row['true_sentiment'] == 1 else "😢 Négatif"
keras_lbl = f"Positif ({row['keras_prob']:.2f})" if row['keras_pred'] == 1 else f"Négatif ({row['keras_prob']:.2f})"
tweet_txt = row['text'].replace('|', '\\|').replace('\n', ' ')
explanation_txt = row['llm_explanation'].replace('|', '\\|').replace('\n', ' ')
qual_markdown += f"| \"{tweet_txt}\" | {true_lbl} | {keras_lbl} | **{row['llm_emotions']}** | {explanation_txt} |\n"
with open("assets/qualitative_table.md", "w", encoding="utf-8") as f:
f.write(qual_markdown)
print("Visualizations and qualitative table successfully generated in the assets directory!", flush=True)
print("=== [Process 2] Finished successfully ===", flush=True)