{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.11","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":105999,"databundleVersionId":12826923,"sourceType":"competition"}],"dockerImageVersionId":31040,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport pandas as pd\nfrom sklearn.model_selection import train_test_split\nfrom PIL import Image\nimport torch\nfrom torch import nn, optim\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms, models\n\n# 1. 경로 및 장치 설정\nTRAIN_CSV = \"/kaggle/input/butterfly-species-classification-dl-pr/butterfly_competition/train.csv\"\nTEST_CSV  = \"/kaggle/input/butterfly-species-classification-dl-pr/butterfly_competition/test.csv\"\nTRAIN_IMG_DIR = \"/kaggle/input/butterfly-species-classification-dl-pr/butterfly_competition/train\"\nTEST_IMG_DIR  = \"/kaggle/input/butterfly-species-classification-dl-pr/butterfly_competition/test\"\n\ndevice = torch.device(\"cpu\")  # CPU 전용\n\n\n","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-06-27T12:41:01.862573Z","iopub.execute_input":"2025-06-27T12:41:01.863000Z","iopub.status.idle":"2025-06-27T12:41:09.040924Z","shell.execute_reply.started":"2025-06-27T12:41:01.862878Z","shell.execute_reply":"2025-06-27T12:41:09.039842Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 2. CSV 로드 및 레이블 인코딩\ndf = pd.read_csv(TRAIN_CSV)\nlabel2id = {lbl:idx for idx,lbl in enumerate(sorted(df['label'].unique()))}\nid2label = {v:k for k,v in label2id.items()}\ndf['label_idx'] = df['label'].map(label2id)\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-27T12:41:09.042248Z","iopub.execute_input":"2025-06-27T12:41:09.042630Z","iopub.status.idle":"2025-06-27T12:41:09.085965Z","shell.execute_reply.started":"2025-06-27T12:41:09.042608Z","shell.execute_reply":"2025-06-27T12:41:09.085159Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 3. Train/Validation 분리 (stratify 해서 클래스 불균형 완화)\ntrain_df, val_df = train_test_split(\n    df, test_size=0.2, stratify=df['label_idx'], random_state=42\n)\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-27T12:41:09.086747Z","iopub.execute_input":"2025-06-27T12:41:09.087028Z","iopub.status.idle":"2025-06-27T12:41:09.103062Z","shell.execute_reply.started":"2025-06-27T12:41:09.087006Z","shell.execute_reply":"2025-06-27T12:41:09.102152Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 4. Dataset 클래스 정의\nclass ButterflyDataset(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 = 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        fname = self.df.loc[idx, 'filename']\n        img_path = os.path.join(self.img_dir, fname)\n        img = Image.open(img_path).convert('RGB')\n        if self.transform:\n            img = self.transform(img)\n        if self.is_test:\n            return img, fname\n        label = self.df.loc[idx, 'label_idx']\n        return img, label\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-27T12:41:09.104901Z","iopub.execute_input":"2025-06-27T12:41:09.105869Z","iopub.status.idle":"2025-06-27T12:41:09.114262Z","shell.execute_reply.started":"2025-06-27T12:41:09.105830Z","shell.execute_reply":"2025-06-27T12:41:09.113347Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 5. 데이터 전처리(Transforms)\ntrain_transforms = transforms.Compose([\n    transforms.Resize((224,224)),\n    transforms.RandomHorizontalFlip(),\n    transforms.ToTensor(),\n])\nval_transforms = transforms.Compose([\n    transforms.Resize((224,224)),\n    transforms.ToTensor(),\n])\ntest_transforms = val_transforms\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-27T12:41:09.115209Z","iopub.execute_input":"2025-06-27T12:41:09.115534Z","iopub.status.idle":"2025-06-27T12:41:09.139676Z","shell.execute_reply.started":"2025-06-27T12:41:09.115508Z","shell.execute_reply":"2025-06-27T12:41:09.138606Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 6. DataLoader\nbatch_size = 32\n\ntrain_ds = ButterflyDataset(train_df, TRAIN_IMG_DIR, transform=train_transforms)\nval_ds   = ButterflyDataset(val_df,   TRAIN_IMG_DIR, transform=val_transforms)\ntest_df  = pd.read_csv(TEST_CSV)\ntest_ds  = ButterflyDataset(test_df,  TEST_IMG_DIR,  transform=test_transforms, is_test=True)\n\ntrain_loader = DataLoader(train_ds, batch_size=batch_size, shuffle=True,  num_workers=2)\nval_loader   = DataLoader(val_ds,   batch_size=batch_size, shuffle=False, num_workers=2)\ntest_loader  = DataLoader(test_ds,  batch_size=batch_size, shuffle=False, num_workers=2)\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-27T12:41:09.140872Z","iopub.execute_input":"2025-06-27T12:41:09.141181Z","iopub.status.idle":"2025-06-27T12:41:09.169110Z","shell.execute_reply.started":"2025-06-27T12:41:09.141152Z","shell.execute_reply":"2025-06-27T12:41:09.168188Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 7. 모델 정의 (ResNet18)\nmodel = models.resnet18(pretrained=True)\nn_features = model.fc.in_features\nmodel.fc = nn.Linear(n_features, len(label2id))\nmodel = model.to(device)\n\ncriterion = nn.CrossEntropyLoss()\noptimizer = optim.Adam(model.parameters(), lr=1e-4)\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-27T12:41:09.170085Z","iopub.execute_input":"2025-06-27T12:41:09.170404Z","iopub.status.idle":"2025-06-27T12:41:09.894146Z","shell.execute_reply.started":"2025-06-27T12:41:09.170372Z","shell.execute_reply":"2025-06-27T12:41:09.893051Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 8. 학습·검증 함수\ndef train_one_epoch():\n    model.train()\n    total_loss = 0\n    correct = 0\n    for imgs, labels in train_loader:\n        imgs, labels = imgs.to(device), labels.to(device)\n        optimizer.zero_grad()\n        outputs = model(imgs)\n        loss = criterion(outputs, labels)\n        loss.backward()\n        optimizer.step()\n\n        total_loss += loss.item() * imgs.size(0)\n        preds = outputs.argmax(dim=1)\n        correct += (preds == labels).sum().item()\n\n    avg_loss = total_loss / len(train_ds)\n    acc = correct / len(train_ds)\n    return avg_loss, acc\n\ndef validate():\n    model.eval()\n    total_loss = 0\n    correct = 0\n    with torch.no_grad():\n        for imgs, labels in val_loader:\n            imgs, labels = imgs.to(device), labels.to(device)\n            outputs = model(imgs)\n            loss = criterion(outputs, labels)\n            total_loss += loss.item() * imgs.size(0)\n            preds = outputs.argmax(dim=1)\n            correct += (preds == labels).sum().item()\n\n    avg_loss = total_loss / len(val_ds)\n    acc = correct / len(val_ds)\n    return avg_loss, acc\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-27T12:41:09.895539Z","iopub.execute_input":"2025-06-27T12:41:09.895798Z","iopub.status.idle":"2025-06-27T12:41:09.903664Z","shell.execute_reply.started":"2025-06-27T12:41:09.895775Z","shell.execute_reply":"2025-06-27T12:41:09.902724Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 9. 메인 루프\nn_epochs = 5\nfor epoch in range(1, n_epochs+1):\n    train_loss, train_acc = train_one_epoch()\n    val_loss, val_acc     = validate()\n    print(f\"Epoch {epoch:02d} | \"\n          f\"Train: loss={train_loss:.4f}, acc={train_acc:.4f} | \"\n          f\"Val:   loss={val_loss:.4f}, acc={val_acc:.4f}\")\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-27T12:41:09.904622Z","iopub.execute_input":"2025-06-27T12:41:09.904967Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# (이전까지는 동일)\n\n# 10. 테스트셋 예측 및 submission.csv 생성\nmodel.eval()\npreds = []\nids   = []\nwith torch.no_grad():\n    for imgs, names in test_loader:\n        imgs = imgs.to(device)\n        outputs = model(imgs)\n        idxs = outputs.argmax(dim=1).cpu().numpy()\n        preds.extend(idxs)\n        ids.extend(names)\n\nsubmission = pd.DataFrame({\n    \"id\":    ids,                            # <-- 'filename'이 아니라 'id' 로 컬럼명 변경\n    \"label\": [id2label[i] for i in preds]\n})\nsubmission.to_csv(\"baseline__submission.csv\", index=False)\nprint(\"=> baseline__submission.csv 생성 완료\")\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}