{"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":"nvidiaTeslaT4","dataSources":[{"sourceId":116062,"databundleVersionId":14084779,"sourceType":"competition"}],"dockerImageVersionId":31192,"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":"2025-12-06T13:55:21.719543Z","iopub.execute_input":"2025-12-06T13:55:21.719822Z","iopub.status.idle":"2025-12-06T13:55:24.145436Z","shell.execute_reply.started":"2025-12-06T13:55:21.719794Z","shell.execute_reply":"2025-12-06T13:55:24.144743Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!pip install bio\n\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-06T13:55:24.148837Z","iopub.execute_input":"2025-12-06T13:55:24.149007Z","iopub.status.idle":"2025-12-06T13:55:32.328445Z","shell.execute_reply.started":"2025-12-06T13:55:24.148991Z","shell.execute_reply":"2025-12-06T13:55:32.327723Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport sys\nimport os\nfrom Bio import SeqIO\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.preprocessing import StandardScaler\nfrom sklearn.neighbors import NearestNeighbors\nfrom tqdm import tqdm\nimport gc\n\n# ==========================================\n# GPU SUPPORT\n# ==========================================\ntry:\n    from cuml.neighbors import NearestNeighbors as cuNearestNeighbors\n    USE_GPU = True\n    print(\"✓ GPU acceleration enabled\")\nexcept:\n    cuNearestNeighbors = NearestNeighbors\n    USE_GPU = False\n    print(\"✓ Using CPU\")\n\n# ==========================================\n# CONFIG (Back to What Works)\n# ==========================================\nclass Config:\n    TRAIN_SEQUENCES = \"/kaggle/input/cafa-6-protein-function-prediction/Train/train_sequences.fasta\"\n    TRAIN_TERMS = \"/kaggle/input/cafa-6-protein-function-prediction/Train/train_terms.tsv\"\n    TEST_SEQUENCES = \"/kaggle/input/cafa-6-protein-function-prediction/Test/testsuperset.fasta\"\n    IA_WEIGHTS = \"/kaggle/input/cafa-6-protein-function-prediction/IA.tsv\"\n    SUBMISSION_FILE = \"submission.tsv\"\n    \n    NUM_TERMS = 500                 # Your original sweet spot\n    BATCH_SIZE = 5000\n    \n    # Simple but effective ensemble\n    K_NEIGHBORS = [20, 25, 30, 35, 40]  \n    VAL_SIZE = 0.15\n    SEED = 42\n\nAMINO_ACIDS = \"ACDEFGHIKLMNPQRSTVWY\"\nAA_IDX = {aa: i for i, aa in enumerate(AMINO_ACIDS)}\n\n# ==========================================\n# SIMPLE BUT EFFECTIVE FEATURES\n# ==========================================\ndef extract_dipeptide(seq):\n    \"\"\"Core dipeptide features - 400\"\"\"\n    counts = np.zeros(400, dtype=np.float32)\n    n = len(seq)\n    if n < 2:\n        return counts\n    \n    for i in range(n - 1):\n        a1, a2 = seq[i], seq[i+1]\n        if a1 in AA_IDX and a2 in AA_IDX:\n            counts[AA_IDX[a1] * 20 + AA_IDX[a2]] += 1\n    \n    return counts / (n - 1)\n\ndef extract_aa_comp(seq):\n    \"\"\"AA composition - 20\"\"\"\n    comp = np.zeros(20, dtype=np.float32)\n    n = len(seq)\n    if n == 0:\n        return comp\n    \n    for aa in seq:\n        if aa in AA_IDX:\n            comp[AA_IDX[aa]] += 1\n    \n    return comp / n\n\ndef extract_properties(seq):\n    \"\"\"Simple physicochemical - 8\"\"\"\n    n = len(seq)\n    if n == 0:\n        return np.zeros(8, dtype=np.float32)\n    \n    hydrophobic = sum(1 for aa in seq if aa in 'AVILMFYW') / n\n    polar = sum(1 for aa in seq if aa in 'STNQ') / n\n    positive = sum(1 for aa in seq if aa in 'KR') / n\n    negative = sum(1 for aa in seq if aa in 'DE') / n\n    aromatic = sum(1 for aa in seq if aa in 'FYW') / n\n    \n    return np.array([\n        hydrophobic, polar, positive, negative, aromatic,\n        positive - negative,  # net charge\n        np.log1p(n),         # log length\n        len(set(seq)) / 20.0 # diversity\n    ], dtype=np.float32)\n\ndef extract_features(seq):\n    \"\"\"Total: 428 features\"\"\"\n    return np.concatenate([\n        extract_dipeptide(seq),\n        extract_aa_comp(seq),\n        extract_properties(seq)\n    ])\n\n# ==========================================\n# DATA LOADING\n# ==========================================\ndef load_sequences(path, limit=None):\n    ids, feats = [], []\n    \n    print(\"Loading sequences...\")\n    for i, rec in enumerate(tqdm(SeqIO.parse(path, \"fasta\"))):\n        if limit and i >= limit:\n            break\n        \n        fid = str(rec.id)\n        cid = fid.split('|')[1] if '|' in fid else fid\n        \n        ids.append(cid)\n        feats.append(extract_features(str(rec.seq)))\n    \n    return pd.DataFrame({'id': ids, 'features': list(feats)})\n\n# ==========================================\n# TERM SELECTION\n# ==========================================\ndef select_top_terms(terms_path, ia_path, n):\n    print(\"Selecting terms...\")\n    \n    df = pd.read_csv(terms_path, sep='\\t', header=None, names=['id', 'term', 'onto'])\n    ia = pd.read_csv(ia_path, sep='\\t', header=None, names=['term', 'w']).set_index('term')\n    \n    counts = df['term'].value_counts()\n    \n    # Score by frequency * (1 + IA)\n    scores = {}\n    for term in counts.index:\n        freq = counts[term]\n        w = ia.loc[term, 'w'] if term in ia.index else 0\n        scores[term] = freq * (1 + w)\n    \n    top = sorted(scores.items(), key=lambda x: x[1], reverse=True)[:n]\n    selected = [t[0] for t in top]\n    \n    print(f\"Selected {len(selected)} terms\")\n    return selected\n\ndef make_labels(terms_path, selected):\n    df = pd.read_csv(terms_path, sep='\\t', header=None, names=['id', 'term', 'onto'])\n    filt = df[df['term'].isin(selected)].copy()\n    filt['v'] = 1\n    \n    pivot = filt.pivot_table(index='id', columns='term', values='v', fill_value=0).astype(np.int8)\n    return pivot.reindex(columns=selected, fill_value=0)\n\n# ==========================================\n# IMPROVED KNN\n# ==========================================\nclass ImprovedKNN:\n    def __init__(self, k):\n        self.k = k\n        self.nn = cuNearestNeighbors(n_neighbors=k, metric='cosine')\n        self.y = None\n        self.scaler = StandardScaler()\n    \n    def fit(self, X, y):\n        X_scaled = self.scaler.fit_transform(X)\n        self.nn.fit(X_scaled)\n        self.y = y.astype(np.float32)\n        return self\n    \n    def predict(self, X):\n        X_scaled = self.scaler.transform(X)\n        dist, idx = self.nn.kneighbors(X_scaled)\n        \n        if hasattr(idx, 'get'):\n            idx = idx.get()\n            dist = dist.get()\n        \n        # Distance weighting: inverse squared\n        weights = 1.0 / (dist ** 2 + 1e-6)\n        weights = weights / weights.sum(axis=1, keepdims=True)\n        \n        neighbors = self.y[idx]\n        probs = np.zeros((X.shape[0], self.y.shape[1]), dtype=np.float32)\n        \n        for i in range(X.shape[0]):\n            probs[i] = (neighbors[i] * weights[i, :, None]).sum(axis=0)\n        \n        return probs\n\n# ==========================================\n# SIMPLE ENSEMBLE\n# ==========================================\nclass SimpleEnsemble:\n    def __init__(self, k_list):\n        self.models = [ImprovedKNN(k) for k in k_list]\n        self.weights = None\n    \n    def fit(self, X, y, X_val, y_val):\n        print(f\"Training {len(self.models)} models...\")\n        \n        for i, m in enumerate(self.models, 1):\n            print(f\"  Model {i}: k={m.k}\")\n            m.fit(X, y)\n        \n        # Simple validation-based weighting\n        print(\"Computing weights...\")\n        val_preds = [m.predict(X_val) for m in self.models]\n        \n        # Try equal and weighted combinations\n        best_w = None\n        best_f = 0\n        \n        # Equal weights\n        pred_equal = np.mean(val_preds, axis=0)\n        f_equal = quick_f1(y_val, pred_equal)\n        if f_equal > best_f:\n            best_f = f_equal\n            best_w = np.ones(len(self.models)) / len(self.models)\n        \n        # Weighted by k (favor middle values)\n        k_vals = np.array([m.k for m in self.models], dtype=float)\n        k_mean = k_vals.mean()\n        k_weights = np.exp(-((k_vals - k_mean) ** 2) / (2 * 5**2))\n        k_weights = k_weights / k_weights.sum()\n        \n        pred_k = sum(w * p for w, p in zip(k_weights, val_preds))\n        f_k = quick_f1(y_val, pred_k)\n        if f_k > best_f:\n            best_f = f_k\n            best_w = k_weights\n        \n        # Individual performance weighting\n        perf_weights = []\n        for p in val_preds:\n            f = quick_f1(y_val, p)\n            perf_weights.append(f)\n        \n        perf_weights = np.array(perf_weights)\n        perf_weights = perf_weights / perf_weights.sum()\n        \n        pred_perf = sum(w * p for w, p in zip(perf_weights, val_preds))\n        f_perf = quick_f1(y_val, pred_perf)\n        if f_perf > best_f:\n            best_f = f_perf\n            best_w = perf_weights\n        \n        self.weights = best_w\n        print(f\"Weights: {self.weights.round(3)}\")\n        return self\n    \n    def predict(self, X):\n        preds = [m.predict(X) for m in self.models]\n        return sum(w * p for w, p in zip(self.weights, preds))\n\ndef quick_f1(y_true, y_pred, thresh=0.15):\n    \"\"\"Quick F1 for weight optimization\"\"\"\n    yb = (y_pred >= thresh).astype(int)\n    tp = (yb * y_true).sum()\n    fp = (yb * (1 - y_true)).sum()\n    fn = ((1 - yb) * y_true).sum()\n    \n    p = tp / (tp + fp + 1e-9)\n    r = tp / (tp + fn + 1e-9)\n    return 2 * p * r / (p + r + 1e-9)\n\n# ==========================================\n# EVALUATION\n# ==========================================\ndef eval_model(y_true, y_pred, weights):\n    best_f, best_t = 0, 0\n    \n    for t in np.arange(0.01, 0.40, 0.005):\n        yb = (y_pred >= t).astype(int)\n        inter = yb * y_true\n        \n        wi = (inter * weights).sum(axis=1).sum()\n        wp = (yb * weights).sum(axis=1).sum()\n        wt = (y_true * weights).sum(axis=1).sum()\n        \n        p = wi / wp if wp > 0 else 0\n        r = wi / wt if wt > 0 else 0\n        \n        if p + r > 0:\n            f = 2 * p * r / (p + r)\n            if f > best_f:\n                best_f = f\n                best_t = t\n    \n    print(f\"\\n✓ F_max: {best_f:.4f} @ threshold {best_t:.3f}\")\n    return best_f, best_t\n\ndef load_weights(path, terms):\n    ia = pd.read_csv(path, sep='\\t', header=None, names=['term', 'w']).set_index('term')\n    w = np.zeros(len(terms), dtype=np.float32)\n    for i, t in enumerate(terms):\n        w[i] = ia.loc[t, 'w'] if t in ia.index else 0\n    return w\n\n# ==========================================\n# TRAINING\n# ==========================================\ndef train():\n    # Load\n    df = load_sequences(Config.TRAIN_SEQUENCES)\n    terms = select_top_terms(Config.TRAIN_TERMS, Config.IA_WEIGHTS, Config.NUM_TERMS)\n    labels = make_labels(Config.TRAIN_TERMS, terms)\n    \n    # Align\n    common = [i for i in df['id'] if i in labels.index]\n    print(f\"Aligned: {len(common)} proteins\")\n    \n    X = np.stack(df.set_index('id').loc[common]['features'].values)\n    y = labels.loc[common].values\n    \n    del df, labels\n    gc.collect()\n    \n    # Split\n    X_tr, X_val, y_tr, y_val = train_test_split(\n        X, y, test_size=Config.VAL_SIZE, random_state=Config.SEED\n    )\n    \n    print(f\"Train: {X_tr.shape[0]} × {X_tr.shape[1]} features\")\n    print(f\"Val: {X_val.shape[0]} proteins, {y_tr.shape[1]} terms\")\n    \n    # Train\n    model = SimpleEnsemble(Config.K_NEIGHBORS)\n    model.fit(X_tr, y_tr, X_val, y_val)\n    \n    del X_tr\n    gc.collect()\n    \n    # Evaluate\n    print(\"\\nEvaluating...\")\n    pred = model.predict(X_val)\n    w = load_weights(Config.IA_WEIGHTS, terms)\n    f, t = eval_model(y_val, pred, w)\n    \n    del X_val, y_val\n    gc.collect()\n    \n    return model, terms, t\n\n# ==========================================\n# SUBMISSION\n# ==========================================\ndef generate_submission(model, terms, thresh):\n    print(f\"\\nGenerating submission (threshold={thresh:.3f})...\")\n    \n    with open(Config.SUBMISSION_FILE, 'w') as f:\n        batch_ids, batch_feats = [], []\n        \n        for rec in tqdm(SeqIO.parse(Config.TEST_SEQUENCES, \"fasta\")):\n            fid = str(rec.id)\n            cid = fid.split('|')[1] if '|' in fid else fid\n            \n            feat = extract_features(str(rec.seq))\n            batch_ids.append(cid)\n            batch_feats.append(feat)\n            \n            if len(batch_ids) >= Config.BATCH_SIZE:\n                X = np.array(batch_feats)\n                probs = model.predict(X)\n                \n                for i, pid in enumerate(batch_ids):\n                    for j, score in enumerate(probs[i]):\n                        if score >= thresh:\n                            f.write(f\"{pid}\\t{terms[j]}\\t{score:.3f}\\n\")\n                \n                batch_ids, batch_feats = [], []\n                gc.collect()\n        \n        if batch_ids:\n            X = np.array(batch_feats)\n            probs = model.predict(X)\n            \n            for i, pid in enumerate(batch_ids):\n                for j, score in enumerate(probs[i]):\n                    if score >= thresh:\n                        f.write(f\"{pid}\\t{terms[j]}\\t{score:.3f}\\n\")\n    \n    print(f\"✓ Saved: {Config.SUBMISSION_FILE}\")\n\n# ==========================================\n# MAIN\n# ==========================================\nif __name__ == \"__main__\":\n    print(\"=\"*60)\n    print(\"  CAFA-6 Protein Function Predictor\")\n    print(\"=\"*60)\n    \n    try:\n        import Bio\n    except:\n        print(\"Install: pip install biopython\")\n        sys.exit(1)\n    \n    model, terms, threshold = train()\n    generate_submission(model, terms, threshold)\n    \n    print(\"\\n\" + \"=\"*60)\n    print(\"✓ COMPLETE\")\n    print(\"=\"*60)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-06T15:40:15.591261Z","iopub.execute_input":"2025-12-06T15:40:15.591918Z"}},"outputs":[],"execution_count":null}]}