{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[],"dockerImageVersionId":28755,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import warnings\nwarnings.filterwarnings(\"ignore\")\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\nimport matplotlib.pyplot as plt\nimport copy\nimport torch\nfrom PIL import Image\nfrom PIL import Image, ImageDraw\nfrom torch.utils.data import Dataset\nimport torchvision.transforms as transforms\nfrom torch.utils.data import random_split\nfrom torch.optim.lr_scheduler import ReduceLROnPlateau\nimport torch.nn as nn\nfrom torchvision import utils\n%matplotlib inline\n\nfrom sklearn.metrics import roc_auc_score\nfrom sklearn.model_selection import train_test_split\nfrom torch.utils.data import DataLoader\n\nimport os\nimport random\nimport torch.nn.functional as F","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-08-12T05:56:21.197053Z","iopub.execute_input":"2026-08-12T05:56:21.198390Z","iopub.status.idle":"2026-08-12T05:56:30.279015Z","shell.execute_reply.started":"2026-08-12T05:56:21.198347Z","shell.execute_reply":"2026-08-12T05:56:30.278413Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from pathlib import Path\n\nDATA_DIR = Path(\"../input/competitions/histopathologic-cancer-detection\")  # kaggle data folder\nTRAIN_LABELS_CSV = DATA_DIR / \"train_labels.csv\"\nTRAIN_IMG_DIR = DATA_DIR / \"train\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-12T05:56:38.649884Z","iopub.execute_input":"2026-08-12T05:56:38.650612Z","iopub.status.idle":"2026-08-12T05:56:38.654424Z","shell.execute_reply.started":"2026-08-12T05:56:38.650584Z","shell.execute_reply":"2026-08-12T05:56:38.653650Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"---\n\n# 1. EDA","metadata":{}},{"cell_type":"code","source":"df = pd.read_csv(TRAIN_LABELS_CSV)\nprint(f\"Sample Length: {len(df)}\")\nprint(df.head())\nprint(df.info())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-12T05:56:43.125945Z","iopub.execute_input":"2026-08-12T05:56:43.126631Z","iopub.status.idle":"2026-08-12T05:56:43.634196Z","shell.execute_reply.started":"2026-08-12T05:56:43.126602Z","shell.execute_reply":"2026-08-12T05:56:43.633364Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Class Counts & Ratio","metadata":{}},{"cell_type":"code","source":"class_counts = df[\"label\"].value_counts()\nclass_ratio = df[\"label\"].value_counts(normalize=True)\nprint(class_counts)\nprint(class_ratio)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-12T05:56:49.982385Z","iopub.execute_input":"2026-08-12T05:56:49.982777Z","iopub.status.idle":"2026-08-12T05:56:49.993458Z","shell.execute_reply.started":"2026-08-12T05:56:49.982748Z","shell.execute_reply":"2026-08-12T05:56:49.992684Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"fig, ax = plt.subplots(figsize=(5,4))\nclass_counts.plot(kind=\"bar\", ax=ax, color=[\"#4C72B0\", \"#DD8452\"])\nax.set_xticklabels([\"0 (Normal)\",\"1 ()\"], rotation = 0)\nax.set_title(\"Class Ratio\")\nax.set_ylabel(\"Sample counts\")\nfor i, v in enumerate(class_counts.values):\n    ax.text(i, v + 1000, f\"{v}\\n({class_ratio.values[i]*100:.1f}%)\", ha=\"center\")\nplt.tight_layout()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-12T05:56:50.920382Z","iopub.execute_input":"2026-08-12T05:56:50.920780Z","iopub.status.idle":"2026-08-12T05:56:51.152643Z","shell.execute_reply.started":"2026-08-12T05:56:50.920754Z","shell.execute_reply":"2026-08-12T05:56:51.151943Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Check Missing value","metadata":{}},{"cell_type":"code","source":"sample_ids = df[\"id\"].sample(min(2000,len(df)), random_state =42)\nmissing = []\nfor _id in sample_ids:\n    p = TRAIN_IMG_DIR / f\"{_id}.tif\"\n    if not p.exists():\n        missing.append(_id)\nprint(len(missing))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-12T05:56:54.387674Z","iopub.execute_input":"2026-08-12T05:56:54.388050Z","iopub.status.idle":"2026-08-12T05:56:59.453644Z","shell.execute_reply.started":"2026-08-12T05:56:54.388022Z","shell.execute_reply":"2026-08-12T05:56:59.452843Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Image Information","metadata":{}},{"cell_type":"code","source":"sizes = set()\nmodes = set()\npixel_min, pixel_max = 255, 0 \nfor _id in df[\"id\"].sample(1000, random_state=1):\n    img = Image.open(TRAIN_IMG_DIR/f\"{_id}.tif\")\n    arr = np.array(img)\n    sizes.add(img.size)\n    modes.add(img.mode)\n    pixel_max = max(pixel_max, arr.max())\n    pixel_min = min(pixel_min, arr.min())\nprint(\"Image Sizes: \", sizes)\nprint(f\"Image Mode: {modes}\")\nprint(f\"Pixel value Range: {pixel_min} ~ {pixel_max}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-12T05:57:08.580199Z","iopub.execute_input":"2026-08-12T05:57:08.580601Z","iopub.status.idle":"2026-08-12T05:57:16.872951Z","shell.execute_reply.started":"2026-08-12T05:57:08.580575Z","shell.execute_reply":"2026-08-12T05:57:16.872142Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Visualize","metadata":{}},{"cell_type":"code","source":"def show_samples(df, label, n=8):\n    subset = df[df[\"label\"] == label].sample(n, random_state=0)\n    fig, axes = plt.subplots(1, n, figsize=(n * 2, 2.5))\n    for ax, (_, row) in zip(axes, subset.iterrows()):\n        img = Image.open(TRAIN_IMG_DIR / f\"{row['id']}.tif\")\n        arr = np.array(img)\n        ax.imshow(arr)\n        # label: Center 32x32\n        h, w = arr.shape[0], arr.shape[1]\n        cx, cy = w // 2, h // 2\n        rect = plt.Rectangle((cx - 16, cy - 16), 32, 32, edgecolor=\"red\", facecolor=\"none\", linewidth=1.5)\n        ax.add_patch(rect)\n        ax.axis(\"off\")\n    fig.suptitle(f\"label = {label}\")\n    plt.tight_layout()\n \nshow_samples(df, 0)\nshow_samples(df, 1)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-12T05:57:20.218633Z","iopub.execute_input":"2026-08-12T05:57:20.219039Z","iopub.status.idle":"2026-08-12T05:57:21.111284Z","shell.execute_reply.started":"2026-08-12T05:57:20.219011Z","shell.execute_reply":"2026-08-12T05:57:21.110514Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"---\n\n# 2. BaselineCNN","metadata":{}},{"cell_type":"code","source":"torch.manual_seed(0)\n\nclass BaselineCNN(nn.Module):\n    def __init__(self, in_channels: int =3):\n        super().__init__()\n\n        def conv_block(in_c, out_c):\n            return nn.Sequential(\n                nn.Conv2d(in_c, out_c, kernel_size =3,padding=1),\n                nn.BatchNorm2d(out_c),\n                nn.ReLU(inplace=True),\n                nn.MaxPool2d(kernel_size=2),  # half resolution\n            )\n        \n        self.features = nn.Sequential(\n            conv_block(in_channels, 32),\n            conv_block(32, 64),\n            conv_block(64, 128),\n            conv_block(128, 256),\n        )\n\n        # after features: shape = (B, 256, 6, 6)\n\n        self.classifier = nn.Sequential(\n            nn.AdaptiveAvgPool2d(1),  # (B, 256, 1, 1) - Average of 6×6=36 values on each channel(256). Use Adaptive.\n            nn.Flatten(),  # (B, 256)\n            nn.Dropout(0.3), # prevent overfitting (automatically disabled on eval())\n            nn.Linear(256, 1), # (B, 1)\n        )\n\n    def forward(self, x):\n        x = self.features(x)\n        x=self.classifier(x)\n        return x.squeeze(1) # (B, 1) -> (B,)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-12T05:57:23.971234Z","iopub.execute_input":"2026-08-12T05:57:23.971683Z","iopub.status.idle":"2026-08-12T05:57:23.983498Z","shell.execute_reply.started":"2026-08-12T05:57:23.971655Z","shell.execute_reply":"2026-08-12T05:57:23.982905Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class Args:\n    epochs = 5\n    batch_size = 256\n    lr = 1e-3\n    val_size = 0.15\n    num_workers = 4\n    seed = 42\n\nargs = Args()\ntorch.manual_seed(args.seed)\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(f\"device: {device}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-12T05:57:25.118977Z","iopub.execute_input":"2026-08-12T05:57:25.119330Z","iopub.status.idle":"2026-08-12T05:57:25.383768Z","shell.execute_reply.started":"2026-08-12T05:57:25.119303Z","shell.execute_reply":"2026-08-12T05:57:25.382901Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df, val_df = train_test_split(df, test_size=args.val_size, stratify=df[\"label\"], random_state=args.seed)\nprint(f\"train: {len(train_df)}, val: {len(val_df)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-12T05:57:25.991535Z","iopub.execute_input":"2026-08-12T05:57:25.992112Z","iopub.status.idle":"2026-08-12T05:57:26.087693Z","shell.execute_reply.started":"2026-08-12T05:57:25.992078Z","shell.execute_reply":"2026-08-12T05:57:26.087027Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import time\nt0 = time.time()\n\nN = len(df)\ntrain_images = np.zeros((N, 96, 96, 3), dtype=np.uint8)\nfor i, _id in enumerate(df[\"id\"].values):\n    train_images[i] = np.array(Image.open(TRAIN_IMG_DIR / f\"{_id}.tif\").convert(\"RGB\"))\n    if i % 20000 == 0:\n        print(f\"{i}/{N}  {time.time()-t0:.0f}s\")\n\nprint(f\"cached {train_images.nbytes/1e9:.2f} GB in {time.time()-t0:.0f}s\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-12T05:58:34.496381Z","iopub.execute_input":"2026-08-12T05:58:34.496646Z","iopub.status.idle":"2026-08-12T06:18:30.155901Z","shell.execute_reply.started":"2026-08-12T05:58:34.496625Z","shell.execute_reply":"2026-08-12T06:18:30.155234Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Custom Dataset","metadata":{}},{"cell_type":"code","source":"class RandomD4:\n    \"\"\"90도 배수 회전 4개 × 반사 2개 = 이면군 D4. 텐서 입력.\"\"\"\n    def __call__(self, x):\n        k = random.randint(0, 3)\n        if k:\n            x = torch.rot90(x, k, dims=[1, 2])\n        if random.random() < 0.5:\n            x = torch.flip(x, dims=[2])\n        return x\n\n\nclass HistoDataset(Dataset):\n    def __init__(self, df: pd.DataFrame, img_dir: Path, transform=None, images=None):\n        self.pos = df.index.to_numpy()          # reset 전에 원본 위치 확보\n        self.labels = df[\"label\"].to_numpy(dtype=np.float32)\n        self.ids = df[\"id\"].to_numpy()\n        self.img_dir = Path(img_dir)\n        self.transform = transform or transforms.ToTensor()\n        self.images = images                     # None이면 기존처럼 디스크에서\n\n    def __len__(self):\n        return len(self.pos)\n\n    def __getitem__(self, idx):\n        if self.images is not None:\n            image = self.images[self.pos[idx]]   # (96,96,3) uint8\n        else:\n            image = np.array(\n                Image.open(self.img_dir / f\"{self.ids[idx]}.tif\").convert(\"RGB\")\n            )\n        image = self.transform(image)            # ToTensor가 ndarray HWC uint8 처리\n        return image, torch.tensor(self.labels[idx])\n\n\ndef get_transforms(mean=(0.5, 0.5, 0.5), std=(0.5, 0.5, 0.5)):\n    train_tf = transforms.Compose([\n        transforms.ToTensor(),                                   # 먼저 텐서로\n        transforms.ColorJitter(brightness=0.2, contrast=0.2,\n                               saturation=0.2, hue=0.05),\n        RandomD4(),\n        transforms.Normalize(mean=mean, std=std),\n    ])\n    val_tf = transforms.Compose([\n        transforms.ToTensor(),\n        transforms.Normalize(mean=mean, std=std),\n    ])\n    return train_tf, val_tf\n\n\ndef compute_mean_std(df: pd.DataFrame, img_dir: Path, sample_size: int = 5000):\n    sample_df = df.sample(min(sample_size, len(df)), random_state=42)\n    pixel_sum = np.zeros(3)\n    pixel_sq_sum = np.zeros(3)\n    n_pixels = 0\n\n    for _, row in sample_df.iterrows():\n        img = Image.open(Path(img_dir) / f\"{row['id']}.tif\").convert(\"RGB\")\n        arr = np.array(img).astype(np.float64) / 255.0\n        pixel_sum += arr.sum(axis=(0, 1))\n        pixel_sq_sum += (arr**2).sum(axis=(0, 1))\n        n_pixels += arr.shape[0] * arr.shape[1]\n\n    mean = pixel_sum / n_pixels\n    std = np.sqrt(pixel_sq_sum / n_pixels - mean**2)\n    return tuple(mean), tuple(std)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-12T06:34:24.015551Z","iopub.execute_input":"2026-08-12T06:34:24.015965Z","iopub.status.idle":"2026-08-12T06:34:24.027357Z","shell.execute_reply.started":"2026-08-12T06:34:24.015936Z","shell.execute_reply":"2026-08-12T06:34:24.026467Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\"\"\"\nmean, std = compute_mean_std(train_df, TRAIN_IMG_DIR, sample_size=10000)\nprint(f\"mean: {mean}\")\nprint(f\"std: {std}\")\n\ntrain_tf, val_tf = get_transforms(mean=mean, std=std)\n\"\"\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-30T10:02:40.470157Z","iopub.execute_input":"2026-07-30T10:02:40.470458Z","iopub.status.idle":"2026-07-30T10:03:50.616299Z","shell.execute_reply.started":"2026-07-30T10:02:40.470433Z","shell.execute_reply":"2026-07-30T10:03:50.615608Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\"\"\"\n\ntrain_ds= HistoDataset(train_df, TRAIN_IMG_DIR, transform=train_tf)\nval_ds = HistoDataset(val_df, TRAIN_IMG_DIR, transform =  val_tf)\n\ntrain_loader =  DataLoader( train_ds, batch_size=args.batch_size, shuffle= True, num_workers=args.num_workers, pin_memory=True)\nval_loader =  DataLoader( val_ds, batch_size=args.batch_size, shuffle= False, num_workers=args.num_workers, pin_memory=True)\n\"\"\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-30T10:12:50.993722Z","iopub.execute_input":"2026-07-30T10:12:50.994271Z","iopub.status.idle":"2026-07-30T10:12:51.001519Z","shell.execute_reply.started":"2026-07-30T10:12:50.994239Z","shell.execute_reply":"2026-07-30T10:12:51.000624Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Training","metadata":{}},{"cell_type":"code","source":"model= BaselineCNN().to(device)\ncriterion = nn.BCEWithLogitsLoss()\noptimizer = torch.optim.Adam(model.parameters(), lr=args.lr)\n\n@torch.no_grad()\ndef evaluate(model, loader, criterion, device):\n    model.eval()\n    running_loss = 0.0\n    all_labels, all_probs = [], []\n \n    for images, labels in loader:\n        images, labels = images.to(device), labels.to(device)\n        logits = model(images)\n        loss = criterion(logits, labels)\n        running_loss += loss.item() * images.size(0)\n \n        probs = torch.sigmoid(logits).cpu().numpy()\n        all_probs.extend(probs)\n        all_labels.extend(labels.cpu().numpy())\n \n    val_loss = running_loss / len(loader.dataset)\n    val_auc = roc_auc_score(all_labels, all_probs)\n    return val_loss, val_auc","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-12T06:34:49.185485Z","iopub.execute_input":"2026-08-12T06:34:49.185744Z","iopub.status.idle":"2026-08-12T06:34:49.510545Z","shell.execute_reply.started":"2026-08-12T06:34:49.185723Z","shell.execute_reply":"2026-08-12T06:34:49.509957Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\"\"\"\nbest_auc = 0.0\nfor epoch in range(1, args.epochs+1):\n    model.train()\n    running_loss = 0.0\n    for images, labels in train_loader:\n        images, labels = images.to(device), labels.to(device)\n        optimizer.zero_grad()\n        logits = model(images)\n        loss= criterion(logits, labels)\n        loss.backward()\n        optimizer.step()\n\n        running_loss +=loss.item()*images.size(0) # ( loss from each sample in batch) / batch_size * images.size(0) (multiply batch_size again)\n    train_loss = running_loss / len(train_loader.dataset)\n\n    val_loss, val_auc = evaluate(model, val_loader, criterion, device)\n \n    print(\n            f\"[epoch {epoch}/{args.epochs}] \"\n            f\"train_loss={train_loss:.4f}  val_loss={val_loss:.4f}  val_auc={val_auc:.4f}\"\n        )\n    if val_auc > best_auc:\n        best_auc = val_auc\n        torch.save(model.state_dict(), \"/kaggle/working/best_model.pt\")\n\nprint(f\"\\nComplete. best val_auc = {best_auc:.4f}\")\n\"\"\"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Test","metadata":{}},{"cell_type":"code","source":"class TestDataset(Dataset):\n\n    def __init__(self, ids, img_dir: Path, transform):\n        self.ids = list(ids)\n        self.img_dir = Path(img_dir)\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.ids)\n\n    def __getitem__(self, idx):\n        img_id = self.ids[idx]\n        image = Image.open(self.img_dir / f\"{img_id}.tif\").convert(\"RGB\")\n        image = self.transform(image)\n        return image, img_id\n\n@torch.no_grad()\ndef predict(model, loader, device):\n    model.eval()\n    all_ids, all_probs = [], []\n\n    for images, ids in loader:\n        images = images.to(device)\n        logits = model(images)\n        probs = torch.sigmoid(logits).cpu().numpy()\n        all_probs.extend(probs)\n        all_ids.extend(ids)\n\n    return all_ids, all_probs","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-12T06:34:51.517728Z","iopub.execute_input":"2026-08-12T06:34:51.518240Z","iopub.status.idle":"2026-08-12T06:34:51.524345Z","shell.execute_reply.started":"2026-08-12T06:34:51.518211Z","shell.execute_reply":"2026-08-12T06:34:51.523557Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\"\"\"\nclass Args_test:\n    batch_size = 256\n    mean = mean\n    std = std\n    num_workers = 0\n    model_path = \"/kaggle/working/best_model.pt\"\n    out_path = \"submission.csv\"\n\nargst = Args_test()\n\ntest_img_dir = DATA_DIR / \"test\"\nsubmission_df = pd.read_csv(DATA_DIR / \"sample_submission.csv\")\n\ntest_tf = transforms.Compose(\n    [\n        transforms.ToTensor(),\n        transforms.Normalize(mean=tuple(argst.mean), std=tuple(argst.std)),\n    ]\n)\n\ntest_ds = TestDataset(submission_df[\"id\"], test_img_dir, transform=test_tf)\ntest_loader = DataLoader(\n    test_ds,\n    batch_size=argst.batch_size,\n    shuffle=False,\n    num_workers=argst.num_workers,\n    pin_memory=True,\n)\n\nmodel = BaselineCNN().to(device)\nmodel.load_state_dict(torch.load(argst.model_path, map_location=device))\n\nids, probs = predict(model, test_loader, device)\n\nprob_map = dict(zip(ids, probs))\nsubmission_df[\"label\"] = submission_df[\"id\"].map(prob_map)\nsubmission_df.to_csv(argst.out_path, index=False)\n\"\"\"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 3. ResNet18","metadata":{}},{"cell_type":"code","source":"from torchvision import models\n\nclass ResNet18Binary(nn.Module):\n    def __init__(self, pretrained=True, freeze_backbone=False):\n        super().__init__()\n        weights = models.ResNet18_Weights.IMAGENET1K_V1 if pretrained else None    \n        self.backbone = models.resnet18(weights=weights)\n        self.backbone.maxpool = nn.Identity()   # 96px 입력\n        \n        if freeze_backbone:\n            for p in self.backbone.parameters():\n                p.requires_grad = False\n\n        # FC(1000-class) to 1-output logit\n        in_features = self.backbone.fc.in_features  # 512\n        self.backbone.fc = nn.Linear(in_features, 1)\n\n    def forward(self, x):\n        return self.backbone(x).squeeze(1)  # (B,1) -> (B,)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-12T06:35:20.313994Z","iopub.execute_input":"2026-08-12T06:35:20.314668Z","iopub.status.idle":"2026-08-12T06:35:20.320109Z","shell.execute_reply.started":"2026-08-12T06:35:20.314638Z","shell.execute_reply":"2026-08-12T06:35:20.319463Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"imagenet_mean = (0.485, 0.456, 0.406)\nimagenet_std  = (0.229, 0.224, 0.225)\n\ntrain_tf_r, val_tf_r = get_transforms(mean=imagenet_mean, std=imagenet_std)\n\ntrain_ds_r = HistoDataset(train_df, TRAIN_IMG_DIR, transform=train_tf_r, images=train_images)\nval_ds_r   = HistoDataset(val_df,   TRAIN_IMG_DIR, transform=val_tf_r,   images=train_images)\n\ntrain_loader_r = DataLoader(train_ds_r, batch_size=args.batch_size, shuffle=True,\n                            num_workers=args.num_workers, pin_memory=True)\nval_loader_r   = DataLoader(val_ds_r, batch_size=args.batch_size, shuffle=False,\n                            num_workers=args.num_workers, pin_memory=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-12T06:35:22.593254Z","iopub.execute_input":"2026-08-12T06:35:22.593644Z","iopub.status.idle":"2026-08-12T06:35:22.599740Z","shell.execute_reply.started":"2026-08-12T06:35:22.593616Z","shell.execute_reply":"2026-08-12T06:35:22.599071Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model_r = ResNet18Binary(pretrained=True, freeze_backbone=False).to(device)\ncriterion = nn.BCEWithLogitsLoss()\n\nEPOCHS_R = 15                             \n\ndecay, no_decay = [], []\nfor n, p in model_r.named_parameters():\n    if p.requires_grad:\n        (no_decay if p.ndim <= 1 else decay).append(p)   # bias/BN\n\noptimizer_r = torch.optim.AdamW(\n    [{\"params\": decay, \"weight_decay\": 1e-4},\n     {\"params\": no_decay, \"weight_decay\": 0.0}], lr=3e-4)\n\nscheduler = torch.optim.lr_scheduler.OneCycleLR(\n    optimizer_r, max_lr=1e-3,\n    total_steps=EPOCHS_R * len(train_loader_r), pct_start=0.1)\n\nscaler = torch.amp.GradScaler(\"cuda\")\nbest_auc_r = 0.0\n\nfor epoch in range(1, EPOCHS_R + 1):\n    model_r.train()\n    running_loss = 0.0\n    for images, labels in train_loader_r:\n        images = images.to(device, non_blocking=True)\n        labels = labels.to(device, non_blocking=True)\n\n        optimizer_r.zero_grad(set_to_none=True)\n        with torch.amp.autocast(\"cuda\"):\n            logits = model_r(images)\n            loss = criterion(logits, labels)\n        scaler.scale(loss).backward()\n        scaler.step(optimizer_r)\n        scaler.update()\n        scheduler.step()                      \n\n        running_loss += loss.item() * images.size(0)\n    train_loss = running_loss / len(train_loader_r.dataset)\n\n    val_loss, val_auc = evaluate(model_r, val_loader_r, criterion, device)\n    cur_lr = optimizer_r.param_groups[0][\"lr\"]\n    print(f\"[epoch {epoch}/{EPOCHS_R}] train_loss={train_loss:.4f} \"\n          f\"val_loss={val_loss:.4f} val_auc={val_auc:.4f} lr={cur_lr:.2e}\")\n\n    if val_auc > best_auc_r:\n        best_auc_r = val_auc\n        torch.save(model_r.state_dict(), \"/kaggle/working/best_resnet18.pt\")\n\nprint(f\"\\nComplete. best val_auc = {best_auc_r:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-12T06:35:25.570135Z","iopub.execute_input":"2026-08-12T06:35:25.570805Z","iopub.status.idle":"2026-08-12T07:36:57.251953Z","shell.execute_reply.started":"2026-08-12T06:35:25.570776Z","shell.execute_reply":"2026-08-12T07:36:57.251205Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class Args_test_r:\n    batch_size = 256\n    mean = (0.485, 0.456, 0.406)\n    std  = (0.229, 0.224, 0.225)\n    num_workers = 4\n    model_path = \"/kaggle/working/best_resnet18.pt\"\n    out_path = \"/kaggle/working/submission.csv\"\n    use_tta = True\n\nargstr = Args_test_r()\n\ntest_img_dir = DATA_DIR / \"test\"\nsubmission_df = pd.read_csv(DATA_DIR / \"sample_submission.csv\")\n\ntest_tf_r = transforms.Compose([\n    transforms.ToTensor(),\n    transforms.Normalize(mean=argstr.mean, std=argstr.std),\n])\n\ntest_ds_r = TestDataset(submission_df[\"id\"], test_img_dir, transform=test_tf_r)\ntest_loader_r = DataLoader(\n    test_ds_r,\n    batch_size=argstr.batch_size,\n    shuffle=False,\n    num_workers=argstr.num_workers,\n    pin_memory=True,\n)\n\nmodel_test = ResNet18Binary(pretrained=False).to(device)\nmodel_test.load_state_dict(torch.load(argstr.model_path, map_location=device))\nmodel_test.eval()\n\n@torch.no_grad()\ndef predict_tta(model, loader, device, use_tta=True):\n    model.eval()\n    all_ids, all_probs = [], []\n\n    for images, ids in loader:\n        images = images.to(device)\n\n        variants = []\n        for k in range(4):\n            r = torch.rot90(images, k, dims=[2, 3])\n            variants.append(r)\n            variants.append(torch.flip(r, dims=[3]))\n            \n        if use_tta:\n            variants += [\n                torch.flip(images, dims=[3]),        \n                torch.flip(images, dims=[2]),            \n                torch.flip(images, dims=[2, 3]),     \n                torch.rot90(images, k=1, dims=[2, 3]),  \n                torch.rot90(images, k=3, dims=[2, 3]),\n            ]\n\n        probs_sum = torch.zeros(images.size(0), device=device)\n        \n        with torch.amp.autocast(\"cuda\"):\n            for v in variants:\n                probs_sum += torch.sigmoid(model(v).float())\n\n        probs = (probs_sum / len(variants)).cpu().numpy()\n\n        all_probs.extend(probs)\n        all_ids.extend(ids)\n\n    return all_ids, all_probs\n\n\nids, probs = predict_tta(model_test, test_loader_r, device, use_tta=argstr.use_tta)\n\nprob_map = dict(zip(ids, probs))\nsubmission_df[\"label\"] = submission_df[\"id\"].map(prob_map)\n\nsubmission_df.to_csv(argstr.out_path, index=False)\nprint(submission_df.head())\nprint(submission_df[\"label\"].describe())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-12T07:37:27.589067Z","iopub.execute_input":"2026-08-12T07:37:27.589546Z","iopub.status.idle":"2026-08-12T07:40:35.804029Z","shell.execute_reply.started":"2026-08-12T07:37:27.589512Z","shell.execute_reply":"2026-08-12T07:40:35.803239Z"}},"outputs":[],"execution_count":null}]}