{"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":13836,"databundleVersionId":1718836,"isSourceIdPinned":false},{"sourceType":"kernelVersion","sourceId":37451603,"isSourceIdPinned":false}],"dockerImageVersionId":31329,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-05-07T02:22:46.541774Z","iopub.execute_input":"2026-05-07T02:22:46.542593Z","iopub.status.idle":"2026-05-07T02:23:06.170173Z","shell.execute_reply.started":"2026-05-07T02:22:46.542548Z","shell.execute_reply":"2026-05-07T02:23:06.167573Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nprint(torch.cuda.is_available())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-07T02:23:06.171605Z","iopub.execute_input":"2026-05-07T02:23:06.172067Z","iopub.status.idle":"2026-05-07T02:23:06.176308Z","shell.execute_reply.started":"2026-05-07T02:23:06.172041Z","shell.execute_reply":"2026-05-07T02:23:06.175643Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import models, transforms\nfrom PIL import Image\nimport pandas as pd\nimport numpy as np\nimport os","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-07T02:23:06.177265Z","iopub.execute_input":"2026-05-07T02:23:06.177618Z","iopub.status.idle":"2026-05-07T02:23:06.191360Z","shell.execute_reply.started":"2026-05-07T02:23:06.177582Z","shell.execute_reply":"2026-05-07T02:23:06.190467Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class CassavaDataset(Dataset) : \n    def __init__(self, df, img_dir, transform = None):\n        self.df = df\n        self.img_dir = img_dir\n        self.transform = transform\n\n\n    def __len__(self) :\n        return len(self.df)\n                  \n    def __getitem__(self, idx) : \n            filename = self.df.iloc[idx]['image_id']\n            label = self.df.iloc[idx]['label']\n            img_path = os.path.join(self.img_dir, filename)\n            img = Image.open(img_path).convert('RGB')\n\n            if self.transform : \n                img = self.transform(img)\n\n            return img, label\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-07T02:23:06.192326Z","iopub.execute_input":"2026-05-07T02:23:06.192668Z","iopub.status.idle":"2026-05-07T02:23:06.205288Z","shell.execute_reply.started":"2026-05-07T02:23:06.192624Z","shell.execute_reply":"2026-05-07T02:23:06.204745Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"base_path = \"/kaggle/input/competitions/cassava-leaf-disease-classification\"\n\ndf = pd.read_csv(f\"{base_path}/train.csv\")\n\ntrain_df = df.sample(frac = 0.8, random_state=42).reset_index(drop=True)\nval_df = df.drop(train_df.index).reset_index(drop=True)\n\nprint(f\"학습 데이터 : {len(train_df)}장\")\nprint(f\"검증 데이터 : {len(val_df)}장\")\n\ntransform = transforms.Compose([\n    transforms.Resize((224, 224)),\n    transforms.ToTensor(),\n    transforms.Normalize(\n        mean = [0.485, 0.456, 0.406],\n        std = [0.229, 0.224, 0.225]\n    )\n])\n\ntrain_dataset = CassavaDataset(train_df, f\"{base_path}/train_images\", transform)\nval_dataset = CassavaDataset(val_df, f\"{base_path}/train_images\", transform)\n\ntrain_loader = DataLoader(train_dataset, batch_size=32, shuffle=True, num_workers=2)\nval_loader = DataLoader(val_dataset, batch_size=32, shuffle=False, num_workers=2)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-07T02:23:06.207389Z","iopub.execute_input":"2026-05-07T02:23:06.207772Z","iopub.status.idle":"2026-05-07T02:23:06.236705Z","shell.execute_reply.started":"2026-05-07T02:23:06.207750Z","shell.execute_reply":"2026-05-07T02:23:06.236061Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\n# competitions 폴더 안에 뭐가 있는지 확인\nprint(os.listdir(\"/kaggle/input/competitions\"))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-07T02:23:06.237451Z","iopub.execute_input":"2026-05-07T02:23:06.237763Z","iopub.status.idle":"2026-05-07T02:23:06.242207Z","shell.execute_reply.started":"2026-05-07T02:23:06.237723Z","shell.execute_reply":"2026-05-07T02:23:06.241439Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nfrom torchvision import models\n\n# pretrained=True 대신 weights 파라미터 사용\n# Kaggle 환경에서 더 안정적으로 작동해요\n#model = models.resnet18(weights=models.ResNet18_Weights.IMAGENET1K_V1)\n\n\n# ⭕ 수정된 코드 (오프라인 제출용)\n# 일단 인터넷 다운로드를 막기 위해 None으로 설정합니다. \n# (나중에 점수를 올리시려면 Add Data로 resnet18.pth를 추가해서 불러오셔야 합니다!)\nmodel = models.resnet18(weights=None)\n\n# 마지막 레이어를 5개 클래스로 교체\nmodel.fc = nn.Linear(512, 5)\n\n# GPU에 올리기\nmodel = model.cuda()\n\n# 손실함수\ncriterion = nn.CrossEntropyLoss()\n\n# 옵티마이저\noptimizer = torch.optim.Adam(model.parameters(), lr=1e-4)\n\nprint(\"모델 준비 완료!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-07T02:23:06.243688Z","iopub.execute_input":"2026-05-07T02:23:06.244359Z","iopub.status.idle":"2026-05-07T02:23:06.425486Z","shell.execute_reply.started":"2026-05-07T02:23:06.244323Z","shell.execute_reply":"2026-05-07T02:23:06.424727Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"num_epochs = 5\nfor epoch in range(num_epochs) : \n\n    model.train()\n    train_loss = 0\n\n    for images, labels in train_loader : \n        images = images.cuda()\n        labels = labels.long().cuda()\n\n        optimizer.zero_grad()\n        outputs = model(images)\n        loss = criterion(outputs, labels)\n        loss.backward()\n        optimizer.step()\n\n        train_loss += loss.item()\n\n    model.eval()\n    correct = 0\n    total = 0\n\n    with torch.no_grad():\n        for images, labels in val_loader : \n            images = images.cuda()\n            labels = labels.long().cuda()\n\n            outputs = model(images)\n            _, predicted = outputs.max(1)\n            correct += predicted.eq(labels).sum().item()\n            total += labels.size(0)\n    val_acc = correct / total #검증정확도 계산\n\n    print(f\"Epoch {epoch+1}/{num_epochs} / \"\n         f\"Loss : {train_loss /len(train_loader):.4f}/\"\n         f\"Val Acc : {val_acc:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-07T02:23:06.426392Z","iopub.execute_input":"2026-05-07T02:23:06.426667Z","iopub.status.idle":"2026-05-07T02:31:38.321077Z","shell.execute_reply.started":"2026-05-07T02:23:06.426642Z","shell.execute_reply":"2026-05-07T02:31:38.320157Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 데이터 한 배치 꺼내서 shape 확인\nimages, labels = next(iter(train_loader))\nprint(\"이미지 shape:\", images.shape)  # [32, 3, 224, 224] 나와야 함\nprint(\"라벨 shape:\", labels.shape)    # [32] 나와야 함\nprint(\"이미지 타입:\", images.dtype)   # torch.float32 나와야 함","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-07T02:31:38.322405Z","iopub.execute_input":"2026-05-07T02:31:38.322864Z","iopub.status.idle":"2026-05-07T02:31:39.000553Z","shell.execute_reply.started":"2026-05-07T02:31:38.322833Z","shell.execute_reply":"2026-05-07T02:31:38.999716Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport torch\n\nbase_path = \"/kaggle/input/competitions/cassava-leaf-disease-classification\"\n\n# 제출용 test 이미지 경로\ntest_df = pd.read_csv(f\"{base_path}/sample_submission.csv\")\n\n# test Dataset 만들기\nmy_transform = transforms.Compose([\n    transforms.Resize((224, 224)),\n    transforms.ToTensor(),\n    transforms.Normalize(\n        mean = [0.485, 0.456, 0.406],\n        std = [0.229, 0.224, 0.225]\n    )\n])\n\n# 2. 방금 만든 'my_transform'을 transform 파라미터에 넣어줍니다.\ntest_dataset = CassavaDataset(\n    df=test_df, \n    img_dir=base_path + \"/test_images\", \n    transform=my_transform # <--- 이렇게 수정해야 합니다!\n)\ntest_loader = DataLoader(test_dataset, batch_size=32, shuffle=False, num_workers=2)\n\n# 예측\nmodel.eval()\npreds = []\n\nwith torch.no_grad():\n    for images, _ in test_loader:\n        images = images.cuda()\n        outputs = model(images)\n        _, predicted = outputs.max(1)  # 가장 높은 확률 클래스 선택\n        preds.extend(predicted.cpu().numpy())  # CPU로 가져와서 저장\n\n# submission 파일 만들기\ntest_df['label'] = preds\ntest_df.to_csv(\"submission.csv\", index=False)\nprint(\"제출 파일 완성\")\nprint(test_df.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-07T02:31:39.002295Z","iopub.execute_input":"2026-05-07T02:31:39.002745Z","iopub.status.idle":"2026-05-07T02:31:39.177478Z","shell.execute_reply.started":"2026-05-07T02:31:39.002708Z","shell.execute_reply":"2026-05-07T02:31:39.176496Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nprint(os.listdir(\"/kaggle/input\"))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-07T02:31:39.178981Z","iopub.execute_input":"2026-05-07T02:31:39.179309Z","iopub.status.idle":"2026-05-07T02:31:39.184887Z","shell.execute_reply.started":"2026-05-07T02:31:39.179280Z","shell.execute_reply":"2026-05-07T02:31:39.184224Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# augmentation 전문 라이브러리 설치\n!pip install albumentations -q","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-07T02:31:39.185821Z","iopub.execute_input":"2026-05-07T02:31:39.186082Z","iopub.status.idle":"2026-05-07T02:31:42.579209Z","shell.execute_reply.started":"2026-05-07T02:31:39.186046Z","shell.execute_reply":"2026-05-07T02:31:42.578303Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import albumentations as A\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport cv2\n\nbase_path = \"/kaggle/input/competitions/cassava-leaf-disease-classification\"\n\n# 샘플 이미지 1장 불러오기\nsample_path = f\"{base_path}/train_images/{df['image_id'][0]}\"\nsample_img = cv2.imread(sample_path)\nsample_img = cv2.cvtColor(sample_img, cv2.COLOR_BGR2RGB)  # BGR → RGB\n\n# 적용할 augmentation 정의\naug = A.Compose([\n    A.HorizontalFlip(p=0.5),              # 50% 확률로 좌우 반전\n    A.VerticalFlip(p=0.5),                # 50% 확률로 상하 반전\n    A.RandomBrightnessContrast(p=0.5),    # 50% 확률로 밝기/대비 조절\n    A.Resize(height=224, width=224),      # 224x224로 크기 맞추기\n])\n\n# 원본 + augmentation 5장 비교\nfig, axes = plt.subplots(1, 6, figsize=(18, 3))\n\naxes[0].imshow(sample_img)\naxes[0].set_title(\"원본\")\naxes[0].axis('off')\n\nfor i in range(1, 6):\n    augmented = aug(image=sample_img)['image']\n    axes[i].imshow(augmented)\n    axes[i].set_title(f\"aug {i}\")\n    axes[i].axis('off')\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-07T02:31:42.580419Z","iopub.execute_input":"2026-05-07T02:31:42.580935Z","iopub.status.idle":"2026-05-07T02:31:44.426590Z","shell.execute_reply.started":"2026-05-07T02:31:42.580886Z","shell.execute_reply":"2026-05-07T02:31:44.425397Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import albumentations.pytorch as AP\n\nclass CassavaDatasetV2(Dataset):\n    def __init__(self, df, img_dir, transform=None):\n        self.df = df\n        self.img_dir = img_dir\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        filename = self.df.iloc[idx]['image_id']\n        label = self.df.iloc[idx]['label']\n\n        # cv2로 이미지 읽기\n        img_path = f\"{self.img_dir}/{filename}\"\n        img = cv2.imread(img_path)\n        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)  # BGR → RGB 변환\n\n        # augmentation 적용\n        if self.transform:\n            augmented = self.transform(image=img)\n            img = augmented['image']  # transform 결과에서 이미지만 꺼냄\n\n        return img, label\n\n# 학습용 transform (augmentation 포함)\ntrain_transform = A.Compose([\n    A.Resize(height=224, width=224),       # 크기 통일\n    A.HorizontalFlip(p=0.5),              # 좌우 반전\n    A.VerticalFlip(p=0.5),               # 상하 반전\n    A.RandomBrightnessContrast(p=0.5),   # 밝기/대비 조절\n    A.Normalize(                          # 정규화\n        mean=[0.485, 0.456, 0.406],\n        std=[0.229, 0.224, 0.225]\n    ),\n    AP.ToTensorV2()                       # numpy → tensor 변환\n])\n\n# 검증용 transform (augmentation 없음)\nval_transform = A.Compose([\n    A.Resize(height=224, width=224),      # 크기만 맞추기\n    A.Normalize(\n        mean=[0.485, 0.456, 0.406],\n        std=[0.229, 0.224, 0.225]\n    ),\n    AP.ToTensorV2()\n])\n\nprint(\"transform 준비 완료!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-07T02:31:44.430751Z","iopub.execute_input":"2026-05-07T02:31:44.431748Z","iopub.status.idle":"2026-05-07T02:31:44.446167Z","shell.execute_reply.started":"2026-05-07T02:31:44.431698Z","shell.execute_reply":"2026-05-07T02:31:44.445233Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from torch.utils.data import DataLoader\n\n# aug 버전 Dataset\ntrain_dataset_v2 = CassavaDatasetV2(\n    train_df,\n    f\"{base_path}/train_images\",\n    train_transform   # augmentation 포함\n)\nval_dataset_v2 = CassavaDatasetV2(\n    val_df,\n    f\"{base_path}/train_images\",\n    val_transform     # augmentation 없음\n)\n\n# DataLoader\ntrain_loader_v2 = DataLoader(train_dataset_v2, batch_size=32, shuffle=True, num_workers=2)\nval_loader_v2 = DataLoader(val_dataset_v2, batch_size=32, shuffle=False, num_workers=2)\n\n# 잘 됐는지 확인\nimages, labels = next(iter(train_loader_v2))\nprint(\"이미지 shape:\", images.shape)   # [32, 3, 224, 224] 나와야 함\nprint(\"라벨 shape:\", labels.shape)     # [32] 나와야 함","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-07T02:31:44.447129Z","iopub.execute_input":"2026-05-07T02:31:44.447489Z","iopub.status.idle":"2026-05-07T02:31:45.118692Z","shell.execute_reply.started":"2026-05-07T02:31:44.447434Z","shell.execute_reply":"2026-05-07T02:31:45.117791Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 모델 새로 초기화 (이전 학습 가중치 리셋해서 공정하게 비교)\nmodel_v2 = models.resnet18(weights=models.ResNet18_Weights.IMAGENET1K_V1)\nmodel_v2.fc = nn.Linear(512, 5)        # 마지막 레이어 5개 클래스로 교체\nmodel_v2 = model_v2.cuda()             # GPU에 올리기\n\ncriterion = nn.CrossEntropyLoss()      # 손실함수\noptimizer = torch.optim.Adam(model_v2.parameters(), lr=1e-4)  # 옵티마이저\n\nnum_epochs = 5\n\nfor epoch in range(num_epochs):\n\n    # 학습\n    model_v2.train()\n    train_loss = 0\n\n    for images, labels in train_loader_v2:   # aug 버전 loader 사용\n        images = images.cuda()\n        labels = labels.long().cuda()\n\n        optimizer.zero_grad()\n        outputs = model_v2(images)\n        loss = criterion(outputs, labels)\n        loss.backward()\n        optimizer.step()\n        train_loss += loss.item()\n\n    # 검증\n    model_v2.eval()\n    correct = 0\n    total = 0\n\n    with torch.no_grad():\n        for images, labels in val_loader_v2:  # aug 버전 loader 사용\n            images = images.cuda()\n            labels = labels.long().cuda()\n\n            outputs = model_v2(images)\n            _, predicted = outputs.max(1)\n            correct += predicted.eq(labels).sum().item()\n            total += labels.size(0)\n\n    val_acc = correct / total\n    print(f\"Epoch {epoch+1}/{num_epochs} | \"\n          f\"Loss: {train_loss/len(train_loader_v2):.4f} | \"\n          f\"Val Acc: {val_acc:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-07T02:31:45.119954Z","iopub.execute_input":"2026-05-07T02:31:45.120391Z","iopub.status.idle":"2026-05-07T02:38:17.128452Z","shell.execute_reply.started":"2026-05-07T02:31:45.120362Z","shell.execute_reply":"2026-05-07T02:38:17.127329Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nfrom torch.utils.data import DataLoader\n\nbase_path = \"/kaggle/input/competitions/cassava-leaf-disease-classification\"\n\n# test 이미지 경로 가져오기\ntest_df = pd.read_csv(f\"{base_path}/sample_submission.csv\")\n\n# test용 transform (augmentation 없이 크기만 맞추기)\ntest_transform = A.Compose([\n    A.Resize(height=224, width=224),\n    A.Normalize(\n        mean=[0.485, 0.456, 0.406],\n        std=[0.229, 0.224, 0.225]\n    ),\n    AP.ToTensorV2()\n])\n\n# test Dataset — 라벨 없으니까 더미로 0 채워넣기\ntest_dataset = CassavaDatasetV2(\n    test_df,\n    f\"{base_path}/test_images\",\n    test_transform\n)\ntest_loader = DataLoader(test_dataset, batch_size=32, shuffle=False, num_workers=2)\n\n# 예측\nmodel_v2.eval()\npreds = []\n\nwith torch.no_grad():\n    for images, _ in test_loader:\n        images = images.cuda()\n        outputs = model_v2(images)\n        _, predicted = outputs.max(1)       # 가장 높은 확률 클래스 선택\n        preds.extend(predicted.cpu().numpy())  # CPU로 가져와서 저장\n\n# submission.csv 만들기\ntest_df['label'] = preds\ntest_df.to_csv(\"submission.csv\", index=False)\nprint(\"제출 파일 완성!\")\nprint(test_df.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-07T02:38:17.129833Z","iopub.execute_input":"2026-05-07T02:38:17.130671Z","iopub.status.idle":"2026-05-07T02:38:17.244964Z","shell.execute_reply.started":"2026-05-07T02:38:17.130637Z","shell.execute_reply":"2026-05-07T02:38:17.244011Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 인터넷 없이 로컬 캐시에서 불러오기\nimport os\nos.environ['TORCH_HOME'] = '/kaggle/working'  # 캐시 경로 지정\n\nimport torch\nimport torch.nn as nn\nfrom torchvision import models\n\n# 인터넷 ON 상태에서 먼저 한 번 다운로드\nmodel_v2 = models.resnet18(weights=models.ResNet18_Weights.IMAGENET1K_V1)\nmodel_v2.fc = nn.Linear(512, 5)\nmodel_v2 = model_v2.cuda()\n\n# 가중치 저장\ntorch.save(model_v2.state_dict(), '/kaggle/working/resnet18_base.pth')\nprint(\"가중치 저장 완료!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-07T02:42:02.036470Z","iopub.execute_input":"2026-05-07T02:42:02.036859Z","iopub.status.idle":"2026-05-07T02:42:02.679085Z","shell.execute_reply.started":"2026-05-07T02:42:02.036832Z","shell.execute_reply":"2026-05-07T02:42:02.678276Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 학습 완료된 모델 가중치 저장\ntorch.save(model_v2.state_dict(), '/kaggle/working/resnet18_trained.pth')\nprint(\"학습 가중치 저장 완료!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-07T02:43:14.320156Z","iopub.execute_input":"2026-05-07T02:43:14.321051Z","iopub.status.idle":"2026-05-07T02:43:14.384695Z","shell.execute_reply.started":"2026-05-07T02:43:14.321016Z","shell.execute_reply":"2026-05-07T02:43:14.383960Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nprint(os.path.exists('/kaggle/working/resnet18_trained.pth'))  # True 나와야 함","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-07T02:44:53.625358Z","iopub.execute_input":"2026-05-07T02:44:53.625819Z","iopub.status.idle":"2026-05-07T02:44:53.630646Z","shell.execute_reply.started":"2026-05-07T02:44:53.625787Z","shell.execute_reply":"2026-05-07T02:44:53.629737Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model_v2 = models.resnet18(weights=None)\nmodel_v2.fc = nn.Linear(512, 5)\nmodel_v2.load_state_dict(torch.load('/kaggle/working/resnet18_trained.pth'))\nmodel_v2 = model_v2.cuda()\nprint(\"불러오기 완료!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-07T02:45:03.181177Z","iopub.execute_input":"2026-05-07T02:45:03.182368Z","iopub.status.idle":"2026-05-07T02:45:03.391728Z","shell.execute_reply.started":"2026-05-07T02:45:03.182336Z","shell.execute_reply":"2026-05-07T02:45:03.391034Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nprint(os.path.exists('/kaggle/working/submission.csv'))  # True 나와야 함","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-07T02:45:32.217543Z","iopub.execute_input":"2026-05-07T02:45:32.217989Z","iopub.status.idle":"2026-05-07T02:45:32.222985Z","shell.execute_reply.started":"2026-05-07T02:45:32.217957Z","shell.execute_reply":"2026-05-07T02:45:32.222092Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 제출 코드 다시 실행\nimport pandas as pd\nfrom torch.utils.data import DataLoader\n\ntest_df = pd.read_csv(f\"{base_path}/sample_submission.csv\")\n\ntest_transform = A.Compose([\n    A.Resize(height=224, width=224),\n    A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),\n    AP.ToTensorV2()\n])\n\ntest_dataset = CassavaDatasetV2(\n    test_df,\n    f\"{base_path}/test_images\",\n    test_transform\n)\ntest_loader = DataLoader(test_dataset, batch_size=32, shuffle=False, num_workers=2)\n\nmodel_v2.eval()\npreds = []\n\nwith torch.no_grad():\n    for images, _ in test_loader:\n        images = images.cuda()\n        outputs = model_v2(images)\n        _, predicted = outputs.max(1)\n        preds.extend(predicted.cpu().numpy())\n\ntest_df['label'] = preds\ntest_df.to_csv(\"submission.csv\", index=False)\nprint(\"제출 파일 완성!\")\nprint(test_df.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-07T02:45:43.404430Z","iopub.execute_input":"2026-05-07T02:45:43.405387Z","iopub.status.idle":"2026-05-07T02:45:43.535696Z","shell.execute_reply.started":"2026-05-07T02:45:43.405355Z","shell.execute_reply":"2026-05-07T02:45:43.534856Z"}},"outputs":[],"execution_count":null}]}