{"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":"none","dataSources":[{"sourceId":116062,"databundleVersionId":14875579,"sourceType":"competition"},{"sourceId":13665986,"sourceType":"datasetVersion","datasetId":8688768}],"dockerImageVersionId":31192,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# ============================================================================\n\n# CELL 1: CẤU HÌNH & IMPORT\n\n# ============================================================================\n\nimport numpy as np\n\nimport pandas as pd\n\nimport os\n\nimport torch\n\nimport torch.nn as nn\n\nimport torch.optim as optim\n\nfrom torch.utils.data import Dataset, DataLoader\n\nfrom sklearn.model_selection import train_test_split\n\nfrom sklearn.preprocessing import StandardScaler\n\nimport gc\n\nimport warnings\n\n!pip install goatools\n\n\n\n# Tắt các cảnh báo không cần thiết\n\nwarnings.filterwarnings('ignore')\n\n\n\n# --- CẤU HÌNH ĐƯỜNG DẪN (Chỉ dùng T5) ---\n\n# Hãy đảm bảo đường dẫn này đúng với dataset T5 bạn đã add\n\nEMBED_DIR = '/kaggle/input/cafa-6-t5-embeddings' \n\nTRAIN_TERMS_PATH = '/kaggle/input/cafa-6-protein-function-prediction/Train/train_terms.tsv'\n\nOBO_PATH = '/kaggle/input/cafa-6-protein-function-prediction/Train/go-basic.obo'\n\n\n\n# --- HYPERPARAMETERS (TỐI ƯU HÓA) ---\n\nBATCH_SIZE = 512       # Batch lớn giúp train nhanh và ổn định gradient\n\nEPOCHS = 25            # Tăng epoch vì dùng Cosine Scheduler\n\nLEARNING_RATE = 1e-3   # Tốc độ học khởi điểm\n\nNUM_LABELS = 1500      # Số lượng nhãn GO phổ biến nhất\n\nSEEDS = [42, 2024, 123, 777, 888] # Ensemble 5 mô hình để kết quả vững chắc nhất\n\n\n\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\nprint(f\"🔧 Device: {device}\")\n\nprint(\"🚀 Strategy: T5 Embeddings Only + MLP Multi-label\")\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-12T13:09:45.33023Z","iopub.execute_input":"2025-12-12T13:09:45.330583Z","iopub.status.idle":"2025-12-12T13:09:49.545556Z","shell.execute_reply.started":"2025-12-12T13:09:45.330552Z","shell.execute_reply":"2025-12-12T13:09:49.544351Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================================\n\n# CELL 2: LOAD DỮ LIỆU & CHUẨN HÓA (STANDARD SCALER)\n\n# ============================================================================\n\nprint(\"\\n\" + \"=\"*60)\n\nprint(\"📂 BƯỚC 1: LOAD DỮ LIỆU T5 & CHUẨN HÓA\")\n\nprint(\"=\"*60)\n\n\n\n# Hàm làm sạch ID để khớp giữa các file\n\ndef clean_id(pid):\n\n    if isinstance(pid, bytes): pid = pid.decode('utf-8')\n\n    pid = str(pid).strip()\n\n    if '|' in pid: \n\n        parts = pid.split('|')\n\n        return parts[1] if len(parts) >= 2 else pid\n\n    return pid\n\nclean_func = np.vectorize(clean_id)\n\n\n\n# 1. Load T5 Embeddings (Train)\n\nprint(\"1️⃣ Loading T5 Train Embeddings...\")\n\ntry:\n\n    X_raw = np.load(os.path.join(EMBED_DIR, 'train_embeds.npy'))\n\n    ids = clean_func(np.load(os.path.join(EMBED_DIR, 'train_ids.npy')))\n\n    print(f\"   ✓ Loaded Shape: {X_raw.shape}\")\n\nexcept Exception as e:\n\n    print(f\"❌ Error loading T5: {e}\"); exit()\n\n\n\n# 2. Load Labels\n\nprint(\"2️⃣ Loading Labels...\")\n\ndf_terms = pd.read_csv(TRAIN_TERMS_PATH, sep='\\t')\n\nif 'EntryID' not in df_terms.columns:\n\n    df_terms = pd.read_csv(TRAIN_TERMS_PATH, sep='\\t', header=None, names=['EntryID', 'term', 'aspect'])\n\ndf_terms['EntryID'] = df_terms['EntryID'].apply(clean_id)\n\n\n\n# 3. Align Data (Tìm ID chung)\n\nprint(\"3️⃣ Aligning Data...\")\n\ncommon_ids = sorted(list(set(ids) & set(df_terms['EntryID'])))\n\nprint(f\"   ✓ Số lượng protein dùng để train: {len(common_ids)}\")\n\n\n\nif len(common_ids) == 0: raise ValueError(\"❌ Không tìm thấy ID chung!\")\n\n\n\n# Map ID -> Index\n\nid_map = {pid: i for i, pid in enumerate(ids)}\n\nfinal_map = {pid: i for i, pid in enumerate(common_ids)}\n\n\n\n# Tạo ma trận X, y\n\nINPUT_DIM = X_raw.shape[1]\n\nX_all = np.zeros((len(common_ids), INPUT_DIM), dtype=np.float32)\n\ny_all = np.zeros((len(common_ids), NUM_LABELS), dtype=np.float32)\n\n\n\nprint(\"   -> Building Feature Matrix X...\")\n\nfor i, pid in enumerate(common_ids):\n\n    X_all[i] = X_raw[id_map[pid]]\n\n\n\nprint(\"   -> Building Label Matrix y (Multi-label)...\")\n\ntop_terms = df_terms['term'].value_counts().head(NUM_LABELS).index.tolist()\n\nterm2idx = {t: i for i, t in enumerate(top_terms)}\n\nidx2term = {i: t for i, t in enumerate(top_terms)}\n\n\n\nrelevant = df_terms[df_terms['EntryID'].isin(common_ids) & df_terms['term'].isin(top_terms)]\n\nfor row in relevant.itertuples(index=False):\n\n    pid = getattr(row, 'EntryID', row[0])\n\n    term = getattr(row, 'term', row[1])\n\n    if pid in final_map and term in term2idx:\n\n        y_all[final_map[pid], term2idx[term]] = 1.0\n\n\n\n# 4. CHUẨN HÓA DỮ LIỆU (QUAN TRỌNG)\n\nprint(\"4️⃣ Applying StandardScaler...\")\n\nscaler = StandardScaler()\n\nX_all = scaler.fit_transform(X_all) # Đưa về Mean=0, Std=1\n\nprint(\"   ✓ Done! Dữ liệu đã được chuẩn hóa.\")\n\n\n\n# Dọn dẹp RAM\n\ndel X_raw, ids, df_terms, relevant\n\ngc.collect()\n\n\n\nprint(f\"✅ DATA READY: X={X_all.shape}, y={y_all.shape}\")\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-12T13:09:49.547702Z","iopub.execute_input":"2025-12-12T13:09:49.548038Z","iopub.status.idle":"2025-12-12T13:09:54.341057Z","shell.execute_reply.started":"2025-12-12T13:09:49.547999Z","shell.execute_reply":"2025-12-12T13:09:54.340107Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================================\n\n# CELL 3: MODEL (RESIDUAL MLP) & DATASET\n\n# ============================================================================\n\n\n\nclass TensorDataset(Dataset):\n\n    def __init__(self, X, y=None):\n\n        self.X = torch.tensor(X, dtype=torch.float32)\n\n        self.y = torch.tensor(y, dtype=torch.float32) if y is not None else None\n\n    def __len__(self): return len(self.X)\n\n    def __getitem__(self, idx):\n\n        if self.y is not None: return self.X[idx], self.y[idx]\n\n        return self.X[idx]\n\n\n\nclass ResidualBlock(nn.Module):\n\n    def __init__(self, dim, dropout=0.4): \n\n        super().__init__()\n\n        self.net = nn.Sequential(\n\n            nn.Linear(dim, dim),\n\n            nn.BatchNorm1d(dim), # BatchNorm cực quan trọng cho MLP sâu\n\n            nn.ReLU(),\n\n            nn.Dropout(dropout)\n\n        )\n\n    def forward(self, x): return x + self.net(x)\n\n\n\nclass ResMLP(nn.Module):\n\n    def __init__(self, inp_dim, out_dim):\n\n        super().__init__()\n\n        self.entry = nn.Sequential(\n\n            nn.Linear(inp_dim, 1024),\n\n            nn.BatchNorm1d(1024),\n\n            nn.ReLU(),\n\n            nn.Dropout(0.35)\n\n        )\n\n        self.blocks = nn.Sequential(\n\n            ResidualBlock(1024),\n\n            ResidualBlock(1024) # 2 khối Residual giúp model học sâu hơn\n\n        )\n\n        self.head = nn.Linear(1024, out_dim)\n\n    \n\n    def forward(self, x):\n\n        x = self.entry(x)\n\n        x = self.blocks(x)\n\n        return self.head(x)\n\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-12T13:09:54.341914Z","iopub.execute_input":"2025-12-12T13:09:54.34214Z","iopub.status.idle":"2025-12-12T13:09:54.351707Z","shell.execute_reply.started":"2025-12-12T13:09:54.34212Z","shell.execute_reply":"2025-12-12T13:09:54.350882Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================================\n\n# CELL 4: TRAINING FUNCTION (COSINE ANNEALING SCHEDULER)\n\n# ============================================================================\n\n\n\ndef train_single_seed(seed, X_tr, y_tr, X_va, y_va, save_path):\n\n    print(f\"\\n🎲 Training Seed: {seed}\")\n\n    torch.manual_seed(seed)\n\n    np.random.seed(seed)\n\n    \n\n    train_loader = DataLoader(TensorDataset(X_tr, y_tr), batch_size=BATCH_SIZE, shuffle=True)\n\n    val_loader = DataLoader(TensorDataset(X_va, y_va), batch_size=BATCH_SIZE, shuffle=False)\n\n    \n\n    model = ResMLP(INPUT_DIM, NUM_LABELS).to(device)\n\n    optimizer = optim.AdamW(model.parameters(), lr=LEARNING_RATE, weight_decay=1e-4) # Weight decay chống overfit\n\n    criterion = nn.BCEWithLogitsLoss()\n\n    \n\n    # CosineAnnealingLR: Giúp model \"nhảy\" ra khỏi local minima và hội tụ tốt hơn\n\n    scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=EPOCHS, eta_min=1e-5)\n\n    \n\n    best_loss = float('inf')\n\n    \n\n    for ep in range(EPOCHS):\n\n        model.train()\n\n        t_loss = 0\n\n        for fx, fy in train_loader:\n\n            fx, fy = fx.to(device), fy.to(device)\n\n            optimizer.zero_grad()\n\n            out = model(fx)\n\n            loss = criterion(out, fy)\n\n            loss.backward()\n\n            optimizer.step()\n\n            t_loss += loss.item()\n\n            \n\n        model.eval()\n\n        v_loss = 0\n\n        with torch.no_grad():\n\n            for fx, fy in val_loader:\n\n                v_loss += criterion(model(fx.to(device)), fy.to(device)).item()\n\n        \n\n        avg_v = v_loss/len(val_loader)\n\n        \n\n        if avg_v < best_loss:\n\n            best_loss = avg_v\n\n            torch.save(model.state_dict(), save_path)\n\n            \n\n        scheduler.step() # Cập nhật learning rate\n\n        \n\n        if (ep+1) % 5 == 0:\n\n            lr_curr = optimizer.param_groups[0]['lr']\n\n            print(f\"   Ep {ep+1}/{EPOCHS}: Train={t_loss/len(train_loader):.4f} | Val={avg_v:.4f} | LR={lr_curr:.6f}\")\n\n            \n\n    return best_loss\n\n\n\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-12T13:09:54.35259Z","iopub.execute_input":"2025-12-12T13:09:54.352917Z","iopub.status.idle":"2025-12-12T13:09:54.37694Z","shell.execute_reply.started":"2025-12-12T13:09:54.352887Z","shell.execute_reply":"2025-12-12T13:09:54.375875Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n# ============================================================================\n\n# CELL 5: CHẠY TRAINING (ENSEMBLE 5 SEEDS)\n\n# ============================================================================\n\nprint(\"\\n\" + \"=\"*60)\n\nprint(\"🚀 BƯỚC 2: TRAIN ENSEMBLE MODELS\")\n\nprint(\"=\"*60)\n\n\n\nos.makedirs('models_t5_final', exist_ok=True)\n\nmodel_paths = []\n\n\n\nfor seed in SEEDS:\n\n    X_tr, X_va, y_tr, y_va = train_test_split(X_all, y_all, test_size=0.1, random_state=seed)\n\n    path = f'models_t5_final/model_{seed}.pth'\n\n    train_single_seed(seed, X_tr, y_tr, X_va, y_va, path)\n\n    model_paths.append(path)\n\n    \n\n    del X_tr, X_va, y_tr, y_va\n\n    gc.collect()\n\n    torch.cuda.empty_cache()\n\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-12T13:09:54.378764Z","iopub.execute_input":"2025-12-12T13:09:54.379063Z","iopub.status.idle":"2025-12-12T13:51:59.001887Z","shell.execute_reply.started":"2025-12-12T13:09:54.379039Z","shell.execute_reply":"2025-12-12T13:51:59.000885Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================================\n\n# CELL 6: DỰ ĐOÁN & HẬU XỬ LÝ\n\n# ============================================================================\n\nprint(\"\\n\" + \"=\"*60)\n\nprint(\"🔮 BƯỚC 3: DỰ ĐOÁN & GO PROPAGATION\")\n\nprint(\"=\"*60)\n\n\n\n# 1. Load Test Data\n\nprint(\"Loading Test T5 Embeddings...\")\n\ntest_emb = np.load(os.path.join(EMBED_DIR, 'test_embeds.npy'))\n\ntest_ids = clean_func(np.load(os.path.join(EMBED_DIR, 'test_ids.npy')))\n\n\n\n# 2. Normalize Test Data (Dùng scaler từ tập train)\n\nprint(\"Applying StandardScaler to Test Data...\")\n\ntest_X = scaler.transform(test_emb)\n\n\n\n# 3. Predict Ensemble\n\nprint(\"Predicting with 5 models...\")\n\nmodels = [ResMLP(INPUT_DIM, NUM_LABELS).to(device) for _ in model_paths]\n\nfor i, m in enumerate(models):\n\n    m.load_state_dict(torch.load(model_paths[i]))\n\n    m.eval()\n\n\n\nTEMP_FILE = 'submission_raw.tsv'\n\nloader = DataLoader(TensorDataset(test_X), batch_size=BATCH_SIZE*2, shuffle=False)\n\n\n\nwith open(TEMP_FILE, 'w') as f:\n\n    start_idx = 0\n\n    with torch.no_grad():\n\n        for fx in loader:\n\n            fx = fx.to(device)\n\n            bs = fx.size(0)\n\n            \n\n            # Tính trung bình cộng xác suất của 5 models\n\n            avg_prob = torch.zeros((bs, NUM_LABELS)).to(device)\n\n            for m in models:\n\n                avg_prob += torch.sigmoid(m(fx))\n\n            avg_prob /= len(models)\n\n            avg_prob = avg_prob.cpu().numpy()\n\n            \n\n            for k in range(bs):\n\n                pid = test_ids[start_idx + k]\n\n                probs = avg_prob[k]\n\n                # Lấy Top 60 để dự phòng cho bước propagation\n\n                top_k = np.argsort(probs)[::-1][:60]\n\n                \n\n                for idx in top_k:\n\n                    sc = probs[idx]\n\n                    if sc > 0.001: # Threshold thấp để giữ thông tin\n\n                        f.write(f\"{pid}\\t{idx2term[idx]}\\t{sc:.3f}\\n\")\n\n            start_idx += bs\n\n\n\nprint(f\"✅ Raw Predictions Saved: {TEMP_FILE}\")\n\ndel test_X, models\n\ngc.collect()\n\n\n\n# 4. GO Hierarchy Propagation\n\nprint(\"Applying GO Propagation (Goatools)...\")\n\nFINAL_OUTPUT = 'submission.tsv'\n\n\n\ntry:\n\n    from goatools.obo_parser import GODag\n\n    if os.path.exists(OBO_PATH):\n\n        godag = GODag(OBO_PATH)\n\n        \n\n        def propagate(pid, df_grp):\n\n            scores = dict(zip(df_grp['Term'], df_grp['Score']))\n\n            new_scores = scores.copy()\n\n            for term, score in scores.items():\n\n                if term in godag:\n\n                    for parent in godag[term].get_all_parents():\n\n                        new_scores[parent] = max(new_scores.get(parent, 0), score)\n\n            return [[pid, t, s] for t, s in new_scores.items() if s >= 0.01]\n\n\n\n        df = pd.read_csv(TEMP_FILE, sep='\\t', names=['ProteinID', 'Term', 'Score'], dtype={'ProteinID': str})\n\n        final_rows = []\n\n        \n\n        # Iterator đơn giản\n\n        for pid, grp in df.groupby('ProteinID'):\n\n            final_rows.extend(propagate(pid, grp))\n\n            \n\n        res_df = pd.DataFrame(final_rows, columns=['ProteinID', 'Term', 'Score'])\n\n        res_df['Score'] = res_df['Score'].apply(lambda x: f\"{x:.3f}\")\n\n        res_df.to_csv(FINAL_OUTPUT, sep='\\t', index=False, header=False)\n\n        print(f\"✅ DONE! Final Submission: {FINAL_OUTPUT}\")\n\n    else:\n\n        print(\"⚠️ No OBO found. Using raw submission.\")\n\n        os.rename(TEMP_FILE, FINAL_OUTPUT)\n\nexcept Exception as e:\n\n    print(f\"⚠️ Error in propagation: {e}. Using raw submission.\")\n\n    if os.path.exists(TEMP_FILE): os.rename(TEMP_FILE, FINAL_OUTPUT)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-12T13:51:59.002779Z","iopub.execute_input":"2025-12-12T13:51:59.003034Z","iopub.status.idle":"2025-12-12T13:58:31.696499Z","shell.execute_reply.started":"2025-12-12T13:51:59.003014Z","shell.execute_reply":"2025-12-12T13:58:31.695319Z"}},"outputs":[],"execution_count":null}]}