{"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":61938,"databundleVersionId":6698770,"sourceType":"competition"}],"dockerImageVersionId":31260,"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 torch\nimport torchvision\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torchvision import transforms\nimport torch.utils.data\nfrom torchvision.transforms import ToTensor\nfrom torchvision.utils import make_grid\nfrom torch.utils.data import Dataset, DataLoader\nfrom torch.utils.data import random_split\nfrom tqdm import tqdm\n%matplotlib inline\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        break\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,"_kg_hide-output":true,"execution":{"iopub.status.busy":"2026-02-02T15:24:22.786986Z","iopub.execute_input":"2026-02-02T15:24:22.787529Z","iopub.status.idle":"2026-02-02T15:24:57.090839Z","shell.execute_reply.started":"2026-02-02T15:24:22.787503Z","shell.execute_reply":"2026-02-02T15:24:57.090012Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from PIL import Image\nimport os\nimport pandas as pd\n\n# DEVICE\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\n# KAGGLE PATHS\nDATA_DIR = \"/kaggle/input/dog-breed-identification-data\"\nTRAIN_IMAGE_DIR = os.path.join(DATA_DIR, \"train\")\nTRAIN_CSV_PATH = os.path.join(DATA_DIR, \"labels.csv\")\n\nIMAGE_SIZE = 224\nBATCH_SIZE = 64\nNUM_CLASSES = 120\n\nclass Dogdataset(Dataset):\n    def __init__(self, csv_file, img_dir, transform=None):\n        self.df = pd.read_csv(csv_file)\n        self.img_dir = img_dir\n        self.transform = transform\n\n        breeds = self.df[\"breed\"].unique()\n        self.label_map = {b: i for i, b in enumerate(breeds)}\n        self.df[\"label\"] = self.df[\"breed\"].map(self.label_map)\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        img_id = self.df.iloc[idx][\"id\"]\n        img_path = os.path.join(self.img_dir, img_id + \".jpg\")\n\n        image = Image.open(img_path).convert(\"RGB\")\n        label = self.df.iloc[idx][\"label\"]\n\n        if self.transform:\n            image = self.transform(image)\n\n        return image, label\n\n# TRANSFORMS\ntrain_transforms = transforms.Compose([\n    transforms.RandomResizedCrop(224, scale=(0.8, 1.0)), \n    transforms.RandomHorizontalFlip(),                   \n    transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation= 0.2), \n    transforms.RandomAffine(degrees=15, translate=(0.1, 0.1)), \n    transforms.RandomPerspective(distortion_scale=0.2, p=0.5),\n    transforms.ToTensor(),\n    transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])\n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-02T15:31:10.537536Z","iopub.execute_input":"2026-02-02T15:31:10.538264Z","iopub.status.idle":"2026-02-02T15:31:10.551164Z","shell.execute_reply.started":"2026-02-02T15:31:10.538236Z","shell.execute_reply":"2026-02-02T15:31:10.550518Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"random_seed = 42\ntorch.manual_seed(random_seed);\n\ndataset = Dogdataset(TRAIN_CSV_PATH, TRAIN_IMAGE_DIR, transforms)\nval_size = 1000\ntrain_size = len(dataset) - val_size\ntrain_ds, val_ds = torch.utils.data.random_split(dataset, [train_size, val_size])\n\n# DATALOADERS\ntrain_loader = DataLoader(train_ds, batch_size=BATCH_SIZE, shuffle=True, num_workers=0, pin_memory = True)\nval_loader = DataLoader(val_ds, batch_size=BATCH_SIZE, shuffle=False, num_workers=0, pin_memory = True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-02T15:31:42.761339Z","iopub.execute_input":"2026-02-02T15:31:42.761934Z","iopub.status.idle":"2026-02-02T15:31:42.782692Z","shell.execute_reply.started":"2026-02-02T15:31:42.761908Z","shell.execute_reply":"2026-02-02T15:31:42.782116Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def accuracy(outputs, labels):\n    _, preds = torch.max(outputs, dim=1)\n    return torch.tensor(torch.sum(preds == labels).item() / len(preds))\n\nclass ImageClassificationBase(nn.Module):\n    def training_step(self, batch):\n        images, labels = batch \n        images, labels = images.to(device), labels.to(device)\n        out = self(images)                  # Generate predictions\n        loss = F.cross_entropy(out, labels, label_smoothing=0.1) # label smoothing to reduce specialisation\n        return loss\n    \n    def validation_step(self, batch):\n        images, labels = batch \n        images, labels = images.to(device), labels.to(device)\n        out = self(images)                    # Generate predictions\n        loss = F.cross_entropy(out, labels)   # Calculate loss\n        acc = accuracy(out, labels)           # Calculate accuracy\n        return {'val_loss': loss.detach(), 'val_acc': acc}\n        \n    def validation_epoch_end(self, outputs):\n        batch_losses = [x['val_loss'] for x in outputs]\n        epoch_loss = torch.stack(batch_losses).mean()   # Combine losses\n        batch_accs = [x['val_acc'] for x in outputs]\n        epoch_acc = torch.stack(batch_accs).mean()      # Combine accuracies\n        return {'val_loss': epoch_loss.item(), 'val_acc': epoch_acc.item()}\n    \n    def epoch_end(self, epoch, result):\n        print(\"Epoch [{}], train_loss: {:.4f}, val_loss: {:.4f}, val_acc: {:.4f}\".format(\n            epoch, result['train_loss'], result['val_loss'], result['val_acc']))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-02T15:27:18.001872Z","iopub.execute_input":"2026-02-02T15:27:18.002383Z","iopub.status.idle":"2026-02-02T15:27:18.009595Z","shell.execute_reply.started":"2026-02-02T15:27:18.002350Z","shell.execute_reply":"2026-02-02T15:27:18.008889Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nimport torch.nn.functional as F\n\nclass ResidualBlock(nn.Module):\n    def __init__(self, in_channels, out_channels, stride=1):\n        super().__init__()\n        \n        self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=3, \n                               stride=stride, padding=1, bias=False)\n        self.bn1 = nn.BatchNorm2d(out_channels)\n     \n        self.shortcut = nn.Sequential()\n        if stride != 1 or in_channels != out_channels:\n            self.shortcut = nn.Sequential(\n                nn.Conv2d(in_channels, out_channels, kernel_size=1, \n                          stride=stride, bias=False),\n                nn.BatchNorm2d(out_channels)\n            )\n\n    def forward(self, x):\n        out = F.relu(self.bn1(self.conv1(x)))\n        \n        out += self.shortcut(x) \n        return F.relu(out)\n\nclass DeepDogModel(ImageClassificationBase): \n    def __init__(self, num_classes=NUM_CLASSES):\n        super().__init__()\n        \n  \n        self.stem = nn.Sequential(\n            nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, bias=False),\n            nn.BatchNorm2d(64),\n            nn.ReLU(),\n            nn.MaxPool2d(kernel_size=3, stride=2, padding=1)\n        )\n\n        self.stage1 = nn.Sequential(\n            ResidualBlock(64, 64, stride=1),\n            ResidualBlock(64, 64, stride=1)\n        )\n\n        self.stage2 = nn.Sequential(\n            ResidualBlock(64, 128, stride=2), \n            ResidualBlock(128, 128, stride=1)\n        )\n\n        \n        self.stage3 = nn.Sequential(\n            ResidualBlock(128, 256, stride=2), \n            ResidualBlock(256, 256, stride=1)\n        )\n\n        \n        self.stage4 = nn.Sequential(\n            ResidualBlock(256, 512, stride=2),\n            ResidualBlock(512, 512, stride=1)\n        )\n\n        # 6. Classifier\n        self.classifier = nn.Sequential(\n            nn.AdaptiveAvgPool2d((1, 1)),\n            nn.Flatten(),\n            nn.Dropout(0.5),\n            nn.Linear(512, num_classes)\n        )\n\n    def forward(self, x):\n        x = self.stem(x)\n        x = self.stage1(x)\n        x = self.stage2(x)\n        x = self.stage3(x)\n        x = self.stage4(x)\n        x = self.classifier(x)\n        return x","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-02T15:27:21.034432Z","iopub.execute_input":"2026-02-02T15:27:21.034753Z","iopub.status.idle":"2026-02-02T15:27:21.044723Z","shell.execute_reply.started":"2026-02-02T15:27:21.034725Z","shell.execute_reply":"2026-02-02T15:27:21.044073Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class DogModel(nn.Module):\n    def __init__(self, num_classes=120):\n        super().__init__()\n\n        self.features = nn.Sequential(\n            nn.Conv2d(3, 32, 3, padding=1),\n            nn.ReLU(),\n            nn.Conv2d(32, 32, 3, padding=1),\n            nn.ReLU(),\n            nn.MaxPool2d(2, 2),  # 224 → 112\n\n            nn.Conv2d(32, 64, 3, padding=1),\n            nn.ReLU(),\n            nn.Conv2d(64, 64, 3, padding=1),\n            nn.ReLU(),\n            nn.MaxPool2d(2, 2),  # 112 → 56\n\n            nn.Conv2d(64, 128, 3, padding=1),\n            nn.ReLU(),\n            nn.Conv2d(128, 128, 3, padding=1),\n            nn.ReLU(),\n            nn.MaxPool2d(2, 2),  # 56 → 28\n\n            nn.Conv2d(128, 256, 3, padding=1),\n            nn.ReLU(),\n            nn.Conv2d(256, 256, 3, padding=1),\n            nn.ReLU(),\n            nn.MaxPool2d(2, 2),  # 28 → 14\n        )\n\n        self.classifier = nn.Sequential(\n            nn.Flatten(),\n            nn.Linear(256 * 14 * 14, 512),\n            nn.ReLU(),\n            nn.Dropout(0.5),\n            nn.Linear(512, 256),\n            nn.ReLU(),\n            nn.Dropout(0.3),\n            nn.Linear(256, num_classes)\n        )\n\n    def forward(self, x):\n        x = self.features(x)\n        x = self.classifier(x)\n        return x \n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-02T15:27:23.940829Z","iopub.execute_input":"2026-02-02T15:27:23.941156Z","iopub.status.idle":"2026-02-02T15:27:23.948579Z","shell.execute_reply.started":"2026-02-02T15:27:23.941130Z","shell.execute_reply":"2026-02-02T15:27:23.947875Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\n\n@torch.no_grad()\ndef evaluate(model, val_loader):\n    model.eval()\n    outputs = [model.validation_step(batch) for batch in val_loader]\n    return model.validation_epoch_end(outputs)\n\nfrom tqdm.auto import tqdm  # The \"auto\" version is great for notebooks\n\ndef fit(epochs, max_lr, model, train_loader, val_loader, weight_decay=0):\n    train_losses = []\n    # Using AdamW + Weight Decay to fight that overfitting\n    optimizer = torch.optim.AdamW(model.parameters(), max_lr, weight_decay=weight_decay)\n    \n    # OneCycleLR: Starts low, peaks in the middle, then tapers off at the end\n    sched = torch.optim.lr_scheduler.OneCycleLR(optimizer, max_lr, epochs=epochs, \n                                                steps_per_epoch=len(train_loader))\n    \n    for epoch in range(epochs):\n        model.train()\n        epoch_train_losses = []\n\n        # TQDM progress bar setup\n        progress = tqdm(train_loader, desc=f\"Epoch {epoch}\", leave=False)\n\n        for batch in progress:\n            loss = model.training_step(batch)\n            loss.backward()\n            nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0) #gradient clipping\n            optimizer.step()\n            optimizer.zero_grad()\n            \n            # Step the scheduler after every batch\n            sched.step()\n\n            epoch_train_losses.append(loss.detach())\n            \n            # Update the progress bar with live stats\n            current_lr = optimizer.param_groups[0]['lr']\n            progress.set_postfix({\n                'loss': f\"{loss.item():.4f}\", \n                'lr': f\"{current_lr:.6f}\"\n            })\n\n        # Validation phase\n        result = evaluate(model, val_loader)\n        avg_train_loss = torch.stack(epoch_train_losses).mean().item()\n        \n        print(\n            f\"Epoch {epoch} | \"\n            f\"train_loss={avg_train_loss:.4f} \"\n            f\"val_loss={result['val_loss']:.4f} val_acc={result['val_acc']:.4f} \"\n            f\"LR={current_lr:.6f}\"\n        )\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-02T15:27:26.630106Z","iopub.execute_input":"2026-02-02T15:27:26.630662Z","iopub.status.idle":"2026-02-02T15:27:26.764791Z","shell.execute_reply.started":"2026-02-02T15:27:26.630635Z","shell.execute_reply":"2026-02-02T15:27:26.764061Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model = DeepDogModel()\nmodel = model.to(device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-02T15:28:14.691424Z","iopub.execute_input":"2026-02-02T15:28:14.691762Z","iopub.status.idle":"2026-02-02T15:28:14.744431Z","shell.execute_reply.started":"2026-02-02T15:28:14.691733Z","shell.execute_reply":"2026-02-02T15:28:14.743863Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"CUDA available:\", torch.cuda.is_available())\n\nif torch.cuda.is_available():\n    print(\"GPU name:\", torch.cuda.get_device_name(0))\n    print(\"Current device:\", torch.cuda.current_device())\n    print(\"Device count:\", torch.cuda.device_count())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-02T15:28:18.707087Z","iopub.execute_input":"2026-02-02T15:28:18.707805Z","iopub.status.idle":"2026-02-02T15:28:18.712568Z","shell.execute_reply.started":"2026-02-02T15:28:18.707774Z","shell.execute_reply":"2026-02-02T15:28:18.711844Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import models, transforms\n\n# ==========================================\n# 1. FIXED DATASET (Solves 'module not callable' error)\n# ==========================================\nclass MyDataset(Dataset):\n    def __init__(self, data, labels, transform=None):\n        self.data = data\n        self.labels = labels\n        # Ensure we don't accidentally pass the module 'transforms'\n        self.transform = transform \n\n    def __len__(self):\n        return len(self.data)\n\n    def __getitem__(self, idx):\n        image = self.data[idx]\n        label = self.labels[idx]\n\n        # Apply transforms if they exist\n        if self.transform:\n            image = self.transform(image)\n        \n        return image, label\n\n# ==========================================\n# 2. EFFICIENTNET WRAPPER (Adds 'training_step')\n# ==========================================\nclass EfficientNetModel(nn.Module):\n    def __init__(self, num_classes):\n        super().__init__()\n        # Load pre-trained EfficientNet\n        self.network = models.efficientnet_b0(weights=models.EfficientNet_B0_Weights.DEFAULT)\n        \n        # Replace the last layer for your specific number of classes\n        num_ftrs = self.network.classifier[1].in_features\n        self.network.classifier[1] = nn.Linear(num_ftrs, num_classes)\n        \n    def forward(self, xb):\n        return self.network(xb)\n    \n    # This is the method your 'fit' function was looking for\n    def training_step(self, batch):\n        images, labels = batch\n        out = self(images)                  # Generate predictions\n        loss = F.cross_entropy(out, labels) # Calculate loss\n        return loss\n\n# ==========================================\n# 3. TRAINING FUNCTION (Matches your input)\n# ==========================================\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\n\n# --- 1. Helper Function for Accuracy ---\ndef accuracy(outputs, labels):\n    _, preds = torch.max(outputs, dim=1)\n    return torch.tensor(torch.sum(preds == labels).item() / len(preds))\n\n# --- 2. The Training Function (Updated) ---\ndef fit(epochs, max_lr, model, train_loader, val_loader, opt_func=torch.optim.Adam):\n    optimizer = opt_func(model.parameters(), lr=max_lr)\n    \n    # Detect device (GPU or CPU) from the model\n    device = next(model.parameters()).device\n    \n    for epoch in range(epochs):\n        # ===========================\n        # Training Phase\n        # ===========================\n        model.train()\n        train_losses = []\n        train_accs = []\n        \n        for batch in train_loader:\n            images, labels = batch\n            \n            # Move data to the same device as the model\n            images = images.to(device)\n            labels = labels.to(device)\n            \n            # Forward pass\n            out = model(images) \n            loss = F.cross_entropy(out, labels)\n            acc = accuracy(out, labels)\n            \n            # Record metrics\n            train_losses.append(loss.item())\n            train_accs.append(acc.item())\n            \n            # Backward pass\n            loss.backward()\n            optimizer.step()\n            optimizer.zero_grad()\n            \n        # ===========================\n        # Validation Phase\n        # ===========================\n        model.eval() # Set model to evaluation mode\n        val_losses = []\n        val_accs = []\n        \n        with torch.no_grad(): # Disable gradient calculation for validation\n            for batch in val_loader:\n                images, labels = batch\n                images = images.to(device)\n                labels = labels.to(device)\n                \n                out = model(images)\n                loss = F.cross_entropy(out, labels)\n                acc = accuracy(out, labels)\n                \n                val_losses.append(loss.item())\n                val_accs.append(acc.item())\n        \n        # ===========================\n        # Calculate & Print Averages\n        # ===========================\n        avg_train_loss = torch.tensor(train_losses).mean().item()\n        avg_train_acc = torch.tensor(train_accs).mean().item()\n        \n        avg_val_loss = torch.tensor(val_losses).mean().item()\n        avg_val_acc = torch.tensor(val_accs).mean().item()\n        \n        print(f\"Epoch [{epoch+1}/{epochs}]\")\n        print(f\"Train - Loss: {avg_train_loss:.4f}, Acc: {avg_train_acc:.4f}\")\n        print(f\"Val   - Loss: {avg_val_loss:.4f}, Acc: {avg_val_acc:.4f}\")\n        print(\"-\" * 30)\n\n# --- Usage Example ---\n# fit(5, 1e-3, model, train_loader, val_loader)\n\n# --- How to Run ---\n# device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n# model = EfficientNetModel(num_classes=5).to(device) # <--- Model goes to GPU here\n# fit(5, 1e-3, model, train_loader, val_loader)\n\n# ==========================================\n# 4. RUNNING IT (With your data shape)\n# ==========================================\n\n# Define Transforms (Correct Way)\n# EfficientNet-B0 expects 224x224 images\nstats = ((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010))\ntrain_tfms = transforms.Compose([\n    transforms.ToPILImage(), # Convert tensor/array to PIL Image first if needed\n    transforms.Resize((224, 224)),\n    transforms.ToTensor(), \n    transforms.Normalize(*stats, inplace=True)\n])\n\n# --- DUMMY DATA SETUP (Replace this with your actual data loading) ---\n# Simulating 50 images, 3 channels, 32x32 size (like CIFAR-10)\ninput_data = torch.rand(50, 3, 32, 32) \ninput_labels = torch.randint(0, 5, (50,)) # 5 Classes\n\n# Create Dataset & Loader\ndataset = MyDataset(input_data, input_labels, transform=train_tfms)\ntrain_loader = DataLoader(dataset, batch_size=16, shuffle=True)\nval_loader = DataLoader(dataset, batch_size=16) # Just reusing for demo\n\n# Initialize Model\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nmodel = EfficientNetModel(num_classes=5).to(device)\n\n# Move data to device (handled inside loop usually, or via a DeviceDataLoader)\n# For this simple snippet, we assume CPU or handle .to(device) inside training_step if needed.\n# Since your error trace didn't show device errors, I kept it simple.\n\n# Train\nfit(200, 1e-3, model, train_loader, val_loader)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-02T15:54:56.380587Z","iopub.execute_input":"2026-02-02T15:54:56.381454Z","iopub.status.idle":"2026-02-02T15:56:08.066334Z","shell.execute_reply.started":"2026-02-02T15:54:56.381419Z","shell.execute_reply":"2026-02-02T15:56:08.065531Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"fit(50,1e-3, model, train_loader, val_loader)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-02T15:28:20.313788Z","iopub.execute_input":"2026-02-02T15:28:20.314180Z","iopub.status.idle":"2026-02-02T15:28:20.344866Z","shell.execute_reply.started":"2026-02-02T15:28:20.314153Z","shell.execute_reply":"2026-02-02T15:28:20.343934Z"}},"outputs":[],"execution_count":null}]}