{"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"}],"dockerImageVersionId":31236,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"#  🧬 Protein Function Prediction — CNN Baseline\n## 🎯 Objective\n\nBuild a simple and fast baseline model using one-hot encoded amino acid sequences + Convolutional Neural Network (CNN) for multi-label Gene Ontology classification.\n\n## 📌 Highlights\n\n🔹 Uses one-hot encoding (20 amino acids → 20-dim vector per residue)\n\n🔹 Handles sequences up to 512 AA long\n\n🔹 Lightweight CNN architecture (~15.9M params)\n\n🔹 ROC-AUC & PR-AUC tracked during training\n\n🔹 EarlyStopping + ModelCheckpoint enabled\n\n## 🌈 Model Architecture Overview\n**Stage\tOperation**\n1. Input\tProtein sequence → One-hot (512 × 20)\n2. Block 1\tConv1D(128) → BN → ReLU → MaxPool → Dropout\n3. Block 2\tConv1D(192) → BN → ReLU → MaxPool → Dropout\n4. Block 3\tConv1D(256) → BN → ReLU → MaxPool → Dropout\n5. Dense\tFlatten → Dense(512) → Dropout(0.5)\n6. Output\tsigmoid layer → Predicts NUM_LABELS GO terms\n\n**Designed for speed + baseline benchmarking before using protein language models.**\n\n## 🔥 Training Strategy\n\n* Optimizer: Adam (3e-4)\n\n* Loss: Binary Cross-Entropy\n\n* Metrics:\n  - ✔ ROC-AUC – general separability\n  - ✔ PR-AUC – more important here due to label imbalance\n\n* EarlyStopping based on PR-AUC\n\n* Saves best model → cnn_best_model.h5\n\n# ⚙ Dataset Pipeline (Why TF-Data?)\n\n## ✨ Benefits of using tf.data streaming:\n\nAdvantage\tExplanation :\n* 🚀 Efficient\tNo full dataset loaded in RAM\n* 🔄 Streaming\tSequences encoded on the fly\n* ⚡ GPU optimized\tUses prefetch(AUTOTUNE)\n* 📦 Scalable\tWorks with big FASTA datasets\n\n\n## 🚀 Future Upgrade Options\n\nEnhancement\tExpected Impact:\n* -Class-weighted / focal loss\tBetter handling imbalance\n* -Random cropping / masking\tGeneralization improvement\n* -Transformers (ESM, ProtT5, ProteinBERT)\tHuge boost in performance\n* -Hierarchical GO-aware loss\tResearch-grade accuracy\n\n## 🏁 Summary\n\nThis CNN baseline offers a fast, memory-friendly starting point.\nGood for exploration, benchmarking & sanity checks before jumping into heavy protein language models.","metadata":{}},{"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# ============================\n# SETUP & IMPORTS\n# ============================\n!pip install biopython -q\n\nimport numpy as np\nimport pandas as pd\nimport tensorflow as tf\nfrom tensorflow import keras\nimport os, gc\nfrom Bio import SeqIO\nfrom sklearn.model_selection import train_test_split\n\nprint(\"TF Version:\", tf.__version__)\nprint(\"GPU Available:\", tf.config.list_physical_devices('GPU'))\n\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-05T21:55:24.004767Z","iopub.execute_input":"2026-01-05T21:55:24.004978Z","iopub.status.idle":"2026-01-05T21:55:47.347139Z","shell.execute_reply.started":"2026-01-05T21:55:24.004951Z","shell.execute_reply":"2026-01-05T21:55:47.346433Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ================================================================\n# 1) File paths (works directly on Kaggle)\n# ================================================================\nTRAIN_FASTA = \"/kaggle/input/cafa-6-protein-function-prediction/Train/train_sequences.fasta\"\nTRAIN_TERMS = \"/kaggle/input/cafa-6-protein-function-prediction/Train/train_terms.tsv\"\nTEST_FASTA  = \"/kaggle/input/cafa-6-protein-function-prediction/Test/testsuperset.fasta\"\n\n# ---------------------------------------------------------------\n# Load FASTA sequences\n# ---------------------------------------------------------------\ndef load_fasta(path):\n    ids, seqs = [], []\n    for r in SeqIO.parse(path, \"fasta\"):\n        ids.append(r.id.split(\"|\")[1] if \"|\" in r.id else r.id)\n        seqs.append(str(r.seq))\n    return ids, seqs\n\n\nprint(\"Reading train fasta...\")\ntrain_ids, train_seqs = load_fasta(TRAIN_FASTA)\nprint(\"Train sequences:\", len(train_seqs))\n\nprint(\"Reading train labels...\")\nterms = pd.read_csv(TRAIN_TERMS, sep=\"\\t\", names=[\"protein\",\"go_term\",\"ontology\"])\nunique_terms = sorted(terms.go_term.unique())\nterm_index = {t:i for i,t in enumerate(unique_terms)}\nNUM_CLASSES = len(unique_terms)\n\nprint(\"GO terms:\", NUM_CLASSES)\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-05T21:55:47.349124Z","iopub.execute_input":"2026-01-05T21:55:47.349645Z","iopub.status.idle":"2026-01-05T21:55:48.460507Z","shell.execute_reply.started":"2026-01-05T21:55:47.349616Z","shell.execute_reply":"2026-01-05T21:55:48.459807Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 🧬 Step 1 — Create Label Matrix (y)\nConverts protein → multi-label GO vector.\n","metadata":{}},{"cell_type":"code","source":"# ================================================================\n# Create multi-label matrix y (must run before pipeline)\n# ================================================================\nterm_index = {t:i for i,t in enumerate(sorted(terms.go_term.unique()))}\nNUM_CLASSES = len(term_index)\n\ny = np.zeros((len(train_ids), NUM_CLASSES), dtype=np.float32)\n\nprotein_to_row = {pid:i for i,pid in enumerate(train_ids)}\n\nfor pid, go, _ in terms.values:\n    r = protein_to_row.get(pid)\n    c = term_index.get(go)\n    if r is not None and c is not None:\n        y[r, c] = 1\n\nprint(\"Label matrix shape:\", y.shape)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-05T21:55:48.461334Z","iopub.execute_input":"2026-01-05T21:55:48.461624Z","iopub.status.idle":"2026-01-05T21:55:50.369929Z","shell.execute_reply.started":"2026-01-05T21:55:48.461584Z","shell.execute_reply":"2026-01-05T21:55:50.369306Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 🔄 Step 2 — Build tf.data Pipeline (memory-safe training)\nEncodes one-hot on the fly — no RAM overflow.\n","metadata":{}},{"cell_type":"code","source":"# ================================================================\n# Build train + validation dataset pipeline\n# ================================================================\nimport tensorflow as tf\nimport numpy as np\n\nNUM_LABELS = y.shape[1]\n\nAA = \"ACDEFGHIKLMNPQRSTVWY\"\nAA_MAP = {a:i for i,a in enumerate(AA)}\nMAX_LEN = 512\n\ndef encode_tf(seq):\n    arr = np.zeros((MAX_LEN, 20), dtype=np.float32)\n    for i,aa in enumerate(seq[:MAX_LEN]):\n        idx = AA_MAP.get(aa)\n        if idx is not None:\n            arr[i, idx] = 1\n    return arr\n\n# ---------------- split before dataset ----------------\nfrom sklearn.model_selection import train_test_split\ntrain_idx, val_idx = train_test_split(\n    np.arange(len(train_seqs)), test_size=0.1, random_state=42\n)\n\ndef generator_train():\n    for i in train_idx:\n        yield encode_tf(train_seqs[i]), y[i]\n\ndef generator_val():\n    for i in val_idx:\n        yield encode_tf(train_seqs[i]), y[i]\n\n\ndef make_ds(gen):\n    return (tf.data.Dataset.from_generator(\n                gen,\n                output_signature=(\n                    tf.TensorSpec(shape=(MAX_LEN,20), dtype=tf.float32),\n                    tf.TensorSpec(shape=(NUM_LABELS,), dtype=tf.float32)\n                ))\n            .batch(32)\n            .prefetch(tf.data.AUTOTUNE))\n\ntrain_ds = make_ds(generator_train)\nval_ds   = make_ds(generator_val)\n\nprint(\"Datasets built ✓\")\n\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-05T21:55:50.370757Z","iopub.execute_input":"2026-01-05T21:55:50.371088Z","iopub.status.idle":"2026-01-05T21:55:50.707219Z","shell.execute_reply.started":"2026-01-05T21:55:50.371066Z","shell.execute_reply":"2026-01-05T21:55:50.706480Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ================================================================\n# CNN MODEL\n# ================================================================\n\nimport tensorflow as tf\nfrom tensorflow import keras\n\nV = 20                     \nNUM_LABELS = y.shape[1]   \n\ndef protein_cnn(input_shape=(MAX_LEN, V), num_labels=NUM_LABELS):\n    i = keras.Input(shape=input_shape)\n\n    x = keras.layers.Conv1D(128,7,padding='same')(i)\n    x = keras.layers.BatchNormalization()(x); x = keras.layers.ReLU()(x)\n    x = keras.layers.MaxPool1D(4)(x); x = keras.layers.Dropout(0.2)(x)\n\n    x = keras.layers.Conv1D(192,5,padding='same')(x)\n    x = keras.layers.BatchNormalization()(x); x = keras.layers.ReLU()(x)\n    x = keras.layers.MaxPool1D(4)(x); x = keras.layers.Dropout(0.2)(x)\n\n    x = keras.layers.Conv1D(256,5,padding='same')(x)\n    x = keras.layers.BatchNormalization()(x); x = keras.layers.ReLU()(x)\n    x = keras.layers.MaxPool1D(2)(x); x = keras.layers.Dropout(0.3)(x)\n\n    x = keras.layers.Flatten()(x)\n    x = keras.layers.Dense(512,activation='relu')(x)\n    x = keras.layers.Dropout(0.5)(x)\n\n    out = keras.layers.Dense(num_labels, activation=\"sigmoid\")(x)\n    return keras.Model(i, out)\n\n\nmodel = protein_cnn()\nmodel.summary()\n\n# ================================================================\n# Compile\n# ================================================================\n\nmodel.compile(\n    optimizer=keras.optimizers.Adam(3e-4),\n    loss=\"binary_crossentropy\",\n    metrics=[tf.keras.metrics.AUC(curve=\"ROC\", name=\"roc_auc\"),\n             tf.keras.metrics.AUC(curve=\"PR\",  name=\"pr_auc\")]\n)\n\n# Early stopping based on PR-AUC (more important in this task)\nearly_stop = keras.callbacks.EarlyStopping(\n    monitor=\"val_pr_auc\",\n    mode=\"max\",\n    patience=3,\n    restore_best_weights=True\n)\n\ncheckpoint = keras.callbacks.ModelCheckpoint(\n    \"cnn_best_model.h5\",\n    monitor=\"val_pr_auc\",\n    mode=\"max\",\n    save_best_only=True,\n    verbose=1\n)\n# ================================================================\n# TF Dataset Pipeline (required for train_ds & val_ds)\n# ================================================================\n\nBATCH_SIZE = 32\n\ndef to_tf(X, y):\n    return tf.data.Dataset.from_tensor_slices((X, y)) \\\n            .shuffle(len(X)) \\\n            .batch(BATCH_SIZE) \\\n            .prefetch(tf.data.AUTOTUNE)\n\n\n# ================================================================\n# Train — using dataset pipeline\n# ================================================================\nhistory = model.fit(\n    train_ds,\n    validation_data=val_ds,\n    epochs=50,\n    callbacks=[early_stop, checkpoint],\n    verbose=1\n)\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-05T21:55:50.708924Z","iopub.execute_input":"2026-01-05T21:55:50.709209Z","iopub.status.idle":"2026-01-05T22:21:56.122949Z","shell.execute_reply.started":"2026-01-05T21:55:50.709186Z","shell.execute_reply":"2026-01-05T22:21:56.122290Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ================================================================\n# 1) Load Test FASTA\n# ================================================================\n\n\nTEST_FASTA = \"/kaggle/input/cafa-6-protein-function-prediction/Test/testsuperset.fasta\"\n# Load term → index mapping again\n# ================================================================\n# Load GO Terms again (needed for mapping output → GO IDs)\n# ================================================================\nTRAIN_TERMS = \"/kaggle/input/cafa-6-protein-function-prediction/Train/train_terms.tsv\"\n\nimport pandas as pd\n\nterms = pd.read_csv(TRAIN_TERMS, sep=\"\\t\", names=[\"protein\",\"go_term\",\"ontology\"])\n\nunique_terms = sorted(terms.go_term.unique())\nterm_index   = {t:i for i,t in enumerate(unique_terms)}   # dict\nindex_term   = {i:t for i,t in enumerate(unique_terms)}   # reverse mapping\n\nprint(\"GO classes:\", len(unique_terms))\n\n\ngo_terms = list(index_term.values())  # used later for prediction mapping\n\ndef load_fasta(path):\n    ids, seqs = [], []\n    for r in SeqIO.parse(path, \"fasta\"):\n        ids.append(r.id.split(\"|\")[1] if \"|\" in r.id else r.id)\n        seqs.append(str(r.seq))\n    return ids, seqs\n\ntest_ids, test_seqs = load_fasta(TEST_FASTA)\nprint(\"Total test proteins:\", len(test_ids))\n\n\n# ================================================================\n# 2) One-Hot Encoding (same as training)\n# ================================================================\nAA = \"ACDEFGHIKLMNPQRSTVWY\"\nAA_MAP = {a:i for i,a in enumerate(AA)}\nMAX_LEN = 512\n\ndef encode(seq):\n    arr = np.zeros((MAX_LEN, 20), dtype=np.float32)\n    for i,aa in enumerate(seq[:MAX_LEN]):\n        idx = AA_MAP.get(aa)\n        if idx is not None:\n            arr[i, idx] = 1\n    return arr\n\n\n# ================================================================\n# 3) Load Best CNN Model\n# ================================================================\nmodel = tf.keras.models.load_model(\"cnn_best_model.h5\", compile=False)\nprint(\"Model loaded ✓\")\n\n\n# ================================================================\n# 4) Predict in Safe Batches (no full RAM usage)\n# ================================================================\nBATCH = 256           # adjust based on GPU RAM\nTHRESHOLD = 0.5       # later tune for best F1!\n\nresults = []\ngo_terms = list(term_index.keys())  # from training\n\nfor i in range(0, len(test_seqs), BATCH):\n    batch = test_seqs[i:i+BATCH]\n    \n    X = np.array([encode(s) for s in batch])     # one-hot encoding chunk\n    preds = model.predict(X, verbose=0)          # (batch, NUM_LABELS)\n\n    for k in range(len(preds)):\n        pid  = test_ids[i+k]\n        pred = preds[k]\n\n        go_list = [go_terms[j] for j,p in enumerate(pred) if p > THRESHOLD]\n\n        if len(go_list)==0:  # avoid empty submission row\n            go_list = [go_terms[np.argmax(pred)]]\n\n        for go in go_list:\n            results.append([pid, go])\n\n\n# ================================================================\n# 5) Save Kaggle Submission\n# ================================================================\ndf = pd.DataFrame(results, columns=[\"Protein\",\"GO_term\"])\ndf.to_csv(\"submission.tsv\", sep=\"\\t\", index=False)\n\nprint(\"📁 Saved → submission.tsv\")\nprint(\"Total output rows:\", len(df))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-05T22:21:56.123973Z","iopub.execute_input":"2026-01-05T22:21:56.124233Z","iopub.status.idle":"2026-01-05T22:38:32.274658Z","shell.execute_reply.started":"2026-01-05T22:21:56.124211Z","shell.execute_reply":"2026-01-05T22:38:32.274029Z"}},"outputs":[],"execution_count":null}]}