{"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":"none","dataSources":[{"sourceType":"competition","sourceId":116062,"databundleVersionId":14875579},{"sourceType":"datasetVersion","sourceId":14066577,"datasetId":8953533,"databundleVersionId":14847099},{"sourceType":"datasetVersion","sourceId":14090851,"datasetId":8886340,"databundleVersionId":14873438},{"sourceType":"datasetVersion","sourceId":13482672,"datasetId":8559888,"databundleVersionId":14202491},{"sourceType":"datasetVersion","sourceId":13991224,"datasetId":8917191,"databundleVersionId":14766799}],"dockerImageVersionId":31234,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-02-23T10:07:57.308226Z","iopub.execute_input":"2026-02-23T10:07:57.309009Z","iopub.status.idle":"2026-02-23T10:07:57.330719Z","shell.execute_reply.started":"2026-02-23T10:07:57.308971Z","shell.execute_reply":"2026-02-23T10:07:57.329439Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!pip install biopython transformers torch obonet pandas tqdm","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-23T10:07:57.376202Z","iopub.execute_input":"2026-02-23T10:07:57.376584Z","iopub.status.idle":"2026-02-23T10:08:01.816528Z","shell.execute_reply.started":"2026-02-23T10:07:57.376554Z","shell.execute_reply":"2026-02-23T10:08:01.815343Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport numpy as np\nimport random\nimport os\n\ndef seed_everything(seed=42):\n    random.seed(seed)\n    os.environ['PYTHONHASHSEED'] = str(seed)\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed(seed)\n    torch.cuda.manual_seed_all(seed) # if you are using multi-GPU\n    \n    # These two lines are critical for GPU reproducibility\n    torch.backends.cudnn.deterministic = True\n    torch.backends.cudnn.benchmark = False\n\nseed_everything(999)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-23T10:08:01.818833Z","iopub.execute_input":"2026-02-23T10:08:01.819277Z","iopub.status.idle":"2026-02-23T10:08:01.831954Z","shell.execute_reply.started":"2026-02-23T10:08:01.819227Z","shell.execute_reply":"2026-02-23T10:08:01.830637Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"X_train1 = np.load('/kaggle/input/datasets/baogduong/nnn-cafa6-protbert-embedding/train_embeddings.npy')\ntrain_ids1 = np.load('/kaggle/input/datasets/baogduong/nnn-cafa6-protbert-embedding/train_ids.npy')\n\n\nX_train2 = np.load('/kaggle/input/datasets/dalloliogm/cafa6-esm-cambrian-embeddings/protein_embeddings_train.npy')\ntrain_ids2 = np.load('/kaggle/input/datasets/dalloliogm/cafa6-esm-cambrian-embeddings/train_ids.npy')\n\n\nX_train3 = np.load('/kaggle/input/datasets/seddiktrk/cafa6-protein-embeddings-esm2/protein_embeddings.npy')\ntrain_ids3 = pd.read_csv('/kaggle/input/datasets/seddiktrk/cafa6-protein-embeddings-esm2/protein_ids.csv')\ntrain_ids3 = train_ids3.iloc[:, 0].values\n\ndatasets = [(X_train1, train_ids1), (X_train2, train_ids2), (X_train3, train_ids3)]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-23T10:08:01.833208Z","iopub.execute_input":"2026-02-23T10:08:01.833680Z","iopub.status.idle":"2026-02-23T10:08:03.761449Z","shell.execute_reply.started":"2026-02-23T10:08:01.833635Z","shell.execute_reply":"2026-02-23T10:08:03.760357Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nfrom sklearn.decomposition import PCA\nimport numpy as np\n\ndef plot_embeddings(datasets, names, sample_size=2000):\n    fig, axes = plt.subplots(1, 3, figsize=(20, 6))\n    \n    for i, (X, ids) in enumerate(datasets):\n        # 1. Sampling for performance (t-SNE/PCA can be slow on full CAFA sets)\n        if X.shape[0] > sample_size:\n            idx = np.random.choice(X.shape[0], sample_size, replace=False)\n            X_sample = X[idx]\n        else:\n            X_sample = X\n\n        # 2. Dimensionality Reduction (PCA)\n        pca = PCA(n_components=2)\n        X_pca = pca.fit_transform(X_sample)\n        \n        # 3. Plotting\n        axes[i].scatter(X_pca[:, 0], X_pca[:, 1], s=5, alpha=0.5, c='teal')\n        axes[i].set_title(f\"{names[i]}\\n(Variance Explained: {np.sum(pca.explained_variance_ratio_):.2%})\")\n        axes[i].set_xlabel(\"PC1\")\n        axes[i].set_ylabel(\"PC2\")\n        axes[i].grid(True, linestyle='--', alpha=0.6)\n\n    plt.tight_layout()\n    plt.savefig('embedding_comparison.png')\n    plt.show()\n\n# Run the visualization\nnames = [\"ProtBert\", \"ESM-Cambrian\", \"ESM-2\"]\nplot_embeddings(datasets, names)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-23T10:08:03.763707Z","iopub.execute_input":"2026-02-23T10:08:03.764139Z","iopub.status.idle":"2026-02-23T10:08:05.063661Z","shell.execute_reply.started":"2026-02-23T10:08:03.764107Z","shell.execute_reply":"2026-02-23T10:08:05.062430Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"    # # 1. Load the labels\n    # train_terms = pd.read_csv('/kaggle/input/cafa-6-protein-function-prediction/Train/train_terms.tsv', sep='\\t')\n    \n    # # 2. Filter terms based on frequency per aspect\n    # # BP > 250, MF > 50, CC > 50\n    # term_counts = train_terms.groupby(['term', 'aspect']).size().reset_index(name='count')\n    \n    # bp_terms = term_counts[(term_counts['aspect'] == 'P') & (term_counts['count'] > 250)]['term'].tolist()\n    # mf_terms = term_counts[(term_counts['aspect'] == 'F') & (term_counts['count'] > 50)]['term'].tolist()\n    # cc_terms = term_counts[(term_counts['aspect'] == 'C') & (term_counts['count'] > 50)]['term'].tolist()\n    \n    # # Combine them and (optional) keep the top 500 most frequent among these \n    # # if you strictly want a limit of 500\n    # combined_terms = bp_terms + mf_terms + cc_terms\n    # top_terms = (\n    #     term_counts[term_counts['term'].isin(combined_terms)]\n    #     .sort_values('count', ascending=False)\n    #     .head(500)['term']\n    #     .tolist()\n    # )\n    \n    # num_labels = len(top_terms)\n    # term_to_idx = {term: i for i, term in enumerate(top_terms)}\n    \n    # # 3. Fast lookup dictionary\n    # protein_to_terms = train_terms[train_terms['term'].isin(top_terms)].groupby('EntryID')['term'].apply(list).to_dict()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-23T10:08:05.064932Z","iopub.execute_input":"2026-02-23T10:08:05.065247Z","iopub.status.idle":"2026-02-23T10:08:05.072134Z","shell.execute_reply.started":"2026-02-23T10:08:05.065220Z","shell.execute_reply":"2026-02-23T10:08:05.071089Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport seaborn as sns\nimport matplotlib.pyplot as plt\n\n# 1. Load the labels\ntrain_terms = pd.read_csv('/kaggle/input/cafa-6-protein-function-prediction/Train/train_terms.tsv', sep='\\t')\n\ndef get_aspect_data(aspect_code, min_count):\n    # Filter for specific aspect\n    aspect_df = train_terms[train_terms['aspect'] == aspect_code]\n    \n    # Get term frequencies\n    counts = aspect_df['term'].value_counts()\n    \n    # Filter by your frequency thresholds\n    selected_terms = counts[counts > min_count].index.tolist()\n    \n    return selected_terms, aspect_df\n\n# Get terms for each separately\nbp_labels, bp_df = get_aspect_data('P', 250)\nmf_labels, mf_df = get_aspect_data('F', 50)\ncc_labels, cc_df = get_aspect_data('C', 50)\n\nprint(f\"BPO Labels: {len(bp_labels)} | MFO Labels: {len(mf_labels)} | CCO Labels: {len(cc_labels)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-23T10:08:05.073551Z","iopub.execute_input":"2026-02-23T10:08:05.074037Z","iopub.status.idle":"2026-02-23T10:08:05.617016Z","shell.execute_reply.started":"2026-02-23T10:08:05.073938Z","shell.execute_reply":"2026-02-23T10:08:05.615722Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def plot_label_distribution(df, aspect_name):\n    terms_per_protein = df.groupby('EntryID')['term'].count()\n    \n    plt.figure(figsize=(10, 4))\n    sns.histplot(terms_per_protein, bins=30, kde=True, color='skyblue')\n    plt.title(f'Number of {aspect_name} Terms per Protein')\n    plt.xlabel('Number of GO Terms')\n    plt.ylabel('Protein Count')\n    plt.show()\n\nplot_label_distribution(bp_df, \"Biological Process (BPO)\")\nplot_label_distribution(mf_df, \"Molecular Function (MFO)\")\nplot_label_distribution(cc_df, \"Cellular Component (CCO)\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-23T10:08:05.618570Z","iopub.execute_input":"2026-02-23T10:08:05.619442Z","iopub.status.idle":"2026-02-23T10:08:07.543617Z","shell.execute_reply.started":"2026-02-23T10:08:05.619411Z","shell.execute_reply":"2026-02-23T10:08:07.542499Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 1. Create the term_to_idx dictionaries (vital for your PyTorch/TensorFlow Dataset)\nbp_term_to_idx = {term: i for i, term in enumerate(bp_labels)}\nmf_term_to_idx = {term: i for i, term in enumerate(mf_labels)}\ncc_term_to_idx = {term: i for i, term in enumerate(cc_labels)}\n\n# 2. Filter the dataframes so they ONLY contain the valid terms (count > min_count)\nbp_df_filtered = bp_df[bp_df['term'].isin(bp_labels)]\nmf_df_filtered = mf_df[mf_df['term'].isin(mf_labels)]\ncc_df_filtered = cc_df[cc_df['term'].isin(cc_labels)]\n\n# 3. Create the fast lookup dictionaries for your training loop\nbp_protein_to_terms = bp_df_filtered.groupby('EntryID')['term'].apply(list).to_dict()\nmf_protein_to_terms = mf_df_filtered.groupby('EntryID')['term'].apply(list).to_dict()\ncc_protein_to_terms = cc_df_filtered.groupby('EntryID')['term'].apply(list).to_dict()\n\nprint(f\"Total BP proteins in dictionary: {len(bp_protein_to_terms)}\")\nprint(f\"Total MF proteins in dictionary: {len(mf_protein_to_terms)}\")\nprint(f\"Total CC proteins in dictionary: {len(cc_protein_to_terms)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-23T10:08:07.545002Z","iopub.execute_input":"2026-02-23T10:08:07.545485Z","iopub.status.idle":"2026-02-23T10:08:10.633523Z","shell.execute_reply.started":"2026-02-23T10:08:07.545455Z","shell.execute_reply":"2026-02-23T10:08:10.632567Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"aspect_data = [\n    (\"BPO\", bp_labels, bp_protein_to_terms, bp_term_to_idx),\n    (\"MFO\", mf_labels, mf_protein_to_terms, mf_term_to_idx),\n    (\"CCO\", cc_labels, cc_protein_to_terms, cc_term_to_idx)\n]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-23T10:08:10.634767Z","iopub.execute_input":"2026-02-23T10:08:10.635152Z","iopub.status.idle":"2026-02-23T10:08:10.668434Z","shell.execute_reply.started":"2026-02-23T10:08:10.635123Z","shell.execute_reply":"2026-02-23T10:08:10.667112Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# def peek_dictionaries(names, dicts, label_lists, n=3):\n#     for name, d, labels in zip(names, dicts, label_lists):\n#         print(f\"--- {name} ASPECT CHECK ---\")\n#         print(f\"Total Proteins: {len(d)}\")\n#         print(f\"Total Unique Labels in this Aspect: {len(labels)}\")\n        \n#         # Grab the first few items to look at\n#         first_keys = list(d.keys())[:n]\n#         for key in first_keys:\n#             print(f\"Protein {key} has {len(d[key])} terms: {d[key][:5]}...\") \n#         print(\"\\n\")\n\n# # Run the check\n# peek_dictionaries(\n#     [\"BIOLOGICAL PROCESS (P)\", \"MOLECULAR FUNCTION (F)\", \"CELLULAR COMPONENT (C)\"],\n#     [bp_protein_to_terms, mf_protein_to_terms, cc_protein_to_terms],\n#     [bp_labels, mf_labels, cc_labels]\n# )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-23T10:08:10.672008Z","iopub.execute_input":"2026-02-23T10:08:10.672537Z","iopub.status.idle":"2026-02-23T10:08:10.699074Z","shell.execute_reply.started":"2026-02-23T10:08:10.672502Z","shell.execute_reply":"2026-02-23T10:08:10.698129Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# import matplotlib.pyplot as plt\n# import seaborn as sns\n# import numpy as np\n\n# # 1. Extract the number of terms per protein for each aspect\n# bp_counts = [len(terms) for terms in bp_protein_to_terms.values()]\n# mf_counts = [len(terms) for terms in mf_protein_to_terms.values()]\n# cc_counts = [len(terms) for terms in cc_protein_to_terms.values()]\n\n# # 2. Create a figure with 3 subplots side-by-side\n# fig, axes = plt.subplots(1, 3, figsize=(18, 5))\n# fig.suptitle('Distribution of GO Terms per Protein', fontsize=16)\n\n# # Function to plot each histogram\n# def plot_histogram(ax, data, color, title):\n#     sns.histplot(data, bins=range(1, max(data)+2), ax=ax, color=color, discrete=True)\n#     ax.set_title(title)\n#     ax.set_xlabel('Number of GO Terms')\n#     ax.set_ylabel('Number of Proteins')\n#     ax.set_xlim(0, max(data) + 1) # Keep the x-axis clean\n    \n#     # Add a vertical line for the median\n#     median_val = np.median(data)\n#     ax.axvline(median_val, color='red', linestyle='dashed', linewidth=2)\n#     ax.text(median_val + 0.5, ax.get_ylim()[1]*0.9, f'Median: {median_val}', color='red')\n\n# # 3. Plot BP, MF, and CC\n# plot_histogram(axes[0], bp_counts, 'skyblue', 'Biological Process (BP)')\n# plot_histogram(axes[1], mf_counts, 'lightgreen', 'Molecular Function (MF)')\n# plot_histogram(axes[2], cc_counts, 'salmon', 'Cellular Component (CC)')\n\n# plt.tight_layout()\n# plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-23T10:08:10.700495Z","iopub.execute_input":"2026-02-23T10:08:10.701290Z","iopub.status.idle":"2026-02-23T10:08:10.721488Z","shell.execute_reply.started":"2026-02-23T10:08:10.701258Z","shell.execute_reply":"2026-02-23T10:08:10.720348Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# def plot_top_terms(df, aspect_name, top_n=20):\n#     top_counts = df['term'].value_counts().head(top_n)\n    \n#     plt.figure(figsize=(12, 6))\n#     sns.barplot(x=top_counts.values, y=top_counts.index, palette='viridis')\n#     plt.title(f'Top {top_n} Most Frequent Terms in {aspect_name}')\n#     plt.xlabel('Occurrence Count')\n#     plt.show()\n\n# plot_top_terms(bp_df, \"BPO\")\n# plot_top_terms(mf_df, \"MFO\")\n# plot_top_terms(cc_df, \"CCF\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-23T10:08:10.722770Z","iopub.execute_input":"2026-02-23T10:08:10.723521Z","iopub.status.idle":"2026-02-23T10:08:10.745856Z","shell.execute_reply.started":"2026-02-23T10:08:10.723489Z","shell.execute_reply":"2026-02-23T10:08:10.744385Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import obonet\nimport networkx as nx\nimport numpy as np\nfrom sklearn.metrics import f1_score\nfrom itertools import product","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-23T10:08:10.747350Z","iopub.execute_input":"2026-02-23T10:08:10.747739Z","iopub.status.idle":"2026-02-23T10:08:10.767425Z","shell.execute_reply.started":"2026-02-23T10:08:10.747696Z","shell.execute_reply":"2026-02-23T10:08:10.766126Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# # =====================================================================\n# # 🛠️ BUILD THE GRAPH HERE (Run this ONCE before your dataset loop)\n# # =====================================================================\n# print(\"Parsing go-basic.obo file...\")\n# # Make sure this path points to where the obo file is in your Kaggle environment\n# obo_path = '/kaggle/input/cafa-6-protein-function-prediction/Train/go-basic.obo' \n# go_graph = obonet.read_obo(obo_path)\n\n# child_parent_pairs = []\n\n# # Loop through your specific 500 terms\n# for child_term in top_terms:\n#     if child_term in go_graph:\n#         # In obonet, 'successors' gets the parents (is_a relationships)\n#         for parent_term in go_graph.successors(child_term):\n#             # We only care about the relationship if BOTH terms are in your top 500\n#             if parent_term in term_to_idx:\n#                 child_parent_pairs.append((term_to_idx[child_term], term_to_idx[parent_term]))\n\n# # CRITICAL STEP: Sort from bottom to top!\n# # We count how many ancestors a node has. More ancestors = deeper in the tree (a child).\n# # Sorting reverse=True ensures we process the deepest children first.\n\n# child_parent_pairs.sort(\n#     key=lambda pair: len(nx.ancestors(go_graph, top_terms[pair[0]])), \n#     reverse=True\n# )\n# print(f\"Found {len(child_parent_pairs)} parent-child relationships among your 500 terms.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-23T10:08:10.768778Z","iopub.execute_input":"2026-02-23T10:08:10.769173Z","iopub.status.idle":"2026-02-23T10:08:10.785567Z","shell.execute_reply.started":"2026-02-23T10:08:10.769131Z","shell.execute_reply":"2026-02-23T10:08:10.784333Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#this shit just to check dimension\nfor id, (X_train, train_ids) in enumerate(datasets, 1):\n    print(f\"Dataset {id}\")\n    print(\"Shape:\", X_train.shape)\n    print(\"IDs:\", len(train_ids))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-23T10:08:10.787107Z","iopub.execute_input":"2026-02-23T10:08:10.787648Z","iopub.status.idle":"2026-02-23T10:08:10.834902Z","shell.execute_reply.started":"2026-02-23T10:08:10.787614Z","shell.execute_reply":"2026-02-23T10:08:10.833741Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for aspect_name, labels, protein_to_terms, term_to_idx in aspect_data:\n\n    print(f\"\\nProcessing aspect: {aspect_name}\")\n\n    filtered_datasets = []\n\n    for id, (X_train, train_ids) in enumerate(datasets, 1):\n        print(f\"\\n--- Processing Dataset {id} ---\")\n\n        cleaned_ids = np.array([p.split('|')[1] if '|' in p else p for p in train_ids])\n\n        valid_mask = np.array([p_id in protein_to_terms for p_id in cleaned_ids])\n\n        X_filtered = X_train[valid_mask]\n        ids_filtered = cleaned_ids[valid_mask]\n\n        filtered_datasets.append((X_filtered, ids_filtered))\n\n        print(f\"Original: {X_train.shape[0]}\")\n        print(f\"Filtered: {X_filtered.shape[0]}\")\n\n    datasets_filtered = filtered_datasets","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-23T10:08:10.836251Z","iopub.execute_input":"2026-02-23T10:08:10.836686Z","iopub.status.idle":"2026-02-23T10:08:11.649291Z","shell.execute_reply.started":"2026-02-23T10:08:10.836640Z","shell.execute_reply":"2026-02-23T10:08:11.648283Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"Parsing go-basic.obo file...\")\nobo_path = '/kaggle/input/cafa-6-protein-function-prediction/Train/go-basic.obo' \ngo_graph = obonet.read_obo(obo_path)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-23T10:08:11.650552Z","iopub.execute_input":"2026-02-23T10:08:11.650993Z","iopub.status.idle":"2026-02-23T10:08:18.732053Z","shell.execute_reply.started":"2026-02-23T10:08:11.650963Z","shell.execute_reply":"2026-02-23T10:08:18.730834Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for aspect_name, labels, protein_to_terms, term_to_idx in aspect_data:\n    individual_f1_scores = [] # Create this here!\n    print(f\"\\n{'='*30}\\nPROCESSING ASPECT: {aspect_name}\\n{'='*30}\")\n    num_labels = len(labels)\n    term_to_idx = {term: i for i, term in enumerate(labels)}\n    \n    # --- EQUIVALENT START ---\n    # In this scope, 'labels' is exactly what 'top_terms' used to be\n    child_parent_pairs = []\n\n    for child_term in labels: # Equivalent to top_terms\n        if child_term in go_graph:\n            for parent_term in go_graph.successors(child_term):\n                if parent_term in term_to_idx:\n                    child_parent_pairs.append((term_to_idx[child_term], term_to_idx[parent_term]))\n\n    # Sort from bottom (deepest children) to top\n    child_parent_pairs.sort(\n        key=lambda pair: len(nx.ancestors(go_graph, labels[pair[0]])), \n        reverse=True\n    )\n    print(f\"[{aspect_name}] Found {len(child_parent_pairs)} relationships among {num_labels} terms.\")\n    # --- EQUIVALENT END ---\n\n    # 3. Inner Loop: Dataset (id, (X_train, train_ids))\n    # ... Training loop starts here ...\n    \n    num_labels = len(labels)\n    \n    # --- PLACE DICTIONARIES HERE ---\n    # We reset these for every aspect because the label size (num_labels) changes!\n    all_val_preds_dict = {1: {}, 2: {}, 3: {}} \n    all_val_targets_dict = {}\n    \n    for id, (X_train, train_ids) in enumerate(datasets, 1):\n        print(f\"\\n--- Training Model {id} ---\")\n        \n        # ... [Your existing clean_id and y_train generation code stays the same] ...\n    \n        # Function to turn \"sp|A0A0C5B5G6|MOTSC_HUMAN\" into \"A0A0C5B5G6\"\n        def clean_id(long_id):\n            if '|' in long_id:\n                return long_id.split('|')[1]\n            return long_id\n        \n        # Apply it to your training list\n        cleaned_train_ids = [clean_id(id) for id in train_ids]\n        \n        # Now they match the test set format!\n        print(f\"Old: {train_ids[0]}\")\n        print(f\"New: {cleaned_train_ids[0]}\")\n        \n        # 4. Build the y_train matrix\n        y_train = np.zeros((len(cleaned_train_ids), num_labels), dtype=np.float32)\n        \n        for i, p_id in enumerate(cleaned_train_ids):\n            if p_id in protein_to_terms:\n                for term in protein_to_terms[p_id]:\n                    y_train[i, term_to_idx[term]] = 1.0\n\n    # ... (After cleaning your IDs and before building y_train) ...\n    \n        # 1. Identify which indices actually have labels in your terms\n        valid_indices = [i for i, p_id in enumerate(cleaned_train_ids) if p_id in protein_to_terms]\n        \n        # 2. Filter EVERYTHING to only include those labeled proteins (this happen cuz the unlebeled proteins in 3rd data)\n        X_train = X_train[valid_indices]\n        cleaned_train_ids = [cleaned_train_ids[i] for i in valid_indices]\n        \n        print(f\"Dataset {id} Filtered: Keeping {len(X_train)} labeled proteins.\")\n    \n        # 3. Build the y_train matrix (Now it's perfectly sized!)\n        y_train = np.zeros((len(cleaned_train_ids), num_labels), dtype=np.float32)\n        for i, p_id in enumerate(cleaned_train_ids):\n            for term in protein_to_terms[p_id]:\n                y_train[i, term_to_idx[term]] = 1.0\n    \n        # ... (Rest of your split and training code) ...\n    \n        import torch\n        import torch.nn as nn\n        from torch.utils.data import DataLoader, TensorDataset\n        from sklearn.model_selection import train_test_split\n        ##\n        \n        X_tensor = torch.tensor(X_train, dtype=torch.float32)\n        y_tensor = torch.tensor(y_train, dtype=torch.float32)\n        \n        # ⚠️ FIX 1: Split EVERY dataset so dimensions match the model perfectly.\n        # We also split the 'cleaned_train_ids' so we know exactly WHICH proteins are in the validation set!\n        X_tr, X_val, y_tr, y_val, id_tr, id_val = train_test_split(\n            X_tensor, y_tensor, cleaned_train_ids, test_size=0.1, random_state=42\n        )\n    \n        train_loader = DataLoader(TensorDataset(X_tr, y_tr), batch_size=128, shuffle=True)\n        val_loader = DataLoader(TensorDataset(X_val, y_val), batch_size=128, shuffle=False)\n    \n        # 4. Define MLP Architecture for 500 labels\n        class ProteinMLP(nn.Module):\n            def __init__(self, input_dim):\n                super(ProteinMLP, self).__init__()\n                self.main = nn.Sequential(\n                    nn.Linear(input_dim, 1024),\n                    nn.BatchNorm1d(1024),\n                    nn.ReLU(),\n                    nn.Dropout(0.3),\n                    \n                    nn.Linear(1024, 512),\n                    nn.BatchNorm1d(512),\n                    nn.ReLU(),\n                    nn.Dropout(0.2),\n                    \n                    nn.Linear(512, num_labels) \n                )\n                \n            def forward(self, x):\n                return self.main(x)\n        \n        # 5. Initialize Model on GPU\n        device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n        model = ProteinMLP(input_dim=X_train.shape[1]).to(device)\n        \n        criterion = nn.BCEWithLogitsLoss()\n        optimizer = torch.optim.Adam(model.parameters(), lr=0.001)\n        # ... [Your existing Epoch Training loop goes here] ...\n        # 6. Training Loop\n        print(f\"Starting training on {device}...\")\n\n\n        train_loss_history = []\n        val_loss_history = []\n\n        for epoch in range(15): # Run for 15 epochs\n            model.train()\n            total_train_loss = 0\n            for batch_X, batch_y in train_loader:\n                batch_X, batch_y = batch_X.to(device), batch_y.to(device)\n                \n                optimizer.zero_grad()\n                outputs = model(batch_X)\n                loss = criterion(outputs, batch_y)\n                loss.backward()\n                optimizer.step()\n                total_train_loss += loss.item()\n            \n            # Validation check\n            model.eval()\n            total_val_loss = 0\n            with torch.no_grad():\n                for vX, vy in val_loader:\n                    vX, vy = vX.to(device), vy.to(device)\n                    v_out = model(vX)\n                    total_val_loss += criterion(v_out, vy).item()\n\n                    avg_train_loss = total_train_loss / len(train_loader)\n            avg_val_loss = total_val_loss / len(val_loader)\n            \n            train_loss_history.append(avg_train_loss)\n            val_loss_history.append(avg_val_loss)\n            \n            print(f\"Epoch {epoch+1} | Train Loss: {avg_train_loss:.4f} | Val Loss: {avg_val_loss:.4f}\")\n            \n        plt.figure(figsize=(8, 5))\n        plt.plot(range(1, 16), train_loss_history, label='Train Loss', marker='o')\n        plt.plot(range(1, 16), val_loss_history, label='Validation Loss', marker='o')\n        plt.title(f'Model {id} Loss Curve ({aspect_name})')\n        plt.xlabel('Epoch')\n        plt.ylabel('BCE Loss')\n        plt.legend()\n        plt.grid(True)\n        plt.show()\n        \n        \n        from sklearn.metrics import f1_score\n        import numpy as np\n        ###\n    \n        # --- VALIDATION AND SCORING (Outside the epoch loop, or at the last epoch) ---\n        model.eval()\n        epoch_preds = []\n        epoch_targets = []\n    \n        with torch.no_grad():\n            for vX, vy in val_loader:\n                vX, vy = vX.to(device), vy.to(device)\n                v_out = model(vX)\n                probs = torch.sigmoid(v_out)\n                \n                # GO True Path Propagation\n                for child_idx, parent_idx in child_parent_pairs:\n                    probs[:, parent_idx] = torch.max(probs[:, parent_idx], probs[:, child_idx])\n                    \n                binary_preds = (probs > 0.7).float()\n                \n                epoch_preds.append(binary_preds.cpu().numpy())\n                epoch_targets.append(vy.cpu().numpy())\n                \n        # Concatenate for this model\n        full_preds = np.vstack(epoch_preds)\n        full_targets = np.vstack(epoch_targets)\n        \n        # ⚠️ FIX 2: Save the predictions mapped to their Protein ID\n        # This allows us to find the overlapping proteins later\n        for i, protein_id in enumerate(id_val):\n            all_val_preds_dict[id][protein_id] = full_preds[i]\n            all_val_targets_dict[protein_id] = full_targets[i] # Save true labels\n            \n        # Calculate independent F1\n        val_f1 = f1_score(full_targets, full_preds, average='micro')\n        print(f\"Model {id} Final Val F1-Score: {val_f1:.4f}\")\n        individual_f1_scores.append(val_f1) # Add this line\n\n        # --- VALIDATION AND SCORING ---\n        model.eval()\n        epoch_probs = [] # Add this to store raw probabilities\n        epoch_targets = []\n\n        with torch.no_grad():\n            for vX, vy in val_loader:\n                vX, vy = vX.to(device), vy.to(device)\n                v_out = model(vX)\n                probs = torch.sigmoid(v_out)\n                \n                # GO True Path Propagation\n                for child_idx, parent_idx in child_parent_pairs:\n                    probs[:, parent_idx] = torch.max(probs[:, parent_idx], probs[:, child_idx])\n                \n                epoch_probs.append(probs.cpu().numpy())\n                epoch_targets.append(vy.cpu().numpy())\n\n        full_probs = np.vstack(epoch_probs)\n        full_targets = np.vstack(epoch_targets)\n        full_preds = (full_probs > 0.7).astype(float)\n\n        # 📈 NEW: Precision-Recall Display\n        from sklearn.metrics import PrecisionRecallDisplay\n        plt.figure(figsize=(7, 5))\n        PrecisionRecallDisplay.from_predictions(\n            full_targets.ravel(), \n            full_probs.ravel(), \n            name=f\"Model {id}\"\n        )\n        plt.title(f\"PR Curve - Model {id} ({aspect_name})\")\n        plt.show()\n\n\n\n\n        \n    #after I done the training loop we go onto ensemble part\n\n    from itertools import product\n    import numpy as np\n    from sklearn.metrics import f1_score\n    import matplotlib.pyplot as plt\n\n# ==========================================================\n# INTERSECTION ENSEMBLE\n# ==========================================================\n    def get_best_weights_intersection(all_val_preds_dict, all_val_targets_dict, common_ids, fixed_thresh=0.7):\n        best_f1 = 0.0\n        best_weights = (0.0, 0.0, 0.0)\n        space = np.linspace(0, 1, 11)\n        common_ids = sorted(list(common_ids))\n\n        if len(common_ids) == 0:\n            print(\"No intersection IDs found.\")\n            return best_weights, best_f1\n\n        for w1, w2 in product(space, space):\n            w3 = 1.0 - w1 - w2\n            if w3 < -1e-9:\n                continue\n            w3 = max(0.0, w3)\n\n            all_preds = []\n            all_targets = []\n\n            for pid in common_ids:\n                # Defensive checks: intersection should have preds in all three, but check anyway\n                if (pid not in all_val_targets_dict or\n                    pid not in all_val_preds_dict.get(1, {}) or\n                    pid not in all_val_preds_dict.get(2, {}) or\n                    pid not in all_val_preds_dict.get(3, {})):\n                    continue\n\n                avg_prob = (\n                    w1 * all_val_preds_dict[1][pid] +\n                    w2 * all_val_preds_dict[2][pid] +\n                    w3 * all_val_preds_dict[3][pid]\n                )\n\n                all_preds.append(avg_prob)\n                all_targets.append(all_val_targets_dict[pid])\n\n            if len(all_targets) == 0:\n                continue\n\n            y_true = np.array(all_targets)\n            y_pred = (np.array(all_preds) > fixed_thresh).astype(int)\n            current_f1 = f1_score(y_true, y_pred, average='micro')\n\n            if current_f1 > best_f1:\n                best_f1 = current_f1\n                best_weights = (w1, w2, w3)\n\n        return best_weights, best_f1\n\n\n# ==========================================================\n# UNION ENSEMBLE (robust to zero-weight situations)\n# ==========================================================\n    def get_best_weights_union(all_val_preds_dict, all_val_targets_dict, all_ids, fixed_thresh=0.7):\n        best_f1 = 0.0\n        best_weights = (0.0, 0.0, 0.0)\n        space = np.linspace(0, 1, 11)\n        sorted_ids = sorted(list(all_ids))\n\n        if len(sorted_ids) == 0:\n            print(\"No union IDs found.\")\n            return best_weights, best_f1\n\n        for w1, w2 in product(space, space):\n            w3 = 1.0 - w1 - w2\n            if w3 < -1e-9:\n                continue\n            w3 = max(0.0, w3)\n\n            all_preds = []\n            all_targets = []\n\n            for pid in sorted_ids:\n                # require a ground truth; skip otherwise\n                if pid not in all_val_targets_dict:\n                    continue\n\n                available_preds = []\n                available_weights = []\n\n                if pid in all_val_preds_dict.get(1, {}):\n                    available_preds.append(all_val_preds_dict[1][pid])\n                    available_weights.append(w1)\n                if pid in all_val_preds_dict.get(2, {}):\n                    available_preds.append(all_val_preds_dict[2][pid])\n                    available_weights.append(w2)\n                if pid in all_val_preds_dict.get(3, {}):\n                    available_preds.append(all_val_preds_dict[3][pid])\n                    available_weights.append(w3)\n\n                if len(available_preds) == 0:\n                    # no model predicted for this pid, fallback to zeros of correct shape\n                    avg = np.zeros_like(all_val_targets_dict[pid], dtype=float)\n                else:\n                    weight_sum = float(np.sum(available_weights))\n                    if weight_sum > 1e-9:\n                        # normal weighted average\n                        avg = np.average(available_preds, axis=0, weights=available_weights)\n                    else:\n                        # all available weights are (near) zero -> fall back to unweighted mean\n                        avg = np.mean(available_preds, axis=0)\n\n                all_preds.append(avg)\n                all_targets.append(all_val_targets_dict[pid])\n\n            if len(all_targets) == 0:\n                continue\n\n            y_true = np.array(all_targets)\n            y_pred = (np.array(all_preds) > fixed_thresh).astype(int)\n            current_f1 = f1_score(y_true, y_pred, average='micro')\n\n            if current_f1 > best_f1:\n                best_f1 = current_f1\n                best_weights = (w1, w2, w3)\n\n        return best_weights, best_f1\n\n\n# ==========================================================\n# RUN ENSEMBLES\n# ==========================================================\n    target_t = 0.7\n\n# Intersection (proteins present in ALL three models)\n    common_ids = (\n        set(all_val_preds_dict.get(1, {}).keys()) &\n        set(all_val_preds_dict.get(2, {}).keys()) &\n        set(all_val_preds_dict.get(3, {}).keys())\n    )\n\n    best_w_int, score_int = get_best_weights_intersection(\n        all_val_preds_dict,\n        all_val_targets_dict,\n        common_ids,\n        fixed_thresh=target_t\n    )\n\n    print(\"\\nIntersection Ensemble\")\n    print(\"Best Weights:\", best_w_int)\n    print(\"Best F1:\", score_int)\n\n# Union (proteins present in ANY model)\n    all_unique_ids = (\n        set(all_val_preds_dict.get(1, {}).keys()) |\n        set(all_val_preds_dict.get(2, {}).keys()) |\n        set(all_val_preds_dict.get(3, {}).keys())\n    )\n\n    best_w_uni, score_uni = get_best_weights_union(\n        all_val_preds_dict,\n        all_val_targets_dict,\n        all_unique_ids,\n        fixed_thresh=target_t\n    )\n\n    print(\"\\nUnion Ensemble\")\n    print(\"Best Weights:\", best_w_uni)\n    print(\"Best F1:\", score_uni)\n\n\n# ==========================================================\n# FINAL COMPARISON PLOT\n# ==========================================================\n# ensure three individual scores exist\n    while len(individual_f1_scores) < 3:\n        individual_f1_scores.append(0.0)\n\n    models = ['Model 1', 'Model 2', 'Model 3', 'Intersection', 'Union']\n    final_scores = individual_f1_scores + [score_int, score_uni]\n\n    plt.figure(figsize=(10, 6))\n    bars = plt.bar(models, final_scores)\n\n    for bar in bars:\n        yval = bar.get_height()\n        plt.text(\n            bar.get_x() + bar.get_width()/2,\n            yval + 0.005,\n            f\"{yval:.4f}\",\n            ha='center',\n            va='bottom'\n        )\n\n    plt.title(f'F1-Score Comparison for {aspect_name}')\n    plt.ylabel('Micro F1-Score')\n    plt.ylim(0, max(final_scores) + 0.1 if len(final_scores) > 0 else 1.0)\n    plt.grid(axis='y', linestyle='--', alpha=0.7)\n    plt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-23T10:08:18.733700Z","iopub.execute_input":"2026-02-23T10:08:18.733983Z","iopub.status.idle":"2026-02-23T10:08:38.014148Z","shell.execute_reply.started":"2026-02-23T10:08:18.733958Z","shell.execute_reply":"2026-02-23T10:08:38.012206Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"    \n    \n    # def get_best_weights_intersection(all_val_preds_dict, all_val_targets_dict, common_ids, fixed_thresh=0.1):\n    #     \"\"\"\n    #     Optimizes weights for proteins present in ALL models (Intersection).\n    #     \"\"\"\n    #     best_f1 = 0\n    #     best_weights = (0, 0, 0)\n        \n    #     # Define search space\n    #     space = np.linspace(0, 1, 11) \n    #     # Convert set to list to ensure consistent indexing\n    #     common_ids_list = list(common_ids)\n        \n    #     print(f\"Searching intersection weights (N={len(common_ids_list)}) with threshold: {fixed_thresh}\")\n    \n    #     for w1, w2 in product(space, space):\n    #         w3 = 1.0 - w1 - w2\n    #         if w3 < -1e-9: continue\n    #         w3 = max(0, w3)\n            \n    #         all_preds = []\n    #         all_targets = []\n            \n    #         for pid in common_ids_list:\n    #             # Simple weighted average since all 3 models are guaranteed to exist\n    #             avg_prob = (w1 * all_val_preds_dict[1][pid] + \n    #                         w2 * all_val_preds_dict[2][pid] + \n    #                         w3 * all_val_preds_dict[3][pid])\n                \n    #             all_preds.append(avg_prob)\n    #             all_targets.append(all_val_targets_dict[pid])\n                \n    #         # Evaluate F1 using the FIXED threshold\n    #         y_true = np.array(all_targets)\n    #         y_pred = (np.array(all_preds) > fixed_thresh).astype(int)\n    #         current_f1 = f1_score(y_true, y_pred, average='micro')\n            \n    #         if current_f1 > best_f1:\n    #             best_f1 = current_f1\n    #             best_weights = (w1, w2, w3)\n                \n    #     return best_weights, best_f1\n    \n    # # 1. FIND THE INTERSECTION (Common to all 3)\n    # common_ids = set(all_val_preds_dict[1].keys()) & \\\n    #              set(all_val_preds_dict[2].keys()) & \\\n    #              set(all_val_preds_dict[3].keys())\n    \n    # # 2. RUN SEARCH WITH FIXED THRESHOLD 0.1\n    # target_t = 0.1\n    # best_w, best_score = get_best_weights_intersection(\n    #     all_val_preds_dict, \n    #     all_val_targets_dict, \n    #     common_ids, \n    #     fixed_thresh=target_t\n    # )\n    \n    # opt_w1, opt_w2, opt_w3 = best_w\n    \n    # # 3. PRINT RESULTS\n    # print(\"-\" * 30)\n    # print(f\"✅ Optimal Intersection Weights (Threshold {target_t}):\")\n    # print(f\"w1: {opt_w1:.2f}, w2: {opt_w2:.2f}, w3: {opt_w3:.2f}\")\n    # print(\"-\" * 30)\n    # print(f\"🌟 MAXIMIZED INTERSECTION F1: {best_score:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-23T10:08:38.015011Z","iopub.status.idle":"2026-02-23T10:08:38.015442Z","shell.execute_reply.started":"2026-02-23T10:08:38.015234Z","shell.execute_reply":"2026-02-23T10:08:38.015256Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# # Create dictionaries to store predictions linked to their Protein IDs\n# # This is the secret to ensembling different sized datasets!\n# all_val_preds_dict = {1: {}, 2: {}, 3: {}} \n# all_val_targets_dict = {} # Targets will be the same for a specific ID, so we just need one dict\n\n# for id, (X_train, train_ids) in enumerate(datasets, 1):\n#     print(f\"\\n--- Training Model {id} ---\")\n    \n#     # ... [Your existing clean_id and y_train generation code stays the same] ...\n\n#     # Function to turn \"sp|A0A0C5B5G6|MOTSC_HUMAN\" into \"A0A0C5B5G6\"\n#     def clean_id(long_id):\n#         if '|' in long_id:\n#             return long_id.split('|')[1]\n#         return long_id\n    \n#     # Apply it to your training list\n#     cleaned_train_ids = [clean_id(id) for id in train_ids]\n    \n#     # Now they match the test set format!\n#     print(f\"Old: {train_ids[0]}\")\n#     print(f\"New: {cleaned_train_ids[0]}\")\n    \n#     # 4. Build the y_train matrix\n#     y_train = np.zeros((len(cleaned_train_ids), num_labels), dtype=np.float32)\n    \n#     for i, p_id in enumerate(cleaned_train_ids):\n#         if p_id in protein_to_terms:\n#             for term in protein_to_terms[p_id]:\n#                 y_train[i, term_to_idx[term]] = 1.0\n\n# # ... (After cleaning your IDs and before building y_train) ...\n\n#     # 1. Identify which indices actually have labels in your 500 terms\n#     valid_indices = [i for i, p_id in enumerate(cleaned_train_ids) if p_id in protein_to_terms]\n    \n#     # 2. Filter EVERYTHING to only include those labeled proteins\n#     X_train = X_train[valid_indices]\n#     cleaned_train_ids = [cleaned_train_ids[i] for i in valid_indices]\n    \n#     print(f\"Dataset {id} Filtered: Keeping {len(X_train)} labeled proteins.\")\n\n#     # 3. Build the y_train matrix (Now it's perfectly sized!)\n#     y_train = np.zeros((len(cleaned_train_ids), num_labels), dtype=np.float32)\n#     for i, p_id in enumerate(cleaned_train_ids):\n#         for term in protein_to_terms[p_id]:\n#             y_train[i, term_to_idx[term]] = 1.0\n\n#     # ... (Rest of your split and training code) ...\n\n#     import torch\n#     import torch.nn as nn\n#     from torch.utils.data import DataLoader, TensorDataset\n#     from sklearn.model_selection import train_test_split\n#     ##\n    \n#     X_tensor = torch.tensor(X_train, dtype=torch.float32)\n#     y_tensor = torch.tensor(y_train, dtype=torch.float32)\n    \n#     # ⚠️ FIX 1: Split EVERY dataset so dimensions match the model perfectly.\n#     # We also split the 'cleaned_train_ids' so we know exactly WHICH proteins are in the validation set!\n#     X_tr, X_val, y_tr, y_val, id_tr, id_val = train_test_split(\n#         X_tensor, y_tensor, cleaned_train_ids, test_size=0.1, random_state=42\n#     )\n    \n#     train_loader = DataLoader(TensorDataset(X_tr, y_tr), batch_size=128, shuffle=True)\n#     val_loader = DataLoader(TensorDataset(X_val, y_val), batch_size=128, shuffle=False)\n\n#     # 4. Define MLP Architecture for 500 labels\n#     class ProteinMLP(nn.Module):\n#         def __init__(self, input_dim):\n#             super(ProteinMLP, self).__init__()\n#             self.main = nn.Sequential(\n#                 nn.Linear(input_dim, 1024),\n#                 nn.BatchNorm1d(1024),\n#                 nn.ReLU(),\n#                 nn.Dropout(0.3),\n                \n#                 nn.Linear(1024, 512),\n#                 nn.BatchNorm1d(512),\n#                 nn.ReLU(),\n#                 nn.Dropout(0.2),\n                \n#                 nn.Linear(512, num_labels) \n#             )\n            \n#         def forward(self, x):\n#             return self.main(x)\n    \n#     # 5. Initialize Model on GPU\n#     device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n#     model = ProteinMLP(input_dim=X_train.shape[1]).to(device)\n    \n#     criterion = nn.BCEWithLogitsLoss()\n#     optimizer = torch.optim.Adam(model.parameters(), lr=0.001)\n#     # ... [Your existing Epoch Training loop goes here] ...\n#     # 6. Training Loop\n#     print(f\"Starting training on {device}...\")\n#     for epoch in range(15): # Run for 15 epochs\n#         model.train()\n#         total_train_loss = 0\n#         for batch_X, batch_y in train_loader:\n#             batch_X, batch_y = batch_X.to(device), batch_y.to(device)\n            \n#             optimizer.zero_grad()\n#             outputs = model(batch_X)\n#             loss = criterion(outputs, batch_y)\n#             loss.backward()\n#             optimizer.step()\n#             total_train_loss += loss.item()\n        \n#         # Validation check\n#         model.eval()\n#         total_val_loss = 0\n#         with torch.no_grad():\n#             for vX, vy in val_loader:\n#                 vX, vy = vX.to(device), vy.to(device)\n#                 v_out = model(vX)\n#                 total_val_loss += criterion(v_out, vy).item()\n                \n#         print(f\"Epoch {epoch+1} | Train Loss: {total_train_loss/len(train_loader):.4f} | Val Loss: {total_val_loss/len(val_loader):.4f}\")\n    \n    \n#     from sklearn.metrics import f1_score\n#     import numpy as np\n#     ###\n\n#     # --- VALIDATION AND SCORING (Outside the epoch loop, or at the last epoch) ---\n#     model.eval()\n#     epoch_preds = []\n#     epoch_targets = []\n    \n#     with torch.no_grad():\n#         for vX, vy in val_loader:\n#             vX, vy = vX.to(device), vy.to(device)\n#             v_out = model(vX)\n#             probs = torch.sigmoid(v_out)\n            \n#             # GO True Path Propagation\n#             for child_idx, parent_idx in child_parent_pairs:\n#                 probs[:, parent_idx] = torch.max(probs[:, parent_idx], probs[:, child_idx])\n                \n#             binary_preds = (probs > 0.5).float()\n            \n#             epoch_preds.append(binary_preds.cpu().numpy())\n#             epoch_targets.append(vy.cpu().numpy())\n            \n#     # Concatenate for this model\n#     full_preds = np.vstack(epoch_preds)\n#     full_targets = np.vstack(epoch_targets)\n    \n#     # ⚠️ FIX 2: Save the predictions mapped to their Protein ID\n#     # This allows us to find the overlapping proteins later\n#     for i, protein_id in enumerate(id_val):\n#         all_val_preds_dict[id][protein_id] = full_preds[i]\n#         all_val_targets_dict[protein_id] = full_targets[i] # Save true labels\n        \n#     # Calculate independent F1\n#     val_f1 = f1_score(full_targets, full_preds, average='micro')\n#     print(f\"Model {id} Final Val F1-Score: {val_f1:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-23T10:08:38.017465Z","iopub.status.idle":"2026-02-23T10:08:38.017963Z","shell.execute_reply.started":"2026-02-23T10:08:38.017721Z","shell.execute_reply":"2026-02-23T10:08:38.017750Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# #this is fucking red flag that model3 which got the most data to train to has batshit low accuracy than those model1 & model2 which is not expected\n# # Before training Model 3, check how many labels were actually found\n# found_labels = 0\n# for p_id in cleaned_train_ids:\n#     if p_id in protein_to_terms:\n#         found_labels += 1\n\n# print(f\"Dataset 3: Found labels for {found_labels} out of {len(cleaned_train_ids)} proteins.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-23T10:08:38.021145Z","iopub.status.idle":"2026-02-23T10:08:38.021617Z","shell.execute_reply.started":"2026-02-23T10:08:38.021460Z","shell.execute_reply":"2026-02-23T10:08:38.021481Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# # 1. Find the common Protein IDs present in all 3 validation sets\n# common_ids = set(all_val_preds_dict[1].keys()) & set(all_val_preds_dict[2].keys()) & set(all_val_preds_dict[3].keys())\n# print(f\"\\nFound {len(common_ids)} proteins common to all validation sets for ensembling.\")\n\n# ensemble_preds = []\n# ensemble_targets = []\n\n# # Give Model 3 more weight because it has the highest F1\n# # w1, w2, w3 = 0.01, 0.01, 0.98\n# w1, w2, w3 = 0.2, 0.2, 0.6\n# # w1, w2, w3 = 0.3, 0.3, 0.4\n\n# for pid in common_ids:\n#     available_preds = []\n#     weights = []\n    \n#     if pid in all_val_preds_dict[1]:\n#         available_preds.append(all_val_preds_dict[1][pid])\n#         weights.append(w1)\n#     if pid in all_val_preds_dict[2]:\n#         available_preds.append(all_val_preds_dict[2][pid])\n#         weights.append(w2)\n#     if pid in all_val_preds_dict[3]:\n#         available_preds.append(all_val_preds_dict[3][pid])\n#         weights.append(w3)\n        \n#     # Weighted Average\n#     avg_pred = np.average(available_preds, axis=0, weights=weights)\n#     final_binary = (avg_pred > 0.1).astype(float)\n    \n#     ensemble_preds.append(final_binary)\n#     ensemble_targets.append(all_val_targets_dict[pid])\n\n\n# # 3. Calculate Ensemble F1\n# ensemble_f1 = f1_score(np.array(ensemble_targets), np.array(ensemble_preds), average='micro')\n# print(f\"🌟 ENSEMBLE F1-Score: {ensemble_f1:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-23T10:08:38.023457Z","iopub.status.idle":"2026-02-23T10:08:38.023872Z","shell.execute_reply.started":"2026-02-23T10:08:38.023674Z","shell.execute_reply":"2026-02-23T10:08:38.023719Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n\n# def get_best_weights_intersection(all_val_preds_dict, all_val_targets_dict, common_ids, fixed_thresh=0.1):\n#     \"\"\"\n#     Optimizes weights for proteins present in ALL models (Intersection).\n#     \"\"\"\n#     best_f1 = 0\n#     best_weights = (0, 0, 0)\n    \n#     # Define search space\n#     space = np.linspace(0, 1, 11) \n#     # Convert set to list to ensure consistent indexing\n#     common_ids_list = list(common_ids)\n    \n#     print(f\"Searching intersection weights (N={len(common_ids_list)}) with threshold: {fixed_thresh}\")\n\n#     for w1, w2 in product(space, space):\n#         w3 = 1.0 - w1 - w2\n#         if w3 < -1e-9: continue\n#         w3 = max(0, w3)\n        \n#         all_preds = []\n#         all_targets = []\n        \n#         for pid in common_ids_list:\n#             # Simple weighted average since all 3 models are guaranteed to exist\n#             avg_prob = (w1 * all_val_preds_dict[1][pid] + \n#                         w2 * all_val_preds_dict[2][pid] + \n#                         w3 * all_val_preds_dict[3][pid])\n            \n#             all_preds.append(avg_prob)\n#             all_targets.append(all_val_targets_dict[pid])\n            \n#         # Evaluate F1 using the FIXED threshold\n#         y_true = np.array(all_targets)\n#         y_pred = (np.array(all_preds) > fixed_thresh).astype(int)\n#         current_f1 = f1_score(y_true, y_pred, average='micro')\n        \n#         if current_f1 > best_f1:\n#             best_f1 = current_f1\n#             best_weights = (w1, w2, w3)\n            \n#     return best_weights, best_f1\n\n# # 1. FIND THE INTERSECTION (Common to all 3)\n# common_ids = set(all_val_preds_dict[1].keys()) & \\\n#              set(all_val_preds_dict[2].keys()) & \\\n#              set(all_val_preds_dict[3].keys())\n\n# # 2. RUN SEARCH WITH FIXED THRESHOLD 0.1\n# target_t = 0.1\n# best_w, best_score = get_best_weights_intersection(\n#     all_val_preds_dict, \n#     all_val_targets_dict, \n#     common_ids, \n#     fixed_thresh=target_t\n# )\n\n# opt_w1, opt_w2, opt_w3 = best_w\n\n# # 3. PRINT RESULTS\n# print(\"-\" * 30)\n# print(f\"✅ Optimal Intersection Weights (Threshold {target_t}):\")\n# print(f\"w1: {opt_w1:.2f}, w2: {opt_w2:.2f}, w3: {opt_w3:.2f}\")\n# print(\"-\" * 30)\n# print(f\"🌟 MAXIMIZED INTERSECTION F1: {best_score:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-23T10:08:38.025841Z","iopub.status.idle":"2026-02-23T10:08:38.026222Z","shell.execute_reply.started":"2026-02-23T10:08:38.026066Z","shell.execute_reply":"2026-02-23T10:08:38.026092Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# import numpy as np\n# from sklearn.metrics import f1_score\n\n# # 1. Find ALL unique Protein IDs across any of the 3 validation sets (Union `|` instead of Intersection `&`)\n# all_unique_ids = set(all_val_preds_dict[1].keys()) | \\\n#                  set(all_val_preds_dict[2].keys()) | \\\n#                  set(all_val_preds_dict[3].keys())\n                 \n# print(f\"\\nFound {len(all_unique_ids)} total unique proteins for the comprehensive ensemble.\")\n\n# ensemble_preds = []\n# ensemble_targets = []\n\n# # Give Model 3 more weight because it has the highest F1\n# w1, w2, w3 = 0.0001, 0.0001, 0.9998\n\n# for pid in all_unique_ids:\n#     available_preds = []\n#     weights = []\n    \n#     if pid in all_val_preds_dict[1]:\n#         available_preds.append(all_val_preds_dict[1][pid])\n#         weights.append(w1)\n#     if pid in all_val_preds_dict[2]:\n#         available_preds.append(all_val_preds_dict[2][pid])\n#         weights.append(w2)\n#     if pid in all_val_preds_dict[3]:\n#         available_preds.append(all_val_preds_dict[3][pid])\n#         weights.append(w3)\n        \n#     # Weighted Average\n#     avg_pred = np.average(available_preds, axis=0, weights=weights)\n#     final_binary = (avg_pred > 0.1).astype(float)\n    \n#     ensemble_preds.append(final_binary)\n#     ensemble_targets.append(all_val_targets_dict[pid])\n\n# # 3. Calculate Final Comprehensive Ensemble F1\n# ensemble_f1 = f1_score(np.array(ensemble_targets), np.array(ensemble_preds), average='micro')\n# print(f\"🌟 COMPREHENSIVE ENSEMBLE F1-Score: {ensemble_f1:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-23T10:08:38.028133Z","iopub.status.idle":"2026-02-23T10:08:38.028537Z","shell.execute_reply.started":"2026-02-23T10:08:38.028390Z","shell.execute_reply":"2026-02-23T10:08:38.028409Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# import numpy as np\n# from sklearn.metrics import f1_score\n\n# # Set weights manually\n# # w1, w2, w3 = 0.0, 0.1, 0.9\n# w1, w2, w3 = 0.2, 0.2, 0.6\n# threshold = 0.1\n\n# ensemble_preds = []\n# ensemble_targets = []\n\n# for pid in all_unique_ids:\n#     available_preds = []\n#     weights = []\n    \n#     if pid in all_val_preds_dict[1]:\n#         available_preds.append(all_val_preds_dict[1][pid])\n#         weights.append(w1)\n#     if pid in all_val_preds_dict[2]:\n#         available_preds.append(all_val_preds_dict[2][pid])\n#         weights.append(w2)\n#     if pid in all_val_preds_dict[3]:\n#         available_preds.append(all_val_preds_dict[3][pid])\n#         weights.append(w3)\n        \n#     # --- FIX START ---\n#     weight_sum = sum(weights)\n    \n#     if weight_sum > 0:\n#         # Only average if we have a non-zero weight contribution\n#         avg_pred = np.average(available_preds, axis=0, weights=weights)\n#         final_binary = (avg_pred > threshold).astype(float)\n#     else:\n#         # If the only models that saw this protein have 0 weight, \n#         # predict zero (no labels)\n#         final_binary = np.zeros_like(all_val_targets_dict[pid])\n#     # --- FIX END ---\n    \n#     ensemble_preds.append(final_binary)\n#     ensemble_targets.append(all_val_targets_dict[pid])\n\n# # Calculate\n# ensemble_f1 = f1_score(np.array(ensemble_targets), np.array(ensemble_preds), average='micro')\n# print(f\"🌟 MANUAL CHECK F1-Score (w={w1},{w2},{w3}): {ensemble_f1:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-23T10:08:38.030706Z","iopub.status.idle":"2026-02-23T10:08:38.031139Z","shell.execute_reply.started":"2026-02-23T10:08:38.030973Z","shell.execute_reply":"2026-02-23T10:08:38.030992Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# import numpy as np\n# from sklearn.metrics import f1_score\n# from itertools import product\n\n# def get_best_weights_fixed_threshold(all_val_preds_dict, all_val_targets_dict, all_ids, fixed_thresh=0.1):\n#     best_f1 = 0\n#     best_weights = (0, 0, 0)\n    \n#     # Define search space (increments of 0.1)\n#     space = np.linspace(0, 1, 11) \n#     sorted_ids = sorted(list(all_ids))\n    \n#     print(f\"Searching for weights with fixed threshold: {fixed_thresh}...\")\n\n#     for w1, w2 in product(space, space):\n#         w3 = 1.0 - w1 - w2\n#         if w3 < -1e-9: continue\n#         w3 = max(0, w3)\n        \n#         all_preds = []\n#         all_targets = []\n        \n#         for pid in sorted_ids:\n#             available_preds = []\n#             available_weights = []\n            \n#             if pid in all_val_preds_dict[1]:\n#                 available_preds.append(all_val_preds_dict[1][pid])\n#                 available_weights.append(w1)\n#             if pid in all_val_preds_dict[2]:\n#                 available_preds.append(all_val_preds_dict[2][pid])\n#                 available_weights.append(w2)\n#             if pid in all_val_preds_dict[3]:\n#                 available_preds.append(all_val_preds_dict[3][pid])\n#                 available_weights.append(w3)\n            \n#             weight_sum = sum(available_weights)\n            \n#             if weight_sum > 0:\n#                 # Weighted average of available models\n#                 avg = np.average(available_preds, axis=0, weights=available_weights)\n#                 all_preds.append(avg)\n#             else:\n#                 # No data for this protein, predict zero\n#                 all_preds.append(np.zeros_like(all_val_targets_dict[pid]))\n            \n#             all_targets.append(all_val_targets_dict[pid])\n            \n#         # Calculate F1\n#         current_f1 = f1_score(np.array(all_targets), (np.array(all_preds) > fixed_thresh).astype(int), average='micro')\n        \n#         if current_f1 > best_f1:\n#             best_f1 = current_f1\n#             best_weights = (w1, w2, w3)\n            \n#     # RETURN BOTH weights and the F1 score found\n#     return best_weights, best_f1\n\n# # 1. FIND THE UNION\n# all_unique_ids = set(all_val_preds_dict[1].keys()) | \\\n#                  set(all_val_preds_dict[2].keys()) | \\\n#                  set(all_val_preds_dict[3].keys())\n\n# # 2. RUN SEARCH\n# target_t = 0.1\n# # Unpack both the weights and the best score\n# best_w, best_score = get_best_weights_fixed_threshold(\n#     all_val_preds_dict, \n#     all_val_targets_dict, \n#     all_unique_ids, \n#     fixed_thresh=target_t\n# )\n\n# opt_w1, opt_w2, opt_w3 = best_w\n\n# # 3. PRINT RESULTS\n# print(\"-\" * 30)\n# print(f\"✅ Optimal Weights for Threshold {target_t}:\")\n# print(f\"w1: {opt_w1:.2f}\")\n# print(f\"w2: {opt_w2:.2f}\")\n# print(f\"w3: {opt_w3:.2f}\")\n# print(\"-\" * 30)\n# print(f\"🌟 MAXIMIZED UNION ENSEMBLE F1: {best_score:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-23T10:08:38.032357Z","iopub.status.idle":"2026-02-23T10:08:38.033425Z","shell.execute_reply.started":"2026-02-23T10:08:38.033218Z","shell.execute_reply":"2026-02-23T10:08:38.033238Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# import numpy as np\n# import pandas as pd\n# import torch\n\n# # 1. Prepare for Prediction\n# model.eval()  # Set model to evaluation mode\n# device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\n# # Convert X_test to a tensor\n# X_test_tensor = torch.tensor(X_test, dtype=torch.float32).to(device)\n\n# # 2. Run Inference in Batches (to avoid memory crashes)\n# predictions = []\n# batch_size = 1000\n\n# print(\"Generating predictions...\")\n# with torch.no_grad():\n#     for i in range(0, len(X_test_tensor), batch_size):\n#         batch = X_test_tensor[i : i + batch_size]\n#         logits = model(batch)\n#         probs = torch.sigmoid(logits) # Convert raw output to 0-1 probability\n#         predictions.append(probs.cpu().numpy())\n\n# # Combine all batches into one big matrix\n# all_preds = np.vstack(predictions)\n# print(f\"Prediction matrix shape: {all_preds.shape}\") \n# # Should be (Number of Test Proteins, 500)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-23T10:08:38.034885Z","iopub.status.idle":"2026-02-23T10:08:38.035158Z","shell.execute_reply.started":"2026-02-23T10:08:38.035029Z","shell.execute_reply":"2026-02-23T10:08:38.035044Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# # 1. Create lists to hold the data\n# sub_ids = []\n# sub_terms = []\n# sub_probs = []\n\n# print(\"Formatting submission data (this may take a minute)...\")\n\n# # 'test_ids' are the IDs you loaded from the test embeddings file\n# # 'top_terms' is the list of 500 GO terms we used for training\n# for i, protein_id in enumerate(test_ids):\n#     # Get the 500 probabilities for this protein\n#     row_probs = all_preds[i]\n    \n#     # Filter for probabilities > 0.01\n#     # We only want to submit functions the model is somewhat confident about\n#     for j, prob in enumerate(row_probs):\n#         if prob > 0.01:\n#             sub_ids.append(protein_id)\n#             sub_terms.append(top_terms[j])\n#             sub_probs.append(round(float(prob), 3))\n\n# # 2. Create the DataFrame\n# submission_df = pd.DataFrame({\n#     'Protein_ID': sub_ids,\n#     'GO_Term': sub_terms,\n#     'Probability': sub_probs\n# })\n\n# # 3. Save as a .tsv file (Kaggle CAFA requirement)\n# # The competition usually expects no header and tab separation\n# submission_df.to_csv('submission.tsv', sep='\\t', index=False, header=False)\n\n# print(f\"Done! Submission file created with {len(submission_df)} rows.\")\n# print(\"You can now find 'submission.tsv' in your Kaggle output folder.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-23T10:08:38.036712Z","iopub.status.idle":"2026-02-23T10:08:38.037311Z","shell.execute_reply.started":"2026-02-23T10:08:38.037119Z","shell.execute_reply":"2026-02-23T10:08:38.037151Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}