{"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":"gpu","dataSources":[{"sourceId":5441,"databundleVersionId":38425,"sourceType":"competition"}],"dockerImageVersionId":31260,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import glob\n\n# check our work directory\nimport os\n\n# to unzip datasets\nimport zipfile\n\n# visualize some datasets\nimport matplotlib.pyplot as plt\n\n# using numpy\nimport numpy as np\n\n# for data load or save\nimport pandas as pd\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport torch.optim as optim\nfrom PIL import Image\nfrom torch.utils.data import DataLoader, Dataset\nfrom torchvision import datasets, models, transforms\n\nplt.style.use([\"dark_background\", \"bmh\"])\nplt.rc(\"axes\", facecolor=\"k\")\nplt.rc(\"figure\", facecolor=\"k\")\nplt.rc(\"figure\", figsize=(10, 10), dpi=100)","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-01-20T08:16:14.120634Z","iopub.execute_input":"2026-01-20T08:16:14.120859Z","iopub.status.idle":"2026-01-20T08:16:14.126943Z","shell.execute_reply.started":"2026-01-20T08:16:14.120837Z","shell.execute_reply":"2026-01-20T08:16:14.126256Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\ntorch.manual_seed(42)\nif device == \"cuda\":\n    torch.cuda.manual_seed_all(42)\ndevice","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-20T08:16:21.991373Z","iopub.execute_input":"2026-01-20T08:16:21.991932Z","iopub.status.idle":"2026-01-20T08:16:22.001184Z","shell.execute_reply.started":"2026-01-20T08:16:21.991906Z","shell.execute_reply":"2026-01-20T08:16:22.000508Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"base_dir = \"/kaggle/input/dogs-vs-cats-redux-kernels-edition\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-20T08:16:23.991754Z","iopub.execute_input":"2026-01-20T08:16:23.992026Z","iopub.status.idle":"2026-01-20T08:16:23.995473Z","shell.execute_reply.started":"2026-01-20T08:16:23.992003Z","shell.execute_reply":"2026-01-20T08:16:23.994738Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Read-only source (competition data)\ninput_dir = \"/kaggle/input/dogs-vs-cats-redux-kernels-edition\"\n\n# Writable destination \nbase_dir = \"/kaggle/working/dogs-vs-cats\"\nos.makedirs(base_dir, exist_ok=True)\n\n# Extract train.zip\ntrain_dir = os.path.join(base_dir, \"train\")\nif not os.path.isdir(train_dir):\n    with zipfile.ZipFile(os.path.join(input_dir, \"train.zip\")) as train_zip:\n        train_zip.extractall(base_dir)\n\n# Extract test.zip\ntest_dir = os.path.join(base_dir, \"test\")\nif not os.path.isdir(test_dir):\n    with zipfile.ZipFile(os.path.join(input_dir, \"test.zip\")) as test_zip:\n        test_zip.extractall(base_dir)\n# by now, you have a folder dogs-vs-cats with both train and test folders that have 25000 and 12500 images in them, respectively","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-20T08:16:26.187340Z","iopub.execute_input":"2026-01-20T08:16:26.187903Z","iopub.status.idle":"2026-01-20T08:16:26.192839Z","shell.execute_reply.started":"2026-01-20T08:16:26.187876Z","shell.execute_reply":"2026-01-20T08:16:26.192242Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_dir = f\"{base_dir}/train\"\ntest_dir = f\"{base_dir}/test\"\n\n# you get two lists with the paths for all of the train images, and the paths for all of the test images\ntrain_list = glob.glob(os.path.join(train_dir, \"*.jpg\"))\ntest_list = glob.glob(os.path.join(test_dir, \"*.jpg\"))\n\nprint(f\"TRAIN LIST -> {train_list[:10]}\\nlen -> {len(train_list)}\")\n\nprint(f\"TEST LIST -> {test_list[:10]}\\nlen -> {len(test_list)}\")\n\n\nprint(f\"Training samples: {len(train_list)}, Tests samples: {len(test_list)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-20T08:38:50.728716Z","iopub.execute_input":"2026-01-20T08:38:50.729027Z","iopub.status.idle":"2026-01-20T08:38:50.793839Z","shell.execute_reply.started":"2026-01-20T08:38:50.729002Z","shell.execute_reply":"2026-01-20T08:38:50.793212Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# randomly view images from the dataset\n# Python Imaging Library\nfrom PIL import Image\n\nrandom_idx = np.random.randint(1, 25000, size=10)\n\nfig = plt.figure(figsize=(16, 6))\ni = 1\nfor idx in random_idx:\n    ax = fig.add_subplot(2, 5, i)\n    img = Image.open(train_list[idx])\n    plt.imshow(img)\n    i += 1\n\nplt.axis(\"off\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-19T17:13:15.059189Z","iopub.execute_input":"2026-01-19T17:13:15.059564Z","iopub.status.idle":"2026-01-19T17:13:16.608937Z","shell.execute_reply.started":"2026-01-19T17:13:15.059525Z","shell.execute_reply":"2026-01-19T17:13:16.607595Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\n    f\"Instances dog: {sum('dog.' in s for s in train_list)}, Instances Cat: {sum('cat.' in s for s in train_list)}\"\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-19T17:28:39.571253Z","iopub.execute_input":"2026-01-19T17:28:39.572021Z","iopub.status.idle":"2026-01-19T17:28:39.586549Z","shell.execute_reply.started":"2026-01-19T17:28:39.571979Z","shell.execute_reply":"2026-01-19T17:28:39.585466Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# you have a list of file paths for both train and test images:\n# [../dog.5121.jpg, ...]\n# you need a tensor and a numerical label\n# you have different transforms for train and test so you must apply them here\nclass DogVsCatDataset(Dataset):\n    def __init__(self, file_list, transform=None):\n        self.file_list = file_list\n        self.transform = transform\n\n    def __len__(self):\n        # to get progress bars, epoch definition and etc.\n        return len(self.file_list)\n\n    def __getitem__(self, idx):\n        if torch.is_tensor(idx):\n            idx = idx.tolist()\n\n        img_name = self.file_list[idx]\n        image = Image.open(img_name)\n        if self.transform:\n            image = self.transform(image)\n    \n        label_category = label = img_name.split(\"/\")[-1].split(\".\")[0]\n        if label_category == \"dog\":\n            label = 1\n        elif label_category == \"cat\":\n            label = 0\n\n        return image, label","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-19T17:29:12.827641Z","iopub.execute_input":"2026-01-19T17:29:12.828551Z","iopub.status.idle":"2026-01-19T17:29:12.837857Z","shell.execute_reply.started":"2026-01-19T17:29:12.828500Z","shell.execute_reply":"2026-01-19T17:29:12.836794Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Explore more transformations\ntrain_transforms = transforms.Compose(\n    [\n        transforms.Resize((224, 224)),\n        transforms.RandomResizedCrop(224),\n        # CNNs are translation equivariant but it depends on the dataset\n        # don't do it for mnist: https://datascience.stackexchange.com/questions/71950/artificially-expanding-the-datasets-through-rotation-of-images-in-mnist\n        transforms.RandomHorizontalFlip(), # augmentation!\n        transforms.ToTensor(),\n    ]\n)\ntest_transforms = transforms.Compose(\n    [\n        transforms.Resize((224, 224)),\n        transforms.RandomResizedCrop(224),\n        transforms.ToTensor(),\n    ]\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-19T17:28:43.572116Z","iopub.execute_input":"2026-01-19T17:28:43.572424Z","iopub.status.idle":"2026-01-19T17:28:43.578031Z","shell.execute_reply.started":"2026-01-19T17:28:43.572398Z","shell.execute_reply":"2026-01-19T17:28:43.577097Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_data = DogVsCatDataset(train_list, transform=train_transforms)\ntest_data = DogVsCatDataset(test_list, transform=test_transforms)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-19T17:29:18.356228Z","iopub.execute_input":"2026-01-19T17:29:18.356648Z","iopub.status.idle":"2026-01-19T17:29:18.360630Z","shell.execute_reply.started":"2026-01-19T17:29:18.356621Z","shell.execute_reply":"2026-01-19T17:29:18.359834Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"batch_size = 64\ntrain_loader = torch.utils.data.DataLoader(\n    dataset=train_data, batch_size=batch_size, shuffle=True\n)\ntest_loader = torch.utils.data.DataLoader(dataset=test_data, shuffle=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-19T17:29:25.442908Z","iopub.execute_input":"2026-01-19T17:29:25.443235Z","iopub.status.idle":"2026-01-19T17:29:25.448625Z","shell.execute_reply.started":"2026-01-19T17:29:25.443206Z","shell.execute_reply":"2026-01-19T17:29:25.447541Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(len(train_data), len(train_loader))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-19T17:29:30.317579Z","iopub.execute_input":"2026-01-19T17:29:30.318359Z","iopub.status.idle":"2026-01-19T17:29:30.322871Z","shell.execute_reply.started":"2026-01-19T17:29:30.318331Z","shell.execute_reply":"2026-01-19T17:29:30.321911Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class CNN(nn.Module):\n    def __init__(self):\n        super().__init__()\n\n        self.layer1 = nn.Sequential(\n            nn.Conv2d(3, 16, kernel_size=3, padding=0, stride=2),\n            nn.BatchNorm2d(16),\n            nn.ReLU(),\n            nn.MaxPool2d(2),\n        )\n\n        self.layer2 = nn.Sequential(\n            nn.Conv2d(16, 32, kernel_size=3, padding=0, stride=2),\n            nn.BatchNorm2d(32),\n            nn.ReLU(),\n            nn.MaxPool2d(2),\n        )\n\n        self.layer3 = nn.Sequential(\n            nn.Conv2d(32, 64, kernel_size=3, padding=0, stride=2),\n            nn.BatchNorm2d(64),\n            nn.ReLU(),\n            nn.MaxPool2d(2),\n        )\n\n        self.fc1 = nn.Linear(3 * 3 * 64, 10)\n        self.dropout = nn.Dropout(0.5)\n        self.fc2 = nn.Linear(10, 2)\n        self.relu = nn.ReLU()\n\n    # Helps\n    # https://datascience.stackexchange.com/questions/40906/determining-size-of-fc-layer-after-conv-layer-in-pytorch\n    def forward(self, x):\n        out = self.layer1(x)\n        out = self.layer2(out)\n        out = self.layer3(out)\n        out = out.view(out.size(0), -1)\n        out = self.relu(self.fc1(out))\n        out = self.fc2(out)\n        return out","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-19T17:29:37.141795Z","iopub.execute_input":"2026-01-19T17:29:37.142325Z","iopub.status.idle":"2026-01-19T17:29:37.150224Z","shell.execute_reply.started":"2026-01-19T17:29:37.142298Z","shell.execute_reply":"2026-01-19T17:29:37.149397Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model = CNN().to(device)\nmodel.train()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-19T17:29:48.309403Z","iopub.execute_input":"2026-01-19T17:29:48.309799Z","iopub.status.idle":"2026-01-19T17:29:48.511566Z","shell.execute_reply.started":"2026-01-19T17:29:48.309771Z","shell.execute_reply":"2026-01-19T17:29:48.510718Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# TIPS FOR FINE TUNNING\nmodel = models.resnet18(weights=True)\nfor param in model.parameters():\n    param.requires_grad = False\n\n# Parameters of newly constructed modules have requires_grad=True by default\nnum_ftrs = model.fc.in_features\nmodel.fc = nn.Linear(num_ftrs, 2)\n\nmodel = model.to(device)\n\noptimizer = optim.Adam(params=model.fc.parameters(), lr=0.001)\ncriterion = nn.CrossEntropyLoss()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-19T17:30:03.150259Z","iopub.execute_input":"2026-01-19T17:30:03.150579Z","iopub.status.idle":"2026-01-19T17:30:04.317605Z","shell.execute_reply.started":"2026-01-19T17:30:03.150554Z","shell.execute_reply":"2026-01-19T17:30:04.316759Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"optimizer = optim.Adam(params=model.parameters(), lr=0.001)\ncriterion = nn.CrossEntropyLoss()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-19T17:30:29.523788Z","iopub.execute_input":"2026-01-19T17:30:29.524118Z","iopub.status.idle":"2026-01-19T17:30:29.528956Z","shell.execute_reply.started":"2026-01-19T17:30:29.524074Z","shell.execute_reply":"2026-01-19T17:30:29.527976Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"epochs = 1\nfor epoch in range(epochs):\n    epoch_loss = 0\n    epoch_accuracy = 0\n\n    for batch_idx, (data, target) in enumerate(train_loader):\n\n        # get data\n        data, target = data.to(device), target.to(device)\n\n        # zero grad\n        optimizer.zero_grad()\n\n        # predictions\n        output = model(data)\n\n        # comput loss and backward\n        loss = criterion(output, target)\n        loss.backward()\n\n        # adjust weights\n        optimizer.step()\n\n        acc = (output.argmax(dim=1) == target).float().mean()\n        epoch_accuracy += acc / len(train_loader)\n        epoch_loss += loss / len(train_loader)\n\n        if batch_idx % 100 == 0:\n            print(\n                \"Train Epoch: {} [{}/{} ({:.0f}%)]\\tLoss: {:.6f}\".format(\n                    epoch,\n                    batch_idx * len(data),\n                    len(train_loader.dataset),\n                    100.0 * batch_idx / len(train_loader),\n                    loss.item(),\n                )\n            )\n    print(\n        f\"Epoch : {epoch+1}, train accuracy : {epoch_accuracy}, train loss : {epoch_loss}\"\n    )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-19T17:30:36.548303Z","iopub.execute_input":"2026-01-19T17:30:36.548652Z","iopub.status.idle":"2026-01-19T17:32:40.912364Z","shell.execute_reply.started":"2026-01-19T17:30:36.548626Z","shell.execute_reply":"2026-01-19T17:32:40.911540Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"probs = []\nmodel.eval()\nwith torch.no_grad():\n    for data, fileid in test_loader:\n        data = data.to(device)\n        preds = model(data)\n        preds_list = F.softmax(preds, dim=1)[:, 1].tolist()\n        probs += list(zip(list(fileid), preds_list))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-19T17:35:34.105578Z","iopub.execute_input":"2026-01-19T17:35:34.106366Z","iopub.status.idle":"2026-01-19T17:37:16.356396Z","shell.execute_reply.started":"2026-01-19T17:35:34.106335Z","shell.execute_reply":"2026-01-19T17:37:16.355297Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"random_idx = np.random.randint(1, len(test_loader), size=10)\n\nclass_ = {0: \"cat\", 1: \"dog\"}\n\n\nfig = plt.figure(figsize=(16, 6))\ni = 1\n\nfor idx in random_idx:\n    ax = fig.add_subplot(2, 5, i)\n\n    img_id = probs[idx][0]\n    pred = probs[idx][1]\n\n    if pred > 0.5:\n        label = 1\n    else:\n        label = 0\n\n    img_path = os.path.join(test_dir, f\"{img_id}.jpg\")\n    img = Image.open(img_path)\n    plt.imshow(img)\n    plt.title(class_[label])\n\n    i += 1\n\nplt.axis(\"off\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-19T17:37:16.358115Z","iopub.execute_input":"2026-01-19T17:37:16.358438Z","iopub.status.idle":"2026-01-19T17:37:17.667841Z","shell.execute_reply.started":"2026-01-19T17:37:16.358412Z","shell.execute_reply":"2026-01-19T17:37:17.666798Z"}},"outputs":[],"execution_count":null}]}