{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":116062,"databundleVersionId":14084779,"isSourceIdPinned":false,"sourceType":"competition"},{"sourceId":13468971,"sourceType":"datasetVersion","datasetId":8550106}],"dockerImageVersionId":31153,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"!pip install obonet pandas numpy tensorflow scikit-learn\n!pip install obonet pandas numpy tensorflow scikit-learn\nimport pandas as pd\nimport numpy as np\nimport obonet\nimport networkx as nx\nimport tensorflow as tf\nfrom tensorflow.keras.models import Sequential\nfrom tensorflow.keras.layers import Dense, Dropout\nfrom sklearn.model_selection import train_test_split\nfrom tensorflow.keras.backend import binary_crossentropy\n","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-10-22T18:31:39.814705Z","iopub.execute_input":"2025-10-22T18:31:39.814984Z","iopub.status.idle":"2025-10-22T18:32:08.217685Z","shell.execute_reply.started":"2025-10-22T18:31:39.814957Z","shell.execute_reply":"2025-10-22T18:32:08.216257Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Chemins d'accès aux fichiers (basés sur votre input)\nEMBEDDINGS_PATH = '/kaggle/input/test-protein-embedding-complet/test_protein_embeddings_complet.npy'\nIDS_PATH = '/kaggle/input/test-protein-embedding-complet/test_protein_ids_complet.npy'\nTERMS_PATH = '/kaggle/input/cafa-6-protein-function-prediction/Train/train_terms.tsv'\nOBO_PATH = '/kaggle/input/cafa-6-protein-function-prediction/Train/go-basic.obo'\n\n# --- Chargement des embeddings (X) et des IDs ---\nX = np.load(EMBEDDINGS_PATH)\ntrain_ids = np.load(IDS_PATH)\nEMBEDDING_DIM = X.shape[1]\nprint(f\"Embeddings chargés. Dimension X: {X.shape}\")\n\n# --- Chargement des Labels (Y) ---\ntrain_terms = pd.read_csv(TERMS_PATH, sep='\\t')\nprint(f\"Termes chargés. Nombre de lignes: {len(train_terms)}\")\n\ntrain_terms = pd.read_csv(TERMS_PATH, sep='\\t')\nprint(\"Noms exacts des colonnes dans train_terms.tsv:\")\nprint(train_terms.columns.tolist())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-21T22:25:36.155509Z","iopub.execute_input":"2025-10-21T22:25:36.155841Z","iopub.status.idle":"2025-10-21T22:25:36.808825Z","shell.execute_reply.started":"2025-10-21T22:25:36.155821Z","shell.execute_reply":"2025-10-21T22:25:36.807482Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 1. Sélection des termes GO (les labels)\n# Nous utilisons seulement les termes GO présents dans le jeu d'entraînement.\n# Nous allons filtrer les termes trop rares pour l'entraînement (par exemple, si moins de 50 protéines ont ce terme).\nTERM_COUNT_THRESHOLD = 50\nterm_counts = train_terms['term'].value_counts()\nselected_terms = term_counts[term_counts >= TERM_COUNT_THRESHOLD].index.tolist()\n\n# 2. Création du mappage ID -> Index\nprotein_to_index = {pid: i for i, pid in enumerate(train_ids)}\nterm_to_index = {term: i for i, term in enumerate(selected_terms)}\nnum_classes = len(selected_terms)\n\n# 3. Construction de la matrice Y binaire\nY_matrix = np.zeros((len(train_ids), num_classes), dtype=np.int8)\n\n# Remplissage de la matrice Y\nfor _, row in train_terms.iterrows():\n    protein_id = row['EntryID']\n    term = row['term']\n    \n    if protein_id in protein_to_index and term in term_to_index:\n        p_idx = protein_to_index[protein_id]\n        t_idx = term_to_index[term]\n        Y_matrix[p_idx, t_idx] = 1\n\nprint(f\"Matrice de labels Y créée. Dimension Y: {Y_matrix.shape}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-21T22:27:23.450198Z","iopub.execute_input":"2025-10-21T22:27:23.450743Z","iopub.status.idle":"2025-10-21T22:27:47.540891Z","shell.execute_reply.started":"2025-10-21T22:27:23.450709Z","shell.execute_reply":"2025-10-21T22:27:47.540048Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- Chargement du graphe GO et calcul des Poids d'IA ---\nprint(\"\\n--- 3. Calcul de l'Information Content (IA) ---\")\n\n# Chargement du graphe (nécessite obonet)\ntry:\n    go_graph = obonet.read_obo(OBO_PATH)\nexcept Exception as e:\n    print(f\"Erreur lors du chargement d'obonet. Assurez-vous que 'obonet' est installé. Erreur: {e}\")\n    # Si le chargement échoue, on continue avec un IC basé uniquement sur la fréquence\n    # sans utiliser la hiérarchie GO pour le moment (moins précis mais fonctionnel)\n    pass\n\n\n# Calcul des fréquences d'apparition (pour l'Information Content - IC)\n# On compte combien de protéines ont chaque terme dans le jeu Y_matrix\nterm_frequencies = Y_matrix.sum(axis=0)\n\n# Calcul de l'Information Content (IC)\n# IC = -log2(P(t)) où P(t) est la probabilité d'un terme t\ntotal_proteins = len(train_ids)\nterm_probabilities = term_frequencies / total_proteins\n# Le terme le plus fréquent (P proche de 1) aura un IC proche de 0\n# Le terme le plus rare (P proche de 0) aura un IC très élevé\n# On ajoute une petite valeur (epsilon) pour éviter log(0)\nepsilon = 1e-10\nIC_values = -np.log2(term_probabilities + epsilon)\n\n# Normalisation pour la fonction de perte\n# Plus l'IC est élevé, plus le poids sera grand.\n# On normalise pour que le poids maximum soit 1.0 (ou proche).\nclass_weights = IC_values / IC_values.max() \nclass_weights_tensor = tf.constant(class_weights, dtype=tf.float32)\n\nprint(f\"Poids d'Information Content (IA) calculés. Nombre de classes: {len(class_weights)}. Max weight: {class_weights.max():.2f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-21T22:29:53.768333Z","iopub.execute_input":"2025-10-21T22:29:53.768735Z","iopub.status.idle":"2025-10-21T22:30:01.259119Z","shell.execute_reply.started":"2025-10-21T22:29:53.768710Z","shell.execute_reply":"2025-10-21T22:30:01.258122Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Inspecter le poids\nprint(\"Valeurs des poids (IA) :\")\nprint(class_weights_tensor)\nprint(f\"Nombre de poids nuls : {tf.reduce_sum(tf.cast(class_weights_tensor == 0, tf.int32))}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-21T22:34:19.364292Z","iopub.execute_input":"2025-10-21T22:34:19.365359Z","iopub.status.idle":"2025-10-21T22:34:19.377802Z","shell.execute_reply.started":"2025-10-21T22:34:19.365322Z","shell.execute_reply":"2025-10-21T22:34:19.376761Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- Étape A : Inspection des IDs ---\n# Récupérer les 5 premiers IDs de la source label (train_terms.tsv)\n# Utilisez le nom de colonne 'EntryID' original, puis le nettoyage.\nids_terms_original = train_terms['EntryID'].head().tolist()\nids_terms_nettoyes = train_terms['EntryID'].str.split('.').str[0].head().tolist()\n\n# Récupérer les 5 premiers IDs de la source embeddings (train_ids.npy)\nids_embeddings = train_ids[:5].tolist()\n\n\nprint(\"IDs labels (EntryID original) :\", ids_terms_original)\nprint(\"IDs labels (Après .split('.')) :\", ids_terms_nettoyes)\nprint(\"IDs embeddings (train_ids.npy) :\", ids_embeddings)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-21T22:41:44.958332Z","iopub.execute_input":"2025-10-21T22:41:44.958722Z","iopub.status.idle":"2025-10-21T22:41:45.824008Z","shell.execute_reply.started":"2025-10-21T22:41:44.958699Z","shell.execute_reply":"2025-10-21T22:41:45.823074Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- Étape de CORRECTION DÉFINITIVE (Ré-exécutez tout le bloc de nettoyage) ---\n\n# 1. Nettoyage des IDs dans les labels (train_terms.tsv)\n# Bien que les exemples soient déjà propres, on garde le split pour la sécurité\ntrain_terms['EntryID_CLEAN'] = train_terms['EntryID'].str.split('.').str[0]\n\n# 2. Nettoyage CRITIQUE des IDs d'embeddings (train_ids.npy)\n# On extrait l'ID Uniprot brut (le segment entre les barres verticales '|')\ndef extract_uniprot_id(full_id):\n    \"\"\"Extrait l'ID Uniprot brut de la chaîne FASTA complète.\"\"\"\n    try:\n        # La 2e position (index 1) après le split par '|' est l'ID Uniprot\n        return full_id.split('|')[1]\n    except IndexError:\n        # Retourne l'ID original si le format n'est pas trouvé (sécurité)\n        return full_id\n\n# Appliquer la fonction pour créer les IDs propres des embeddings\ntrain_ids_cleaned = np.array([extract_uniprot_id(str(i)) for i in train_ids]) \n\n# 3. Recréation du Dictionnaire de Mapping (avec les IDs nettoyés)\n# Les clés seront maintenant les IDs bruts (ex: 'A0A0C5B5G6')\nprotein_to_index = {pid: i for i, pid in enumerate(train_ids_cleaned)}\n\n# Vérification pour s'assurer qu'au moins quelques IDs correspondent (optionnel mais utile)\nprint(f\"\\nExemples d'IDs nettoyés des embeddings : {train_ids_cleaned[:5].tolist()}\")\nprint(f\"L'ID 'Q5W0B1' est-il dans le dictionnaire ? {'Q5W0B1' in protein_to_index}\")\n\n\n# --- 4. Remplissage de la matrice Y (CODE FINAL) ---\n# Re-initialiser la matrice Y \nY_matrix = np.zeros((len(train_ids_cleaned), num_classes), dtype=np.int8)\n\nfilled_count = 0\nfor _, row in train_terms.iterrows():\n    # Utiliser l'ID nettoyé du terme\n    protein_id_clean = str(row['EntryID_CLEAN'])\n    term = row['term']\n    \n    # La condition devrait maintenant être VRAIE si l'ID existe dans le jeu d'embeddings\n    if protein_id_clean in protein_to_index and term in term_to_index:\n        p_idx = protein_to_index[protein_id_clean]\n        t_idx = term_to_index[term]\n        Y_matrix[p_idx, t_idx] = 1\n        filled_count += 1\n\n# --- VÉRIFICATION FINALE ---\nprint(f\"\\nNombre total de labels positifs ('1') remplis : {Y_matrix.sum()}\")\nprint(f\"Nombre total de lignes traitées : {filled_count}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-21T22:42:30.125575Z","iopub.execute_input":"2025-10-21T22:42:30.125927Z","iopub.status.idle":"2025-10-21T22:42:56.376552Z","shell.execute_reply.started":"2025-10-21T22:42:30.125903Z","shell.execute_reply":"2025-10-21T22:42:56.375492Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from tensorflow.keras.models import Sequential\nfrom tensorflow.keras.layers import Dense, Dropout\n\n# --- DÉFINITION DE L'ARCHITECTURE MLP ) ---\n# Ceci crée la variable 'model'\nmodel = Sequential([\n    # Couche d'entrée : la dimension est la taille des embeddings (320)\n    Dense(1024, activation='relu', input_shape=(320,)), \n    Dropout(0.2),\n    # Couche cachée intermédiaire (peut varier)\n    Dense(512, activation='relu'),\n    Dropout(0.2),\n    # Couche de sortie : 1585 neurones (le nombre de termes GO) avec activation sigmoïde pour la multi-classification\n    Dense(1585, activation='sigmoid') \n])\n\n# Vérifiez l'architecture\nmodel.summary()\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- Définition de la Perte, de l'Architecture et Entraînement ---\n\n# 1. Définir la fonction de perte pondérée (si les poids IA sont déjà calculés et variés)\n# Nous avions une perte de validation très faible précédemment. Nous allons\n# nous assurer que le modèle apprend quelque chose en utilisant la perte standard.\n\n# Si vous voulez avancer sans risque de bug de la perte IA:\n# loss_function = 'binary_crossentropy' \n\n# Si vous voulez essayer à nouveau la perte IA corrigée (en espérant que le bug était lié à Y nul):\n# loss_function = weighted_binary_crossentropy \n\n\n# --- Séparation des données pour l'entraînement et la validation ---\nX_train, X_val, Y_train, Y_val = train_test_split(\n    X, \n    Y_matrix, \n    test_size=0.1, \n    random_state=42 \n)\n\n# --- Compilation et Entraînement (avec binary_crossentropy pour la sécurité) ---\n# Si le GPU est détecté, l'entraînement sera rapide.\nmodel.compile(\n    optimizer='adam',\n    loss='binary_crossentropy', # UTILISATION DE LA PERTE STANDARD POUR VÉRIFICATION\n    metrics=[tf.keras.metrics.Precision(name='precision'), tf.keras.metrics.Recall(name='recall')]\n)\n\nprint(\"\\nDébut de l'entraînement du modèle MLP...\")\n\nearly_stopping = tf.keras.callbacks.EarlyStopping(\n    monitor='val_loss', \n    patience=5, \n    restore_best_weights=True\n)\n\nhistory = model.fit(\n    X_train, Y_train,\n    epochs=50, \n    batch_size=64,\n    validation_data=(X_val, Y_val),\n    callbacks=[early_stopping] \n)\n\nprint(\"\\nEntraînement terminé.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-21T22:44:20.684951Z","iopub.execute_input":"2025-10-21T22:44:20.685887Z","iopub.status.idle":"2025-10-21T22:55:20.755107Z","shell.execute_reply.started":"2025-10-21T22:44:20.685850Z","shell.execute_reply":"2025-10-21T22:55:20.753989Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# second entraînement\nimport tensorflow as tf\nfrom tensorflow.keras import backend as K\nimport numpy as np # Assurez-vous d'avoir importé numpy si class_weights_tensor vient d'un numpy array\n\n# --- Fonction de perte pondérée par l'IA (CORRIGÉE) ---\ndef weighted_binary_crossentropy_FINAL(y_true, y_pred):\n    \"\"\"\n    Fonction de perte pondérée par l'Information Content (IA).\n    Utilise le backend de Keras pour gérer correctement les formes.\n    \"\"\"\n    # 1. Calcul de la BCE pour chaque élément (sans réduction)\n    # y_true et y_pred sont de forme (batch_size, 1585)\n    bce = K.binary_crossentropy(y_true, y_pred) \n    \n    # 2. Ajout de dimension (1585,) -> (1, 1585) pour le broadcasting sécurisé\n    # Ceci assure que chaque ligne du batch est multipliée par le vecteur de poids.\n    weights = class_weights_tensor[None, :] \n    \n    # 3. Multiplication élément par élément : (64, 1585) * (1, 1585)\n    weighted_bce = bce * weights\n    \n    # 4. Retourne la moyenne de la perte sur toutes les classes et le batch\n    return K.mean(weighted_bce)\n\n# --- Recréer le Tenseur de Poids si nécessaire ---\n# Si votre class_weights_tensor a été perdu ou redéfini, assurez-vous qu'il soit bien un tenseur TF\n# class_weights_tensor = tf.constant(votre_array_de_poids_IA_de_numpy, dtype=tf.float32)\n\nprint(\"Fonction de perte pondérée par l'IA mise à jour et chargée.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-21T23:04:35.178327Z","iopub.execute_input":"2025-10-21T23:04:35.179313Z","iopub.status.idle":"2025-10-21T23:04:35.186232Z","shell.execute_reply.started":"2025-10-21T23:04:35.179276Z","shell.execute_reply":"2025-10-21T23:04:35.185232Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- Compilation du Modèle avec la Perte IA ---\n\nprint(\"\\n--- Relance de la compilation avec la Perte IA ---\")\n\n# Recompilez le modèle en utilisant la nouvelle fonction de perte\nmodel.compile(\n    optimizer='adam',\n    # UTILISATION DE LA PERTE PONDÉRÉE PAR L'IA CORRIGÉE\n    loss=weighted_binary_crossentropy_FINAL, \n    metrics=[tf.keras.metrics.Precision(name='precision'), tf.keras.metrics.Recall(name='recall')]\n)\n\n# --- Entraînement ---\n\nprint(\"\\nDébut du second entraînement (Optimisation Fmax)...\")\n\n# Lancer le nouvel entraînement\n# L'EarlyStopping utilisera la 'val_loss' pondérée pour déterminer le meilleur point d'arrêt\nhistory_ia = model.fit(\n    X_train, Y_train,\n    epochs=50, # Vous pouvez augmenter les époques si le premier entrainement a utilisé moins de 20\n    batch_size=64,\n    validation_data=(X_val, Y_val),\n    callbacks=[early_stopping] \n)\n\nprint(\"\\nEntraînement avec la Perte IA terminé.\")\nNEW_WEIGHTS_FILE = 'best_cafa6_mlp_ic_weighted.weights.h5' \nmodel.save_weights(NEW_WEIGHTS_FILE)\n\nprint(f\"Sauvegarde des poids terminée : {NEW_WEIGHTS_FILE}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-21T23:04:52.432307Z","iopub.execute_input":"2025-10-21T23:04:52.432689Z","iopub.status.idle":"2025-10-21T23:07:26.851447Z","shell.execute_reply.started":"2025-10-21T23:04:52.432663Z","shell.execute_reply":"2025-10-21T23:07:26.850337Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport tensorflow as tf\nfrom tensorflow.keras import backend as K\n\n# --- 0. Définition des chemins des sources de la compétition ---\n# Ces fichiers sont nécessaires pour la recréation\nTRAIN_TERMS_FILE = '/kaggle/input/cafa-6-protein-function-prediction/Train/train_terms.tsv'\n\n# --- CORRECTION CLEF : RECRÉATION DU MAPPING GO À PARTIR DE LA SOURCE ---\nprint(\"Tentative de recréation du mapping GO...\")\ntry:\n    # 1. Charger le fichier de termes source\n    df_terms = pd.read_csv(TRAIN_TERMS_FILE, sep='\\t')\n    \n    # 2. Identifier les 1585 termes que vous avez utilisés (les plus fréquents)\n    # On suppose que vous avez filtré sur les termes les plus fréquents (> 220 occurrences, le seuil standard)\n    term_counts = df_terms['term'].value_counts()\n    \n    # Définir le seuil utilisé pour la compétition (typiquement 220 pour avoir ~1585 termes)\n    # Ceci doit correspondre au seuil que vous avez utilisé lors de la création de Y !\n    # Si le nombre de termes est 1585, le seuil était probablement autour de 220.\n    TARGET_TERM_COUNT = 1585 \n    \n    # Ajustement heuristique pour obtenir le bon nombre de termes (cela suppose un ordre précis)\n    # Pour l'inférence, nous allons simplement prendre les 1585 termes les plus fréquents.\n    # C'est la méthode la plus probable que vous ayez utilisée.\n    \n    # Prendre les termes les plus fréquents dans l'ordre de leur fréquence (hypothèse la plus forte)\n    go_terms_list_recreated = term_counts.head(TARGET_TERM_COUNT).index.tolist()\n    \n    # Créer le dictionnaire de conversion (index -> ID GO)\n    index_to_term = {i: term for i, term in enumerate(go_terms_list_recreated)}\n    \n    print(f\"✅ Mapping index -> GO ID recréé avec succès. Total termes : {len(index_to_term)}\")\n    \n    # --- Continuer le chargement des autres fichiers pour l'inférence ---\n    \n    # ... (code de chargement X_test et test_ids_clean) ...\n    \n    # Ensuite, le code de prédiction et de soumission...\n    \nexcept Exception as e:\n    print(f\"❌ ÉCHEC : Impossible de recréer le mapping GO à partir de {TRAIN_TERMS_FILE}. Détails : {e}\")\n    index_to_term = {}","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-21T23:27:58.530200Z","iopub.execute_input":"2025-10-21T23:27:58.531275Z","iopub.status.idle":"2025-10-21T23:27:58.985378Z","shell.execute_reply.started":"2025-10-21T23:27:58.531234Z","shell.execute_reply":"2025-10-21T23:27:58.984459Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\nimport tensorflow as tf\nimport os \nimport pandas as pd\nfrom tensorflow.keras import backend as K\n\n# --- 0. Initialisation et Définitions ---\n\n# Chemins d'accès confirmés par l'utilisateur (utilisés pour l'inférence)\nTEST_IDS_FILE = '/kaggle/input/test-protein-embedding-complet/test_protein_ids_complet.npy'\nTEST_EMBEDDINGS_FILE = '/kaggle/input/test-protein-embedding-complet/test_protein_embeddings_complet.npy' \nTRAIN_TERMS_FILE = '/kaggle/input/cafa-6-protein-function-prediction/Train/train_terms.tsv'\n\n# Assurez-vous que 'model' (avec les poids entraînés) et 'class_weights_tensor' sont définis dans votre notebook !\n\ndef weighted_binary_crossentropy_FINAL(y_true, y_pred):\n    # La fonction de perte doit être définie si le modèle est recompilé\n    bce = K.binary_crossentropy(y_true, y_pred) \n    weights = class_weights_tensor[None, :] \n    weighted_bce = bce * weights\n    return K.mean(weighted_bce)\n\ntry:\n    # Re-compiler le modèle pour s'assurer que les métriques sont disponibles (les poids sont restaurés par EarlyStopping)\n    model.compile(\n        optimizer='adam',\n        loss=weighted_binary_crossentropy_FINAL, \n        metrics=[tf.keras.metrics.Precision(name='precision'), tf.keras.metrics.Recall(name='recall')]\n    )\nexcept NameError:\n    print(\"❌ ATTENTION : La variable 'model' ou 'class_weights_tensor' n'est pas définie. Veuillez recharger le modèle.\")\n\n# --- 1. CHARGEMENT DES DONNÉES DE TEST ET RECRÉATION DU MAPPING GO ---\nprint(\"\\n--- 1. Chargement et Recréation du Mapping ---\")\n\n# 1. Charger les IDs de test\ntry:\n    test_ids = np.load(TEST_IDS_FILE) \n    test_ids_clean = np.array([i.split('|')[1] if '|' in str(i) else str(i) for i in test_ids])\n    print(f\"✅ IDs chargés. Nombre de protéines : {len(test_ids_clean)}\")\nexcept Exception as e:\n    print(f\"❌ Erreur de chargement IDs : {e}\")\n    test_ids_clean = np.array([]) \n\n# 2. Charger les embeddings de test (X_test)\ntry:\n    X_test = np.load(TEST_EMBEDDINGS_FILE) \n    print(f\"✅ Embeddings chargés. Forme : {X_test.shape}\")\nexcept Exception as e:\n    print(f\"❌ Erreur de chargement Embeddings : {e}\")\n    X_test = np.array([])\n\n# 3. RECRÉATION du mapping des termes GO (Solution de déblocage)\ntry:\n    df_terms = pd.read_csv(TRAIN_TERMS_FILE, sep='\\t')\n    term_counts = df_terms['term'].value_counts()\n    # On suppose que 1585 termes correspondent aux 1585 plus fréquents\n    go_terms_list_recreated = term_counts.head(1585).index.tolist()\n    index_to_term = {i: term for i, term in enumerate(go_terms_list_recreated)}\n    print(f\"✅ Mapping index -> GO ID recréé. Total termes : {len(index_to_term)}\")\nexcept Exception as e:\n    print(f\"❌ ÉCHEC FATAL : Impossible de recréer le mapping GO. {e}\")\n    index_to_term = {} \n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-21T23:29:01.909347Z","iopub.execute_input":"2025-10-21T23:29:01.909739Z","iopub.status.idle":"2025-10-21T23:29:22.968134Z","shell.execute_reply.started":"2025-10-21T23:29:01.909713Z","shell.execute_reply":"2025-10-21T23:29:22.967094Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#  INFÉRENCE (CRITIQUE) ---\n\nprint(\"Début de l'inférence sur le jeu de test complet...\")\n\n# ⚠️ X_test doit contenir les embeddings complets de test (forme: 224309, 320)\n# La variable Y_pred_proba stockera les probabilités de prédiction brutes du MLP\nY_pred_proba = model.predict(X_test) \n\nprint(f\"✅ Inférence terminée. Dimensions des prédictions brutes : {Y_pred_proba.shape}\")\nprint(f\"Le modèle a prédit {Y_pred_proba.shape[0]} protéines sur {Y_pred_proba.shape[1]} termes GO.\")\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Propagation GO\n!pip install obonet networkx\n\nimport networkx as nx\nimport numpy as np\n\nimport obonet\n# Remplacez 'chemin/vers/go-basic.obo' par le chemin réel dans votre Kaggle Input\nOBO_FILE_PATH = '/kaggle/input/cafa-6-protein-function-prediction/Train/go-basic.obo' \ngo_graph = obonet.read_obo(OBO_FILE_PATH)\nprint(f\"Graphe GO chargé avec {go_graph.number_of_nodes()} nœuds.\")\n\n\n# --- 1. SÉCURITÉ ET PRÉPARATION ---\n\n# Crée une copie des prédictions brutes pour les modifier (très important !)\nY_pred_proba_propagated = np.copy(Y_pred_proba)\n\n# Récupère l'ensemble des termes GO prédits par le modèle\ngo_terms_in_model = set(index_to_term.values())\n\n# --- 2. FONCTION DE PROPAGATION HIERARCHIQUE ---\n\n# Nous allons itérer sur les nœuds du graphe GO par ordre topologique inverse\n# (des feuilles vers les racines) pour assurer que les parents sont mis à jour après les enfants.\n\n# On utilise les termes qui sont à la fois dans notre modèle et dans le graphe GO\nnodes_to_propagate = [n for n in nx.topological_sort(go_graph) if n in go_terms_in_model]\n# Inverser l'ordre : du bas (feuilles) vers le haut (racines)\nnodes_to_propagate.reverse()\n\nprint(f\"\\nDébut de la propagation pour {len(nodes_to_propagate)} termes GO...\")\n\n# --- 3. APPLICATION DE LA PROPAGATION ---\n\nfor term_id_child in nodes_to_propagate:\n    try:\n        # Récupère l'index de ce terme dans la matrice de prédiction\n        idx_child = term_to_index[term_id_child]\n\n        # Trouvez tous les parents directs (relations 'is a' ou 'part of')\n        # On utilise go_graph.predecessors(node) pour trouver les parents directs\n        # dans le graphe OBO où les arêtes pointent d'enfant à parent.\n        for term_id_parent in go_graph.predecessors(term_id_child):\n            \n            # Assurez-vous que le parent est un terme que notre modèle prédit (un des 1585)\n            if term_id_parent in term_to_index:\n                idx_parent = term_to_index[term_id_parent]\n                \n                # Règle de propagation: Score(Parent) = max(Score(Parent), Score(Enfant))\n                # np.maximum effectue cette opération ligne par ligne (pour toutes les protéines à la fois)\n                \n                # Mise à jour des scores du parent pour toutes les protéines\n                Y_pred_proba_propagated[:, idx_parent] = np.maximum(\n                    Y_pred_proba_propagated[:, idx_parent], \n                    Y_pred_proba_propagated[:, idx_child]\n                )\n\n    except KeyError:\n        # Ignore les termes qui sont dans le graphe GO mais que nous n'avons pas sélectionnés (pas l'un des 1585)\n        continue \n\nprint(\"✅ Propagation GO terminée.\")\nprint(f\"Les prédictions finales sont stockées dans Y_pred_proba_propagated (Forme: {Y_pred_proba_propagated.shape}).\")\n\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\nimport os\nfrom operator import itemgetter\n\n# --- 3. FORMATAGE ET ÉCRITURE DU FICHIER DE SOUMISSION CONFORME CAFA ---\nprint(\"\\n--- 3. Formatage et Écriture du Fichier de Soumission CAFA FINAL ---\")\n\n# ⚠️ L'ENTRÉE EST MAINTENANT LE RESULTAT DE LA PROPAGATION GO\nY_FINAL_SCORES = Y_pred_proba_propagated\n\n# 🚨 Rappel de la contrainte (score minimum pour ce type de compétition)\n# Le score DOIT être dans l'intervalle (0, 1.000]. Votre seuil est 0.01.\n# Le minimum requis est de 0.5 (selon vos informations mémorisées).\n# Cependant, pour le formatage CAFA, on garde souvent un seuil bas (0.01) \n# pour inclure tous les termes avant la limite de 1500.\n\nif len(Y_FINAL_SCORES) > 0 and len(index_to_term) == 1585:\n    \n    # Le score DOIT être dans l'intervalle (0, 1.000].\n    SEUIL_SOUMISSION = 0.01 # Gardons ce seuil pour la capture initiale\n    # La limite est de 1500 termes MAX par protéine.\n    MAX_TERMS_PER_PROTEIN = 1500\n    \n    submission_lines = []\n\n    for i in range(len(test_ids_clean)):\n        protein_id = test_ids_clean[i]\n        # Utilisez les scores finaux propagés\n        probabilities = Y_FINAL_SCORES[i]\n        \n        predictions = []\n        \n        # 1. Identifier les prédictions au-dessus du seuil (doit être > 0.0)\n        indices_predits = np.where(probabilities > SEUIL_SOUMISSION)[0]\n        \n        # Créer des paires (terme, probabilité) pour le tri\n        for idx in indices_predits:\n            term_id = index_to_term.get(idx)\n            prob = probabilities[idx]\n            \n            if term_id and term_id != 'GO:UNKNOWN':\n                predictions.append((term_id, prob))\n\n        # 2. Trier par probabilité (du plus haut au plus bas)\n        predictions.sort(key=itemgetter(1), reverse=True)\n        \n        # 3. Appliquer la limite de 1500 termes\n        final_predictions = predictions[:MAX_TERMS_PER_PROTEIN]\n        \n        # 4. Formatage des lignes\n        for term_id, prob in final_predictions:\n            # Score doit être dans (0, 1.000] avec max 3 chiffres significatifs.\n            formatted_prob = f\"{prob:.3f}\"\n            \n            # Format: ID_prot [TAB] GO_ID [TAB] Score (une association par ligne)\n            submission_lines.append(f\"{protein_id}\\t{term_id}\\t{formatted_prob}\")\n\n    # --- ÉCRIRE LE FICHIER (SANS HEADER) ---\n    \n    # 💥 CORRECTION CRITIQUE : Le nom du fichier DOIT être 'submission.tsv' pour Kaggle.\n    SUBMISSION_FILE_NAME = 'submission.tsv'\n\n    with open(os.path.join('/kaggle/working', SUBMISSION_FILE_NAME), 'w') as f:\n        # CRITIQUE : Écriture des lignes SANS HEADER\n        f.write('\\n'.join(submission_lines))\n\n    print(f\"\\n✅ Fichier de soumission créé : {SUBMISSION_FILE_NAME}. Nombre total de prédictions : {len(submission_lines)}\")\n    print(\"Le fichier est maintenant nommé correctement pour la soumission à Kaggle.\")\nelse:\n    print(\"\\n❌ ÉCHEC FINAL : Vérifiez le chargement de Y_FINAL_SCORES (Y_pred_proba_propagated) ou la taille de index_to_term.\")","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}