{"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":[{"sourceId":116062,"databundleVersionId":14875579,"sourceType":"competition"}],"dockerImageVersionId":31234,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# ============================================================\n# 0) Imports\n# ============================================================\nimport os\nimport numpy as np\nimport pandas as pd\n\n# ML\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.multiclass import OneVsRestClassifier\nfrom sklearn.linear_model import LogisticRegression\nfrom sklearn.preprocessing import StandardScaler\n\n# ============================================================\n# 1) Paths\n# ============================================================\nDATA_DIR = \"/kaggle/input/cafa-6-protein-function-prediction\"\nTRAIN_DIR = os.path.join(DATA_DIR, \"Train\")\nTEST_DIR  = os.path.join(DATA_DIR, \"Test\")\n\ntrain_fasta = os.path.join(TRAIN_DIR, \"train_sequences.fasta\")\ntrain_terms = os.path.join(TRAIN_DIR, \"train_terms.tsv\")\ntest_fasta  = os.path.join(TEST_DIR, \"testsuperset.fasta\")\n\n# ============================================================\n# 2) FASTA reader\n# ============================================================\ndef read_fasta(path):\n    records = []\n    with open(path, \"r\") as f:\n        pid = None\n        seq_parts = []\n        for line in f:\n            line = line.strip()\n            if not line:\n                continue\n            if line.startswith(\">\"):\n                if pid is not None:\n                    records.append((pid, \"\".join(seq_parts)))\n                header = line[1:]\n                pid = header.split(\"|\")[1] if \"|\" in header else header.split()[0]\n                seq_parts = []\n            else:\n                seq_parts.append(line)\n        if pid is not None:\n            records.append((pid, \"\".join(seq_parts)))\n    return pd.DataFrame(records, columns=[\"protein_id\", \"sequence\"])\n\n# ============================================================\n# 3) Load sequences\n# ============================================================\ntrain_seq = read_fasta(train_fasta)\ntest_seq  = read_fasta(test_fasta)\n\nprint(\"train_seq:\", train_seq.shape)\nprint(\"test_seq :\", test_seq.shape)\n\n# ============================================================\n# 4) Load terms + build train_df\n# ============================================================\ntrain_terms_df = pd.read_csv(train_terms, sep=\"\\t\").rename(columns={\"EntryID\": \"protein_id\"})\n\nterms_per_protein = (\n    train_terms_df.groupby(\"protein_id\")[\"term\"].apply(list).reset_index()\n).rename(columns={\"term\": \"terms\"})\n\ntrain_df = train_seq.merge(terms_per_protein, on=\"protein_id\", how=\"left\")\ntrain_df[\"terms\"] = train_df[\"terms\"].apply(lambda x: x if isinstance(x, list) else [])\n\ntrain_df[\"n_terms\"] = train_df[\"terms\"].apply(len)\n\nprint(\"train_df:\", train_df.shape)\nprint(train_df[\"n_terms\"].describe())\n\n# ============================================================\n# 5) Label encoding (Top-K)\n# ============================================================\nfrom collections import Counter\n\nTOP_K = 1000\n\nall_terms = []\nfor ts in train_df[\"terms\"]:\n    all_terms.extend(ts)\n\nterm_counts = Counter(all_terms)\ntop_terms = [t for t, _ in term_counts.most_common(TOP_K)]\nterm_to_idx = {t:i for i,t in enumerate(top_terms)}\n\nN = len(train_df)\nY = np.zeros((N, TOP_K), dtype=np.int8)\nfor i, ts in enumerate(train_df[\"terms\"]):\n    for t in ts:\n        j = term_to_idx.get(t)\n        if j is not None:\n            Y[i, j] = 1\n\nprint(\"Y:\", Y.shape, \"sum=\", Y.sum())\n\n# ============================================================\n# 6) Feature extraction (we vullen dit straks: AA vs k-mers)\n# ============================================================\nFEATURES = \"aa\"  # later: \"kmer\"\n\n# placeholder:\nX_train = None\nX_test  = None\n\n# ============================================================\n# 7) Train/valid\n# ============================================================\n# placeholder\n\n# ============================================================\n# 8) Train full + predict test\n# ============================================================\n# placeholder\n\n# ============================================================\n# 9) Submission\n# ============================================================\n# placeholder\n","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-01-20T09:00:59.238634Z","iopub.execute_input":"2026-01-20T09:00:59.239022Z","iopub.status.idle":"2026-01-20T09:01:06.210168Z","shell.execute_reply.started":"2026-01-20T09:00:59.238988Z","shell.execute_reply":"2026-01-20T09:01:06.209190Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"k-mers features met TF-IDF (character n-grams).","metadata":{}},{"cell_type":"code","source":"# ============================================================\n# 6) Feature extraction: k-mers via TF-IDF char n-grams\n# ============================================================\nfrom sklearn.feature_extraction.text import TfidfVectorizer\n\nFEATURES = \"kmer\"\nNGRAM_RANGE = (2, 4)     # start: 3-mers t/m 5-mers\nMAX_FEATURES = 100000    # cap voor geheugen/snelheid (tune later)\n\nvectorizer = TfidfVectorizer(\n    analyzer=\"char\",\n    ngram_range=NGRAM_RANGE,\n    lowercase=False,      # aminozuren zijn al uppercase\n    max_features=MAX_FEATURES\n)\n\nX_train = vectorizer.fit_transform(train_df[\"sequence\"])\nX_test  = vectorizer.transform(test_seq[\"sequence\"])\n\nprint(\"X_train:\", X_train.shape)\nprint(\"X_test :\", X_test.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-20T09:01:10.536418Z","iopub.execute_input":"2026-01-20T09:01:10.536925Z","iopub.status.idle":"2026-01-20T09:06:33.470256Z","shell.execute_reply.started":"2026-01-20T09:01:10.536890Z","shell.execute_reply":"2026-01-20T09:06:33.469360Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Stap 7.1 — Train/valid split","metadata":{}},{"cell_type":"code","source":"# from sklearn.model_selection import train_test_split\n\n# X_tr, X_val, Y_tr, Y_val = train_test_split(\n#     X_train,\n#     Y,\n#     test_size=0.2,\n#     random_state=42\n# )\n\n# print(X_tr.shape, Y_tr.shape)\n# print(X_val.shape, Y_val.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-18T20:15:12.295385Z","iopub.execute_input":"2026-01-18T20:15:12.296979Z","iopub.status.idle":"2026-01-18T20:15:12.783593Z","shell.execute_reply.started":"2026-01-18T20:15:12.296941Z","shell.execute_reply":"2026-01-18T20:15:12.782657Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Stap 7.2 — Model definiëren (k-mer versie)","metadata":{}},{"cell_type":"code","source":"# from sklearn.multiclass import OneVsRestClassifier\n# from sklearn.linear_model import LogisticRegression\n\n# model_kmer = OneVsRestClassifier(\n#     LogisticRegression(\n#         solver=\"saga\",\n#         max_iter=80,\n#         C=0.5,\n#         n_jobs=1\n#     )\n# )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-15T13:31:17.902901Z","iopub.execute_input":"2026-01-15T13:31:17.903674Z","iopub.status.idle":"2026-01-15T13:31:17.908488Z","shell.execute_reply.started":"2026-01-15T13:31:17.903636Z","shell.execute_reply":"2026-01-15T13:31:17.907480Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"SGDClassifier ipv LogisticRegression","metadata":{}},{"cell_type":"code","source":"# from sklearn.linear_model import SGDClassifier\n# from sklearn.multiclass import OneVsRestClassifier\n\n# model_kmer = OneVsRestClassifier(\n#     SGDClassifier(\n#         loss=\"log_loss\",      # = logistic regression\n#         alpha=1e-5,           # regularisatie (vergelijkbaar met 1/C orde)\n#         max_iter=20,\n#         tol=1e-3,\n#         n_jobs=-1\n#     )\n# )\n\n# model_kmer.fit(X_tr, Y_tr)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-18T20:15:37.359273Z","iopub.execute_input":"2026-01-18T20:15:37.359768Z","iopub.status.idle":"2026-01-18T20:28:48.162523Z","shell.execute_reply.started":"2026-01-18T20:15:37.359739Z","shell.execute_reply":"2026-01-18T20:28:48.161672Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Stap 7.3 — Trainen","metadata":{}},{"cell_type":"markdown","source":"Stap 7.4 — Validatie (Mean AP)","metadata":{}},{"cell_type":"code","source":"# from sklearn.metrics import average_precision_score\n# import numpy as np\n\n# Y_val_pred = model_kmer.predict_proba(X_val)\n\n# # average=None => AP per label in één call\n# ap = average_precision_score(Y_val, Y_val_pred, average=None)\n\n# # sommige labels kunnen NaN zijn als er geen positives zijn; die filteren we weg\n# ap = ap[~np.isnan(ap)]\n\n# print(\"Mean AP over labels:\", float(ap.mean()))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-18T20:29:47.629333Z","iopub.execute_input":"2026-01-18T20:29:47.630543Z","iopub.status.idle":"2026-01-18T20:30:26.407009Z","shell.execute_reply.started":"2026-01-18T20:29:47.630496Z","shell.execute_reply":"2026-01-18T20:30:26.405790Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"model_kmer.fit(X_tr, Y_tr)","metadata":{}},{"cell_type":"code","source":"from sklearn.linear_model import SGDClassifier\nfrom sklearn.multiclass import OneVsRestClassifier\n\nmodel_full = OneVsRestClassifier(\n    SGDClassifier(\n        loss=\"log_loss\",\n        alpha=1e-5,\n        max_iter=200,\n        tol=1e-3,\n        n_jobs=-1,\n        early_stopping=True,\n        validation_fraction=0.1,\n        n_iter_no_change=5,\n        class_weight='balanced'\n    )\n)\n\nmodel_full.fit(X_train, Y)\n\n# >>> DIRECT DAARNA OPSLAAN <<<\nimport joblib\njoblib.dump(vectorizer, \"tfidf_vectorizer.joblib\")\njoblib.dump(model_full, \"sgd_model.joblib\")\n\nprint(\"Model en vectorizer opgeslagen\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-20T09:13:30.538118Z","iopub.execute_input":"2026-01-20T09:13:30.538509Z","iopub.status.idle":"2026-01-20T09:58:56.746161Z","shell.execute_reply.started":"2026-01-20T09:13:30.538480Z","shell.execute_reply":"2026-01-20T09:58:56.744920Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# >>> DIRECT DAARNA OPSLAAN <<<\nimport joblib\njoblib.dump(vectorizer, \"tfidf_vectorizer.joblib\")\njoblib.dump(model_full, \"sgd_model.joblib\")\n\nprint(\"Model en vectorizer opgeslagen\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-19T15:39:04.970573Z","iopub.execute_input":"2026-01-19T15:39:04.970863Z","iopub.status.idle":"2026-01-19T15:39:07.114420Z","shell.execute_reply.started":"2026-01-19T15:39:04.970843Z","shell.execute_reply":"2026-01-19T15:39:07.113277Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# import joblib\n\n# vectorizer = joblib.load(\"tfidf_vectorizer.joblib\")\n# model_full = joblib.load(\"sgd_model.joblib\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Predict op test","metadata":{}},{"cell_type":"code","source":"P_test = model_full.predict_proba(X_test)\nprint(P_test.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-20T10:03:36.131526Z","iopub.execute_input":"2026-01-20T10:03:36.132263Z","iopub.status.idle":"2026-01-20T10:14:35.641263Z","shell.execute_reply.started":"2026-01-20T10:03:36.132230Z","shell.execute_reply":"2026-01-20T10:14:35.639750Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Submission maken","metadata":{}},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\n\nTHRESH = 0.05\nTOP_N_PER_PROTEIN = 100\n\nrows = []\ntest_ids = test_seq[\"protein_id\"].values\n\nfor i, pid in enumerate(test_ids):\n    probs = P_test[i]\n    order = np.argsort(-probs)\n\n    n_added = 0\n    for j in order:\n        s = probs[j]\n        if s < THRESH:\n            break\n        rows.append((pid, top_terms[j], float(s)))\n        n_added += 1\n        if n_added >= TOP_N_PER_PROTEIN:\n            break\n\nsub = pd.DataFrame(rows, columns=[\"protein_id\", \"term\", \"score\"])\nprint(sub.head())\nprint(\"Rows:\", len(sub), \" Unique proteins:\", sub[\"protein_id\"].nunique())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-20T10:29:19.544330Z","iopub.execute_input":"2026-01-20T10:29:19.547114Z","iopub.status.idle":"2026-01-20T10:29:45.706645Z","shell.execute_reply.started":"2026-01-20T10:29:19.547034Z","shell.execute_reply":"2026-01-20T10:29:45.705726Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Opslaan en uploaden","metadata":{}},{"cell_type":"code","source":"sub.to_csv(\"submission.tsv\", sep=\"\\t\", index=False, header=False)\nprint(\"Saved submission.tsv\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-20T10:29:49.738399Z","iopub.execute_input":"2026-01-20T10:29:49.739307Z","iopub.status.idle":"2026-01-20T10:30:17.406572Z","shell.execute_reply.started":"2026-01-20T10:29:49.739268Z","shell.execute_reply":"2026-01-20T10:30:17.405528Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"sanity check","metadata":{}},{"cell_type":"code","source":"# check 3 willekeurige regels\nwith open(\"submission.tsv\", \"r\") as f:\n    for _ in range(3):\n        line = f.readline().rstrip(\"\\n\")\n        print(line, \" | cols:\", len(line.split(\"\\t\")))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-20T10:30:23.692033Z","iopub.execute_input":"2026-01-20T10:30:23.693175Z","iopub.status.idle":"2026-01-20T10:30:23.699507Z","shell.execute_reply.started":"2026-01-20T10:30:23.693097Z","shell.execute_reply":"2026-01-20T10:30:23.698346Z"}},"outputs":[],"execution_count":null}]}