""" App Gradio pour visualiser les embeddings de romans français Interface minimaliste avec style Seaborn """ import gradio as gr import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns from datasets import load_dataset import warnings warnings.filterwarnings('ignore') # ==================== CONFIGURATION ==================== DATASET_NAME = "hoololi/novels-embeddings-dataset" # Configuration du style visuel plt.style.use('default') sns.set_style("whitegrid") sns.set_palette("deep") # ==================== CHARGEMENT DES DONNÉES ==================== # Charger le dataset au démarrage print("🔄 Initialisation de l'application...") try: # Approche hybride : essayer plusieurs URLs parquet print("📊 Tentative de chargement parquet...") # URLs possibles pour le fichier parquet possible_urls = [ f"https://huggingface.co/datasets/{DATASET_NAME}/resolve/main/data/train-00000-of-00001-5211c5ec6e8e9559.parquet", # Nom exact avec hash f"https://huggingface.co/datasets/{DATASET_NAME}/resolve/main/data/train-00000-of-00001.parquet", f"https://huggingface.co/datasets/{DATASET_NAME}/resolve/main/train.parquet", f"https://huggingface.co/datasets/{DATASET_NAME}/resolve/main/data.parquet", f"https://huggingface.co/datasets/{DATASET_NAME}/resolve/main/dataset.parquet" ] df_full = None for url in possible_urls: try: print(f"🔗 Essai: {url}") df_full = pd.read_parquet(url) print(f"✅ Succès avec: {url}") break except Exception as url_error: print(f"❌ Échec: {url_error}") continue if df_full is not None: print(f"✅ Fichier parquet chargé: {len(df_full)} lignes") print(f"📊 Colonnes disponibles: {list(df_full.columns)}") # Sélectionner seulement les colonnes nécessaires columns_needed = ['roman', 'tsne_x', 'tsne_y'] # Vérifier que les colonnes existent missing_cols = [col for col in columns_needed if col not in df_full.columns] if missing_cols: print(f"⚠️ Colonnes manquantes: {missing_cols}") print(f"📋 Colonnes disponibles: {list(df_full.columns)}") raise Exception(f"Colonnes requises manquantes: {missing_cols}") dataset_df = df_full[columns_needed].copy() print(f"✅ Dataset filtré: {len(dataset_df)} phrases") available_novels = sorted(dataset_df['roman'].unique().tolist()) print(f"📚 Romans disponibles: {available_novels}") else: raise Exception("Aucune URL parquet n'a fonctionné") except Exception as e: print(f"❌ Erreur de chargement parquet: {e}") print("🔄 Fallback vers données de test...") # Fallback : données de test try: import numpy as np np.random.seed(42) test_data = [] romans = ["Proust - Test", "Dumas - Test", "Chrétien - Test", "Zola - Test"] for i, roman in enumerate(romans): for j in range(100): # 100 points par roman test_data.append({ 'roman': roman, 'tsne_x': np.random.normal(i*10, 5), # Clusters séparés 'tsne_y': np.random.normal(i*5, 3) }) dataset_df = pd.DataFrame(test_data) available_novels = romans print(f"✅ Données de test créées: {len(dataset_df)} phrases") except Exception as test_error: print(f"❌ Impossible de créer les données de test: {test_error}") dataset_df = None available_novels = [] # ==================== FONCTIONS DE VISUALISATION ==================== def create_embeddings_plot(selected_novels, figsize=(10, 8)): """Crée le graphique t-SNE avec les romans sélectionnés""" if dataset_df is None or not selected_novels: # Graphique vide en cas d'erreur fig, ax = plt.subplots(figsize=figsize) ax.text(0.5, 0.5, 'Aucune donnée à afficher', ha='center', va='center', transform=ax.transAxes, fontsize=16) ax.set_xlim(0, 1) ax.set_ylim(0, 1) return fig # Filtrer les données selon les romans sélectionnés filtered_df = dataset_df[dataset_df['roman'].isin(selected_novels)] if filtered_df.empty: fig, ax = plt.subplots(figsize=figsize) ax.text(0.5, 0.5, 'Aucun roman sélectionné', ha='center', va='center', transform=ax.transAxes, fontsize=16) ax.set_xlim(0, 1) ax.set_ylim(0, 1) return fig # Calculer les bornes globales pour garder l'échelle fixe all_x = dataset_df['tsne_x'] all_y = dataset_df['tsne_y'] x_margin = (all_x.max() - all_x.min()) * 0.05 y_margin = (all_y.max() - all_y.min()) * 0.05 x_min, x_max = all_x.min() - x_margin, all_x.max() + x_margin y_min, y_max = all_y.min() - y_margin, all_y.max() + y_margin # Créer le graphique avec style Seaborn fig, ax = plt.subplots(figsize=figsize) # Scatter plot avec Seaborn scatter = sns.scatterplot( data=filtered_df, x='tsne_x', y='tsne_y', hue='roman', alpha=0.7, s=60, # Taille des points ax=ax ) # Configuration du graphique plt.xlabel('t-SNE dimension 1', fontsize=12) plt.ylabel('t-SNE dimension 2', fontsize=12) # Fixer les bornes pour garder l'échelle constante plt.xlim(x_min, x_max) plt.ylim(y_min, y_max) # Configuration de la légende - en bas legend = plt.legend( title='Romans', bbox_to_anchor=(0.5, -0.15), loc='upper center', fontsize=10, title_fontsize=12, ncol=2 # Répartir sur 2 colonnes pour gagner de la place ) legend.set_frame_on(True) legend.get_frame().set_facecolor('white') legend.get_frame().set_alpha(0.9) # Grille et style plt.grid(True, alpha=0.3) plt.tight_layout() return fig # ==================== INTERFACE GRADIO ==================== def create_gradio_interface(): """Crée l'interface Gradio minimaliste""" if not available_novels: # Interface d'erreur with gr.Blocks(title="Erreur - Dataset non disponible") as demo: gr.Markdown("# ❌ Erreur") gr.Markdown(f"Impossible de charger le dataset `{DATASET_NAME}`") return demo # Interface principale with gr.Blocks( title="📚 Embeddings de Romans Français", theme=gr.themes.Soft(), css=""" .gradio-container { max-width: 1200px; margin: auto; } .plot-container { display: flex; justify-content: center; } """ ) as demo: # Header gr.Markdown( """ # 📚 Visualisation d'Embeddings de Romans Français Explorez comment les styles littéraires se regroupent dans l'espace des embeddings. """ ) # Contrôles - Checkboxes horizontales pour chaque roman gr.Markdown("### 🎛️ Sélection des romans") with gr.Row(): # Créer les checkboxes dynamiquement en horizontal novel_checkboxes = {} for novel in available_novels: novel_checkboxes[novel] = gr.Checkbox( label=novel, value=True, # Tous cochés par défaut interactive=True ) # Graphique principal plot_display = gr.Plot( label="📊 Projection t-SNE des embeddings", value=create_embeddings_plot(available_novels) ) # Événements - Mise à jour quand les checkboxes changent checkbox_inputs = list(novel_checkboxes.values()) def update_from_checkboxes(*checkbox_values): # Reconstruire le dictionnaire novel -> bool novel_selection = {novel: value for novel, value in zip(available_novels, checkbox_values)} selected_novels = [novel for novel, selected in novel_selection.items() if selected] # Créer le graphique fig = create_embeddings_plot(selected_novels) return fig # Connecter tous les checkboxes à la fonction de mise à jour for checkbox in checkbox_inputs: checkbox.change( fn=update_from_checkboxes, inputs=checkbox_inputs, outputs=[plot_display] ) return demo # ==================== LANCEMENT ==================== def create_demo(): """Fonction pour Hugging Face Spaces""" return create_gradio_interface() if __name__ == "__main__": # Créer et lancer l'interface demo = create_gradio_interface() demo.launch( share=True, debug=True, server_name="0.0.0.0", server_port=7860 ) # Export pour Spaces demo = create_gradio_interface()