{"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":14152050,"sourceType":"datasetVersion","datasetId":9003439}],"dockerImageVersionId":31193,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport gc\nimport sys\nimport os\nfrom collections import defaultdict\n\n# ==================================================================================\n# 1. CẤU HÌNH & ĐƯỜNG DẪN\n# ==================================================================================\ndef find_file(filename, search_paths=['.', '/kaggle/working/', '/kaggle/input/cafa6-submission/']):\n    \"\"\"Tìm file trong các thư mục phổ biến\"\"\"\n    for path in search_paths:\n        full_path = os.path.join(path, filename)\n        if os.path.exists(full_path): return full_path\n    return None\n\n# --- CẬP NHẬT TÊN FILE THEO YÊU CẦU ---\n# File chính (Main Model) - Tin tưởng nhất\nFILE_1 = find_file('ESM_submission_0.314.tsv')\n\n# Các file phụ trợ (Support Models)\nFILE_2 = find_file('ESM_submission _275.tsv')    # File ESM thứ 2\nFILE_3 = find_file('KNN_submission_153.tsv')    # File KNN\nFILE_4 = find_file('TF-TDF_submission_159.tsv') # File TF-IDF (Thay cho HM cũ)\n\n# File OBO (Cấu trúc Gene Ontology)\nFILE_OBO = '/kaggle/input/cafa-6-protein-function-prediction/Train/go-basic.obo'\nOUTPUT_FILE = 'submission.tsv'\n\n# --- TRỌNG SỐ ENSEMBLE ---\nW_MAIN    = 0.8   # Giảm nhẹ W_MAIN vì ESM_314 đã rất mạnh nhưng cần hỗ trợ\nW_SUPPORT = 0.2   # Tăng W_SUPPORT để tận dụng đa dạng mô hình\n\n# Trọng số trong nhóm Support (Tổng = 1.0)\nW_SUB_ESM2 = 0.50  # ESM_275\nW_SUB_KNN  = 0.30  # KNN\nW_SUB_TF   = 0.20  # TF-IDF\n\nPOWER_FACTOR = 1.15 \nMIN_SCORE    = 0.001 \n\n# ==================================================================================\n# 2. CÁC HÀM XỬ LÝ GO & DỮ LIỆU\n# ==================================================================================\ndef load_go_graph(obo_file):\n    \"\"\"Đọc file OBO để lấy quan hệ cha-con\"\"\"\n    print(f\">>> Đang tải đồ thị GO từ {os.path.basename(obo_file)}...\")\n    parents = defaultdict(set)\n    try:\n        with open(obo_file, 'r') as f:\n            term_id = \"\"\n            for line in f:\n                line = line.strip()\n                if line.startswith(\"id: GO:\"):\n                    term_id = line[4:]\n                elif line.startswith(\"is_a: GO:\") and term_id:\n                    parents[term_id].add(line.split()[1].split('!')[0])\n        return parents\n    except Exception as e:\n        print(f\"Lỗi đọc OBO: {e}\")\n        return None\n\ndef get_ancestors(term, parents_map, cache):\n    \"\"\"Tìm tổ tiên đệ quy có caching\"\"\"\n    if term in cache: return cache[term]\n    ancestors = set(parents_map.get(term, []))\n    for parent in list(ancestors):\n        ancestors.update(get_ancestors(parent, parents_map, cache))\n    cache[term] = ancestors\n    return ancestors\n\ndef load_clean(path, w=1.0, names=None, dtypes=None):\n    \"\"\"Load và làm sạch file submission\"\"\"\n    if not path or not os.path.exists(path): \n        print(f\"⚠️ Không tìm thấy file: {path}\")\n        return pd.DataFrame(columns=names)\n    \n    print(f\"Đang đọc: {os.path.basename(path)} (Trọng số: {w})...\")\n    df = pd.read_csv(path, sep='\\t', header=None, names=names, dtype=dtypes)\n    \n    # Chuẩn hóa dữ liệu\n    df['Protein'] = df['Protein'].str.strip()\n    df = df[df['Score'] >= 0.001] # Lọc rác\n    df['Score'] *= w              # Nhân trọng số ngay khi load\n    return df\n\n# ==================================================================================\n# 3. CHƯƠNG TRÌNH CHÍNH\n# ==================================================================================\ndef main():\n    dtypes = {0: 'str', 1: 'str', 2: 'float32'}\n    names = ['Protein', 'GO_Term', 'Score']\n\n    print(\"=== BẮT ĐẦU ENSEMBLE ===\")\n    \n    # --- BƯỚC 1: XÂY DỰNG LỚP HỖ TRỢ (SUPPORT LAYER) ---\n    print(\"\\n>>> [1/4] Xây dựng lớp Support (ESM_2 + KNN + TF-IDF)...\")\n    \n    # Load và gộp 3 file phụ\n    df_esm2 = load_clean(FILE_2, W_SUB_ESM2, names, dtypes)\n    df_knn  = load_clean(FILE_3, W_SUB_KNN, names, dtypes)\n    df_tf   = load_clean(FILE_4, W_SUB_TF, names, dtypes)\n    \n    # Cộng dồn điểm số của nhóm Support\n    # Score_Support = (0.5 * ESM2) + (0.3 * KNN) + (0.2 * TF-IDF)\n    df_support = pd.concat([df_esm2, df_knn, df_tf], copy=False)\n    df_support = df_support.groupby(['Protein', 'GO_Term'], as_index=False)['Score'].sum()\n    \n    del df_esm2, df_knn, df_tf\n    gc.collect()\n\n    # --- BƯỚC 2: TỔNG HỢP VỚI FILE CHÍNH ---\n    print(\"\\n>>> [2/4] Tổng hợp: Main (60%) + Support (40%)...\")\n    \n    df_main = load_clean(FILE_1, 1.0, names, dtypes) # Load nguyên bản\n    \n    # Áp dụng trọng số cấp cao\n    df_main['Score'] *= W_MAIN\n    df_support['Score'] *= W_SUPPORT\n    \n    # Final = Main + Support\n    final_df = pd.concat([df_main, df_support], ignore_index=True)\n    final_df = final_df.groupby(['Protein', 'GO_Term'], as_index=False)['Score'].sum()\n    \n    # Cắt trần điểm số (không quá 1.0)\n    final_df['Score'] = final_df['Score'].clip(0.0, 1.0)\n    \n    del df_main, df_support\n    gc.collect()\n\n    # --- BƯỚC 3: LAN TRUYỀN (PROPAGATION) ---\n    print(\"\\n>>> [3/4] Lan truyền điểm số theo cấu trúc GO...\")\n    parents_map = load_go_graph(FILE_OBO)\n    \n    if parents_map:\n        # Chuyển DataFrame sang Dict để xử lý nhanh hơn\n        print(\"   -> Chuyển đổi dữ liệu...\")\n        # Chỉ xử lý những dòng có điểm đáng kể để tiết kiệm thời gian\n        data_clean = final_df[final_df['Score'] >= 0.005]\n        protein_data = defaultdict(dict)\n        for p, g, s in zip(data_clean['Protein'], data_clean['GO_Term'], data_clean['Score']):\n            protein_data[p][g] = s\n        \n        del final_df, data_clean\n        gc.collect()\n\n        print(\"   -> Đang lan truyền (Propagating)...\")\n        new_rows = []\n        ancestors_cache = {}\n        sys.setrecursionlimit(10000)\n        \n        # Duyệt từng Protein\n        for i, (protein, terms_dict) in enumerate(protein_data.items()):\n            propagated = terms_dict.copy()\n            \n            # Với mỗi term dự đoán được\n            for term, score in terms_dict.items():\n                if score < 0.05: continue # Bỏ qua điểm quá thấp để tối ưu\n                \n                # Lan truyền lên cha (Ancestors)\n                ancestors = get_ancestors(term, parents_map, ancestors_cache)\n                for ancestor in ancestors:\n                    # Quy tắc: Điểm cha = Max(Điểm cha cũ, Điểm con hiện tại)\n                    if score > propagated.get(ancestor, 0.0):\n                        propagated[ancestor] = score\n            \n            # Lưu kết quả\n            for t, s in propagated.items():\n                new_rows.append((protein, t, s))\n            \n            if i % 10000 == 0: print(f\"      Đã xử lý {i} protein...\", end='\\r')\n\n        df_propagated = pd.DataFrame(new_rows, columns=names)\n        print(f\"\\n      Hoàn tất lan truyền. Số dòng dữ liệu: {len(df_propagated)}\")\n    else:\n        print(\"⚠️ Không tìm thấy file OBO, bỏ qua bước lan truyền.\")\n        df_propagated = final_df\n\n    # --- BƯỚC 4: HIỆU CHỈNH & LƯU FILE ---\n    print(\"\\n>>> [4/4] Hiệu chỉnh & Lưu file...\")\n    \n    # 1. Hiệu chỉnh (Calibration)\n    # Giữ nguyên điểm rất cao (>=0.99)\n    df_high = df_propagated[df_propagated['Score'] >= 0.99].copy()\n    df_high['Score'] = 1.0\n    \n    # Giảm nhẹ điểm thấp để giảm False Positive (Mũ > 1 sẽ làm số nhỏ bé đi)\n    df_low = df_propagated[df_propagated['Score'] < 0.99].copy()\n    df_low['Score'] = np.power(df_low['Score'], POWER_FACTOR)\n    \n    final_df = pd.concat([df_high, df_low], ignore_index=True)\n    \n    # 2. Lọc & Định dạng\n    final_df = final_df[final_df['Score'] >= MIN_SCORE]\n    final_df['Score'] = final_df['Score'].round(3)\n    \n    # 3. Sắp xếp đúng chuẩn CAFA\n    final_df.sort_values(by=['Protein', 'Score'], ascending=[True, False], inplace=True)\n    \n    # 4. Lưu\n    final_df.to_csv(OUTPUT_FILE, sep='\\t', header=False, index=False)\n    print(f\"✅ THÀNH CÔNG! File đã lưu tại: {OUTPUT_FILE}\")\n    print(f\"   Dung lượng file: {os.path.getsize(OUTPUT_FILE) / (1024*1024):.2f} MB\")\n\nif __name__ == \"__main__\":\n    main()","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-12-14T13:22:21.074099Z","iopub.execute_input":"2025-12-14T13:22:21.074654Z","iopub.status.idle":"2025-12-14T13:26:27.313499Z","shell.execute_reply.started":"2025-12-14T13:22:21.074626Z","shell.execute_reply":"2025-12-14T13:26:27.312812Z"}},"outputs":[],"execution_count":null}]}