{"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":106372,"databundleVersionId":12864984}],"dockerImageVersionId":31329,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"정확도: 0.92105","metadata":{}},{"cell_type":"markdown","source":"# 데이터 준비 부분\n하이퍼 파라미터 설정값을 미리 저장하고 seed를 고정한다.\n이후 데이터셋을 구성한다.","metadata":{}},{"cell_type":"code","source":"import os\nimport numpy as np\nimport pandas as pd\nimport torch\nimport torch.nn as nn\nfrom sklearn.model_selection import train_test_split\nimport torchvision.transforms as transforms\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision.datasets.folder import default_loader\nfrom sklearn.preprocessing import LabelEncoder\nfrom tqdm import tqdm\nimport random\nimport joblib\nfrom torchvision import models","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-01T22:57:26.422478Z","iopub.execute_input":"2026-05-01T22:57:26.422807Z","iopub.status.idle":"2026-05-01T22:57:30.755594Z","shell.execute_reply.started":"2026-05-01T22:57:26.422781Z","shell.execute_reply":"2026-05-01T22:57:30.754894Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(os.listdir(\"/kaggle/input/competitions/2025-bamboo-summer-competiton-dl-pr\"))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-01T22:57:30.757622Z","iopub.execute_input":"2026-05-01T22:57:30.758144Z","iopub.status.idle":"2026-05-01T22:57:30.763658Z","shell.execute_reply.started":"2026-05-01T22:57:30.758107Z","shell.execute_reply":"2026-05-01T22:57:30.762833Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#하이퍼 파라미터\nCFG = {\n    'IMG_SIZE':224, \n    'EPOCHS':20,\n    'LEARNING_RATE':1e-4,\n    'BATCH_SIZE':32,\n    'NUM_WORKERS': 2,\n    'SEED':42\n}\n\n# 디바이스 설정\ndevice = torch.device('cuda') if torch.cuda.is_available() else torch.device('cpu')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-01T22:57:30.764491Z","iopub.execute_input":"2026-05-01T22:57:30.764694Z","iopub.status.idle":"2026-05-01T22:57:31.014002Z","shell.execute_reply.started":"2026-05-01T22:57:30.764673Z","shell.execute_reply":"2026-05-01T22:57:31.013051Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Seed 고정 => 너무 많은 랜덤성 줄이기\ndef seed_everything(seed):\n    random.seed(seed)\n    os.environ['PYTHONHASHSEED'] = str(seed)\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed(seed)\n    torch.backends.cudnn.deterministic = True\n    torch.backends.cudnn.benchmark = True\n\nseed_everything(CFG['SEED'])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-01T22:57:31.015061Z","iopub.execute_input":"2026-05-01T22:57:31.015423Z","iopub.status.idle":"2026-05-01T22:57:31.033161Z","shell.execute_reply.started":"2026-05-01T22:57:31.015390Z","shell.execute_reply":"2026-05-01T22:57:31.032209Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 라벨 인코딩 및 구성 확인\n\ntrain = pd.read_csv(\"/kaggle/input/competitions/2025-bamboo-summer-competiton-dl-pr/train.csv\")\ntest = pd.read_csv(\"/kaggle/input/competitions/2025-bamboo-summer-competiton-dl-pr/test.csv\")\n\n# 라벨 인코딩\nle = LabelEncoder()\ntrain[\"label_encoded\"] = le.fit_transform(train[\"label\"])\n\n# 첫 5개 예시\ntrain.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-01T22:57:31.034263Z","iopub.execute_input":"2026-05-01T22:57:31.035151Z","iopub.status.idle":"2026-05-01T22:57:31.071425Z","shell.execute_reply.started":"2026-05-01T22:57:31.035119Z","shell.execute_reply":"2026-05-01T22:57:31.070581Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 데이터셋\nclass BamDataset(Dataset):\n    def __init__(self, dataframe, img_dir, transform = None, train = True):\n        self.df = dataframe.reset_index(drop = True) # 인덱스 추가 없이 재정렬\n        self.img_dir = img_dir # 이미지들 있는 폴더\n        self.transform = transform\n        self.train = train # 학습/테스트 구분\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        row = self.df.iloc[idx] # idx번째 행\n        img_path = os.path.join(self.img_dir, row[\"filename\"]) #사진 별 경로\n        image = default_loader(img_path) # PIL 이미지 로드\n        if self.transform:\n            image = self.transform(image)\n\n        if self.train:\n            label = row[\"label_encoded\"]\n            return image, label # 학습 데이터는 라벨 필요\n        else:\n            return image, row[\"filename\"] # 제출할 때 파일 이름이 필요해서 테스트 데이터에 같이 넣어둠","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-01T22:57:31.072364Z","iopub.execute_input":"2026-05-01T22:57:31.072995Z","iopub.status.idle":"2026-05-01T22:57:31.079839Z","shell.execute_reply.started":"2026-05-01T22:57:31.072943Z","shell.execute_reply":"2026-05-01T22:57:31.078725Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 전처리\n\ntrain_transform = transforms.Compose([\n    transforms.Resize((CFG['IMG_SIZE'], CFG['IMG_SIZE'])),\n    transforms.RandomHorizontalFlip(),\n    transforms.ColorJitter(),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),\n])\ntest_transform = transforms.Compose([\n    transforms.Resize((CFG['IMG_SIZE'], CFG['IMG_SIZE'])),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),\n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-01T22:57:31.080962Z","iopub.execute_input":"2026-05-01T22:57:31.081412Z","iopub.status.idle":"2026-05-01T22:57:31.093051Z","shell.execute_reply.started":"2026-05-01T22:57:31.081384Z","shell.execute_reply":"2026-05-01T22:57:31.092356Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# train/test 데이터로 분리\n# 해당 대회에서는 학습, 테스트셋이 따로 있어서 학습 데이터를 학습/검증으로 2개로 나눔\n\ntrain_df, val_df = train_test_split(\n    train,\n    test_size=0.2,\n    stratify=train[\"label_encoded\"] # 클래스의 비율을 유지하면서 분리\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-01T22:57:31.093929Z","iopub.execute_input":"2026-05-01T22:57:31.094952Z","iopub.status.idle":"2026-05-01T22:57:31.114405Z","shell.execute_reply.started":"2026-05-01T22:57:31.094914Z","shell.execute_reply":"2026-05-01T22:57:31.113716Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 데이터 로더\n\ntrain_dataset = BamDataset(train_df, img_dir=os.path.join(\"/kaggle/input/competitions/2025-bamboo-summer-competiton-dl-pr/train\"), transform=train_transform)\nval_dataset = BamDataset(val_df, img_dir=os.path.join(\"/kaggle/input/competitions/2025-bamboo-summer-competiton-dl-pr/train\"), transform=test_transform)\n\ntrain_loader = DataLoader(train_dataset, batch_size=CFG['BATCH_SIZE'], shuffle=True, num_workers=CFG['NUM_WORKERS'])\nval_loader = DataLoader(val_dataset, batch_size=CFG['BATCH_SIZE'], shuffle=False, num_workers=CFG['NUM_WORKERS'])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-01T22:57:31.115249Z","iopub.execute_input":"2026-05-01T22:57:31.115542Z","iopub.status.idle":"2026-05-01T22:57:31.121630Z","shell.execute_reply.started":"2026-05-01T22:57:31.115519Z","shell.execute_reply":"2026-05-01T22:57:31.120834Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 파인튜닝","metadata":{}},{"cell_type":"code","source":"num_classes = len(le.classes_)\n\nmodel = models.vit_b_16(weights=models.ViT_B_16_Weights.DEFAULT)\nnum_ftrs = model.heads.head.in_features\nmodel.heads.head = nn.Linear(num_ftrs, num_classes)\n\nmodel = model.to(device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-01T22:57:31.122561Z","iopub.execute_input":"2026-05-01T22:57:31.123120Z","iopub.status.idle":"2026-05-01T22:57:32.802105Z","shell.execute_reply.started":"2026-05-01T22:57:31.123084Z","shell.execute_reply":"2026-05-01T22:57:32.801388Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 학습","metadata":{}},{"cell_type":"code","source":"criterion = nn.CrossEntropyLoss()\noptimizer = torch.optim.AdamW(model.parameters(), lr=CFG['LEARNING_RATE'])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-01T22:57:32.803083Z","iopub.execute_input":"2026-05-01T22:57:32.803453Z","iopub.status.idle":"2026-05-01T22:57:32.808424Z","shell.execute_reply.started":"2026-05-01T22:57:32.803427Z","shell.execute_reply":"2026-05-01T22:57:32.807441Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for epoch in range(CFG['EPOCHS']):\n    model.train()\n    running_loss = 0.0\n    # tdqm으로 진행률을 바 형태로 시각적으로 확인 가능\n    for images, labels in tqdm(train_loader, desc=f\"[Train] Epoch {epoch+1}/{CFG['EPOCHS']}\"):\n        images, labels = images.to(device), labels.to(device)\n\n        optimizer.zero_grad()\n        outputs = model(images)\n        loss = criterion(outputs, labels)\n        loss.backward()\n        optimizer.step()\n        running_loss += loss.item()\n\n    avg_train_loss = running_loss / len(train_loader) # 에폭 평균 로스\n\n    model.eval()\n    val_loss = 0.0\n    correct = 0\n    total = 0\n    with torch.no_grad(): # 평가 때는 gradient 계산 멈춰서 메모리 절약\n        for images, labels in tqdm(val_loader, desc=f\"[Val] Epoch {epoch+1}/{CFG['EPOCHS']}\"):\n            images, labels = images.to(device), labels.to(device)\n            outputs = model(images)\n            loss = criterion(outputs, labels)\n            val_loss += loss.item()\n\n            preds = torch.argmax(outputs, dim=1)\n            correct += (preds == labels).sum().item() # 잘 예측한 개수\n            total += labels.size(0)\n\n    avg_val_loss = val_loss / len(val_loader)\n    val_acc = correct / total\n\n    print(f\"Epoch {epoch+1} | Train Loss: {avg_train_loss:.4f} | Val Loss: {avg_val_loss:.4f} | Val Acc: {val_acc:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-01T22:57:32.809535Z","iopub.execute_input":"2026-05-01T22:57:32.809893Z","iopub.status.idle":"2026-05-01T23:57:33.262489Z","shell.execute_reply.started":"2026-05-01T22:57:32.809833Z","shell.execute_reply":"2026-05-01T23:57:33.261412Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 제출\n최종적으로 테스트셋을 통해 제출 파일을 만든다.","metadata":{}},{"cell_type":"code","source":"test_dataset = BamDataset(test, img_dir=os.path.join(\"/kaggle/input/competitions/2025-bamboo-summer-competiton-dl-pr/test\"), transform=test_transform, train=False)\ntest_loader = DataLoader(test_dataset, batch_size=CFG['BATCH_SIZE'], shuffle=False, num_workers=CFG['NUM_WORKERS'])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-01T23:57:33.266046Z","iopub.execute_input":"2026-05-01T23:57:33.266484Z","iopub.status.idle":"2026-05-01T23:57:33.272125Z","shell.execute_reply.started":"2026-05-01T23:57:33.266452Z","shell.execute_reply":"2026-05-01T23:57:33.271258Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.eval()\npredictions = []\nfilenames = []\n\nwith torch.no_grad():\n    for images, fnames in tqdm(test_loader, desc=\"Predicting\"):\n        images = images.to(device)\n        outputs = model(images)\n        probs = torch.softmax(outputs, dim=1)\n        preds = torch.argmax(probs, dim=1).cpu().numpy()\n        labels = le.inverse_transform(preds)\n        predictions.extend(labels)\n        filenames.extend(fnames)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-01T23:57:33.273026Z","iopub.execute_input":"2026-05-01T23:57:33.273341Z","iopub.status.idle":"2026-05-01T23:57:50.764063Z","shell.execute_reply.started":"2026-05-01T23:57:33.273270Z","shell.execute_reply":"2026-05-01T23:57:50.763117Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"submission = pd.read_csv('/kaggle/input/competitions/2025-bamboo-summer-competiton-dl-pr/sample_submission.csv')\nsubmission['label'] = predictions\nsubmission.to_csv(os.path.join(\"baseline_submission.csv\"), index=False)\nprint(\"✅ submission.csv 생성 완료!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-01T23:57:50.765407Z","iopub.execute_input":"2026-05-01T23:57:50.765775Z","iopub.status.idle":"2026-05-01T23:57:50.793198Z","shell.execute_reply.started":"2026-05-01T23:57:50.765742Z","shell.execute_reply":"2026-05-01T23:57:50.792373Z"}},"outputs":[],"execution_count":null}]}