{"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":"nvidiaTeslaT4","dataSources":[{"sourceId":116062,"databundleVersionId":14875579,"sourceType":"competition"},{"sourceId":14434464,"sourceType":"datasetVersion","datasetId":9219898},{"sourceId":14442471,"sourceType":"datasetVersion","datasetId":9225197}],"dockerImageVersionId":31236,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"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-01-09T09:29:52.403754Z","iopub.execute_input":"2026-01-09T09:29:52.404446Z","iopub.status.idle":"2026-01-09T09:29:52.710785Z","shell.execute_reply.started":"2026-01-09T09:29:52.404417Z","shell.execute_reply":"2026-01-09T09:29:52.710017Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!pip3 install /kaggle/input/biopython/biopython_wheels/biopython-1.86-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl --no-deps","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-09T09:30:44.204198Z","iopub.execute_input":"2026-01-09T09:30:44.205048Z","iopub.status.idle":"2026-01-09T09:30:48.987021Z","shell.execute_reply.started":"2026-01-09T09:30:44.205018Z","shell.execute_reply":"2026-01-09T09:30:48.986295Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!pip3 install /kaggle/input/obonet-package/obonet/obonet-1.1.1-py3-none-any.whl --no-deps","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-09T09:30:48.988716Z","iopub.execute_input":"2026-01-09T09:30:48.988951Z","iopub.status.idle":"2026-01-09T09:30:50.284964Z","shell.execute_reply.started":"2026-01-09T09:30:48.988925Z","shell.execute_reply":"2026-01-09T09:30:50.284239Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!pip3 install /kaggle/input/obonet-package/obonet/networkx-3.6.1-py3-none-any.whl --no-deps","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-09T09:30:50.286243Z","iopub.execute_input":"2026-01-09T09:30:50.286563Z","iopub.status.idle":"2026-01-09T09:30:53.692939Z","shell.execute_reply.started":"2026-01-09T09:30:50.286526Z","shell.execute_reply":"2026-01-09T09:30:53.692069Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nfrom Bio import SeqIO\n\n# Paths\nFASTA_PATH = '/kaggle/input/cafa-6-protein-function-prediction/Train/train_sequences.fasta'\n\n# Reading sequences\nsequences = []\nids = []\n\nfor record in SeqIO.parse(FASTA_PATH, \"fasta\"):\n    ids.append(record.id)\n    sequences.append(str(record.seq))\n\n# Store in DataFrame for easier manipulation\ndf_train_seq = pd.DataFrame({\n    'EntryID': ids,\n    'sequence': sequences\n})\n\nprint(f\"Total sequences loaded: {len(df_train_seq)}\")\ndf_train_seq.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-09T09:30:59.372049Z","iopub.execute_input":"2026-01-09T09:30:59.372700Z","iopub.status.idle":"2026-01-09T09:31:00.450880Z","shell.execute_reply.started":"2026-01-09T09:30:59.372665Z","shell.execute_reply":"2026-01-09T09:31:00.450162Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Path to training terms\nTERMS_PATH = '/kaggle/input/cafa-6-protein-function-prediction/Train/train_terms.tsv'\n\n# Load the labels\ndf_train_terms = pd.read_csv(TERMS_PATH, sep=\"\\t\")\n\nprint(f\"Total annotations loaded: {len(df_train_terms)}\")\nprint(f\"Unique proteins in terms: {df_train_terms['EntryID'].nunique()}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-09T09:31:02.502108Z","iopub.execute_input":"2026-01-09T09:31:02.503012Z","iopub.status.idle":"2026-01-09T09:31:02.839942Z","shell.execute_reply.started":"2026-01-09T09:31:02.502979Z","shell.execute_reply":"2026-01-09T09:31:02.839208Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import obonet\nimport networkx as nx\n\n# Path to Ontology file\nOBO_PATH = '/kaggle/input/cafa-6-protein-function-prediction/Train/go-basic.obo'\n\n# Load the ontology graph\n# Using obonet for the latest syntax compatibility\ngo_graph = obonet.read_obo(OBO_PATH)\n\ndef get_ancestors(graph, term):\n    \"\"\"\n    Retrieves all parent/ancestor terms for a given GO term.\n    \"\"\"\n    if term not in graph:\n        return set()\n    # In obonet, 'descendants' refers to ancestors because of edge direction\n    return nx.descendants(graph, term) | {term}\n\nprint(f\"Ontology graph loaded with {len(go_graph)} nodes.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-09T09:31:03.689842Z","iopub.execute_input":"2026-01-09T09:31:03.690365Z","iopub.status.idle":"2026-01-09T09:31:08.905194Z","shell.execute_reply.started":"2026-01-09T09:31:03.690337Z","shell.execute_reply":"2026-01-09T09:31:08.904485Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nfrom transformers import AutoTokenizer, EsmModel\n\n# Define device and model name\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nmodel_ckpt = \"facebook/esm2_t33_650M_UR50D\"\n\n# 1. Load Tokenizer\ntokenizer = AutoTokenizer.from_pretrained(model_ckpt)\n\n# 2. Load Model using 'dtype' instead of deprecated 'torch_dtype'\n# Using float16 to save VRAM on T4 GPUs\nmodel = EsmModel.from_pretrained(model_ckpt, dtype=torch.float16)\n\n# 3. Setup Multi-GPU (DataParallel)\nif torch.cuda.device_count() > 1:\n    print(f\"Using {torch.cuda.device_count()} GPUs for parallel inference.\")\n    model = nn.DataParallel(model)\n\nmodel = model.to(device)\nmodel.eval() # Set to evaluation mode","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-09T09:31:08.906566Z","iopub.execute_input":"2026-01-09T09:31:08.906972Z","iopub.status.idle":"2026-01-09T09:32:01.041313Z","shell.execute_reply.started":"2026-01-09T09:31:08.906948Z","shell.execute_reply":"2026-01-09T09:32:01.040700Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from tqdm.auto import tqdm\nimport numpy as np\n\ndef get_embeddings(sequences, batch_size=16, max_len=1024):\n    \"\"\"\n    Converts protein sequences into numerical embeddings using ESM-2.\n    \"\"\"\n    all_embeddings = []\n    \n    # Process in batches to avoid Out of Memory (OOM) errors\n    for i in tqdm(range(0, len(sequences), batch_size)):\n        batch_seqs = sequences[i : i + batch_size]\n        \n        # Tokenize and move to GPU\n        inputs = tokenizer(\n            batch_seqs, \n            return_tensors=\"pt\", \n            padding=True, \n            truncation=True, \n            max_length=max_len\n        ).to(device)\n        \n        with torch.no_grad():\n            # Use Mixed Precision (AMP) for faster inference on T4\n            with torch.amp.autocast('cuda'):\n                outputs = model(**inputs)\n        \n        # Mean Pooling: Calculate the average of all residue (amino acid) vectors\n        # Shape change: [batch, sequence_len, 1280] -> [batch, 1280]\n        batch_embeddings = outputs.last_hidden_state.mean(dim=1).cpu().numpy()\n        all_embeddings.append(batch_embeddings)\n        \n    return np.vstack(all_embeddings)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-09T09:32:01.042565Z","iopub.execute_input":"2026-01-09T09:32:01.043206Z","iopub.status.idle":"2026-01-09T09:32:01.050606Z","shell.execute_reply.started":"2026-01-09T09:32:01.043178Z","shell.execute_reply":"2026-01-09T09:32:01.049999Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Assuming df_train_seq is already defined in your first cell\nsequences_list = df_train_seq['sequence'].tolist()\n\nprint(f\"Starting extraction for {len(sequences_list)} sequences...\")\n\n# Using batch_size=16 as a starting point for T4 x2\n# You can increase this if you see low GPU utilization\ntrain_embeddings = get_embeddings(sequences_list, batch_size=16)\n\n# Save the result to disk\nnp.save('train_embeddings_esm2_650M.npy', train_embeddings)\n\nprint(f\"Extraction complete. Final shape: {train_embeddings.shape}\")\nprint(\"Embeddings saved to 'train_embeddings_esm2_650M.npy'\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-09T09:33:44.542827Z","iopub.execute_input":"2026-01-09T09:33:44.543159Z","iopub.status.idle":"2026-01-09T11:06:11.992991Z","shell.execute_reply.started":"2026-01-09T09:33:44.543132Z","shell.execute_reply":"2026-01-09T11:06:11.992325Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport networkx as nx\nimport obonet\nfrom tqdm.auto import tqdm\n\n# 1. Load Ontology\nOBO_PATH = '/kaggle/input/cafa-6-protein-function-prediction/Train/go-basic.obo'\ngo_graph = obonet.read_obo(OBO_PATH)\n\ndef get_ancestors(graph, term):\n    if term not in graph:\n        return set()\n    return nx.descendants(graph, term) | {term}\n\n# 2. Load Terms\nTERMS_PATH = '/kaggle/input/cafa-6-protein-function-prediction/Train/train_terms.tsv'\ndf_train_terms = pd.read_csv(TERMS_PATH, sep=\"\\t\")\n\n# 3. Select Top Terms (e.g., top 1500) to keep the model efficient\n# CAFA-6 has thousands of terms, but 1500 covers the most important ones\nTOP_N = 1500\ntop_terms = df_train_terms['term'].value_counts().head(TOP_N).index.tolist()\ntop_terms_set = set(top_terms)\n\n# 4. Propagate\nprotein_to_terms = df_train_terms.groupby('EntryID')['term'].apply(set).to_dict()\npropagated_labels = {}\n\nprint(\"Propagating labels...\")\nfor protein_id, terms in tqdm(protein_to_terms.items()):\n    all_anc = set()\n    for t in terms:\n        all_anc.update(get_ancestors(go_graph, t))\n    # Filter only if in our Top N list\n    propagated_labels[protein_id] = all_anc.intersection(top_terms_set)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-09T11:22:31.839777Z","iopub.execute_input":"2026-01-09T11:22:31.840467Z","iopub.status.idle":"2026-01-09T11:22:50.184416Z","shell.execute_reply.started":"2026-01-09T11:22:31.840436Z","shell.execute_reply":"2026-01-09T11:22:50.183568Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Cek apakah IDs dari FASTA ada yang cocok sama EntryID di df_train_terms\nfasta_ids_set = set(ids) # 'ids' adalah list dari SeqIO parse tadi\nterms_ids_set = set(df_train_terms['EntryID'].unique())\n\ncommon_ids = fasta_ids_set.intersection(terms_ids_set)\nprint(f\"IDs in FASTA: {len(fasta_ids_set)}\")\nprint(f\"IDs in Terms file: {len(terms_ids_set)}\")\nprint(f\"Common IDs (Yang cocok): {len(common_ids)}\")\n\nif len(common_ids) == 0:\n    print(\"ERROR: Gak ada ID yang cocok! Cek format ID di FASTA vs TSV.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-09T11:26:53.869137Z","iopub.execute_input":"2026-01-09T11:26:53.869471Z","iopub.status.idle":"2026-01-09T11:26:53.921626Z","shell.execute_reply.started":"2026-01-09T11:26:53.869444Z","shell.execute_reply":"2026-01-09T11:26:53.920836Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Lihat 5 contoh ID dari FASTA\nprint(\"Contoh ID dari FASTA (Biopython):\")\nprint(ids[:5])\n\n# Lihat 5 contoh ID dari Terms file\nprint(\"\\nContoh ID dari Terms file:\")\nprint(df_train_terms['EntryID'].unique()[:5])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-09T11:27:34.412658Z","iopub.execute_input":"2026-01-09T11:27:34.413377Z","iopub.status.idle":"2026-01-09T11:27:34.449039Z","shell.execute_reply.started":"2026-01-09T11:27:34.413347Z","shell.execute_reply":"2026-01-09T11:27:34.448459Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def clean_id(fasta_id):\n    \"\"\"\n    Cleans FASTA headers like 'sp|P12345|ABC' into 'P12345'.\n    Adjust this logic based on the results from Cell 1.\n    \"\"\"\n    if \"|\" in fasta_id:\n        return fasta_id.split(\"|\")[1]\n    return fasta_id\n\n# 1. Bersihkan semua ID dari FASTA\nclean_ids = [clean_id(fid) for fid in ids]\n\n# 2. Cek ulang kecocokan\nfasta_ids_set = set(clean_ids)\nterms_ids_set = set(df_train_terms['EntryID'].unique())\ncommon_ids = fasta_ids_set.intersection(terms_ids_set)\n\nprint(f\"Total IDs in FASTA: {len(fasta_ids_set)}\")\nprint(f\"Common IDs found: {len(common_ids)}\")\n\nif len(common_ids) > 0:\n    print(\"Sip! ID sudah cocok. Lanjut ke pembuatan Matrix.\")\n    # Update list ids yang asli dengan yang sudah bersih agar sinkron dengan .npy\n    ids = clean_ids \nelse:\n    print(\"Masih gak cocok? Coba post hasil print dari Cell 1 di sini.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-09T11:27:51.157160Z","iopub.execute_input":"2026-01-09T11:27:51.157666Z","iopub.status.idle":"2026-01-09T11:27:51.242855Z","shell.execute_reply.started":"2026-01-09T11:27:51.157636Z","shell.execute_reply":"2026-01-09T11:27:51.242228Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\nfrom tqdm.auto import tqdm\n\n# 1. Setup Top Terms\nTOP_N = 1500\ntop_terms = df_train_terms['term'].value_counts().head(TOP_N).index.tolist()\ntop_terms_set = set(top_terms)\nterm_to_idx = {term: i for i, term in enumerate(top_terms)}\n\n# 2. Filtering & Propagation\n# Hanya proses protein yang ada di FASTA kita\ndf_filtered = df_train_terms[df_train_terms['EntryID'].isin(fasta_ids_set)]\nprotein_to_terms = df_filtered.groupby('EntryID')['term'].apply(set).to_dict()\n\npropagated_labels = {}\nprint(\"Propagating ancestors...\")\nfor pid, terms in tqdm(protein_to_terms.items()):\n    all_anc = set()\n    for t in terms:\n        all_anc.update(get_ancestors(go_graph, t))\n    \n    final_terms = all_anc.intersection(top_terms_set)\n    if final_terms:\n        propagated_labels[pid] = final_terms\n\n# 3. Filling the Matrix (y_train)\n# Matrix ini akan SINKRON dengan urutan 'ids' dan file '.npy'\ny_train = np.zeros((len(ids), TOP_N), dtype=np.float32)\n\nprint(\"Filling y_train matrix...\")\nfor i, pid in enumerate(tqdm(ids)):\n    if pid in propagated_labels:\n        for term in propagated_labels[pid]:\n            y_train[i, term_to_idx[term]] = 1.0\n\nprint(f\"Final y_train shape: {y_train.shape}\")\nprint(f\"Total Positive Labels: {y_train.sum()}\")\nprint(f\"Average Labels per Protein: {y_train.sum() / len(ids):.2f}\")\n\n# Simpan agar tidak kerja dua kali\nnp.save('y_train_1500.npy', y_train)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-09T11:28:04.068925Z","iopub.execute_input":"2026-01-09T11:28:04.069226Z","iopub.status.idle":"2026-01-09T11:28:18.599985Z","shell.execute_reply.started":"2026-01-09T11:28:04.069199Z","shell.execute_reply":"2026-01-09T11:28:18.599325Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import DataLoader, TensorDataset\n\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import DataLoader, TensorDataset\n\nclass ProteinClassifier(nn.Module):\n    def __init__(self, input_dim, output_dim):\n        super(ProteinClassifier, self).__init__()\n        self.network = 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.ReLU(),\n            nn.Dropout(0.2),\n            \n            nn.Linear(512, output_dim) \n            # REMOVED Sigmoid: BCEWithLogitsLoss handles it internally\n        )\n        \n    def forward(self, x):\n        return self.network(x)\n\n# 1. Quick Data Check\npositive_labels = y_train.sum()\nprint(f\"Total positive labels (1s) in y_train: {positive_labels}\")\nif positive_labels == 0:\n    print(\"WARNING: y_train is empty! Check your Label Propagation step.\")\n\n# 2. Setup Tensors\nX_train_tensor = torch.from_numpy(train_embeddings).float()\ny_train_tensor = torch.from_numpy(y_train).float()\n\ndataset = TensorDataset(X_train_tensor, y_train_tensor)\ntrain_loader = DataLoader(dataset, batch_size=128, shuffle=True)\n\n# 3. Initialize Model and Stability-focused Loss\nmodel_class = ProteinClassifier(input_dim=1280, output_dim=TOP_N).to(device)\n\n# BCEWithLogitsLoss is more stable for many labels\ncriterion = nn.BCEWithLogitsLoss() \n\n# Increased LR to 5e-4 for faster initial learning\noptimizer = optim.Adam(model_class.parameters(), lr=5e-4)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-09T11:29:41.630987Z","iopub.execute_input":"2026-01-09T11:29:41.631672Z","iopub.status.idle":"2026-01-09T11:29:41.708840Z","shell.execute_reply.started":"2026-01-09T11:29:41.631642Z","shell.execute_reply":"2026-01-09T11:29:41.708224Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"epochs = 20\n\nprint(\"Starting Revised Training...\")\nfor epoch in range(epochs):\n    model_class.train()\n    total_loss = 0\n    \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_class(batch_X)\n        loss = criterion(outputs, batch_y)\n        \n        loss.backward()\n        optimizer.step()\n        \n        total_loss += loss.item()\n    \n    # Printing with higher precision to see tiny movements\n    avg_loss = total_loss / len(train_loader)\n    print(f\"Epoch {epoch+1:02d}/{epochs} | Loss: {avg_loss:.8f}\")\n\ntorch.save(model_class.state_dict(), 'protein_classifier_v2.pth')\nprint(\"Model Saved as v2!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-09T11:29:48.869908Z","iopub.execute_input":"2026-01-09T11:29:48.870610Z","iopub.status.idle":"2026-01-09T11:30:38.294697Z","shell.execute_reply.started":"2026-01-09T11:29:48.870582Z","shell.execute_reply":"2026-01-09T11:30:38.293986Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nfrom Bio import SeqIO\nfrom tqdm.auto import tqdm\n\n# Path from your image\nTEST_FASTA_PATH = '/kaggle/input/cafa-6-protein-function-prediction/Test/testsuperset.fasta'\n\ntest_ids = []\ntest_sequences = []\n\n# Using the same cleaning logic to ensure ID consistency\ndef clean_id(fasta_id):\n    if \"|\" in fasta_id:\n        return fasta_id.split(\"|\")[1]\n    return fasta_id\n\nfor record in SeqIO.parse(TEST_FASTA_PATH, \"fasta\"):\n    test_ids.append(clean_id(record.id))\n    test_sequences.append(str(record.seq))\n\nprint(f\"Total test sequences loaded: {len(test_ids)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-09T11:32:49.817523Z","iopub.execute_input":"2026-01-09T11:32:49.817835Z","iopub.status.idle":"2026-01-09T11:32:52.439069Z","shell.execute_reply.started":"2026-01-09T11:32:49.817809Z","shell.execute_reply":"2026-01-09T11:32:52.438300Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Reuse the get_embeddings function you defined earlier\nprint(\"Starting ESM-2 inference on test set...\")\n# Using batch_size=16 or 32 depending on your VRAM comfort\ntest_embeddings = get_embeddings(test_sequences, batch_size=32)\n\n# Save test embeddings\nnp.save('test_embeddings_esm2_650M.npy', test_embeddings)\nprint(f\"Test embeddings extracted: {test_embeddings.shape}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-09T11:36:24.474413Z","iopub.execute_input":"2026-01-09T11:36:24.475011Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\n\n# 1. Prediction Mode\nmodel_class.eval()\nX_test_tensor = torch.from_numpy(test_embeddings).float().to(device)\n\nprint(\"Generating predictions...\")\nall_probs = []\npred_batch_size = 256 # Adjust based on VRAM\n\nwith torch.no_grad():\n    for i in range(0, len(X_test_tensor), pred_batch_size):\n        batch_X = X_test_tensor[i : i + pred_batch_size]\n        logits = model_class(batch_X)\n        # Apply sigmoid to convert logits to 0-1 probabilities\n        probs = torch.sigmoid(logits).cpu().numpy()\n        all_probs.append(probs)\n\ntest_preds = np.vstack(all_probs)\n\n# 2. Format to CAFA standard (EntryID, Term, Score)\n# top_terms: must be the exact 1500 terms used during training\nsubmission_list = []\nthreshold = 0.001 # Standard threshold; increase if file size is too large (>500MB)\n\nprint(\"Formatting submission data...\")\nfor i, protein_id in enumerate(test_ids):\n    scores = test_preds[i]\n    # Identify indices meeting the threshold\n    valid_indices = np.where(scores >= threshold)[0]\n    \n    for idx in valid_indices:\n        submission_list.append([\n            protein_id, \n            top_terms[idx], \n            round(float(scores[idx]), 3)\n        ])\n\n# 3. Save as TSV (No Header, Tab Separated) per your sample image\ndf_sub = pd.DataFrame(submission_list)\ndf_sub.to_csv('submission.tsv', sep=\"\\t\", index=False, header=False)\n\nprint(f\"Submission file created with {len(df_sub)} rows.\")","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}