{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceType":"competition","sourceId":14774,"databundleVersionId":875431}],"dockerImageVersionId":31287,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport subprocess\nimport random\nfrom pathlib import Path\n\nimport numpy as np\nimport pandas as pd\nimport cv2\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader\nimport torchvision.transforms as T\nfrom tqdm.notebook import tqdm\n\n# ── 1. 외부 라이브러리 설치 및 임포트 ──────────────────────────\ntry:\n    import timm\nexcept ImportError:\n    # PyTorch 이미지 모델 라이브러리(timm)가 없을 경우 설치\n    subprocess.run(['pip', 'install', '-q', 'timm'], check=False)\n    import timm\n\n# ── 2. 데이터 경로 자동 탐색 함수 ──────────────────────────────\ndef find_data_dir(root_path, target_name='aptos'):\n    \"\"\"\n    Kaggle input 폴더 내에서 'aptos'가 포함되고 train.csv가 존재하는 경로를 탐색\n    \"\"\"\n    for root, dirs, files in os.walk(root_path):\n        for d in dirs:\n            if target_name in d.lower():\n                candidate = Path(root) / d\n                if (candidate / 'train.csv').exists():\n                    return candidate\n    return None\n\n# 경로 설정\nINPUT_ROOT = Path('/kaggle/input')\nDATA_DIR = find_data_dir(INPUT_ROOT)\n\nif DATA_DIR is None:\n    raise FileNotFoundError(\"데이터셋 폴더를 찾을 수 없습니다. Add Input을 통해 데이터를 추가해주세요.\")\n\nTRAIN_IMG_DIR = DATA_DIR / 'train_images'\nTEST_IMG_DIR = DATA_DIR / 'test_images'\n\n# ── 3. 하이퍼파라미터 및 환경 설정 ────────────────────────────\nCFG = {\n    'IMG_SIZE': 380,    # 입력 이미지 크기 (EfficientNet-B4 권장 크기)\n    'BATCH_SIZE': 8,    # 배치 크기 (메모리 사정에 따라 조절)\n    'EPOCHS': 5,        # 학습 횟수\n    'LR': 1e-4,         # 학습률\n    'SEED': 42          # 재현성을 위한 시드값\n}\n\n# GPU 사용 가능 여부 확인\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n\ndef seed_everything(seed):\n    \"\"\"\n    난수 시드 고정을 통한 실험 재현성 보장\n    \"\"\"\n    random.seed(seed)\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed_all(seed)\n    os.environ['PYTHONHASHSEED'] = str(seed)\n    torch.backends.cudnn.deterministic = True\n\nseed_everything(CFG['SEED'])\n\n# ── 4. 이미지 전처리 함수 ──────────────────────────────────────\ndef ben_graham_preprocessing(img):\n    \"\"\"\n    Ben Graham's 전처리: 가우시안 블러를 활용해 배경 조명을 제거하고 미세 특징 강조\n    \"\"\"\n    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n    blur = cv2.GaussianBlur(img, (0, 0), 10)\n    # 원본 이미지와 블러 이미지의 차이를 이용해 특징 추출\n    return cv2.addWeighted(img, 4, blur, -4, 128)\n\n# ── 5. 데이터셋 클래스 정의 ────────────────────────────────────\nclass APTOSDataset(Dataset):\n    def __init__(self, df, img_dir, transform=None, is_test=False):\n        self.df = df.reset_index(drop=True)\n        self.img_dir = Path(img_dir)\n        self.transform = transform\n        self.is_test = is_test\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        img_path = self.img_dir / f\"{row['id_code']}.png\"\n        \n        # 이미지 로드\n        img = cv2.imread(str(img_path))\n        if img is None:\n            # 로드 실패 시 제로 텐서 반환 (예외 처리)\n            return torch.zeros((3, CFG['IMG_SIZE'], CFG['IMG_SIZE'])), torch.tensor(0.0)\n\n        # 전처리 및 리사이즈\n        img = ben_graham_preprocessing(img)\n        img = cv2.resize(img, (CFG['IMG_SIZE'], CFG['IMG_SIZE']))\n        \n        # 이미지 증강 적용\n        if self.transform:\n            img = self.transform(img)\n            \n        if self.is_test:\n            return img\n        \n        # Ordinal Regression을 위해 레이블을 실수형(float)으로 반환\n        return img, torch.tensor(row['diagnosis'], dtype=torch.float32)\n\n# ── 6. 실행 함수 (Main Pipeline) ─────────────────────────────\ndef run():\n    # 데이터 로드\n    df_train = pd.read_csv(DATA_DIR / 'train.csv')\n    df_test = pd.read_csv(DATA_DIR / 'test.csv')\n\n    # 데이터 변환(Augmentation) 설정\n    train_tsfm = T.Compose([\n        T.ToPILImage(),\n        T.RandomHorizontalFlip(),\n        T.ToTensor(),\n        T.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])\n    ])\n\n    # 모델 생성: EfficientNet-B4 (Regression을 위해 num_classes=1 설정)\n    model = timm.create_model('efficientnet_b4', pretrained=True, num_classes=1).to(device)\n    \n    # 최적화 도구 및 손실 함수 설정\n    optimizer = optim.AdamW(model.parameters(), lr=CFG['LR'])\n    criterion = nn.MSELoss()\n    \n    # 자동 혼합 정밀도(AMP) 가속을 위한 Scaler\n    scaler = torch.amp.GradScaler('cuda')\n\n    # 데이터 로더 준비\n    train_ds = APTOSDataset(df_train, TRAIN_IMG_DIR, transform=train_tsfm)\n    train_loader = DataLoader(train_ds, batch_size=CFG['BATCH_SIZE'], shuffle=True, num_workers=2)\n\n    # [학습 단계]\n    print(f\"학습 시작: 총 {CFG['EPOCHS']} 에포크\")\n    model.train()\n    for epoch in range(1, CFG['EPOCHS'] + 1):\n        total_loss = 0\n        for imgs, labels in tqdm(train_loader, desc=f\"Epoch {epoch}\"):\n            imgs, labels = imgs.to(device), labels.to(device)\n            \n            optimizer.zero_grad()\n            # AMP 가속 적용\n            with torch.amp.autocast(device_type='cuda'):\n                output = model(imgs).squeeze()\n                loss = criterion(output, labels)\n            \n            scaler.scale(loss).backward()\n            scaler.step(optimizer)\n            scaler.update()\n            \n            total_loss += loss.item()\n        \n        print(f\"Epoch {epoch} 완료 | 평균 Loss: {total_loss/len(train_loader):.4f}\")\n\n    # [추론 단계]\n    print(\"테스트 데이터 예측 중...\")\n    test_ds = APTOSDataset(df_test, TEST_IMG_DIR, transform=train_tsfm, is_test=True)\n    test_loader = DataLoader(test_ds, batch_size=CFG['BATCH_SIZE'], shuffle=False)\n    \n    model.eval()\n    preds = []\n    with torch.no_grad():\n        for imgs in tqdm(test_loader, desc=\"Predicting\"):\n            out = model(imgs.to(device))\n            preds.extend(out.cpu().numpy())\n    \n    # [결과 후처리 및 저장]\n    # 예측된 실수값을 반올림 후 0~4 범위로 제한하여 정수형으로 변환\n    df_test['diagnosis'] = np.clip(np.round(preds), 0, 4).astype(int)\n    df_test[['id_code', 'diagnosis']].to_csv('submission.csv', index=False)\n    print(\"완료: submission.csv가 저장되었습니다.\")\n\nif __name__ == \"__main__\":\n    run()","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-03-01T06:44:53.846054Z","iopub.execute_input":"2026-03-01T06:44:53.846339Z"}},"outputs":[],"execution_count":null}]}