{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":116062,"databundleVersionId":14875579,"sourceType":"competition"},{"sourceId":13489718,"sourceType":"datasetVersion","datasetId":8563555},{"sourceId":13680623,"sourceType":"datasetVersion","datasetId":8699749},{"sourceId":14118193,"sourceType":"datasetVersion","datasetId":8994088},{"sourceId":268734519,"sourceType":"kernelVersion"},{"sourceId":269071398,"sourceType":"kernelVersion"},{"sourceId":270571028,"sourceType":"kernelVersion"},{"sourceId":275639766,"sourceType":"kernelVersion"}],"dockerImageVersionId":31193,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os, json, pathlib, stat, subprocess\n\nKAGGLE_USERNAME = \"hungdongne\"\nKAGGLE_KEY      = \"fa21d90eaba17dbbe63ed8d56ed13d2e\"\n\ncfg_dir = pathlib.Path.home() / \".config\" / \"kaggle\"\ncfg_dir.mkdir(parents=True, exist_ok=True)\n\nkjson = cfg_dir / \"kaggle.json\"\nkjson.write_text(json.dumps({\"username\": KAGGLE_USERNAME, \"key\": KAGGLE_KEY}))\nos.chmod(kjson, 0o600)\n\nprint(\"Wrote:\", kjson)\n\nsubprocess.run([\"kaggle\", \"--version\"], check=False)\nsubprocess.run([\"kaggle\", \"competitions\", \"list\", \"-p\", \"1\"], check=True)\nprint(\"✅ Kaggle CLI authenticated OK\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-14T04:47:09.999781Z","iopub.execute_input":"2025-12-14T04:47:09.999962Z","iopub.status.idle":"2025-12-14T04:47:11.129738Z","shell.execute_reply.started":"2025-12-14T04:47:09.999947Z","shell.execute_reply":"2025-12-14T04:47:11.129032Z"}},"outputs":[{"name":"stdout","text":"Wrote: /root/.config/kaggle/kaggle.json\nKaggle API 1.7.4.5\nref                                                                                 deadline             category                reward  teamCount  userHasEntered  \n----------------------------------------------------------------------------------  -------------------  ---------------  -------------  ---------  --------------  \nhttps://www.kaggle.com/competitions/ai-mathematical-olympiad-progress-prize-3       2026-04-15 23:59:00  Featured         2,207,152 Usd        744           False  \nhttps://www.kaggle.com/competitions/hull-tactical-market-prediction                 2025-12-15 23:59:00  Featured           100,000 Usd       3528           False  \nhttps://www.kaggle.com/competitions/vesuvius-challenge-surface-detection            2026-02-13 23:59:00  Research           100,000 Usd        333           False  \nhttps://www.kaggle.com/competitions/google-tunix-hackathon                          2026-01-12 23:59:00  Featured           100,000 Usd         81           False  \nhttps://www.kaggle.com/competitions/csiro-biomass                                   2026-01-28 23:59:00  Research            75,000 Usd       1984           False  \nhttps://www.kaggle.com/competitions/recodai-luc-scientific-image-forgery-detection  2026-01-15 23:59:00  Research            55,000 Usd        816           False  \nhttps://www.kaggle.com/competitions/santa-2025                                      2026-01-30 23:59:00  Featured            50,000 Usd       1667           False  \nhttps://www.kaggle.com/competitions/MABe-mouse-behavior-detection                   2025-12-15 23:59:00  Research            50,000 Usd       1415           False  \nhttps://www.kaggle.com/competitions/cafa-6-protein-function-prediction              2026-02-02 23:59:00  Research            50,000 Usd       1106            True  \nhttps://www.kaggle.com/competitions/physionet-ecg-image-digitization                2026-01-22 23:59:00  Research            50,000 Usd        700           False  \nhttps://www.kaggle.com/competitions/nfl-big-data-bowl-2026-analytics                2025-12-17 23:59:00  Featured            50,000 Usd         88           False  \nhttps://www.kaggle.com/competitions/playground-series-s5e12                         2025-12-31 23:59:00  Playground                Swag       1830           False  \nhttps://www.kaggle.com/competitions/titanic                                         2030-01-01 00:00:00  Getting Started      Knowledge      16483           False  \nhttps://www.kaggle.com/competitions/house-prices-advanced-regression-techniques     2030-01-01 00:00:00  Getting Started      Knowledge       5967           False  \nhttps://www.kaggle.com/competitions/home-data-for-ml-course                         2030-01-01 23:59:00  Getting Started      Knowledge       5036           False  \nhttps://www.kaggle.com/competitions/spaceship-titanic                               2030-01-01 00:00:00  Getting Started      Knowledge       2573           False  \nhttps://www.kaggle.com/competitions/digit-recognizer                                2030-01-01 00:00:00  Getting Started      Knowledge       1497           False  \nhttps://www.kaggle.com/competitions/nlp-getting-started                             2030-01-01 00:00:00  Getting Started      Knowledge        763           False  \nhttps://www.kaggle.com/competitions/store-sales-time-series-forecasting             2030-06-30 23:59:00  Getting Started      Knowledge        624           False  \nhttps://www.kaggle.com/competitions/llm-classification-finetuning                   2030-07-01 23:59:00  Getting Started      Knowledge        305           False  \n✅ Kaggle CLI authenticated OK\n","output_type":"stream"}],"execution_count":1},{"cell_type":"code","source":"import re, gzip, shlex, subprocess, json, time, math\nfrom pathlib import Path\nfrom collections import defaultdict\nfrom io import StringIO\n\nimport pandas as pd\nfrom tqdm.auto import tqdm\n\nCOMP = \"cafa-6-protein-function-prediction\"\n\nCOMP_DIR   = Path(\"/kaggle/input/cafa-6-protein-function-prediction\")\nTEST_FASTA = COMP_DIR / \"Test\" / \"testsuperset.fasta\"\nOBO_PATH   = COMP_DIR / \"Train\" / \"go-basic.obo\"\n\nassert TEST_FASTA.exists(), TEST_FASTA\nassert OBO_PATH.exists(), OBO_PATH\n\n# 4 nguồn submission bạn đang dùng\nSUB_FILES = {\n    \"GOA_NEG\": Path(\"/kaggle/input/cafa-6-blend-goa-negative-propagation/submission.tsv\"),\n    \"CNN\":     Path(\"/kaggle/input/cafa-6-protein-function-prediction-with-1d-cnn/submission.tsv\"),\n    \"EDA\":     Path(\"/kaggle/input/cafa-6-protein-function-starter-eda-model/submission.tsv\"),\n    \"GAF\":     Path(\"/kaggle/input/gaf-submission/submission.tsv\"),\n}\nfor k,p in SUB_FILES.items():\n    assert p.exists(), (k, p)\n\nWORK = Path(\"/kaggle/working\")\nWORK.mkdir(parents=True, exist_ok=True)\n\n# Lần đầu set True để tạo cache sorted; lần sau đổi False cho nhanh\nMAKE_SORTED = True\n\nprint(\"WORK:\", WORK)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-14T04:47:11.131458Z","iopub.execute_input":"2025-12-14T04:47:11.131678Z","iopub.status.idle":"2025-12-14T04:47:12.769946Z","shell.execute_reply.started":"2025-12-14T04:47:11.131661Z","shell.execute_reply":"2025-12-14T04:47:12.769166Z"}},"outputs":[{"name":"stdout","text":"WORK: /kaggle/working\n","output_type":"stream"}],"execution_count":2},{"cell_type":"code","source":"def normalize_id(pid: str) -> str:\n    pid = str(pid).strip()\n    core = pid.split()[0]\n    parts = core.split(\"|\")\n    return parts[1] if len(parts) >= 2 else core\n\ndef read_fasta_ids(path: Path):\n    ids = []\n    with open(path, \"r\") as f:\n        for line in f:\n            if line.startswith(\">\"):\n                ids.append(normalize_id(line[1:].strip()))\n    return ids\n\ntest_ids = read_fasta_ids(TEST_FASTA)\ntest_ids_sorted = sorted(set(test_ids))\ntest_set = set(test_ids_sorted)\n\nprint(\"num test proteins:\", len(test_ids_sorted))\nprint(\"first5:\", test_ids_sorted[:5])\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-14T04:47:12.770785Z","iopub.execute_input":"2025-12-14T04:47:12.771181Z","iopub.status.idle":"2025-12-14T04:47:14.177352Z","shell.execute_reply.started":"2025-12-14T04:47:12.771156Z","shell.execute_reply":"2025-12-14T04:47:14.176577Z"}},"outputs":[{"name":"stdout","text":"num test proteins: 224309\nfirst5: ['A0A017SE81', 'A0A017SE85', 'A0A017SEF3', 'A0A017SEX7', 'A0A017SEY2']\n","output_type":"stream"}],"execution_count":3},{"cell_type":"code","source":"def parse_go_obo_parents(obo_path: Path, include_partof=True):\n    parents = defaultdict(list)\n    term_id = None\n    in_term = False\n    with open(obo_path, \"r\") as f:\n        for line in f:\n            line = line.rstrip(\"\\n\")\n            if line == \"[Term]\":\n                in_term = True\n                term_id = None\n                continue\n            if line.startswith(\"[\") and line != \"[Term]\":\n                in_term = False\n                term_id = None\n                continue\n            if not in_term:\n                continue\n\n            if line.startswith(\"id: GO:\"):\n                term_id = line.split(\"id:\")[1].strip()\n                continue\n            if term_id is None:\n                continue\n\n            if line.startswith(\"is_a: GO:\"):\n                p = line.split(\"is_a:\")[1].strip().split()[0]\n                parents[term_id].append(p)\n            elif include_partof and line.startswith(\"relationship:\"):\n                m = re.match(r\"relationship:\\s+part_of\\s+(GO:\\d+)\", line)\n                if m:\n                    parents[term_id].append(m.group(1))\n    return parents\n\nparents = parse_go_obo_parents(OBO_PATH, include_partof=True)\nprint(\"terms with parents:\", len(parents))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-14T04:47:14.17816Z","iopub.execute_input":"2025-12-14T04:47:14.178355Z","iopub.status.idle":"2025-12-14T04:47:14.743047Z","shell.execute_reply.started":"2025-12-14T04:47:14.178339Z","shell.execute_reply":"2025-12-14T04:47:14.742397Z"}},"outputs":[{"name":"stdout","text":"terms with parents: 40119\n","output_type":"stream"}],"execution_count":4},{"cell_type":"code","source":"ASPECT_TOKENS = {\"C\",\"F\",\"P\",\"CC\",\"MF\",\"BP\",\"cc\",\"mf\",\"bp\"}\n\ndef try_float(x):\n    try: return float(x)\n    except: return None\n\ndef parse_triplet_any(line: str):\n    line = line.strip()\n    if not line:\n        return None\n\n    # detect separator\n    if \"\\t\" in line:\n        toks = [t for t in line.split(\"\\t\") if t != \"\"]\n    elif \",\" in line:\n        toks = [t for t in line.split(\",\") if t != \"\"]\n    else:\n        toks = line.split()\n\n    if len(toks) < 3:\n        return None\n\n    low = \" \".join(toks).lower()\n    # skip headers\n    if (\"protein\" in low and \"go\" in low) or low.startswith(\"entry\") or low.startswith(\"id\"):\n        return None\n\n    go = next((t for t in toks if t.startswith(\"GO:\")), None)\n    if go is None:\n        return None\n\n    score = None\n    for t in reversed(toks):\n        v = try_float(t)\n        if v is not None:\n            score = v\n            break\n    if score is None or score <= 0:\n        return None\n    if score > 1.0:\n        score = 1.0\n\n    pid = None\n    for t in toks:\n        if t == go or t in ASPECT_TOKENS:\n            continue\n        if try_float(t) is not None:\n            continue\n        pid = t\n        break\n    if pid is None:\n        pid = toks[0]\n    pid = normalize_id(pid)\n\n    return pid, go, score\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-14T04:47:14.74372Z","iopub.execute_input":"2025-12-14T04:47:14.743919Z","iopub.status.idle":"2025-12-14T04:47:14.751213Z","shell.execute_reply.started":"2025-12-14T04:47:14.743903Z","shell.execute_reply":"2025-12-14T04:47:14.750593Z"}},"outputs":[],"execution_count":5},{"cell_type":"code","source":"def canonicalize_to_tsv(in_path: Path, out_path: Path):\n    kept = 0\n    with open(in_path, \"r\") as fin, open(out_path, \"w\") as fout:\n        for line in fin:\n            trip = parse_triplet_any(line)\n            if trip is None:\n                continue\n            pid, go, s = trip\n            if pid not in test_set:\n                continue\n            fout.write(f\"{pid}\\t{go}\\t{s}\\n\")\n            kept += 1\n    return kept\n\ndef sort_tsv_to_gz(in_tsv: Path, out_gz: Path):\n    cmd = (\n        \"bash -lc \" +\n        shlex.quote(\n            f\"LC_ALL=C sort -k1,1 -k2,2 {shlex.quote(str(in_tsv))} | gzip -c > {shlex.quote(str(out_gz))}\"\n        )\n    )\n    subprocess.check_call(cmd, shell=True)\n\nsorted_paths = {}\nfor name, p in SUB_FILES.items():\n    outgz = WORK / f\"sorted_{name}.tsv.gz\"\n\n    if (not MAKE_SORTED) and outgz.exists() and outgz.stat().st_size > 0:\n        print(f\"[{name}] reuse sorted MB={outgz.stat().st_size/1024/1024:.1f}\")\n        sorted_paths[name] = outgz\n        continue\n\n    canon = WORK / f\"canon_{name}.tsv\"\n    kept = canonicalize_to_tsv(p, canon)\n    print(f\"[{name}] kept lines:\", kept)\n\n    sort_tsv_to_gz(canon, outgz)\n    canon.unlink(missing_ok=True)\n\n    print(f\"[{name}] sorted size(MB): {outgz.stat().st_size/1024/1024:.1f}\")\n    sorted_paths[name] = outgz\n\nsorted_paths\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-14T04:47:14.75202Z","iopub.execute_input":"2025-12-14T04:47:14.752266Z","iopub.status.idle":"2025-12-14T04:53:15.310083Z","shell.execute_reply.started":"2025-12-14T04:47:14.752243Z","shell.execute_reply":"2025-12-14T04:53:15.309459Z"}},"outputs":[{"name":"stdout","text":"[GOA_NEG] kept lines: 34232402\n[GOA_NEG] sorted size(MB): 155.4\n[CNN] kept lines: 18510835\n[CNN] sorted size(MB): 133.9\n[EDA] kept lines: 12461983\n[EDA] sorted size(MB): 58.0\n[GAF] kept lines: 2431175\n[GAF] sorted size(MB): 8.6\n","output_type":"stream"},{"execution_count":6,"output_type":"execute_result","data":{"text/plain":"{'GOA_NEG': PosixPath('/kaggle/working/sorted_GOA_NEG.tsv.gz'),\n 'CNN': PosixPath('/kaggle/working/sorted_CNN.tsv.gz'),\n 'EDA': PosixPath('/kaggle/working/sorted_EDA.tsv.gz'),\n 'GAF': PosixPath('/kaggle/working/sorted_GAF.tsv.gz')}"},"metadata":{}}],"execution_count":6},{"cell_type":"code","source":"class SubReader:\n    def __init__(self, gz_path: Path):\n        self.f = gzip.open(gz_path, \"rt\")\n        self.eof = False\n        self.cur_pid = None\n        self.cur_map = {}\n        self.buf = None\n        self._read_next_group()\n\n    def _readline(self):\n        if self.buf is not None:\n            x = self.buf\n            self.buf = None\n            return x\n        return self.f.readline()\n\n    def _read_next_group(self):\n        if self.eof:\n            self.cur_pid, self.cur_map = None, {}\n            return\n\n        pid = None\n        m = {}\n        while True:\n            line = self._readline()\n            if not line:\n                self.eof = True\n                break\n            line = line.strip()\n            if not line:\n                continue\n\n            p, go, s = line.split(\"\\t\")\n            if pid is None:\n                pid = p\n            if p != pid:\n                self.buf = line\n                break\n\n            try:\n                s = float(s)\n            except:\n                continue\n            if go not in m or s > m[go]:\n                m[go] = s\n\n        self.cur_pid, self.cur_map = pid, m\n\n    def advance_to(self, target):\n        while (not self.eof) and (self.cur_pid is not None) and (self.cur_pid < target):\n            self._read_next_group()\n        if self.cur_pid == target:\n            out = self.cur_map\n            self._read_next_group()\n            return out\n        return {}\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-14T04:53:15.311837Z","iopub.execute_input":"2025-12-14T04:53:15.312121Z","iopub.status.idle":"2025-12-14T04:53:15.319316Z","shell.execute_reply.started":"2025-12-14T04:53:15.312104Z","shell.execute_reply":"2025-12-14T04:53:15.318533Z"}},"outputs":[],"execution_count":7},{"cell_type":"code","source":"def noisy_or(prev, s):\n    return 1.0 - ((1.0 - prev) * (1.0 - s))\n\ndef propagate_max(scores: dict, parents, prop_from_topn: int, max_added: int):\n    items = sorted(scores.items(), key=lambda x: x[1], reverse=True)[:prop_from_topn]\n    added = 0\n    for go, s in items:\n        stack = list(parents.get(go, []))\n        seen = set()\n        while stack:\n            p = stack.pop()\n            if p in seen:\n                continue\n            seen.add(p)\n            if s > scores.get(p, 0.0):\n                scores[p] = s\n                added += 1\n                if added >= max_added:\n                    return\n            stack.extend(parents.get(p, []))\n\ndef build_submission(cfg, out_gz: Path):\n    readers = {k: SubReader(v) for k,v in sorted_paths.items()}\n\n    topk           = cfg[\"TOPK_PER_PROTEIN\"]\n    prop_from_topn = cfg[\"PROP_FROM_TOPN\"]\n    max_prop_added = cfg[\"MAX_PROP_ADDED\"]\n    thresh_add     = cfg[\"THRESH_ADD\"]\n    P              = cfg[\"P\"]\n\n    total_written = 0\n    with gzip.open(out_gz, \"wt\") as out:\n        for pid in test_ids_sorted:\n            base = readers[\"GOA_NEG\"].advance_to(pid)\n            final = dict(base) if base else {}\n\n            for k in [\"CNN\",\"EDA\",\"GAF\"]:\n                d = readers[k].advance_to(pid)\n                if not d:\n                    continue\n\n                w  = P[k][\"w\"]\n                pw = P[k][\"p\"]\n                cap= P[k][\"cap\"]\n                merge = P[k].get(\"merge\",\"noisy_or\")  # noisy_or / max\n\n                for go, sc in d.items():\n                    s = float(sc)\n                    if s <= 0:\n                        continue\n                    if s > 1.0:\n                        s = 1.0\n                    s = (s ** pw) * w\n                    if s > cap:\n                        s = cap\n                    if s < thresh_add:\n                        continue\n\n                    if merge == \"max\":\n                        final[go] = max(final.get(go, 0.0), s)\n                    else:\n                        final[go] = noisy_or(final.get(go, 0.0), s)\n\n            if final:\n                propagate_max(final, parents, prop_from_topn, max_prop_added)\n            else:\n                for root in [\"GO:0008150\",\"GO:0003674\",\"GO:0005575\"]:\n                    final[root] = 0.001\n\n            top = sorted(final.items(), key=lambda x: x[1], reverse=True)[:topk]\n            for go, s in top:\n                if s > 1.0: s = 1.0\n                out.write(f\"{pid}\\t{go}\\t{s:.6f}\\n\")\n            total_written += len(top)\n\n    return total_written\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-14T04:53:15.319857Z","iopub.execute_input":"2025-12-14T04:53:15.320019Z","iopub.status.idle":"2025-12-14T04:53:15.337158Z","shell.execute_reply.started":"2025-12-14T04:53:15.320005Z","shell.execute_reply":"2025-12-14T04:53:15.33654Z"}},"outputs":[],"execution_count":8},{"cell_type":"code","source":"def kaggle_cmd(args):\n    env = os.environ.copy()\n    p = subprocess.run(args, capture_output=True, text=True, env=env)\n    return p.returncode, p.stdout, p.stderr\n\ndef submit_and_get_score(sub_tsv: Path, msg: str, wait_sec=1200, poll_sec=30):\n    rc, out, err = kaggle_cmd([\"kaggle\", \"competitions\", \"submit\", \"-c\", COMP, \"-f\", str(sub_tsv), \"-m\", msg])\n    print(out if out else err)\n    if rc != 0:\n        raise RuntimeError(err)\n\n    t0 = time.time()\n    while time.time() - t0 < wait_sec:\n        rc, out, err = kaggle_cmd([\"kaggle\", \"competitions\", \"submissions\", COMP, \"-v\"])\n        if rc != 0:\n            print(\"poll failed, retry...\", (err[:200] if err else \"\"))\n            time.sleep(poll_sec)\n            continue\n\n        df = pd.read_csv(StringIO(out))\n\n        msg_col = None\n        for c in df.columns:\n            if c.lower() in [\"description\", \"message\", \"comments\"]:\n                msg_col = c; break\n        if msg_col is None:\n            msg_col = df.columns[0]\n\n        score_col = None\n        for c in df.columns:\n            if (\"public\" in c.lower()) and (\"score\" in c.lower()):\n                score_col = c; break\n        if score_col is None:\n            for c in df.columns:\n                if \"score\" in c.lower():\n                    score_col = c; break\n\n        status_col = None\n        for c in df.columns:\n            if c.lower() == \"status\":\n                status_col = c; break\n\n        hit = df[df[msg_col].astype(str).str.contains(msg, na=False)]\n        if len(hit) > 0:\n            row = hit.iloc[0]\n            status = row[status_col] if status_col else None\n            score  = row[score_col] if score_col else None\n            print(\"status:\", status, \"| score:\", score)\n            if pd.notna(score):\n                return float(score)\n\n        time.sleep(poll_sec)\n\n    print(\"Timeout waiting score.\")\n    return None\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-14T04:53:15.338149Z","iopub.execute_input":"2025-12-14T04:53:15.338384Z","iopub.status.idle":"2025-12-14T04:53:15.354311Z","shell.execute_reply.started":"2025-12-14T04:53:15.338361Z","shell.execute_reply":"2025-12-14T04:53:15.353717Z"}},"outputs":[],"execution_count":9},{"cell_type":"code","source":"PRESETS = [\n    # 1) ULTRA-RECALL (rất rủi ro): topk cực lớn + propagate rất mạnh + THRESH_ADD = 0\n    {\n        \"name\":\"RISK_1_ULTRA_RECALL\",\n        \"TOPK_PER_PROTEIN\": 1900,\n        \"PROP_FROM_TOPN\":   320,\n        \"MAX_PROP_ADDED\":   7500,\n        \"THRESH_ADD\":       0.00,\n        \"P\": {\n            \"GOA_NEG\": {\"w\":1.00,\"p\":1.00,\"cap\":1.00},\n            \"CNN\":     {\"w\":0.42,\"p\":1.06,\"cap\":0.96,\"merge\":\"noisy_or\"},\n            \"EDA\":     {\"w\":0.38,\"p\":1.04,\"cap\":0.96,\"merge\":\"noisy_or\"},\n            \"GAF\":     {\"w\":0.32,\"p\":1.00,\"cap\":0.92,\"merge\":\"noisy_or\"},\n        }\n    },\n\n    # 2) PROP-HEAVY (rủi ro): vẫn recall cao nhưng có chút kiểm soát (THRESH_ADD=0.01)\n    {\n        \"name\":\"RISK_2_PROP_HEAVY\",\n        \"TOPK_PER_PROTEIN\": 1400,\n        \"PROP_FROM_TOPN\":   280,\n        \"MAX_PROP_ADDED\":   9000,\n        \"THRESH_ADD\":       0.01,\n        \"P\": {\n            \"GOA_NEG\": {\"w\":1.00,\"p\":1.00,\"cap\":1.00},\n            \"CNN\":     {\"w\":0.44,\"p\":1.10,\"cap\":0.94,\"merge\":\"noisy_or\"},\n            \"EDA\":     {\"w\":0.34,\"p\":1.08,\"cap\":0.94,\"merge\":\"noisy_or\"},\n            \"GAF\":     {\"w\":0.30,\"p\":1.00,\"cap\":0.90,\"merge\":\"noisy_or\"},\n        }\n    },\n\n    # 3) GAF-ALLIN (rủi ro theo kiểu “all-in” vào model mạnh nhất của bạn)\n    # Dùng cái này nếu file 0.29 của bạn là GAF / hoặc GAF đang thắng.\n    {\n        \"name\":\"RISK_3_GAF_ALLIN\",\n        \"TOPK_PER_PROTEIN\": 1200,\n        \"PROP_FROM_TOPN\":   240,\n        \"MAX_PROP_ADDED\":   6000,\n        \"THRESH_ADD\":       0.015,\n        \"P\": {\n            \"GOA_NEG\": {\"w\":1.00,\"p\":1.00,\"cap\":1.00},\n            \"CNN\":     {\"w\":0.26,\"p\":1.08,\"cap\":0.90,\"merge\":\"noisy_or\"},\n            \"EDA\":     {\"w\":0.20,\"p\":1.06,\"cap\":0.88,\"merge\":\"noisy_or\"},\n            \"GAF\":     {\"w\":0.58,\"p\":1.02,\"cap\":0.96,\"merge\":\"noisy_or\"},\n        }\n    },\n\n    # 4) SHARP-SPIKY (rủi ro): tăng p để “nhọn” score + cap cao + noisy_or\n    # Hay ăn điểm nếu model base có ranking tốt nhưng threshold/cut đang làm mất recall.\n    {\n        \"name\":\"RISK_4_SHARP_SPIKY\",\n        \"TOPK_PER_PROTEIN\": 950,\n        \"PROP_FROM_TOPN\":   210,\n        \"MAX_PROP_ADDED\":   5200,\n        \"THRESH_ADD\":       0.02,\n        \"P\": {\n            \"GOA_NEG\": {\"w\":1.00,\"p\":1.00,\"cap\":1.00},\n            \"CNN\":     {\"w\":0.50,\"p\":1.26,\"cap\":0.93,\"merge\":\"noisy_or\"},\n            \"EDA\":     {\"w\":0.36,\"p\":1.18,\"cap\":0.92,\"merge\":\"noisy_or\"},\n            \"GAF\":     {\"w\":0.22,\"p\":1.04,\"cap\":0.88,\"merge\":\"noisy_or\"},\n        }\n    },\n\n    # 5) RECALL + ANTI-OVERSHOOT (rủi ro “đỡ bẩn”): max cho CNN/EDA nhưng vẫn prop/topk lớn\n    # Khi noisy_or làm bạn bị FP quá nhiều, preset này đôi khi bất ngờ lên điểm.\n    {\n        \"name\":\"RISK_5_RECALL_MAX\",\n        \"TOPK_PER_PROTEIN\": 1300,\n        \"PROP_FROM_TOPN\":   260,\n        \"MAX_PROP_ADDED\":   6500,\n        \"THRESH_ADD\":       0.01,\n        \"P\": {\n            \"GOA_NEG\": {\"w\":1.00,\"p\":1.00,\"cap\":1.00},\n            \"CNN\":     {\"w\":0.46,\"p\":1.16,\"cap\":0.92,\"merge\":\"max\"},\n            \"EDA\":     {\"w\":0.36,\"p\":1.10,\"cap\":0.90,\"merge\":\"max\"},\n            \"GAF\":     {\"w\":0.28,\"p\":1.00,\"cap\":0.90,\"merge\":\"noisy_or\"},\n        }\n    },\n]\n\n[p[\"name\"] for p in PRESETS]\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-14T04:53:15.355201Z","iopub.execute_input":"2025-12-14T04:53:15.355636Z","iopub.status.idle":"2025-12-14T04:53:15.376877Z","shell.execute_reply.started":"2025-12-14T04:53:15.355612Z","shell.execute_reply":"2025-12-14T04:53:15.376315Z"}},"outputs":[{"execution_count":10,"output_type":"execute_result","data":{"text/plain":"['RISK_1_ULTRA_RECALL',\n 'RISK_2_PROP_HEAVY',\n 'RISK_3_GAF_ALLIN',\n 'RISK_4_SHARP_SPIKY',\n 'RISK_5_RECALL_MAX']"},"metadata":{}}],"execution_count":10},{"cell_type":"code","source":"# AUTO RUN ALL PRESETS (4 lần) - không cần đổi index thủ công\nimport shlex, subprocess\nfrom pathlib import Path\n\nWORK = Path(\"/kaggle/working\")\nWORK.mkdir(exist_ok=True)\n\nresults = []\n\nfor cfg in PRESETS:   # chạy lần lượt P_A, P_B, P_C, P_D\n    name = cfg[\"name\"]\n\n    out_gz = WORK / f\"{name}.tsv.gz\"\n    print(\"\\n==== BUILD:\", name, \"====\")\n    nlines = build_submission(cfg, out_gz)\n    print(\"built:\", out_gz, \"MB=\", out_gz.stat().st_size/1024/1024, \"| lines~\", nlines)\n\n    # CAFA6 yêu cầu file tên submission.tsv\n    sub_tsv = WORK / \"submission.tsv\"\n    subprocess.check_call(\n        f\"bash -lc 'gunzip -c {shlex.quote(str(out_gz))} > {shlex.quote(str(sub_tsv))}'\",\n        shell=True\n    )\n    print(\"ready:\", sub_tsv, \"MB=\", sub_tsv.stat().st_size/1024/1024)\n\n    msg = f\"{name}|TOPK={cfg['TOPK_PER_PROTEIN']}|PROP={cfg['PROP_FROM_TOPN']}|TH={cfg['THRESH_ADD']}|CNNm={cfg['P']['CNN'].get('merge','noisy_or')}\"\n    print(\"==== SUBMIT:\", msg, \"====\")\n\n    score = submit_and_get_score(sub_tsv, msg, wait_sec=1200, poll_sec=30)\n    print(\"SCORE:\", score)\n\n    results.append((name, score))\n\nprint(\"\\n==== SUMMARY ====\")\nfor name, sc in results:\n    print(name, \"->\", sc)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-14T04:53:15.377556Z","iopub.execute_input":"2025-12-14T04:53:15.377811Z","iopub.status.idle":"2025-12-14T06:34:13.578131Z","shell.execute_reply.started":"2025-12-14T04:53:15.377795Z","shell.execute_reply":"2025-12-14T06:34:13.57727Z"}},"outputs":[{"name":"stdout","text":"\n==== BUILD: RISK_1_ULTRA_RECALL ====\nbuilt: /kaggle/working/RISK_1_ULTRA_RECALL.tsv.gz MB= 237.74160957336426 | lines~ 53226960\nready: /kaggle/working/submission.tsv MB= 1373.2064247131348\n==== SUBMIT: RISK_1_ULTRA_RECALL|TOPK=1900|PROP=320|TH=0.0|CNNm=noisy_or ====\nSuccessfully submitted to CAFA 6 Protein Function Prediction\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.COMPLETE | score: 0.366\nSCORE: 0.366\n\n==== BUILD: RISK_2_PROP_HEAVY ====\nbuilt: /kaggle/working/RISK_2_PROP_HEAVY.tsv.gz MB= 234.8783769607544 | lines~ 52597918\nready: /kaggle/working/submission.tsv MB= 1356.999647140503\n==== SUBMIT: RISK_2_PROP_HEAVY|TOPK=1400|PROP=280|TH=0.01|CNNm=noisy_or ====\nSuccessfully submitted to CAFA 6 Protein Function Prediction\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.COMPLETE | score: 0.365\nSCORE: 0.365\n\n==== BUILD: RISK_3_GAF_ALLIN ====\nbuilt: /kaggle/working/RISK_3_GAF_ALLIN.tsv.gz MB= 227.69478607177734 | lines~ 50869794\nready: /kaggle/working/submission.tsv MB= 1312.4757556915283\n==== SUBMIT: RISK_3_GAF_ALLIN|TOPK=1200|PROP=240|TH=0.015|CNNm=noisy_or ====\nSuccessfully submitted to CAFA 6 Protein Function Prediction\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.COMPLETE | score: 0.359\nSCORE: 0.359\n\n==== BUILD: RISK_4_SHARP_SPIKY ====\nbuilt: /kaggle/working/RISK_4_SHARP_SPIKY.tsv.gz MB= 225.12279796600342 | lines~ 50150915\nready: /kaggle/working/submission.tsv MB= 1293.951024055481\n==== SUBMIT: RISK_4_SHARP_SPIKY|TOPK=950|PROP=210|TH=0.02|CNNm=noisy_or ====\nSuccessfully submitted to CAFA 6 Protein Function Prediction\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.COMPLETE | score: 0.357\nSCORE: 0.357\n\n==== BUILD: RISK_5_RECALL_MAX ====\nbuilt: /kaggle/working/RISK_5_RECALL_MAX.tsv.gz MB= 228.37819862365723 | lines~ 51993332\nready: /kaggle/working/submission.tsv MB= 1341.4242858886719\n==== SUBMIT: RISK_5_RECALL_MAX|TOPK=1300|PROP=260|TH=0.01|CNNm=max ====\nSuccessfully submitted to CAFA 6 Protein Function Prediction\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.PENDING | score: nan\nstatus: SubmissionStatus.COMPLETE | score: 0.348\nSCORE: 0.348\n\n==== SUMMARY ====\nRISK_1_ULTRA_RECALL -> 0.366\nRISK_2_PROP_HEAVY -> 0.365\nRISK_3_GAF_ALLIN -> 0.359\nRISK_4_SHARP_SPIKY -> 0.357\nRISK_5_RECALL_MAX -> 0.348\n","output_type":"stream"}],"execution_count":11},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}