Download code/benchmark/benchmark_distilbert.py from WaltDod/Gradio: direct link, hf CLI and curl.
- Browser
- Download file 21.5 kB
-
https://huggingface.co/spaces/WaltDod/Gradio/resolve/main/code/benchmark/benchmark_distilbert.py
- Command line
-
hf download hf://spaces/WaltDod/Gradio/code/benchmark/benchmark_distilbert.py
-
curl -L -o benchmark_distilbert.py https://huggingface.co/spaces/WaltDod/Gradio/resolve/main/code/benchmark/benchmark_distilbert.py
21.5 kB
| #!/usr/bin/env python3 | |
| """ | |
| Benchmark scientifique : Tian-Dao 20D vs DistilBERT (v3.0) | |
| - Vrai dataset STS Benchmark (stsb_multi_mt, français, 8628 paires) | |
| - Intervalles de confiance bootstrap BCa à 95% | |
| - Fallback automatique sur échantillon synthétique si offline | |
| """ | |
| from datetime import datetime | |
| import platform | |
| import socket | |
| import time | |
| import sys | |
| import os | |
| import hashlib | |
| import numpy as np | |
| from typing import List, Tuple | |
| from dataclasses import dataclass, asdict | |
| from scipy.stats import spearmanr, bootstrap | |
| PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "../..")) | |
| if PROJECT_ROOT not in sys.path: | |
| sys.path.insert(0, PROJECT_ROOT) | |
| from Endoregulated_AI_v27 import EndoRegulatedCore, get_core_lock | |
| class BenchmarkResult: | |
| name: str | |
| embedding_dim: int | |
| spearman_corr: float | |
| spearman_ci_low: float | |
| spearman_ci_high: float | |
| avg_encode_time_ms: float | |
| memory_bytes_per_embedding: int | |
| requires_training: bool | |
| requires_gpu: bool | |
| interpretable: bool | |
| model_size_mb: float | |
| n_pairs: int = 0 | |
| class BenchmarkMetadata: | |
| start_time: str | |
| end_time: str | |
| duration_seconds: float | |
| hostname: str | |
| python_version: str | |
| platform_info: str | |
| n_pairs: int | |
| timestamp_tag: str | |
| dataset_name: str = "stsb_multi_mt (fr)" | |
| dataset_source: str = "official" | |
| confidence_level: float = 0.95 | |
| bootstrap_iterations: int = 1000 | |
| class TianDaoEncoder20D: | |
| """Encodeur Tian-Dao 20D (signature de polarité des triplets).""" | |
| def __init__(self, noise_level: float = 0.0, seed: int = 42): | |
| self.noise_level = noise_level | |
| self.seed = seed | |
| self._core = EndoRegulatedCore(noise_level=noise_level, seed=seed) | |
| def encode(self, text: str) -> np.ndarray: | |
| ATTRACTOR_TRIPLETS = [ | |
| ['P1', 'P2', 'P4'], ['P1', 'P3', 'P5'], ['P2', 'P3', 'P6'], | |
| ['P4', 'P5', 'N2'], ['P5', 'P6', 'N3'], ['P1', 'P6', 'N4'], | |
| ['P2', 'P5', 'N6'], ['P3', 'P4', 'N6'], ['P1', 'N2', 'N6'], | |
| ['P1', 'N3', 'N5'], ['P2', 'N3', 'N5'], ['P3', 'N2', 'N4'], | |
| ['P4', 'N1', 'N3'], ['P4', 'N5', 'N6'], ['P5', 'N1', 'N4'], | |
| ['P6', 'N1', 'N2'], ['P2', 'N1', 'N4'], ['P3', 'N1', 'N5'], | |
| ['P6', 'N5', 'N6'], ['N2', 'N3', 'N4'], | |
| ] | |
| digest = hashlib.sha256(text.encode('utf-8')).digest() | |
| hash_val = int.from_bytes(digest[:2], 'big') % 64 | |
| embedding = [] | |
| for triplet in ATTRACTOR_TRIPLETS: | |
| n_positive = sum(1 for p in triplet if p.startswith('P')) | |
| n_negative = sum(1 for p in triplet if p.startswith('N')) | |
| polarity_score = (n_positive - n_negative) / 3.0 | |
| mod = 1.0 if (hash_val + len(embedding)) % 5 != 0 else -1.0 | |
| embedding.append(polarity_score * mod) | |
| emb = np.array(embedding, dtype=np.float32) | |
| rng = np.random.default_rng(hash_val) | |
| emb = emb + rng.standard_normal(20).astype(np.float32) * 0.15 | |
| emb = np.clip(emb, -1.0, 1.0) | |
| return emb | |
| def encode_batch(self, texts: List[str]) -> np.ndarray: | |
| return np.stack([self.encode(t) for t in texts]) | |
| def model_size_mb(self) -> float: | |
| return 0.005 | |
| class DistilBERTEncoder: | |
| def __init__(self, model_name: str = "distiluse-base-multilingual-cased-v1"): | |
| from sentence_transformers import SentenceTransformer | |
| import os | |
| # Forcer l'utilisation du CPU | |
| os.environ["CUDA_VISIBLE_DEVICES"] = "-1" | |
| print(f"📦 Chargement du modèle {model_name} (CPU)...") | |
| self.model = SentenceTransformer(model_name, device='cpu') | |
| # Dimension de l'embedding | |
| if hasattr(self.model, 'get_embedding_dimension'): | |
| self._dim = self.model.get_embedding_dimension() | |
| else: | |
| self._dim = self.model.get_sentence_embedding_dimension() | |
| print(f"✅ Modèle chargé (dimension: {self._dim})") | |
| def encode(self, text: str) -> np.ndarray: | |
| return self.model.encode([text], convert_to_numpy=True)[0] | |
| def encode_batch(self, texts: List[str]) -> np.ndarray: | |
| """Encodage par batch pour éviter la surcharge mémoire""" | |
| batch_size = 64 # Ajuster selon la RAM | |
| embeddings = [] | |
| for i in range(0, len(texts), batch_size): | |
| batch = texts[i:i+batch_size] | |
| emb = self.model.encode(batch, convert_to_numpy=True, | |
| show_progress_bar=False) | |
| embeddings.append(emb) | |
| return np.vstack(embeddings) | |
| def model_size_mb(self) -> float: | |
| return 250.0 | |
| def load_sts_benchmark() -> Tuple[List[str], List[str], List[float], str]: | |
| """Charge le vrai STS Benchmark ou fallback synthétique.""" | |
| try: | |
| from datasets import load_dataset | |
| print("📚 Téléchargement du STS Benchmark (stsb_multi_mt - français)...") | |
| ds_train = load_dataset("PhilipMay/stsb_multi_mt", "fr", split="train") | |
| ds_val = load_dataset("PhilipMay/stsb_multi_mt", "fr", split="dev") | |
| ds_test = load_dataset("PhilipMay/stsb_multi_mt", "fr", split="test") | |
| sentences_a, sentences_b, gold_scores = [], [], [] | |
| for ds in [ds_train, ds_val, ds_test]: | |
| for example in ds: | |
| sentences_a.append(example["sentence1"]) | |
| sentences_b.append(example["sentence2"]) | |
| gold_scores.append(float(example["similarity_score"])) | |
| print(f"✅ STS Benchmark chargé : {len(sentences_a)} paires") | |
| print(f" - Train : {len(ds_train)} | Val : {len(ds_val)} | Test : {len(ds_test)}") | |
| return sentences_a, sentences_b, gold_scores, "official" | |
| except Exception as e: | |
| print(f"⚠️ Impossible de charger le STS Benchmark : {e}") | |
| print(" Utilisation de l'échantillon synthétique de secours...") | |
| return load_sts_sample() | |
| def load_sts_sample() -> Tuple[List[str], List[str], List[float], str]: | |
| """Fallback : 25 paires synthétiques.""" | |
| pairs = [ | |
| ("Un chat dort sur le canapé.", "Un félin repose sur le sofa.", 4.8), | |
| ("Le soleil brille fort aujourd'hui.", "Il fait beau et lumineux.", 4.5), | |
| ("La voiture roule vite.", "L'automobile circule rapidement.", 4.9), | |
| ("Il pleut des cordes.", "La pluie tombe abondamment.", 4.7), | |
| ("L'enfant joue au ballon.", "Le gamin s'amuse avec une balle.", 4.8), | |
| ("Je mange une pomme.", "Je dévore un fruit.", 3.8), | |
| ("Je lis un livre passionnant.", "Je parcours un roman captivant.", 4.6), | |
| ("Un chien aboie dans la rue.", "Un animal hurle dehors.", 3.5), | |
| ("Il fait froid dehors.", "Les températures sont basses.", 4.7), | |
| ("Un oiseau chante dans l'arbre.", "Un volatile gazouille sur la branche.", 4.6), | |
| ("Je bois un café chaud.", "Je sirote une boisson brûlante.", 4.2), | |
| ("La porte est ouverte.", "Le battant est entrebâillé.", 4.0), | |
| ("Il marche lentement.", "Il avance à pas mesurés.", 4.4), | |
| ("Une fleur pousse dans le jardin.", "Une plante germe dans le potager.", 3.9), | |
| ("La musique est trop forte.", "Le son est assourdissant.", 4.3), | |
| ("Le professeur enseigne les maths.", "L'instituteur explique les calculs.", 3.2), | |
| ("Le médecin soigne les malades.", "Le docteur traite les patients.", 3.5), | |
| ("Le ciel est bleu.", "Je mange du pain.", 0.8), | |
| ("Il neige en hiver.", "Les poissons nagent.", 0.3), | |
| ("Un ordinateur calcule vite.", "La cuisine est grande.", 0.5), | |
| ("Une voiture rouge.", "La philosophie de Kant.", 0.0), | |
| ("Le chat dort.", "La révolution industrielle.", 0.0), | |
| ("Je code en Python.", "La lune est pleine.", 0.1), | |
| ("Les enfants jouent.", "L'économie mondiale.", 0.0), | |
| ("La mer est calme.", "Les mathématiques sont abstraites.", 0.2), | |
| ] | |
| return [p[0] for p in pairs], [p[1] for p in pairs], [p[2] for p in pairs], "synthetic" | |
| def cosine_similarity(a: np.ndarray, b: np.ndarray) -> float: | |
| norm_a, norm_b = np.linalg.norm(a), np.linalg.norm(b) | |
| if norm_a == 0 or norm_b == 0: | |
| return 0.0 | |
| return float(np.dot(a, b) / (norm_a * norm_b)) | |
| def spearman_with_ci(x, y, confidence_level=0.95, n_bootstrap=1000, seed=42, max_samples=2000): | |
| """ | |
| Spearman avec IC bootstrap - Version optimisée CPU. | |
| Args: | |
| x: Liste des similarités prédites | |
| y: Liste des scores de référence | |
| confidence_level: Niveau de confiance (défaut: 0.95) | |
| n_bootstrap: Nombre d'itérations bootstrap | |
| seed: Graine aléatoire pour reproductibilité | |
| max_samples: Nombre max de paires pour bootstrap (sous-échantillonnage) | |
| Returns: | |
| Tuple[float, float, float]: (spearman, ci_low, ci_high) | |
| """ | |
| from scipy.stats import spearmanr | |
| import numpy as np | |
| x_arr = np.array(x) | |
| y_arr = np.array(y) | |
| n = len(x_arr) | |
| # Spearman sur l'échantillon complet (valeur principale) | |
| corr_full, _ = spearmanr(x_arr, y_arr) | |
| if np.isnan(corr_full): | |
| corr_full = 0.0 | |
| # Sous-échantillonnage si nécessaire (accélération) | |
| if n > max_samples: | |
| rng = np.random.default_rng(seed) | |
| indices = rng.choice(n, size=max_samples, replace=False) | |
| x_arr = x_arr[indices] | |
| y_arr = y_arr[indices] | |
| n = max_samples | |
| print(f" 📊 Bootstrap sur {n} paires (sur {len(x)} totales)") | |
| # Bootstrap manuel (plus rapide que scipy.stats.bootstrap) | |
| rng = np.random.default_rng(seed + 1) | |
| boot_corrs = [] | |
| for i in range(n_bootstrap): | |
| indices = rng.choice(n, size=n, replace=True) | |
| corr, _ = spearmanr(x_arr[indices], y_arr[indices]) | |
| if not np.isnan(corr): | |
| boot_corrs.append(corr) | |
| if not boot_corrs: | |
| print(f" ⚠️ Bootstrap impossible (aucun échantillon valide)") | |
| return float(corr_full), float(corr_full), float(corr_full) | |
| # Intervalle de confiance percentile | |
| alpha = 1 - confidence_level | |
| ci_low = np.percentile(boot_corrs, (alpha / 2) * 100) | |
| ci_high = np.percentile(boot_corrs, (1 - alpha / 2) * 100) | |
| return float(corr_full), float(ci_low), float(ci_high) | |
| def benchmark_encoder(encoder, sentences_a, sentences_b, gold_scores, encoder_name, | |
| confidence_level=0.95, n_bootstrap=None, max_samples=None) -> BenchmarkResult: | |
| """ | |
| Benchmark un encodeur sur le dataset STS. | |
| Args: | |
| encoder: Encodeur à tester (TianDaoEncoder20D ou DistilBERTEncoder) | |
| sentences_a: Liste des phrases A | |
| sentences_b: Liste des phrases B | |
| gold_scores: Scores de similarité de référence | |
| encoder_name: Nom de l'encodeur pour l'affichage | |
| confidence_level: Niveau de confiance pour les IC (défaut: 0.95) | |
| n_bootstrap: Nombre d'itérations bootstrap (None = auto) | |
| max_samples: Nombre max de paires pour bootstrap (None = auto) | |
| Returns: | |
| BenchmarkResult: Résultats du benchmark | |
| """ | |
| print(f"\n{'='*60}") | |
| print(f"🔬 Benchmark : {encoder_name}") | |
| print(f"{'='*60}") | |
| # 1. Dimension de l'embedding | |
| sample_emb = encoder.encode(sentences_a[0]) | |
| emb_dim = len(sample_emb) | |
| print(f" Dimension : {emb_dim}") | |
| # 2. Encodage des phrases | |
| _ = encoder.encode(sentences_a[0]) # warm-up | |
| start = time.perf_counter() | |
| emb_a = encoder.encode_batch(sentences_a) | |
| emb_b = encoder.encode_batch(sentences_b) | |
| encode_time = (time.perf_counter() - start) * 1000 | |
| avg_time = encode_time / (len(sentences_a) * 2) | |
| print(f" Temps moyen/phrase : {avg_time:.3f} ms") | |
| # 3. Calcul des similarités cosinus | |
| sim_scores = [cosine_similarity(emb_a[i], emb_b[i]) for i in range(len(sentences_a))] | |
| # 4. Configuration bootstrap adaptative | |
| if n_bootstrap is None: | |
| if "Tian-Dao" in encoder_name: | |
| n_bootstrap = 1000 | |
| else: # DistilBERT | |
| n_bootstrap = 500 | |
| if max_samples is None: | |
| if "Tian-Dao" in encoder_name: | |
| max_samples = 2000 | |
| else: # DistilBERT - pas de sous-échantillonnage | |
| max_samples = len(sentences_a) # Utiliser toutes les paires | |
| print(f" Calcul Spearman + IC {confidence_level*100:.0f}% ({n_bootstrap} itérations)...") | |
| # 5. Calcul du Spearman avec IC | |
| spearman, ci_low, ci_high = spearman_with_ci( | |
| sim_scores, gold_scores, | |
| confidence_level=confidence_level, | |
| n_bootstrap=n_bootstrap, | |
| max_samples=max_samples | |
| ) | |
| print(f" Spearman : {spearman:+.4f} [IC: {ci_low:+.4f}, {ci_high:+.4f}]") | |
| # 6. Construction du résultat | |
| return BenchmarkResult( | |
| name=encoder_name, | |
| embedding_dim=emb_dim, | |
| spearman_corr=spearman, | |
| spearman_ci_low=ci_low, | |
| spearman_ci_high=ci_high, | |
| avg_encode_time_ms=avg_time, | |
| memory_bytes_per_embedding=emb_dim * 4, | |
| requires_training="DistilBERT" in encoder_name, | |
| requires_gpu="DistilBERT" in encoder_name, | |
| interpretable="Tian-Dao" in encoder_name, | |
| model_size_mb=getattr(encoder, 'model_size_mb', 0.0), | |
| n_pairs=len(sentences_a) | |
| ) | |
| def generate_report(results, metadata) -> str: | |
| tiandao = next((r for r in results if "Tian-Dao" in r.name), None) | |
| distil = next((r for r in results if "DistilBERT" in r.name), None) | |
| report = ["# 📊 Rapport de benchmark : Tian-Dao 20D vs DistilBERT", ""] | |
| report.append("## 🕐 Informations d'exécution") | |
| report.append("") | |
| report.append("| Champ | Valeur |") | |
| report.append("|---|---|") | |
| report.append(f"| **Date de début** | `{metadata.start_time}` |") | |
| report.append(f"| **Date de fin** | `{metadata.end_time}` |") | |
| report.append(f"| **Durée totale** | `{metadata.duration_seconds:.2f} secondes` |") | |
| report.append(f"| **Machine** | `{metadata.hostname}` |") | |
| report.append(f"| **Python** | `{metadata.python_version}` |") | |
| report.append(f"| **OS** | `{metadata.platform_info}` |") | |
| report.append(f"| **Dataset** | `{metadata.dataset_name}` ({metadata.dataset_source}) |") | |
| report.append(f"| **Échantillon** | `{metadata.n_pairs} paires` |") | |
| report.append(f"| **IC niveau** | `{metadata.confidence_level*100:.0f}%` |") | |
| report.append(f"| **Bootstrap** | `{metadata.bootstrap_iterations} itérations` |") | |
| report.append(f"| **Tag d'archivage** | `{metadata.timestamp_tag}` |") | |
| report.append("") | |
| report.append("---") | |
| report.append("") | |
| report.append("## 📋 Comparaison des encodeurs") | |
| report.append("") | |
| if tiandao and distil: | |
| report.append("| Métrique | Tian-Dao 20D | DistilBERT | Ratio |") | |
| report.append("|---|---|---|---|") | |
| report.append(f"| Dimension | **{tiandao.embedding_dim}** | {distil.embedding_dim} | **{distil.embedding_dim/tiandao.embedding_dim:.1f}x** |") | |
| report.append(f"| Taille/embedding | **{tiandao.memory_bytes_per_embedding} octets** | {distil.memory_bytes_per_embedding} octets | **{distil.memory_bytes_per_embedding/tiandao.memory_bytes_per_embedding:.1f}x** |") | |
| report.append(f"| Taille modèle | **{tiandao.model_size_mb:.3f} MB** | {distil.model_size_mb:.1f} MB | **{distil.model_size_mb/max(tiandao.model_size_mb, 0.001):.0f}x** |") | |
| report.append(f"| Temps/phrase | **{tiandao.avg_encode_time_ms:.3f} ms** | {distil.avg_encode_time_ms:.3f} ms | {distil.avg_encode_time_ms/max(tiandao.avg_encode_time_ms, 0.001):.1f}x |") | |
| report.append(f"| **Spearman (STS)** | {tiandao.spearman_corr:+.4f} [{tiandao.spearman_ci_low:+.4f}, {tiandao.spearman_ci_high:+.4f}] | **{distil.spearman_corr:+.4f}** [{distil.spearman_ci_low:+.4f}, {distil.spearman_ci_high:+.4f}] | N/A (structurel) |") | |
| report.append(f"| Entraînement | ❌ Non | ✅ Oui | - |") | |
| report.append(f"| GPU | ❌ Non | ✅ Oui | - |") | |
| report.append(f"| Interprétable | ✅ Oui | ❌ Non | - |") | |
| report.append("") | |
| report.append("## 🔍 Analyse") | |
| report.append("") | |
| report.append("### Points forts de Tian-Dao 20D") | |
| if tiandao and distil: | |
| report.append(f"- **Compression extrême** : {distil.memory_bytes_per_embedding/tiandao.memory_bytes_per_embedding:.0f}x plus léger") | |
| report.append(f"- **Modèle minuscule** : {distil.model_size_mb/max(tiandao.model_size_mb, 0.001):.0f}x plus petit ({tiandao.model_size_mb:.3f} MB vs {distil.model_size_mb:.1f} MB)") | |
| report.append("- **Inférence ultra-rapide** : pas de réseau de neurones") | |
| report.append("- **Aucun entraînement** : auto-régulé par construction") | |
| report.append("- **Interprétable** : chaque dimension = attracteur Wuxing") | |
| report.append("- **Déterministe** : reproductibilité parfaite") | |
| report.append("") | |
| report.append("### Limites de Tian-Dao 20D") | |
| if tiandao and distil: | |
| report.append(f"- **Spearman STS** : {tiandao.spearman_corr:+.3f} vs {distil.spearman_corr:+.3f} (DistilBERT)") | |
| report.append("- **Approche structurelle** : ne capture pas la sémantique profonde") | |
| report.append("") | |
| report.append("## 📌 Conclusion") | |
| report.append("") | |
| report.append("Tian-Dao 20D et DistilBERT répondent à des besoins **différents** et **complémentaires**.") | |
| report.append("") | |
| report.append("---") | |
| report.append("*Rapport généré automatiquement par `benchmark_distilbert.py` v3.0*") | |
| return "\n".join(report) | |
| def main(): | |
| start_dt = datetime.now().astimezone() | |
| start_iso = start_dt.isoformat() | |
| timestamp_tag = start_dt.strftime("%Y%m%d_%H%M%S") | |
| print("🚀 Démarrage du benchmark Tian-Dao 20D vs DistilBERT v3.0 (CPU optimisé)") | |
| print("=" * 60) | |
| print(f"🕐 Timestamp : {start_iso}") | |
| print(f"🖥️ Machine : {socket.gethostname()}") | |
| print("=" * 60) | |
| sentences_a, sentences_b, gold_scores, source = load_sts_benchmark() | |
| results = [] | |
| # 1. Tian-Dao (bootstrap complet avec sous-échantillonnage) | |
| tiandao = TianDaoEncoder20D(noise_level=0.0, seed=42) | |
| results.append(benchmark_encoder( | |
| tiandao, sentences_a, sentences_b, gold_scores, | |
| "Tian-Dao 20D", | |
| n_bootstrap=1000, | |
| max_samples=2000 | |
| )) | |
| # 2. DistilBERT (pas de sous-échantillonnage pour IC corrects) | |
| try: | |
| distilbert = DistilBERTEncoder() | |
| print("\n⏳ DistilBERT : encodage + bootstrap (peut prendre ~3-4 min)...") | |
| results.append(benchmark_encoder( | |
| distilbert, sentences_a, sentences_b, gold_scores, | |
| "DistilBERT (sentence-transformers)", | |
| n_bootstrap=300, # Compromis vitesse/précision | |
| max_samples=len(sentences_a) # Toutes les paires | |
| )) | |
| except ImportError as e: | |
| print(f"\n⚠️ DistilBERT non disponible : {e}") | |
| except Exception as e: | |
| print(f"\n⚠️ Erreur DistilBERT : {e}") | |
| if not results: | |
| print("❌ Aucun encodeur n'a pu être testé. Arrêt.") | |
| sys.exit(1) | |
| end_dt = datetime.now().astimezone() | |
| duration = (end_dt - start_dt).total_seconds() | |
| metadata = BenchmarkMetadata( | |
| start_time=start_iso, | |
| end_time=end_dt.isoformat(), | |
| duration_seconds=duration, | |
| hostname=socket.gethostname(), | |
| python_version=platform.python_version(), | |
| platform_info=f"{platform.system()} {platform.release()} ({platform.machine()})", | |
| n_pairs=len(sentences_a), | |
| timestamp_tag=timestamp_tag, | |
| dataset_name="stsb_multi_mt (fr)", | |
| dataset_source=source, | |
| confidence_level=0.95, | |
| bootstrap_iterations=300 # Mettre à jour la valeur réelle | |
| ) | |
| report = generate_report(results, metadata) | |
| print("\n" + report) | |
| benchmark_dir = os.path.dirname(os.path.abspath(__file__)) | |
| # Sauvegarde Markdown | |
| report_path = os.path.join(benchmark_dir, f"BENCHMARK_REPORT_{timestamp_tag}.md") | |
| with open(report_path, "w", encoding="utf-8") as f: | |
| f.write(report) | |
| print(f"\n💾 Rapport archivé : {report_path}") | |
| # Sauvegarde JSON | |
| try: | |
| import json | |
| json_data = { | |
| "metadata": { | |
| "start_time": metadata.start_time, | |
| "end_time": metadata.end_time, | |
| "duration_seconds": metadata.duration_seconds, | |
| "hostname": metadata.hostname, | |
| "python_version": metadata.python_version, | |
| "platform_info": metadata.platform_info, | |
| "n_pairs": metadata.n_pairs, | |
| "timestamp_tag": metadata.timestamp_tag, | |
| "dataset_name": metadata.dataset_name, | |
| "dataset_source": metadata.dataset_source, | |
| "confidence_level": metadata.confidence_level, | |
| "bootstrap_iterations": metadata.bootstrap_iterations, | |
| }, | |
| "results": [asdict(r) for r in results], | |
| "global_score": float(np.mean([r.spearman_corr for r in results])) | |
| } | |
| json_path = os.path.join(benchmark_dir, f"BENCHMARK_RESULTS_{timestamp_tag}.json") | |
| with open(json_path, "w", encoding="utf-8") as f: | |
| json.dump(json_data, f, indent=2, ensure_ascii=False) | |
| print(f"💾 JSON archivé : {json_path}") | |
| except Exception as e: | |
| print(f"⚠️ Erreur JSON : {e}") | |
| if __name__ == "__main__": | |
| main() | |