{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceType":"competition","sourceId":116062,"databundleVersionId":14875579},{"sourceType":"datasetVersion","sourceId":13482672,"datasetId":8559888,"databundleVersionId":14202491}],"dockerImageVersionId":31259,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\n\n# Ce script va lister TOUS les fichiers accessibles\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        if \"train_terms.tsv\" in filename:\n            print(f\"✅ TROUVÉ ! Le vrai chemin est : {os.path.join(dirname, filename)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-03T19:20:54.862338Z","iopub.execute_input":"2026-03-03T19:20:54.862773Z","iopub.status.idle":"2026-03-03T19:20:54.888799Z","shell.execute_reply.started":"2026-03-03T19:20:54.86273Z","shell.execute_reply":"2026-03-03T19:20:54.888047Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n# On cherche spécifiquement le fichier d'embeddings\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        if \"train_embeddings.npy\" in filename or \"embeddings\" in filename.lower():\n            print(f\"✅ TROUVÉ ! Le chemin de l'embedding est : {os.path.join(dirname, filename)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-03T19:21:00.62167Z","iopub.execute_input":"2026-03-03T19:21:00.622492Z","iopub.status.idle":"2026-03-03T19:21:00.630468Z","shell.execute_reply.started":"2026-03-03T19:21:00.622462Z","shell.execute_reply":"2026-03-03T19:21:00.629822Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"","metadata":{}},{"cell_type":"code","source":"# 2) Trouver le vrai chemin de train_sequences.fasta\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        if \"train_sequences.fasta\" in filename:\n            print(os.path.join(dirname, filename))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-03T19:21:06.64192Z","iopub.execute_input":"2026-03-03T19:21:06.642715Z","iopub.status.idle":"2026-03-03T19:21:06.650909Z","shell.execute_reply.started":"2026-03-03T19:21:06.642683Z","shell.execute_reply":"2026-03-03T19:21:06.650242Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"🎯 OBJECTIF\n\nRéécrire un pipeline :\n\nSplit propre Train / Validation / Test\n\nPas de data leakage\n\nÉvaluation multi-label correcte\n\nRAG sans fuite d’information\n\nCode clair, structuré, publiable\n","metadata":{}},{"cell_type":"markdown","source":"🧠 STRUCTURE DU NOUVEAU NOTEBOOK\n\nOrganisez votre notebook Kaggle ainsi :\n\n1. Imports\n2. Chargement des données\n3. Prétraitement\n4. ENCODAGE MULTI-LABEL / CHARGEMENT DES EMBEDDINGS\n5. Split train / validation / test\n6. Dataset PyTorch\n7. Modèle\n8. Entraînement\n9. Construction base RAG (train seulement)\n10. Validation propre\n11. Analyse des résultats","metadata":{}},{"cell_type":"markdown","source":"# 🔵 ÉTAPE 1 — IMPORTS PROPRES","metadata":{}},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-03T19:21:24.6092Z","iopub.execute_input":"2026-03-03T19:21:24.609423Z","iopub.status.idle":"2026-03-03T19:21:24.620324Z","shell.execute_reply.started":"2026-03-03T19:21:24.609403Z","shell.execute_reply":"2026-03-03T19:21:24.61963Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n\nfrom sklearn.preprocessing import MultiLabelBinarizer\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import f1_score, precision_score, recall_score\nfrom sklearn.neighbors import NearestNeighbors\n\nfrom torch.utils.data import Dataset, DataLoader\nfrom tqdm import tqdm","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-03T19:21:35.282781Z","iopub.execute_input":"2026-03-03T19:21:35.283183Z","iopub.status.idle":"2026-03-03T19:21:35.28726Z","shell.execute_reply.started":"2026-03-03T19:21:35.283155Z","shell.execute_reply":"2026-03-03T19:21:35.286538Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 🔵 ÉTAPE 2 — CHARGEMENT DONNÉES","metadata":{}},{"cell_type":"code","source":"train_terms = pd.read_csv(\n    \"/kaggle/input/competitions/cafa-6-protein-function-prediction/Train/train_terms.tsv\",\n    sep=\"\\t\"\n)\nprint(train_terms.shape)\ntrain_terms.head()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-03T19:21:39.862866Z","iopub.execute_input":"2026-03-03T19:21:39.863197Z","iopub.status.idle":"2026-03-03T19:21:40.075011Z","shell.execute_reply.started":"2026-03-03T19:21:39.863168Z","shell.execute_reply":"2026-03-03T19:21:40.074377Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\ntrain_terms_path = \"/kaggle/input/competitions/cafa-6-protein-function-prediction/Train/train_terms.tsv\"\n\ntrain_fasta_path = \"/kaggle/input/competitions/cafa-6-protein-function-prediction/Train/train_sequences.fasta\"\n\nembeddings_path = \"/kaggle/input/datasets/seddiktrk/cafa6-protein-embeddings-esm2/protein_embeddings.npy\"\n\n\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-03T19:21:54.724858Z","iopub.execute_input":"2026-03-03T19:21:54.725633Z","iopub.status.idle":"2026-03-03T19:21:54.729212Z","shell.execute_reply.started":"2026-03-03T19:21:54.725603Z","shell.execute_reply":"2026-03-03T19:21:54.728301Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"#  Étape de Nettoyage (Le Mapping .obo)\nVoici le code pour nettoyer vos labels. Ce script parcourt le fichier .obo pour trouver les termes obsolètes et les remplacer par les nouveaux.\n\nÀ quel niveau l'inclure ? Juste AVANT l'Étape 3 (celle où vous créez labels_per_protein).","metadata":{}},{"cell_type":"code","source":"def get_obo_mapping(obo_path):\n    \"\"\"Analyse le fichier .obo pour créer un dictionnaire de remplacement\"\"\"\n    mapping = {}\n    current_id = None\n    replaced_by = None\n    is_obsolete = False\n    \n    with open(obo_path, 'r') as f:\n        for line in f:\n            line = line.strip()\n            if line.startswith('id: '):\n                # Nouveau terme rencontré, on réinitialise\n                current_id = line.split('id: ')[1]\n                replaced_by = None\n                is_obsolete = False\n            elif line.startswith('is_obsolete: true'):\n                is_obsolete = True\n            elif line.startswith('replaced_by: '):\n                replaced_by = line.split('replaced_by: ')[1]\n            \n            # Si on a fini un bloc [Term] et qu'il est obsolète\n            if is_obsolete and replaced_by and current_id:\n                mapping[current_id] = replaced_by\n    return mapping\n\n# --- UTILISATION DANS VOTRE PIPELINE ---\n# 1. Charger le dictionnaire de remplacement\nobo_path = \"/kaggle/input/competitions/cafa-6-protein-function-prediction/Train/go-basic.obo\"\nreplace_dict = get_obo_mapping(obo_path)\n\n# 2. Appliquer le remplacement sur vos termes d'entraînement\ntrain_terms['term'] = train_terms['term'].replace(replace_dict)\n\nprint(f\"✅ {len(replace_dict)} termes obsolètes ont été redirigés.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-03T19:22:08.205124Z","iopub.execute_input":"2026-03-03T19:22:08.205694Z","iopub.status.idle":"2026-03-03T19:22:38.095302Z","shell.execute_reply.started":"2026-03-03T19:22:08.205665Z","shell.execute_reply":"2026-03-03T19:22:38.094504Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 🔵 ÉTAPE 3 — CONSTRUCTION DES LABELS PAR PROTÉINE","metadata":{}},{"cell_type":"code","source":"labels_per_protein = train_terms.groupby(\"EntryID\")[\"term\"].apply(list)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-03T19:22:47.519623Z","iopub.execute_input":"2026-03-03T19:22:47.520393Z","iopub.status.idle":"2026-03-03T19:22:48.856301Z","shell.execute_reply.started":"2026-03-03T19:22:47.520361Z","shell.execute_reply":"2026-03-03T19:22:48.855716Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 🔵 ÉTAPE 4 — ENCODAGE MULTI-LABEL","metadata":{}},{"cell_type":"code","source":"mlb = MultiLabelBinarizer()\nY = mlb.fit_transform(labels_per_protein)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-03T19:22:54.520786Z","iopub.execute_input":"2026-03-03T19:22:54.521727Z","iopub.status.idle":"2026-03-03T19:22:55.787845Z","shell.execute_reply.started":"2026-03-03T19:22:54.521698Z","shell.execute_reply":"2026-03-03T19:22:55.78722Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# sauvegarde\nnum_labels = len(mlb.classes_)\nprint(\"Nombre total de labels :\", num_labels)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-03T19:22:59.230774Z","iopub.execute_input":"2026-03-03T19:22:59.231499Z","iopub.status.idle":"2026-03-03T19:22:59.23551Z","shell.execute_reply.started":"2026-03-03T19:22:59.231471Z","shell.execute_reply":"2026-03-03T19:22:59.234766Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 🔵 ÉTAPE 4.5 — CHARGEMENT DES EMBEDDINGS\n","metadata":{}},{"cell_type":"code","source":"# 🔵 ÉTAPE 4.5 — CHARGEMENT DES EMBEDDINGS\n\nembeddings = np.load(embeddings_path)\n\nprint(\"Shape embeddings :\", embeddings.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-03T19:30:49.479575Z","iopub.execute_input":"2026-03-03T19:30:49.480376Z","iopub.status.idle":"2026-03-03T19:30:49.940423Z","shell.execute_reply.started":"2026-03-03T19:30:49.480345Z","shell.execute_reply":"2026-03-03T19:30:49.939627Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"Nombre protéines (labels) :\", len(labels_per_protein))\nprint(\"Nombre embeddings :\", embeddings.shape[0])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-03T19:30:54.877677Z","iopub.execute_input":"2026-03-03T19:30:54.878169Z","iopub.status.idle":"2026-03-03T19:30:54.882428Z","shell.execute_reply.started":"2026-03-03T19:30:54.878143Z","shell.execute_reply":"2026-03-03T19:30:54.881695Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Étape A : Préparation des données (À insérer avant le Split)","metadata":{}},{"cell_type":"markdown","source":"# 🔵 ÉTAPE 5 — SPLIT PROPRE","metadata":{}},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport torch\nimport gc\nfrom sklearn.model_selection import train_test_split\n\n# 1. RE-DÉFINITION DU MAPPING (Nécessaire après un crash)\nmapping_path = \"/kaggle/input/datasets/seddiktrk/cafa6-protein-embeddings-esm2/protein_ids.csv\"\nprotein_ids_df = pd.read_csv(mapping_path)\n# On crée le dictionnaire id_to_idx\nid_to_idx = {pid: i for i, pid in enumerate(protein_ids_df.iloc[:, 0])}\n\n# 2. ALIGNEMENT DES INDICES\n# On suppose que labels_per_protein existe encore (si non, relancez l'étape 3)\nrelevant_indices = [id_to_idx[pid] for pid in labels_per_protein.index if pid in id_to_idx]\n\n# 3. CRÉATION DES MATRICES X ET Y\n# On charge les embeddings uniquement pour les protéines annotées pour sauver la RAM\nX = embeddings[relevant_indices] \nY_dense = Y.astype(np.bool_) # On utilise le Y créé par MultiLabelBinarizer\n\n# 4. SPLIT PAR INDICES (Méthode sans crash)\nindices = np.arange(len(X))\nidx_train, idx_temp = train_test_split(indices, test_size=0.20, random_state=42)\nidx_val, idx_test = train_test_split(idx_temp, test_size=0.50, random_state=42)\n\n# 5. GÉNÉRATION DES SUBSETS FINAUX\nX_train, y_train = X[idx_train], Y_dense[idx_train]\nX_val, y_val = X[idx_val], Y_dense[idx_val]\nX_test, y_test = X[idx_test], Y_dense[idx_test]\n\nprint(f\"✅ SUCCÈS TOTAL !\")\nprint(f\"Structure finale : Train {X_train.shape} | Val {X_val.shape} | Test {X_test.shape}\")\n\n# 6. NETTOYAGE RADICAL POUR L'ENTRAÎNEMENT\n# On supprime les objets géants qui ne servent plus\ndel X, Y_dense, embeddings, protein_ids_df, relevant_indices, indices\ngc.collect()\n\nprint(\"🧹 RAM nettoyée. Vous pouvez maintenant lancer l'étape 7 (Modèle).\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-03T19:31:07.10918Z","iopub.execute_input":"2026-03-03T19:31:07.109951Z","iopub.status.idle":"2026-03-03T19:31:14.574355Z","shell.execute_reply.started":"2026-03-03T19:31:07.109922Z","shell.execute_reply":"2026-03-03T19:31:14.573624Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"#  Étape 5.5 : Préparation des données de Taxonomie\nAvant de relancer le modèle, vous devez préparer les IDs d'espèces. Ajoutez ce bloc juste après votre split de données actuel :","metadata":{}},{"cell_type":"code","source":"# 1. Charger la taxonomie\ntaxo_df = pd.read_csv(\"/kaggle/input/competitions/cafa-6-protein-function-prediction/Train/train_taxonomy.tsv\", \n                      sep=\"\\t\", names=[\"EntryID\", \"TaxonID\"])\n\n# 2. Créer le mapping Protéine -> Taxon\nprotein_to_taxon = dict(zip(taxo_df[\"EntryID\"], taxo_df[\"TaxonID\"]))\n\n# 3. Récupérer les taxons pour vos sets (en utilisant l'index des protéines)\n# On utilise les indices de split que vous avez déjà créés (idx_train, etc.)\n# Note : 'relevant_indices' vient de votre étape 5 initiale\nall_taxons = np.array([protein_to_taxon.get(pid, 0) for pid in labels_per_protein.index])\n\nfrom sklearn.preprocessing import LabelEncoder\nle_taxo = LabelEncoder()\nall_taxons_encoded = le_taxo.fit_transform(all_taxons)\n\ntax_train = all_taxons_encoded[idx_train]\ntax_val = all_taxons_encoded[idx_val]\ntax_test = all_taxons_encoded[idx_test]\n\nnum_taxons = len(le_taxo.classes_)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-03T19:51:32.242654Z","iopub.execute_input":"2026-03-03T19:51:32.243145Z","iopub.status.idle":"2026-03-03T19:51:32.347884Z","shell.execute_reply.started":"2026-03-03T19:51:32.243118Z","shell.execute_reply":"2026-03-03T19:51:32.347012Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 🔵 ÉTAPE 6 & 7 — Dataset, Loaders et Modèle","metadata":{}},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.data import Dataset, DataLoader\n\n# 1. Configuration du Device\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\n# 2. Classe Dataset mise à jour pour inclure la Taxonomie\nclass ProteinTaxoDataset(Dataset):\n    def __init__(self, X_esm, X_tax, y):\n        self.X_esm = X_esm\n        self.X_tax = X_tax\n        self.y = y\n\n    def __len__(self):\n        return len(self.X_esm)\n\n    def __getitem__(self, idx):\n        return (\n            torch.tensor(self.X_esm[idx], dtype=torch.float32),\n            torch.tensor(self.X_tax[idx], dtype=torch.long), # ID espèce\n            torch.tensor(self.y[idx], dtype=torch.float32)\n        )\n\n# 3. Création des DataLoaders avec les 3 matrices\ntrain_loader = DataLoader(ProteinTaxoDataset(X_train, tax_train, y_train), batch_size=64, shuffle=True)\nval_loader = DataLoader(ProteinTaxoDataset(X_val, tax_val, y_val), batch_size=64, shuffle=False)\n\n# 4. Architecture du Modèle\nclass ProteinTaxoClassifier(nn.Module):\n    def __init__(self, input_dim, num_taxons, num_labels):\n        super().__init__()\n        # Embedding pour la taxonomie (dim 16)\n        self.taxon_embed = nn.Embedding(num_taxons, 16)\n        \n        # Entrée = 1280 (ESM) + 16 (Taxo) = 1296\n        self.layer1 = nn.Linear(input_dim + 16, 1024)\n        self.bn1 = nn.BatchNorm1d(1024)\n        self.dropout1 = nn.Dropout(0.3)\n        self.layer2 = nn.Linear(1024, 512)\n        self.bn2 = nn.BatchNorm1d(512)\n        self.output = nn.Linear(512, num_labels)\n\n    def forward(self, x_esm, x_tax):\n        tax_features = self.taxon_embed(x_tax)\n        combined = torch.cat([x_esm, tax_features], dim=1) # Fusion\n        \n        x = F.relu(self.bn1(self.layer1(combined)))\n        x = self.dropout1(x)\n        x = F.relu(self.bn2(self.layer2(x)))\n        x = torch.sigmoid(self.output(x))\n        return x\n\n# 5. Initialisation (Correction du NameError ici)\ninput_dim = X_train.shape[1] # 1280\nnum_labels = y_train.shape[1] # 26125\n\n# On utilise bien le nom \"ProteinTaxoClassifier\" et on passe \"num_taxons\"\nmodel = ProteinTaxoClassifier(input_dim, num_taxons, num_labels).to(device)\n\nprint(f\"✅ Modèle hybride prêt sur {device} !\")\nprint(f\"Configuration : ESM({input_dim}) + Taxo(16) -> MLP -> Labels({num_labels})\")\n\n\n\n\n\n\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-03T19:51:37.247611Z","iopub.execute_input":"2026-03-03T19:51:37.247913Z","iopub.status.idle":"2026-03-03T19:51:37.387622Z","shell.execute_reply.started":"2026-03-03T19:51:37.247885Z","shell.execute_reply":"2026-03-03T19:51:37.386883Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 🔵 ÉTAPE 8 — Entraînement Hybride (ESM + Taxonomie)","metadata":{}},{"cell_type":"code","source":"from tqdm import tqdm\n\n# On s'assure que le critère et l'optimiseur sont liés au nouveau modèle\ncriterion = nn.BCELoss()\noptimizer = torch.optim.Adam(model.parameters(), lr=1e-3)\n\nepochs = 10\nprint(f\"🚀 Lancement de l'entraînement hybride pour {epochs} époques...\")\n\nfor epoch in range(epochs):\n    model.train()\n    total_loss = 0\n    \n    # Correction : On déballe 3 éléments (x_esm, x_tax, y)\n    loop = tqdm(train_loader, leave=True)\n    for x_esm_batch, x_tax_batch, y_batch in loop:\n        # Transfert vers le GPU/CPU\n        x_esm_batch = x_esm_batch.to(device)\n        x_tax_batch = x_tax_batch.to(device)\n        y_batch = y_batch.to(device)\n\n        # Remise à zéro des gradients\n        optimizer.zero_grad()\n        \n        # Passage dans le modèle avec les deux entrées\n        outputs = model(x_esm_batch, x_tax_batch)\n        \n        # Calcul de l'erreur\n        loss = criterion(outputs, y_batch)\n        \n        # Rétropropagation\n        loss.backward()\n        optimizer.step()\n\n        total_loss += loss.item()\n        loop.set_description(f\"Epoch [{epoch+1}/{epochs}]\")\n        loop.set_postfix(loss=loss.item())\n\n    avg_loss = total_loss / len(train_loader)\n    print(f\"🏁 Epoch {epoch+1} terminée. Loss moyenne : {avg_loss:.6f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-03T19:56:05.953318Z","iopub.execute_input":"2026-03-03T19:56:05.953846Z","iopub.status.idle":"2026-03-03T19:58:36.598567Z","shell.execute_reply.started":"2026-03-03T19:56:05.953818Z","shell.execute_reply":"2026-03-03T19:58:36.597382Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 🔵 ÉTAPE 9 — Construction du RAG (Retrieval Augmented Generation)","metadata":{}},{"cell_type":"code","source":"from sklearn.neighbors import NearestNeighbors\n\nprint(\"🧠 Construction de la base de connaissances RAG (Voisins les plus proches)...\")\n\n# 1. On initialise le moteur sur le Train Set uniquement\n# On utilise la métrique 'cosine' car elle est plus performante pour les embeddings ESM-2\nnn_engine = NearestNeighbors(n_neighbors=5, metric=\"cosine\", n_jobs=-1)\nnn_engine.fit(X_train)\n\n# 2. Sauvegarde des labels d'entraînement pour la recherche\n# On les garde sous forme de matrice pour un accès ultra-rapide\ny_train_rag = y_train \n\nprint(\"✅ Base RAG prête. Le modèle peut maintenant 'consulter' ses connaissances.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-03T20:00:21.537542Z","iopub.execute_input":"2026-03-03T20:00:21.538387Z","iopub.status.idle":"2026-03-03T20:00:21.579299Z","shell.execute_reply.started":"2026-03-03T20:00:21.538351Z","shell.execute_reply":"2026-03-03T20:00:21.578511Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 🔵 ÉTAPE 10 — Validation et Fusion \"Hyper-RAG-GO\"\n\nC'est l'étape cruciale. Nous allons comparer deux méthodes sur le set de validation :\n\nModèle Seul (MLP).\n\nModèle + RAG (Fusion).","metadata":{}},{"cell_type":"code","source":"# La fonction de fusion reste identique\ndef hyper_rag_fusion(model_probs, neighbors_idx, alpha=0.6):\n    neighbor_labels = y_train_rag[neighbors_idx] \n    rag_probs = np.mean(neighbor_labels, axis=1) \n    combined_probs = (alpha * model_probs) + ((1 - alpha) * rag_probs)\n    return combined_probs\n\n# --- Phase de Test sur la Validation ---\nmodel.eval()\nall_final_probs = []\nall_true_labels = []\n\nprint(\"🚀 Évaluation du pipeline fusionné (ESM + Taxo + RAG) sur la validation...\")\n\nwith torch.no_grad():\n    # CORRECTION : Une seule itération sur tqdm(val_loader)\n    for x_val_esm, x_val_tax, y_val in tqdm(val_loader):\n        \n        # A. Prédiction du modèle (Nécessite les 2 entrées)\n        x_esm_gpu = x_val_esm.to(device)\n        x_tax_gpu = x_val_tax.to(device)\n        model_out = model(x_esm_gpu, x_tax_gpu).cpu().numpy()\n        \n        # B. Recherche RAG (Basée uniquement sur l'embedding ESM-2)\n        # On utilise .numpy() car le moteur RAG sklearn tourne sur CPU\n        distances, indices = nn_engine.kneighbors(x_val_esm.numpy())\n        \n        # C. Fusion\n        fused_out = hyper_rag_fusion(model_out, indices, alpha=0.7)\n        \n        all_final_probs.append(fused_out)\n        all_true_labels.append(y_val.numpy())\n\n# Conversion en matrices globales\nfinal_predictions = np.vstack(all_final_probs)\ntrue_labels = np.vstack(all_true_labels)\n\nprint(\"✅ Évaluation terminée. Prêt pour le calcul des métriques (Étape 11).\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-03T20:03:55.583483Z","iopub.execute_input":"2026-03-03T20:03:55.584324Z","iopub.status.idle":"2026-03-03T20:04:42.116847Z","shell.execute_reply.started":"2026-03-03T20:03:55.584294Z","shell.execute_reply":"2026-03-03T20:04:42.115952Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 🔵 ÉTAPE 11 — Calcul des Métriques et Rapport de Recherche\nLe score F1 est la métrique de référence pour CAFA. Exécutez ce bloc pour obtenir votre tableau de bord de résultats :","metadata":{}},{"cell_type":"code","source":"from sklearn.metrics import f1_score, precision_score, recall_score\n\n# 1. Seuil de décision (Thresholding)\n# On commence par un seuil standard de 0.2 pour le multi-label profond\nthreshold = 0.2\ny_pred_binary = (final_predictions > threshold).astype(int)\n\n# 2. Calcul des métriques globales\nprecision = precision_score(true_labels, y_pred_binary, average='micro')\nrecall = recall_score(true_labels, y_pred_binary, average='micro')\nf1 = f1_score(true_labels, y_pred_binary, average='micro')\n\nprint(f\"\\n📊 --- TABLEAU DE BORD HYPER-RAG-GO (Seuil {threshold}) ---\")\nprint(f\"✅ Précision Micro : {precision:.4f}\")\nprint(f\"✅ Rappel (Recall) : {recall:.4f}\")\nprint(f\"✅ SCORE F1 FINAL  : {f1:.4f}\")\nprint(\"-----------------------------------------------------------\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-03T20:04:52.34318Z","iopub.execute_input":"2026-03-03T20:04:52.343902Z","iopub.status.idle":"2026-03-03T20:05:20.001308Z","shell.execute_reply.started":"2026-03-03T20:04:52.343873Z","shell.execute_reply":"2026-03-03T20:05:20.000456Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 🔵 ÉTAPE 12 — Optimisation du Seuil (La \"Touche Master\")\nUn bon chercheur ne s'arrête pas au premier seuil venu. Pour votre article, montrons comment trouver le meilleur compromis :","metadata":{}},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\nthresholds = np.linspace(0.05, 0.5, 10)\nf1_scores = []\n\nfor t in thresholds:\n    preds = (final_predictions > t).astype(int)\n    f1_scores.append(f1_score(true_labels, preds, average='micro'))\n\nplt.figure(figsize=(10, 5))\nplt.plot(thresholds, f1_scores, marker='o', linestyle='-', color='darkblue')\nplt.title(\"Optimisation du Seuil de Confiance pour le Score F1\")\nplt.xlabel(\"Seuil (Threshold)\")\nplt.ylabel(\"Score F1-Micro\")\nplt.grid(True, alpha=0.3)\nplt.show()\n\nbest_threshold = thresholds[np.argmax(f1_scores)]\nprint(f\"💡 Le seuil optimal pour votre article est : {best_threshold:.2f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-03T20:05:49.178656Z","iopub.execute_input":"2026-03-03T20:05:49.179183Z","iopub.status.idle":"2026-03-03T20:07:24.735284Z","shell.execute_reply.started":"2026-03-03T20:05:49.179154Z","shell.execute_reply":"2026-03-03T20:07:24.73454Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 🔵 ÉTAPE FINALE — La Validation sur le Set de Test (Le \"vrai\" chiffre)","metadata":{}},{"cell_type":"code","source":"# --- ÉVALUATION FINALE SUR LE TEST SET (Séquence + Espèce + RAG) ---\nmodel.eval()\nall_test_probs = []\nall_test_true = []\n\nprint(\"🏁 Évaluation finale sur le set de TEST (Strict)...\")\n\n# Utilisation du nouveau Dataset qui contient les 3 informations\ntest_ds = ProteinTaxoDataset(X_test, tax_test, y_test)\ntest_loader = DataLoader(test_ds, batch_size=64)\n\nwith torch.no_grad():\n    for x_test_esm, x_test_tax, y_test_batch in tqdm(test_loader):\n        # 1. Prédiction du Modèle (ESM + TAXO)\n        x_esm_gpu = x_test_esm.to(device)\n        x_tax_gpu = x_test_tax.to(device)\n        m_out = model(x_esm_gpu, x_tax_gpu).cpu().numpy()\n        \n        # 2. Recherche RAG (Basée sur l'embedding ESM uniquement)\n        dist, idx = nn_engine.kneighbors(x_test_esm.numpy())\n        \n        # 3. Fusion Hyper-RAG (Alpha=0.7)\n        # On utilise le même seuil optimal de 0.2\n        fused = hyper_rag_fusion(m_out, idx, alpha=0.7)\n        \n        all_test_probs.append(fused)\n        all_test_true.append(y_test_batch.numpy())\n\n# Calcul final des scores\nfinal_test_preds = (np.vstack(all_test_probs) > 0.2).astype(int)\nfinal_test_true = np.vstack(all_test_true)\n\nf1_test = f1_score(final_test_true, final_test_preds, average='micro')\nprint(f\"\\n🏆 SCORE F1 FINAL (HYBRIDE ESM+TAXO+RAG) : {f1_test:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-03T20:12:48.68365Z","iopub.execute_input":"2026-03-03T20:12:48.684341Z","iopub.status.idle":"2026-03-03T20:13:44.646903Z","shell.execute_reply.started":"2026-03-03T20:12:48.684306Z","shell.execute_reply":"2026-03-03T20:13:44.646053Z"}},"outputs":[],"execution_count":null}]}