{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":112899,"databundleVersionId":13449579,"sourceType":"competition"},{"sourceId":258103957,"sourceType":"kernelVersion"},{"sourceId":259146858,"sourceType":"kernelVersion"},{"sourceId":259302152,"sourceType":"kernelVersion"},{"sourceId":260834421,"sourceType":"kernelVersion"},{"sourceId":260897311,"sourceType":"kernelVersion"}],"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os, sys, math, warnings\nimport numpy as np\nimport pandas as pd\n\nwarnings.filterwarnings(\"ignore\")\n\n# ====== 1) Khai báo đường dẫn submissions ======\n# Lưu ý: bạn đưa 1 dòng bị dính 2 path, mình tách sẵn ra dưới đây.\nSUB_PATHS = [\n    \"/kaggle/input/x-ray/submission.csv\",\n    \"/kaggle/input/pre-trained-fine-tuning-for-x-rays/sample_submission.csv\",\n    \"/kaggle/input/kaggle-chest-x-ray-submission-notebook/submission.csv\",\n    \"/kaggle/input/vit-imageaug-gput42/submission.csv\",\n    \"/kaggle/input/grand-xray-slam-division-a-tpu/submission.csv\",\n]\n\n# ====== 2) Định nghĩa cột ID và nhãn (khớp code train bạn đưa) ======\nID_CANDIDATES = [\"Image_name\", \"image_name\", \"id\", \"ID\", \"image_id\"]\nTARGET_LABELS = [\n    'Atelectasis', 'Cardiomegaly', 'Consolidation', 'Edema',\n    'Enlarged Cardiomediastinum', 'Fracture', 'Lung Lesion',\n    'Lung Opacity', 'No Finding', 'Pleural Effusion', 'Pleural Other',\n    'Pneumonia', 'Pneumothorax', 'Support Devices'\n]\n\n# ====== 3) Helpers ======\ndef _find_id_col(df: pd.DataFrame):\n    for c in ID_CANDIDATES:\n        if c in df.columns:\n            return c\n    # fallback: chọn cột đầu tiên\n    return df.columns[0]\n\ndef _clip01(x):\n    return np.clip(x, 1e-6, 1-1e-6)\n\ndef _to_logit(p):\n    p = _clip01(p)\n    return np.log(p/(1-p))\n\ndef _from_logit(z):\n    return 1/(1+np.exp(-z))\n\ndef _safe_read_csv(p):\n    try:\n        if os.path.exists(p):\n            df = pd.read_csv(p)\n            return df\n        else:\n            print(f\"[WARN] File không tồn tại: {p}\")\n            return None\n    except Exception as e:\n        print(f\"[WARN] Lỗi đọc {p}: {e}\")\n        return None\n\ndef _ensure_numeric(df, cols):\n    for c in cols:\n        if c in df.columns:\n            df[c] = pd.to_numeric(df[c], errors='coerce')\n    return df\n\n# ====== 4) Load tất cả submissions hợp lệ ======\nloaded = []\nfor p in SUB_PATHS:\n    df = _safe_read_csv(p)\n    if df is None:\n        continue\n    id_col = _find_id_col(df)\n\n    # Chuẩn hóa ID để tránh mismatch ký tự trắng/dtype\n    if id_col in df.columns:\n        df[id_col] = df[id_col].astype(str).str.strip()\n\n    # Giữ lại ID + các label cần\n    keep_cols = [id_col] + [c for c in TARGET_LABELS if c in df.columns]\n    df = df[keep_cols].copy()\n    df = _ensure_numeric(df, [c for c in TARGET_LABELS if c in df.columns])\n\n    # báo nhanh tình trạng\n    missing = [c for c in TARGET_LABELS if c not in df.columns]\n    if missing:\n        print(f\"[INFO] {os.path.basename(p)} thiếu {len(missing)} nhãn (bỏ qua các cột này): {missing[:4]}{'...' if len(missing)>4 else ''}\")\n\n    # đảm bảo id đứng đầu\n    df.columns = [id_col] + [c for c in df.columns if c != id_col]\n    df.attrs['id_col'] = id_col\n    df.attrs['path'] = p\n    loaded.append(df)\n\nif len(loaded) < 2:\n    raise RuntimeError(\"Cần >= 2 submission hợp lệ để ensemble.\")\n\nprint(f\"[INFO] Đã nạp {len(loaded)} submissions hợp lệ.\")\n\n# ====== 5) Chọn submission gốc làm chuẩn thứ tự ID ======\n# Ưu tiên file có đủ nhiều nhãn\nloaded_sorted = sorted(loaded, key=lambda d: sum([c in d.columns for c in TARGET_LABELS]), reverse=True)\nbase_df = loaded_sorted[0]\nbase_id = base_df.attrs['id_col']\nprint(f\"[INFO] Dùng {os.path.basename(base_df.attrs['path'])} làm chuẩn thứ tự ID (id_col='{base_id}').\")\n\n# ====== 6) Inner-join theo ID để chắc chắn các hàng trùng nhau (FIXED) ======\ndef project(df, id_col, cols):\n    cols_use = [c for c in cols if c in df.columns]\n    out = df[[id_col] + cols_use].copy()\n    out[id_col] = out[id_col].astype(str).str.strip()\n    return out\n\n# giao nhãn thật sự có ở ít nhất 2 file\nlabel_presence = {c: 0 for c in TARGET_LABELS}\nfor d in loaded:\n    for c in TARGET_LABELS:\n        if c in d.columns:\n            label_presence[c] += 1\nusable_labels = [c for c, v in label_presence.items() if v >= 2]\nif not usable_labels:\n    raise RuntimeError(\"Không có nhãn giao nhau giữa các submissions.\")\n\nprint(f\"[INFO] Số nhãn dùng để ensemble: {len(usable_labels)} / {len(TARGET_LABELS)}\")\n\n# tạo bảng hợp nhất theo ID chuẩn\nmerged = base_df[[base_id]].copy()\nmerged[base_id] = merged[base_id].astype(str).str.strip()\n\nfor i, d in enumerate(loaded):\n    did = d.attrs['id_col']\n    # đổi tên cột nhãn để tránh đụng tên (thêm hậu tố m{i})\n    rename_map = {c: f\"{c}__m{i}\" for c in usable_labels if c in d.columns}\n    dd = project(d, did, usable_labels).rename(columns=rename_map)\n\n    # merge theo ID; nếu id cùng tên với base_id thì KHÔNG drop base_id\n    if did == base_id:\n        merged = merged.merge(dd, on=base_id, how='inner')\n    else:\n        merged = merged.merge(dd, left_on=base_id, right_on=did, how='inner')\n        # chỉ drop cột right key khi nó KHÁC base_id\n        if did != base_id and did in merged.columns:\n            merged.drop(columns=[did], inplace=True)\n\nif merged.shape[0] == 0:\n    raise RuntimeError(\"Không còn bản ghi chung giữa các submissions sau khi join.\")\n\nprint(f\"[INFO] Số ảnh sau khi align: {merged.shape[0]}\")\n\n# ====== 7) Tạo các tensor xác suất theo từng nhãn ======\nmodel_count = len(loaded)\nper_label_arrays = {}  # label -> (N, M)\nN = merged.shape[0]\n\nfor c in usable_labels:\n    cols_c = [f\"{c}__m{i}\" for i in range(model_count) if f\"{c}__m{i}\" in merged.columns]\n    if len(cols_c) == 0:\n        continue\n    arr = merged[cols_c].astype(float).to_numpy()\n    # clip để tránh logit inf\n    arr = _clip01(arr)\n    per_label_arrays[c] = arr\n\n# ====== 8) Các phương pháp ensemble ======\ndef ensemble_mean():\n    out = pd.DataFrame({base_id: merged[base_id].values})\n    for c, arr in per_label_arrays.items():\n        out[c] = arr.mean(axis=1)\n    return out\n\ndef ensemble_logit_mean():\n    out = pd.DataFrame({base_id: merged[base_id].values})\n    for c, arr in per_label_arrays.items():\n        z = _to_logit(arr)\n        out[c] = _from_logit(z.mean(axis=1))\n    return out\n\ndef ensemble_rank_average():\n    out = pd.DataFrame({base_id: merged[base_id].values})\n    for c, arr in per_label_arrays.items():\n        M = arr.shape[1]\n        # rank theo cột (mỗi model), rồi chuẩn hóa về [0,1]\n        ranks = np.zeros_like(arr, dtype=float)\n        for j in range(M):\n            order = arr[:, j].argsort()\n            r = np.empty_like(order, dtype=float)\n            r[order] = np.arange(len(order))\n            r = r / (len(order)-1 if len(order) > 1 else 1)\n            ranks[:, j] = r\n        out[c] = ranks.mean(axis=1)\n    return out\n\n# ====== 9) Sinh ba file blend ======\nblend_mean = ensemble_mean()\nblend_logit = ensemble_logit_mean()\nblend_rank = ensemble_rank_average()\n\n# Bảo toàn tất cả label gốc: nếu label nào không usable thì điền 0.0\ndef finalize_columns(df_out):\n    # giữ nguyên thứ tự ID như base_df\n    df_out = base_df[[base_id]].merge(df_out, on=base_id, how='left')\n    for c in TARGET_LABELS:\n        if c not in df_out.columns:\n            df_out[c] = 0.0\n    # sắp cột: ID + labels\n    df_out = df_out[[base_id] + TARGET_LABELS]\n    # điền missing, ép float và clip\n    for c in TARGET_LABELS:\n        df_out[c] = df_out[c].fillna(0.0).astype(float)\n        df_out[c] = _clip01(df_out[c])\n    return df_out\n\nblend_mean  = finalize_columns(blend_mean)\nblend_logit = finalize_columns(blend_logit)\nblend_rank  = finalize_columns(blend_rank)\n\nblend_mean.to_csv(\"blend_mean.csv\", index=False)\nblend_logit.to_csv(\"blend_logit.csv\", index=False)\nblend_rank.to_csv(\"blend_rank.csv\", index=False)\n\nprint(\"[DONE] Đã tạo: blend_mean.csv, blend_logit.csv, blend_rank.csv\")\nprint(\"Khuyến nghị nộp: blend_logit.csv (logit-mean thường ổn định nhất cho multi-label).\")\n","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"outputs":[],"execution_count":null}]}