{"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":37333,"databundleVersionId":3949526,"sourceType":"competition"}],"dockerImageVersionId":31089,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# ==============================================\n# Mayo-Clinic-STRIP-AI | ResNet-18 embedding → SVM baseline\n# ==============================================\n!pip install -q timm tqdm\n\nfrom pathlib import Path\nfrom collections import defaultdict\nimport random, warnings, gc\nimport numpy as np, pandas as pd\nimport cv2, tifffile, torch\nfrom torch.utils.data import Dataset, DataLoader\nimport albumentations as A; import albumentations.pytorch\nimport timm\nfrom tqdm.auto import tqdm\nfrom sklearn.preprocessing import StandardScaler\nfrom sklearn.decomposition import PCA\nfrom sklearn.pipeline import Pipeline\nfrom sklearn.svm import SVC\nfrom sklearn.metrics import roc_auc_score\n\nwarnings.filterwarnings(\"ignore\")\n\n# ------------------ CONFIG ------------------\nclass CFG:\n    TILE_SIZE  = 448        # 小一点，加速 & 省显存\n    BATCH      = 16\n    NUM_WK     = 0         # worker 数\n    SEED       = 42\n    DEBUG      = True\n    DEBUG_FRAC = 0.5        # ← 0.5 × 数据\n    DEVICE     = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n\nrandom.seed(CFG.SEED); np.random.seed(CFG.SEED); torch.manual_seed(CFG.SEED)\n\nDATA = Path(\"/kaggle/input/mayo-clinic-strip-ai\")\ntrain_csv = pd.read_csv(DATA/\"train.csv\")\ntest_csv  = pd.read_csv(DATA/\"test.csv\")\n\n# ---------- DEBUG 抽样 ----------\nif CFG.DEBUG:\n    pats = train_csv.patient_id.unique()\n    sel  = np.random.RandomState(CFG.SEED).choice(\n        pats, size=int(len(pats)*CFG.DEBUG_FRAC), replace=False)\n    train_csv = train_csv[train_csv.patient_id.isin(sel)]\n    print(f\"[DEBUG] {len(train_csv)} tiles  ({train_csv.patient_id.nunique()} patients)\")\n\nlabel_map = {l:i for i,l in enumerate(sorted(train_csv.label.unique()))}\ntrain_csv[\"label_id\"] = train_csv.label.map(label_map)\n\n# simple 80 / 20 patient split\nval_pat = (train_csv.groupby(\"patient_id\").first()\n           .sample(frac=0.2, random_state=CFG.SEED).index)\ntrain_df = train_csv[~train_csv.patient_id.isin(val_pat)].reset_index(drop=True)\nval_df   = train_csv[ train_csv.patient_id.isin(val_pat)].reset_index(drop=True)\n\n# ---------- Dataset ----------\ntfm = A.Compose([\n    A.Resize(CFG.TILE_SIZE, CFG.TILE_SIZE),\n    A.Normalize((0.485,0.456,0.406),(0.229,0.224,0.225)),\n    A.pytorch.ToTensorV2(),\n])\n\nclass TileDS(Dataset):\n    def __init__(self, df, split):\n        self.df, self.split = df, split\n    def __len__(self): return len(self.df)\n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        folder = \"train\" if \"label_id\" in row else \"test\"\n        img = tifffile.imread(DATA/folder/f\"{row.image_id}.tif\")\n        if img.ndim==2: img=cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)\n        img = tfm(image=img)[\"image\"]\n        y   = row.label_id if \"label_id\" in row else -1\n        return img.float(), y, row.patient_id\n\ndef loader(df, split):\n    return DataLoader(TileDS(df, split),\n                      batch_size=CFG.BATCH, shuffle=False,\n                      num_workers=CFG.NUM_WK)\n\n# ---------- ResNet-18 embedder ----------\nfe = timm.create_model(\"resnet18\", pretrained=True,\n                       num_classes=0, global_pool=\"avg\").to(CFG.DEVICE).eval()\n\n@torch.no_grad()\ndef get_embed(dl):\n    feat, y, pid = [], [], []\n    for x, lbl, p in tqdm(dl, leave=False):\n        f = fe(x.to(CFG.DEVICE,non_blocking=True)).cpu().numpy()\n        feat.append(f); y.extend(lbl.numpy()); pid.extend(p)\n    return np.vstack(feat), np.array(y), np.array(pid)\n\ndef pool_mean(feats, labels, pids):\n    bag = defaultdict(list)\n    for f,l,p in zip(feats,labels,pids): bag[p].append((f,l))\n    X, y, ids = [], [], []\n    for p,lst in bag.items():\n        vec,_y = zip(*lst)\n        X.append(np.mean(vec,0)); y.append(_y[0]); ids.append(p)\n    return np.vstack(X), np.array(y), np.array(ids)\n\n# ---------- extract embeddings ----------\nprint(\"⏳ embeddings …\")\nX_tr,y_tr,_ = pool_mean(*get_embed(loader(train_df,\"train\")))\nX_va,y_va,_ = pool_mean(*get_embed(loader(val_df,\"train\")))\n\n# ---------- PCA 128 → 线性 SVM ----------\npipe = Pipeline([\n    (\"scaler\", StandardScaler(with_mean=False)),\n    (\"pca\",    PCA(n_components=128, whiten=True, random_state=CFG.SEED)),\n    (\"svm\",    SVC(kernel=\"linear\", probability=True,\n                   class_weight=\"balanced\", C=1.0, random_state=CFG.SEED)),\n])\n\npipe.fit(X_tr, y_tr)\npred_va = pipe.predict_proba(X_va)[:,1]\nprint(f\"Val AUC = {roc_auc_score(y_va, pred_va):.3f}\")\n\n# ---------- test ----------\nprint(\"⏳ test embedding …\")\nX_test, _, pid_test = pool_mean(*get_embed(loader(test_csv,\"test\")))\nprob_laa = pipe.predict_proba(X_test)[:,1]\n\nbag = defaultdict(list)\nfor p,pr in zip(pid_test, prob_laa): bag[p].append(pr)\n\nsub = pd.DataFrame({\n    \"patient_id\": list(bag.keys()),\n    \"LAA\": [np.mean(v) for v in bag.values()]\n})\nsub[\"CE\"] = 1 - sub[\"LAA\"]\nsub[[\"CE\",\"LAA\"]] = sub[[\"CE\",\"LAA\"]].clip(1e-15,1-1e-15)\nsub = sub[[\"patient_id\",\"CE\",\"LAA\"]]\nsub.to_csv(\"submission.csv\", index=False)\nprint(\"✅ submission.csv saved!\", sub.head())","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-08-04T17:33:07.186784Z","iopub.execute_input":"2025-08-04T17:33:07.187076Z","iopub.status.idle":"2025-08-04T18:53:37.733434Z"}},"outputs":[],"execution_count":null}]}