{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.14","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":3362,"databundleVersionId":31148,"sourceType":"competition"}],"dockerImageVersionId":30787,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Cats vs. Dogs Classification with MobileNet in PyTorch 🐱🐶\n\nIn this notebook, we’ll classify images as either **cats** or **dogs** using a pre-trained MobileNet model in PyTorch. \n\nMobileNet is a lightweight, efficient deep learning model, designed for devices with limited resources, such as mobile and embedded systems. Its efficiency comes from using **depthwise separable convolutions**, which reduce the number of parameters and computations. This makes it faster and smaller compared to other models.\n\nWe’ll:\n1. Load a pre-trained MobileNet model.\n2. Prepare the Cats vs. Dogs dataset.\n3. Fine-tune MobileNet on this dataset.\n4. Evaluate its performance and measure resource metrics.\n5. Visualize predictions.\n\nLet's dive in!","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19"}},{"cell_type":"markdown","source":"## Step 1: Import Libraries\n\nHere, we import the libraries we need for this project:\n\n- **`numpy` and `pandas`**: For handling data and arrays.\n- **`os`**: To work with file paths.\n- **`torch` and `torch.nn`**: PyTorch libraries for building and training our deep learning model.\n- **`cv2`**: For image processing tasks.\n- **`matplotlib.pyplot`**: For visualizing images and results.\n- **`torchvision` and `transforms`**: Tools for computer vision, including image transformations.\n- **`tqdm`**: To add progress bars.\n- **`PIL`**: For loading and working with images.\n\nThe last line, **`%matplotlib inline`**, makes plots appear directly in the notebook.","metadata":{}},{"cell_type":"code","source":"import numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\nimport os\nimport re\nimport torch\nimport torch.nn as nn\nimport cv2\nimport matplotlib.pyplot as plt\nimport torchvision\nimport torchvision.models as models\nfrom torch.utils.data import Dataset, DataLoader, ConcatDataset, random_split\nfrom torchvision import transforms\nimport torch.nn as nn\nimport torch.optim as optim\nimport copy\nfrom tqdm import tqdm\n# import tqdm\nfrom PIL import Image\nimport zipfile\nfrom collections import Counter\n\n%matplotlib inline","metadata":{"execution":{"iopub.status.busy":"2024-10-27T03:26:29.60285Z","iopub.execute_input":"2024-10-27T03:26:29.603953Z","iopub.status.idle":"2024-10-27T03:26:29.616562Z","shell.execute_reply.started":"2024-10-27T03:26:29.603899Z","shell.execute_reply":"2024-10-27T03:26:29.61547Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Step 2: Unzip the Dataset\n\nThe Dogs vs. Cats dataset is provided as a zip file in Kaggle competitions. \nWe need to unzip the `train.zip` file so we can access the images for data exploration and model training.\n","metadata":{}},{"cell_type":"code","source":"# Path to the zip file\nzip_path = \"/kaggle/input/dogs-vs-cats/train.zip\"\n\n# Open the zip file\nwith zipfile.ZipFile(zip_path, 'r') as zip_ref:\n    # List all files in the zip archive\n    image_files = [f for f in zip_ref.namelist() if f.endswith('.jpg')]\n    print(\"Number of images (Train):\", len(image_files))","metadata":{"execution":{"iopub.status.busy":"2024-10-27T03:26:29.618495Z","iopub.execute_input":"2024-10-27T03:26:29.61909Z","iopub.status.idle":"2024-10-27T03:26:29.986482Z","shell.execute_reply.started":"2024-10-27T03:26:29.619023Z","shell.execute_reply":"2024-10-27T03:26:29.985322Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Step 2: Define Data Transformations\n\nWe apply transformations to preprocess images:\n- Resize to 224x224 (required by MobileNet).\n- Convert images to tensors.\n- Normalize pixel values.","metadata":{}},{"cell_type":"code","source":"# Define image transformations\ntransform = transforms.Compose([\n    transforms.Resize((224, 224)),\n    transforms.ToTensor(),\n    transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])\n])","metadata":{"execution":{"iopub.status.busy":"2024-10-27T03:26:29.988115Z","iopub.execute_input":"2024-10-27T03:26:29.98851Z","iopub.status.idle":"2024-10-27T03:26:29.995924Z","shell.execute_reply.started":"2024-10-27T03:26:29.988467Z","shell.execute_reply":"2024-10-27T03:26:29.994719Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Display each transformation step\nfig, axes = plt.subplots(1, 4, figsize=(15, 5))\n\n# Original Image\nsample_img_name = image_files[0]  # Using the first image as a sample\nwith zipfile.ZipFile(zip_path, 'r') as zip_ref:\n    with zip_ref.open(sample_img_name) as img_file:\n        original_image = Image.open(img_file).convert(\"RGB\")\n\naxes[0].imshow(original_image)\naxes[0].set_title(\"Original Image\")\naxes[0].axis(\"off\")\n\n# Resize Image\nresized_image = transforms.Resize((224, 224))(original_image)\naxes[1].imshow(resized_image)\naxes[1].set_title(\"Resized to 224x224\")\naxes[1].axis(\"off\")\n\n# Convert to Tensor\ntensor_image = transforms.ToTensor()(resized_image)\naxes[2].imshow(tensor_image.permute(1, 2, 0))\naxes[2].set_title(\"Converted to Tensor\")\naxes[2].axis(\"off\")\n\n# Normalize Image\nnormalize_transform = transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])\nnormalized_image = normalize_transform(tensor_image)\n# For visualization, we re-normalize it to bring pixel values back to [0,1] range\naxes[3].imshow(normalized_image.permute(1, 2, 0).clamp(0, 1))\naxes[3].set_title(\"Normalized Image\")\naxes[3].axis(\"off\")\n\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2024-10-27T03:26:29.998466Z","iopub.execute_input":"2024-10-27T03:26:29.998818Z","iopub.status.idle":"2024-10-27T03:26:31.065454Z","shell.execute_reply.started":"2024-10-27T03:26:29.998777Z","shell.execute_reply":"2024-10-27T03:26:31.064479Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Step 3: Create a Custom Dataset Class\n\nThis custom dataset class reads images directly from the zip file and labels them as cats (0) or dogs (1) based on their filename.","metadata":{}},{"cell_type":"code","source":"# Custom dataset class to read images from zip\nclass CatsDogsDataset(Dataset):\n    def __init__(self, zip_file, image_files, transform=None):\n        self.zip_file = zip_file\n        self.image_files = image_files\n        self.transform = transform\n    \n    def __len__(self):\n        return len(self.image_files)\n    \n    def __getitem__(self, idx):\n        # Get the file name from the list of images\n        img_name = self.image_files[idx]\n        \n        # Open the image from the zip file\n        with zipfile.ZipFile(self.zip_file, 'r') as zip_ref:\n            with zip_ref.open(img_name) as img_file:\n                image = Image.open(img_file).convert(\"RGB\")\n\n        # Regular expression to capture \"cat\" or \"dog\" at the start of the filename\n        match = re.search(r'/(cat|dog)\\.', img_name)\n        if match:\n            class_name = match.group(1)  # This will be \"cat\" or \"dog\"\n            label = 0 if class_name == \"cat\" else 1  # 0 for cat, 1 for dog\n#             print(f\"Filename: {img_name}, Class: {class_name}, Label: {label}\")\n        else:\n            print(\"Could not determine label for filename:\", img_name)\n\n        # Apply transformations\n        if self.transform:\n            image = self.transform(image)\n        \n        return image, label","metadata":{"execution":{"iopub.status.busy":"2024-10-27T03:26:31.066915Z","iopub.execute_input":"2024-10-27T03:26:31.067512Z","iopub.status.idle":"2024-10-27T03:26:31.077575Z","shell.execute_reply.started":"2024-10-27T03:26:31.067467Z","shell.execute_reply":"2024-10-27T03:26:31.076725Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Initialize the dataset\ndataset = CatsDogsDataset(zip_path, image_files, transform=transform)","metadata":{"execution":{"iopub.status.busy":"2024-10-27T03:26:31.078897Z","iopub.execute_input":"2024-10-27T03:26:31.079473Z","iopub.status.idle":"2024-10-27T03:26:31.087751Z","shell.execute_reply.started":"2024-10-27T03:26:31.07943Z","shell.execute_reply":"2024-10-27T03:26:31.086917Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Step 4: Split the Dataset and Set Up DataLoaders\n\nThe **DataLoader** is used to load the dataset in mini-batches for training and evaluation. We configure it with:\n- **batch_size**: Number of images in each batch (e.g., 32).\n- **shuffle**: Shuffling data each epoch to improve model generalization.\n- **num_workers**: Number of subprocesses for loading data (useful to speed up data loading).\n\nTo train the model, we split the dataset into:\n- **Training Set**: 80% of the data.\n- **Validation Set**: 20% of the data.\n\nWe use DataLoaders to load the data in batches.\n\n","metadata":{}},{"cell_type":"code","source":"# Split dataset into training and validation sets (assuming `dataset` is already defined)\ntrain_size = int(0.8 * len(dataset))\nval_size = len(dataset) - train_size\ntrain_dataset, val_dataset = random_split(dataset, [train_size, val_size])\n\n# Create DataLoaders\ntrain_loader = DataLoader(train_dataset, batch_size=64, shuffle=True, num_workers=4)\nval_loader = DataLoader(val_dataset, batch_size=64, shuffle=False, num_workers=4)","metadata":{"execution":{"iopub.status.busy":"2024-10-27T03:26:31.088959Z","iopub.execute_input":"2024-10-27T03:26:31.089528Z","iopub.status.idle":"2024-10-27T03:26:31.097795Z","shell.execute_reply.started":"2024-10-27T03:26:31.089486Z","shell.execute_reply":"2024-10-27T03:26:31.096977Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Step 5: Display a Sample Batch\n\nHere, we retrieve a single batch of images and labels from the **train_loader** to visualize them. This helps confirm that our DataLoader is correctly loading and batching images.\n","metadata":{}},{"cell_type":"code","source":"# Get a single batch of images and labels\nbatch_images, batch_labels = next(iter(train_loader))\n\n# Display the batch of images\nfig, axes = plt.subplots(4, 8, figsize=(12, 6))\nfig.suptitle(f\"Mini-Batch of {len(batch_images)} Images\", fontsize=16)\n\n# Loop through each image in the batch and display it\nfor i, (img, label) in enumerate(zip(batch_images[:32], batch_labels[:32])):  # Limit to 32 images for display\n    ax = axes[i // 8, i % 8]\n    img = img.permute(1, 2, 0).numpy()  # Convert from Tensor format (C, H, W) to (H, W, C)\n    img = img * 0.5 + 0.5  # De-normalize for visualization\n    ax.imshow(img)\n    ax.set_title(\"Cat\" if label.item() == 0 else \"Dog\")\n    ax.axis(\"off\")\n\nplt.tight_layout(rect=[0, 0.03, 1, 0.95])\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2024-10-27T03:26:31.099091Z","iopub.execute_input":"2024-10-27T03:26:31.099627Z","iopub.status.idle":"2024-10-27T03:27:09.722712Z","shell.execute_reply.started":"2024-10-27T03:26:31.099585Z","shell.execute_reply":"2024-10-27T03:27:09.72172Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Step 6: Check Data Distribution and Set Up DataLoaders\n\nBefore splitting the dataset, we’ll check how many images of each class (cats and dogs) we have. This helps us understand the distribution of our data.\n\n1. **Count Images by Class**: We’ll go through the dataset and count the number of cat and dog images.\n2. **Split the Dataset**: Split the data into 80% training and 20% validation sets.\n3. **Create DataLoaders**: Set up DataLoaders to load data in mini-batches for training and validation.\n","metadata":{}},{"cell_type":"code","source":"# Count the number of cat and dog images\nlabels = []\nfor img_name in dataset.image_files:  # dataset.image_files contains the list of all image filenames\n    if \"cat\" in img_name:\n        labels.append(\"cat\")\n    else:\n        labels.append(\"dog\")\n\n# Calculate and display the counts\nlabel_counts = Counter(labels)\nprint(\"Data Distribution:\")\nprint(f\"Cats: {label_counts['cat']}\")\nprint(f\"Dogs: {label_counts['dog']}\")\n\n# Optional: Visualize the distribution with a bar chart\nimport matplotlib.pyplot as plt\n\nplt.figure(figsize=(6, 4))\nplt.bar(label_counts.keys(), label_counts.values(), color=['blue', 'orange'])\nplt.xlabel('Class')\nplt.ylabel('Number of Images')\nplt.title('Data Distribution of Cats and Dogs')\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2024-10-27T03:27:09.725454Z","iopub.execute_input":"2024-10-27T03:27:09.725788Z","iopub.status.idle":"2024-10-27T03:27:09.952934Z","shell.execute_reply.started":"2024-10-27T03:27:09.725754Z","shell.execute_reply":"2024-10-27T03:27:09.952074Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Step 7: Load and Modify MobileNetV3\n\nTo use MobileNetV3 for the Cats vs. Dogs classification:\n1. **Choose a MobileNetV3 Version**: We can use either `mobilenet_v3_small` or `mobilenet_v3_large` based on model size and complexity.\n2. **Load Pre-trained Model**: Start with a model pre-trained on ImageNet.\n3. **Modify the Output Layer**: Change the final layer to have 2 output classes (cats and dogs).\n4. **Move Model to Device**: Send the model to the GPU if available, otherwise to the CPU.\n","metadata":{}},{"cell_type":"code","source":"# Load pre-trained MobileNetV3-Large model\nmodel = models.mobilenet_v3_small(pretrained=True)\n\n# Modify the last layer to have 2 output classes (cats and dogs)\n# MobileNetV3's classifier is a Sequential layer with a single Linear layer at index 3\nmodel.classifier[3] = torch.nn.Linear(in_features=model.classifier[3].in_features, out_features=2)\n\n# Move model to device (GPU if available, otherwise CPU)\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nmodel.to(device)\n\n# Print the modified model architecture\nprint(model)\n","metadata":{"execution":{"iopub.status.busy":"2024-10-27T03:27:09.954188Z","iopub.execute_input":"2024-10-27T03:27:09.954582Z","iopub.status.idle":"2024-10-27T03:27:10.062867Z","shell.execute_reply.started":"2024-10-27T03:27:09.954527Z","shell.execute_reply":"2024-10-27T03:27:10.061904Z"},"collapsed":true,"jupyter":{"outputs_hidden":true},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Freezing Layers to Speed Up Training\n\nTo reduce training time, we’ll:\n1. **Freeze Most Layers**: Only the classifier layer will remain trainable.\n2. **Use Optimizer for Trainable Parameters**: Optimizes only the last layers.\n\nThis reduces the number of parameters, making training faster.","metadata":{}},{"cell_type":"code","source":"# Freeze all layers except the classifier\nfor param in model.parameters():\n    param.requires_grad = False\n\n# Unfreeze only the classifier layer\nfor param in model.classifier.parameters():\n    param.requires_grad = True\n\n# Define optimizer for trainable parameters only\noptimizer = optim.Adam(filter(lambda p: p.requires_grad, model.parameters()), lr=0.001)\n\n# Check number of parameters\ntrainable_params = sum(p.numel() for p in model.parameters() if p.requires_grad)\ntotal_params = sum(p.numel() for p in model.parameters())\nprint(f\"Total parameters: {total_params}\")\nprint(f\"Trainable parameters: {trainable_params}\")","metadata":{"execution":{"iopub.status.busy":"2024-10-27T03:27:10.064116Z","iopub.execute_input":"2024-10-27T03:27:10.064428Z","iopub.status.idle":"2024-10-27T03:27:10.07579Z","shell.execute_reply.started":"2024-10-27T03:27:10.064395Z","shell.execute_reply":"2024-10-27T03:27:10.074753Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Step 8: Define Loss Function and Optimizer\n\nTo train our MobileNetV3 model, we need:\n1. **Loss Function (Criterion)**: `CrossEntropyLoss` measures how well the model’s predictions match the actual labels.\n2. **Optimizer**: `Adam` optimizer helps update the model’s weights based on the computed gradients.\n","metadata":{}},{"cell_type":"code","source":"# Define the loss function\ncriterion = nn.CrossEntropyLoss()\n\n# Define the optimizer\noptimizer = optim.Adam(model.parameters(), lr=0.001)","metadata":{"execution":{"iopub.status.busy":"2024-10-27T03:27:10.076998Z","iopub.execute_input":"2024-10-27T03:27:10.077651Z","iopub.status.idle":"2024-10-27T03:27:10.084273Z","shell.execute_reply.started":"2024-10-27T03:27:10.077607Z","shell.execute_reply":"2024-10-27T03:27:10.083312Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Step 10: Training Loop\n\nWe now set up the main loop to train the model for several epochs. In each epoch, we:\n1. **Train the Model**: Use `train_one_epoch` to train on the training set.\n2. **Validate the Model**: Use `validate` to evaluate performance on the validation set.\n3. **Print Metrics**: Display training and validation loss and accuracy after each epoch.\n","metadata":{}},{"cell_type":"code","source":"# Move model to the device (CPU or GPU)\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nmodel.to(device)\n\n# Set number of epochs and initialize tracking lists\nnum_epochs = 5\ntrain_losses, val_losses = [], []\ntrain_accuracies, val_accuracies = [], []\n\n# Initialize live plot\nplt.ion()  # Turn on interactive mode for live plotting\nfig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))\n\n# Training loop with live plotting\nfor epoch in range(num_epochs):\n    # Train for one epoch with tqdm\n    model.train()\n    running_loss, correct, total = 0.0, 0, 0\n    train_loader_tqdm = tqdm(train_loader, desc=f\"Epoch {epoch+1}/{num_epochs} - Training\", leave=False)\n    for images, labels in train_loader_tqdm:\n        images, labels = images.to(device), labels.to(device)\n\n        optimizer.zero_grad()\n        outputs = model(images)\n        loss = criterion(outputs, labels)\n        loss.backward()\n        optimizer.step()\n\n        # Update metrics\n        running_loss += loss.item() * images.size(0)\n        _, predicted = torch.max(outputs, 1)\n        correct += (predicted == labels).sum().item()\n        total += labels.size(0)\n        \n    train_loss = running_loss / total\n    train_accuracy = correct / total\n    train_losses.append(train_loss)\n    train_accuracies.append(train_accuracy)\n\n    # Validate the model with tqdm\n    model.eval()\n    running_loss, correct, total = 0.0, 0, 0\n    val_loader_tqdm = tqdm(val_loader, desc=f\"Epoch {epoch+1}/{num_epochs} - Validation\", leave=False)\n    with torch.no_grad():\n        for images, labels in val_loader_tqdm:\n            images, labels = images.to(device), labels.to(device)\n            outputs = model(images)\n            loss = criterion(outputs, labels)\n\n            # Update metrics\n            running_loss += loss.item() * images.size(0)\n            _, predicted = torch.max(outputs, 1)\n            correct += (predicted == labels).sum().item()\n            total += labels.size(0)\n            \n    val_loss = running_loss / total\n    val_accuracy = correct / total\n    val_losses.append(val_loss)\n    val_accuracies.append(val_accuracy)\n    \n    # Print metrics\n    print(f\"Epoch [{epoch+1}/{num_epochs}]\")\n    print(f\"Train Loss: {train_loss:.4f}, Train Accuracy: {train_accuracy:.4f}\")\n    print(f\"Validation Loss: {val_loss:.4f}, Validation Accuracy: {val_accuracy:.4f}\")\n    print(\"-\" * 30)","metadata":{"execution":{"iopub.status.busy":"2024-10-27T03:27:10.085573Z","iopub.execute_input":"2024-10-27T03:27:10.085964Z","iopub.status.idle":"2024-10-27T04:04:25.959322Z","shell.execute_reply.started":"2024-10-27T03:27:10.085921Z","shell.execute_reply":"2024-10-27T04:04:25.957427Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Step 11: Plot Training and Validation Metrics\n\nVisualizing the loss and accuracy over epochs helps us understand how well the model is learning. Ideally, both training and validation loss should decrease, while accuracy should increase.\n","metadata":{}},{"cell_type":"code","source":"# Plot Loss\nplt.figure(figsize=(12, 5))\nplt.subplot(1, 2, 1)\nplt.plot(range(1, num_epochs + 1), train_losses, label='Train Loss')\nplt.plot(range(1, num_epochs + 1), val_losses, label='Validation Loss')\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"Loss\")\nplt.legend()\nplt.title(\"Training and Validation Loss\")\n\n# Plot Accuracy\nplt.subplot(1, 2, 2)\nplt.plot(range(1, num_epochs + 1), train_accuracies, label='Train Accuracy')\nplt.plot(range(1, num_epochs + 1), val_accuracies, label='Validation Accuracy')\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"Accuracy\")\nplt.legend()\nplt.title(\"Training and Validation Accuracy\")\n\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2024-10-27T04:04:25.961312Z","iopub.status.idle":"2024-10-27T04:04:25.961771Z","shell.execute_reply.started":"2024-10-27T04:04:25.961554Z","shell.execute_reply":"2024-10-27T04:04:25.961583Z"},"trusted":true},"execution_count":null,"outputs":[]}]}