{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.13.15","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},"colab":{"provenance":[]}},"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\n# for 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-10-06T02:20:15.866656Z","iopub.execute_input":"2026-10-06T02:20:15.866991Z","iopub.status.idle":"2026-10-06T02:20:15.872229Z","shell.execute_reply.started":"2026-10-06T02:20:15.866962Z","shell.execute_reply":"2026-10-06T02:20:15.871155Z"},"id":"2bEMlQ8e-plF"},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 라이브러리 참조\nimport os\nimport random\nimport zipfile\nimport numpy as np\nimport pandas as pd\nimport torch\nimport torchvision\nimport PIL\nfrom torchvision import transforms\nfrom tqdm import tqdm\n\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nprint(device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-06T02:20:15.874352Z","iopub.execute_input":"2026-10-06T02:20:15.874737Z","iopub.status.idle":"2026-10-06T02:20:15.917943Z","shell.execute_reply.started":"2026-10-06T02:20:15.874708Z","shell.execute_reply":"2026-10-06T02:20:15.917011Z"},"id":"Z2vCY_cL-plH"},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Dogs vs. Cats Redux 데이터 압축 해제\ncompetition_path = '/kaggle/input/competitions/dogs-vs-cats-redux-kernels-edition'\nroot_path = '/kaggle/working/'\n\nos.makedirs(root_path, exist_ok=True)\n\ntrain_dir = os.path.join(root_path, 'train')\ntest_dir = os.path.join(root_path, 'test')\n\nif not os.path.exists(train_dir):\n    with zipfile.ZipFile(os.path.join(competition_path, 'train.zip'), 'r') as z:\n        z.extractall(root_path)\n\nif not os.path.exists(test_dir):\n    with zipfile.ZipFile(os.path.join(competition_path, 'test.zip'), 'r') as z:\n        z.extractall(root_path)\n\nprint('train:', len(os.listdir(train_dir)))\nprint('test :', len(os.listdir(test_dir)))\n\n# cat = 0, dog = 1\nlabel_map = {'cat': 0, 'dog': 1}\nlabel_map_rev = {0: 'cat', 1: 'dog'}","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-06T02:20:15.919976Z","iopub.execute_input":"2026-10-06T02:20:15.920762Z","iopub.status.idle":"2026-10-06T02:20:28.239813Z","shell.execute_reply.started":"2026-10-06T02:20:15.920724Z","shell.execute_reply":"2026-10-06T02:20:28.23895Z"},"id":"VfE4RxHn-plH","outputId":"bcebae3d-60b9-4081-9241-f3dbef1c669f"},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# dataset\nclass Custom_dataset(torch.utils.data.Dataset):\n    def __init__(self, split, rootpath, transform, label_map=None):\n        super(Custom_dataset, self).__init__()\n        self.split = split.upper()\n        self.rootpath = rootpath\n        self.transform = transform\n        self.label_map = label_map\n\n        data_dir = os.path.join(self.rootpath, self.split.lower())\n        self.image_list = [\n            f for f in os.listdir(data_dir)\n            if f.lower().endswith(('.jpg', '.jpeg', '.png'))\n        ]\n\n        if self.split == 'TRAIN':\n            # cat.0.jpg, dog.0.jpg 형태이므로 파일명 앞부분에서 라벨을 얻음\n            self.image_list.sort(key=lambda x: (x.split('.')[0], int(x.split('.')[1])))\n        elif self.split == 'TEST':\n            # 문자열 정렬을 하면 1, 10, 100 ... 순서가 되므로 숫자로 정렬\n            self.image_list.sort(key=lambda x: int(os.path.splitext(x)[0]))\n\n    def __len__(self):\n        return len(self.image_list)\n\n    def __getitem__(self, index):\n        filename = self.image_list[index]\n        img_path = os.path.join(self.rootpath, self.split.lower(), filename)\n        img = PIL.Image.open(img_path).convert('RGB')\n        img = self.transform(img)\n\n        if self.split == 'TRAIN':\n            cls = filename.split('.')[0]\n            label = self.label_map[cls]\n            return img, label\n        else:\n            image_id = int(os.path.splitext(filename)[0])\n            return img, image_id","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-06T02:20:28.240861Z","iopub.execute_input":"2026-10-06T02:20:28.241148Z","iopub.status.idle":"2026-10-06T02:20:28.251881Z","shell.execute_reply.started":"2026-10-06T02:20:28.241122Z","shell.execute_reply":"2026-10-06T02:20:28.250754Z"},"id":"MUN_wNwy-plI"},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ImageNet pretrained VGG16을 쓰므로 같은 mean/std로 정규화\ntrain_transform = transforms.Compose([\n    transforms.Resize((224, 224)),\n    transforms.RandomHorizontalFlip(),\n    transforms.ToTensor(),\n    transforms.Normalize((0.485, 0.456, 0.406),\n                         (0.229, 0.224, 0.225))\n])\n\neval_transform = transforms.Compose([\n    transforms.Resize((224, 224)),\n    transforms.ToTensor(),\n    transforms.Normalize((0.485, 0.456, 0.406),\n                         (0.229, 0.224, 0.225))\n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-06T02:20:28.254221Z","iopub.execute_input":"2026-10-06T02:20:28.254552Z","iopub.status.idle":"2026-10-06T02:20:28.275709Z","shell.execute_reply.started":"2026-10-06T02:20:28.254523Z","shell.execute_reply":"2026-10-06T02:20:28.274735Z"},"id":"S4ZlPd9i-plI"},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# train / validation / test dataset\ntrain_base = Custom_dataset(\n    split='train', rootpath=root_path,\n    transform=train_transform, label_map=label_map\n)\nval_base = Custom_dataset(\n    split='train', rootpath=root_path,\n    transform=eval_transform, label_map=label_map\n)\ntest_dataset = Custom_dataset(\n    split='test', rootpath=root_path,\n    transform=eval_transform\n)\n\n# 같은 인덱스를 기준으로 90% train, 10% validation 분할\ngenerator = torch.Generator().manual_seed(42)\nindices = torch.randperm(len(train_base), generator=generator).tolist()\nval_size = int(len(indices) * 0.1)\nval_idx = indices[:val_size]\ntrain_idx = indices[val_size:]\n\ntrain_dataset = torch.utils.data.Subset(train_base, train_idx)\nval_dataset = torch.utils.data.Subset(val_base, val_idx)\n\ntrain_loader = torch.utils.data.DataLoader(\n    train_dataset, batch_size=16, shuffle=True,\n    num_workers=2, pin_memory=True\n)\nval_loader = torch.utils.data.DataLoader(\n    val_dataset, batch_size=16, shuffle=False,\n    num_workers=2, pin_memory=True\n)\ntest_loader = torch.utils.data.DataLoader(\n    test_dataset, batch_size=16, shuffle=False,\n    num_workers=2, pin_memory=True\n)\n\nprint('train:', len(train_dataset))\nprint('valid:', len(val_dataset))\nprint('test :', len(test_dataset))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-06T02:20:28.276904Z","iopub.execute_input":"2026-10-06T02:20:28.277236Z","iopub.status.idle":"2026-10-06T02:20:28.468143Z","shell.execute_reply.started":"2026-10-06T02:20:28.277208Z","shell.execute_reply":"2026-10-06T02:20:28.46714Z"},"id":"hsO1eH8N-plJ"},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# VGG 16 backbone\nclass VGG16(torch.nn.Module):\n    def __init__(self,  n_classes):\n        super(VGG16, self).__init__()\n        self.n_classes = n_classes\n\n        self.conv1_1 = torch.nn.Conv2d(in_channels=3, out_channels=64, kernel_size=(3,3), stride=1, padding=1)\n        self.conv1_2 = torch.nn.Conv2d(in_channels=64, out_channels=64, kernel_size=(3,3), stride=1, padding=1)\n\n        self.conv2_1 = torch.nn.Conv2d(in_channels=64, out_channels=128, kernel_size=(3,3), stride=1, padding=1)\n        self.conv2_2 = torch.nn.Conv2d(in_channels=128, out_channels=128, kernel_size=(3,3), stride=1, padding=1)\n\n        self.conv3_1 = torch.nn.Conv2d(in_channels=128, out_channels=256, kernel_size=(3,3), stride=1, padding=1)\n        self.conv3_2 = torch.nn.Conv2d(in_channels=256, out_channels=256, kernel_size=(3,3), stride=1, padding=1)\n        self.conv3_3 = torch.nn.Conv2d(in_channels=256, out_channels=256, kernel_size=(3,3), stride=1, padding=1)\n\n        self.conv4_1 = torch.nn.Conv2d(in_channels=256, out_channels=512, kernel_size=(3,3), stride=1, padding=1)\n        self.conv4_2 = torch.nn.Conv2d(in_channels=512, out_channels=512, kernel_size=(3,3), stride=1, padding=1)\n        self.conv4_3 = torch.nn.Conv2d(in_channels=512, out_channels=512, kernel_size=(3,3), stride=1, padding=1)\n\n        self.conv5_1 = torch.nn.Conv2d(in_channels=512, out_channels=512, kernel_size=(3,3), stride=1, padding=1)\n        self.conv5_2 = torch.nn.Conv2d(in_channels=512, out_channels=512, kernel_size=(3,3), stride=1, padding=1)\n        self.conv5_3 = torch.nn.Conv2d(in_channels=512, out_channels=512, kernel_size=(3,3), stride=1, padding=1)\n\n        self.fc1 = torch.nn.Linear(in_features = 7*7*512, out_features=4096, bias=True)\n        self.fc2 = torch.nn.Linear(in_features = 4096, out_features=4096, bias=True)\n        self.fc3 = torch.nn.Linear(in_features = 4096, out_features=self.n_classes, bias=True)\n\n        self.activation = torch.nn.ReLU()\n        self.pool = torch.nn.MaxPool2d(kernel_size = 2, stride = 2)\n        self.dropout = torch.nn.Dropout(p=0.5)\n\n\n    def forward(self, x):\n        out = self.activation(self.conv1_1(x))\n        out = self.activation(self.conv1_2(out))\n        out = self.pool(out)\n\n        out = self.activation(self.conv2_1(out))\n        out = self.activation(self.conv2_2(out))\n        out = self.pool(out)\n\n        out = self.activation(self.conv3_1(out))\n        out = self.activation(self.conv3_2(out))\n        out = self.activation(self.conv3_3(out))\n        out = self.pool(out)\n\n        out = self.activation(self.conv4_1(out))\n        out = self.activation(self.conv4_2(out))\n        out = self.activation(self.conv4_3(out))\n        out = self.pool(out)\n\n        out = self.activation(self.conv5_1(out))\n        out = self.activation(self.conv5_2(out))\n        out = self.activation(self.conv5_3(out))\n        out = self.pool(out)\n\n        out = out.view(-1, out.shape[1]*out.shape[2]*out.shape[3])\n        # out = self.activation(self.fc1(out))\n        # out = self.activation(self.fc2(out))\n        out = self.dropout(self.activation(self.fc1(out)))\n        out = self.dropout(self.activation(self.fc2(out)))\n        out = self.fc3(out)\n\n        return out","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-06T02:20:28.469822Z","iopub.execute_input":"2026-10-06T02:20:28.47046Z","iopub.status.idle":"2026-10-06T02:20:28.487775Z","shell.execute_reply.started":"2026-10-06T02:20:28.470427Z","shell.execute_reply":"2026-10-06T02:20:28.48666Z"},"id":"8xQm5lSX-plJ"},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# model 가중치 불러오기\nimport torch.nn as nn\n\n# cat / dog 2개 클래스\nmodel = VGG16(n_classes=2).to(device)\nmy_model_dict = model.state_dict()\nlayer_names = list(my_model_dict.keys())\n\n# ImageNet 사전학습 VGG16 가중치 불러오기\nweights = torchvision.models.VGG16_Weights.DEFAULT\npretrained_dict = torchvision.models.vgg16(weights=weights).state_dict()\npretrained_layer_names = list(pretrained_dict.keys())\n\n# 마지막 분류층(fc3)을 제외하고 pretrained weight 로드\nfor i, name in enumerate(layer_names[:-2]):\n    my_model_dict[name] = pretrained_dict[pretrained_layer_names[i]]\n\nmodel.load_state_dict(my_model_dict)\n\n# 2-class classification\nloss = nn.CrossEntropyLoss()\noptim = torch.optim.SGD(model.parameters(), lr=0.0001, momentum=0.9)\nepochs = 3","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-06T02:20:28.489119Z","iopub.execute_input":"2026-10-06T02:20:28.490128Z","iopub.status.idle":"2026-10-06T02:20:35.455706Z","shell.execute_reply.started":"2026-10-06T02:20:28.490081Z","shell.execute_reply":"2026-10-06T02:20:35.454669Z"},"id":"bRpAuu8c-plJ","outputId":"a509159b-0c83-4669-8369-4968b67013af"},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for epoch in range(epochs):\n    # train\n    model.train()\n    train_correct = 0\n    train_loss = 0.0\n\n    for x, y in tqdm(train_loader, desc=f'train {epoch + 1}/{epochs}'):\n        x = x.to(device, non_blocking=True)\n        y = y.to(device, non_blocking=True)\n\n        out = model(x)\n        cost = loss(out, y)\n\n        optim.zero_grad()\n        cost.backward()\n        optim.step()\n\n        pred = torch.argmax(out, dim=1)\n        train_correct += (pred == y).sum().item()\n        train_loss += cost.item() * x.size(0)\n\n    train_loss /= len(train_loader.dataset)\n    train_acc = train_correct / len(train_loader.dataset)\n\n    # validation\n    model.eval()\n    val_correct = 0\n    val_loss = 0.0\n\n    with torch.no_grad():\n        for x, y in tqdm(val_loader, desc=f'valid {epoch + 1}/{epochs}'):\n            x = x.to(device, non_blocking=True)\n            y = y.to(device, non_blocking=True)\n\n            out = model(x)\n            cost = loss(out, y)\n            pred = torch.argmax(out, dim=1)\n\n            val_correct += (pred == y).sum().item()\n            val_loss += cost.item() * x.size(0)\n\n    val_loss /= len(val_loader.dataset)\n    val_acc = val_correct / len(val_loader.dataset)\n\n    print(\n        f'epoch {epoch + 1} | '\n        f'train loss {train_loss:.4f} acc {train_acc:.4f} | '\n        f'val loss {val_loss:.4f} acc {val_acc:.4f}'\n    )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-06T02:20:35.457031Z","iopub.execute_input":"2026-10-06T02:20:35.45747Z"},"id":"sp8JYtCQ-plJ","outputId":"e6e648d6-cfaf-4c1b-ec49-d02050ee94f9"},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# test 예측\nmodel.eval()\nid_list = []\nprob_list = []\n\nwith torch.no_grad():\n    for x, image_id in tqdm(test_loader):\n        x = x.to(device, non_blocking=True)\n        out = model(x)\n\n        dog_prob = torch.softmax(out, dim=1)[:, 1]\n\n        id_list.extend(image_id.numpy().tolist())\n        prob_list.extend(dog_prob.cpu().numpy().tolist())","metadata":{"trusted":true,"id":"a-3mlOmO-plJ","outputId":"e002b475-2ee9-4a22-be5a-5433b9153c45"},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# sample_submission의 id 순서에 맞춰 dog probability 저장\npred_df = pd.DataFrame({\n    'id': id_list,\n    'label': prob_list\n})\n\nsubmit = pd.read_csv(os.path.join(competition_path, 'sample_submission.csv'))\nsubmit = submit[['id']].merge(pred_df, on='id', how='left')\n\nassert submit['label'].notna().all()\nsubmit.to_csv('/kaggle/working/baseline.csv', index=False)\nsubmit.head()","metadata":{"trusted":true,"id":"8Le4p_QU-plJ"},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true,"id":"lhEAkIaR-plK"},"outputs":[],"execution_count":null}]}