{"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":84209,"databundleVersionId":9414711,"sourceType":"competition"},{"sourceId":9945817,"sourceType":"datasetVersion","datasetId":6115791}],"dockerImageVersionId":30787,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\n# List the files in the input directory to see the competition dataset\ninput_dir = '/kaggle/input/'\nprint(os.listdir(input_dir))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-11-24T20:12:40.915023Z","iopub.execute_input":"2024-11-24T20:12:40.915443Z","iopub.status.idle":"2024-11-24T20:12:40.924168Z","shell.execute_reply.started":"2024-11-24T20:12:40.915392Z","shell.execute_reply":"2024-11-24T20:12:40.923264Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"1. Business problem: This is a competition based on \"Computer Vision\", where the participants have to deploy a model that will be able to classify a healthy leaf of a plant from a diseased leaf. The model will have a great potential to assist farmers, increase productivity, and promote sustainable practices.\n   \n2. Dataset: The size is 6.91 GB with 4239 files which includes 4236 images and 3 csv files. You can download the dataset from [here] (https://www.kaggle.com/datasets/amandam1/healthy-vs-diseased-leaf-image-dataset).\n3. Pipeline:\n   1.  Import all necessary libraries for Pytorch implementation.\n   2.  Analyze the dataset.\n   3.  Create a CustomDataset and incoporate it into DalaLoader.\n   4.  Load a pretrained model and fine tune it for maximum accuracy.\n   5.  Train the model.\n   6.  Evaluate the model.\n   7.  Plot graph for loss and accuracy.\n   8.  Use the model to test on test_loader.\n   9.  Finally inference.","metadata":{}},{"cell_type":"markdown","source":"**Import Libraries**","metadata":{"execution":{"iopub.status.busy":"2024-11-26T18:40:58.339356Z","iopub.execute_input":"2024-11-26T18:40:58.339759Z","iopub.status.idle":"2024-11-26T18:40:58.349983Z","shell.execute_reply.started":"2024-11-26T18:40:58.339718Z","shell.execute_reply":"2024-11-26T18:40:58.348767Z"}}},{"cell_type":"markdown","source":"1. tqdm is a python library used to display a progress bar during training.\n2. DataLoader is a Pytorch tool that helps to load shuffle data in batches use them easily during training.\n","metadata":{}},{"cell_type":"code","source":"import pandas as pd\nimport torch\nfrom torchvision import transforms\nfrom PIL import Image\nimport os\nimport matplotlib.pyplot as plt\nfrom tqdm import tqdm\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader,random_split\nimport torch.nn as nn\nimport torchvision.models as models","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-11-26T18:41:06.891413Z","iopub.execute_input":"2024-11-26T18:41:06.891792Z","iopub.status.idle":"2024-11-26T18:41:13.995465Z","shell.execute_reply.started":"2024-11-26T18:41:06.891759Z","shell.execute_reply":"2024-11-26T18:41:13.994727Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**Data Analysis**","metadata":{}},{"cell_type":"markdown","source":"1. Check for any null values.\n2. Visualise an 4 images and check if the image and the label is matching.\n3. Check for the shape of the images for resolution and the number of channels.","metadata":{}},{"cell_type":"code","source":"df = pd.read_csv(\"/kaggle/input/computer-vision-xm/train.csv\")\nprint(df.info())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-11-26T17:40:57.800488Z","iopub.execute_input":"2024-11-26T17:40:57.800835Z","iopub.status.idle":"2024-11-26T17:40:57.816446Z","shell.execute_reply.started":"2024-11-26T17:40:57.800804Z","shell.execute_reply":"2024-11-26T17:40:57.815523Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport cv2\nimport pandas as pd\nfrom matplotlib import pyplot as plt\n\n# Path to your images folder and train.csv\nimages_folder = \"/kaggle/input/computer-vision-xm/images/kaggle/working/Reorganized_Data/images\"\ntrain_csv_path = \"/kaggle/input/computer-vision-xm/train.csv\"\n\n# Load the CSV file\ntrain_data = pd.read_csv(train_csv_path)\n\n# Initialize a counter to display only the first 4 images with labels\ncounter = 0\n\n# Create a figure for displaying 4 images in a single row\nfig, axes = plt.subplots(1, 4, figsize=(20, 5))\n\n# Iterate over the rows in the DataFrame\nfor _, row in train_data.iterrows():\n    # Get image file name and label from the DataFrame\n    filename = row['Images']  # Column name for the image file\n    label = row['Labels']     # Column name for the label\n\n    # Construct the full image path\n    img_path = os.path.join(images_folder, filename)\n\n    # Check if the file exists\n    if os.path.exists(img_path):\n        # Load the image\n        image = cv2.imread(img_path)\n\n        print(f\"Image: {filename} - Shape: {image.shape}\")\n\n        # Display the image in the current subplot\n        axes[counter].imshow(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))\n        axes[counter].set_title(f\"Label: {label}\")\n        axes[counter].axis(\"off\")\n\n        # Increment counter after displaying an image\n        counter += 1\n\n        # Stop after displaying 4 images\n        if counter == 4:\n            break\n\n# Adjust layout and display the plot\nplt.tight_layout()\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-11-26T17:41:00.118344Z","iopub.execute_input":"2024-11-26T17:41:00.118696Z","iopub.status.idle":"2024-11-26T17:41:10.735622Z","shell.execute_reply.started":"2024-11-26T17:41:00.118664Z","shell.execute_reply":"2024-11-26T17:41:10.734725Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**CustomDataset**","metadata":{}},{"cell_type":"markdown","source":"CustomDataset is built on torch.utils.data.Dataset, giving it a structure to load and process data. You can customize how the data is handled, including applying transformations. It works well with PyTorch’s DataLoader, which makes it easy to batch, shuffle, and prepare the data for training. In this particular case, we have a csv files that is used here to take the images and split it into train and test. Then we use transforms, the transformations ToTensor(), Resize(), and Normalize() are used to prepare images for neural networks. ToTensor() converts the image into a format that the model can use. Resize() makes sure all images have the same size. Normalize() adjusts the pixel values so they are on a similar scale, helping the model learn faster and more smoothly. These steps ensure that the data is ready and consistent for training.","metadata":{}},{"cell_type":"code","source":"class CustomDataset(torch.utils.data.Dataset):\n    def __init__(self, csv_file, img_dir, is_train=True, transform=None):\n        self.data = pd.read_csv(csv_file)\n        self.img_dir = img_dir\n        self.is_train = is_train  # Flag to differentiate between training and testing dataset\n        self.transform = transform\n\n    def __len__(self):\n        # Return the number of samples in the dataset\n        return len(self.data)\n\n    def __getitem__(self, idx):\n        # Get the image filename from the CSV and construct the full path\n        img_name = os.path.join(self.img_dir, self.data.iloc[idx, 1])  \n        \n        # Open the image\n        image = Image.open(img_name)\n        \n        # Apply the transformation if provided\n        if self.transform:\n            image = self.transform(image)\n        \n        if self.is_train:\n           \n            label = self.data.iloc[idx, 2] \n            return image, label\n        else:\n           \n            return image\n\n# Define transformations (if needed)\ntransform = transforms.Compose([\n    transforms.ToTensor(),\n    transforms.Resize((128, 128)),\n    transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))\n])\n\n# Load CSV files and create train and test datasets\ntrain_csv = '/kaggle/input/computer-vision-xm/train.csv'  # Path to your train CSV\ntest_csv = '/kaggle/input/computer-vision-xm/test.csv'    # Path to your test CSV\n\n# Image directory\nimg_dir = '/kaggle/input/computer-vision-xm/images/kaggle/working/Reorganized_Data/images'  # Adjust as per your structure\n\n# Create train and test datasets\ntrain_dataset = CustomDataset(csv_file=train_csv, img_dir=img_dir,  is_train=True, transform=transform)\ntest_dataset = CustomDataset(csv_file=test_csv, img_dir=img_dir,  is_train=False, transform=transform)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-11-25T01:50:11.622512Z","iopub.execute_input":"2024-11-25T01:50:11.623312Z","iopub.status.idle":"2024-11-25T01:50:11.643318Z","shell.execute_reply.started":"2024-11-25T01:50:11.623275Z","shell.execute_reply":"2024-11-25T01:50:11.642375Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**DataLoader**","metadata":{}},{"cell_type":"markdown","source":"Since, I need to check for validation accuracy,  I decided to split the train dataset by maintaing 80% data and sharing the remaining to the validation dataset. Finally, I was able to create Dataloader for train, val and test.","metadata":{}},{"cell_type":"code","source":"# Split the dataset into training and validation sets\ntrain_size = int(0.8 * len(train_dataset))\nval_size = len(train_dataset) - train_size\ntrain_subset, val_subset = random_split(train_dataset, [train_size, val_size])\n\n# Define the DataLoader for training\ntrain_loader = DataLoader(train_subset, batch_size=64, shuffle=True, num_workers=2)\n\n# Define the DataLoader for validation\nval_loader = DataLoader(val_subset, batch_size=64, shuffle=False, num_workers=2)\n\n# Define the DataLoader for testing (no need to shuffle test data)\ntest_loader = DataLoader(test_dataset, batch_size=64, shuffle=False, num_workers=2)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-11-25T01:50:12.777706Z","iopub.execute_input":"2024-11-25T01:50:12.778037Z","iopub.status.idle":"2024-11-25T01:50:12.78468Z","shell.execute_reply.started":"2024-11-25T01:50:12.778007Z","shell.execute_reply":"2024-11-25T01:50:12.7838Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"However, it is very important to analyze the data within the dataloader before feeding it to the model. The shape of the data should be complatible with the expected input by the model otherwise, we are bound to face difficulties while training the model.","metadata":{}},{"cell_type":"code","source":"def check_loader(loader, loader_name):\n    print(f\"\\nChecking {loader_name}...\")\n    for batch_idx, (images, labels) in enumerate(loader):\n        print(f\"Batch {batch_idx + 1}:\")\n        print(f\"  Images shape: {images.shape}\")\n        print(f\"  Labels shape: {labels.shape}\")\n        print(f\"  Labels: {labels[:5].tolist()}\")  # Print the first 5 labels\n        break  # Check only the first batch\n\n# Check each DataLoader\ncheck_loader(train_loader, \"Train Loader\")\ncheck_loader(val_loader, \"Validation Loader\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-11-25T02:01:00.287427Z","iopub.execute_input":"2024-11-25T02:01:00.287776Z","iopub.status.idle":"2024-11-25T02:03:16.366285Z","shell.execute_reply.started":"2024-11-25T02:01:00.287746Z","shell.execute_reply":"2024-11-25T02:03:16.364888Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**Final Model**","metadata":{}},{"cell_type":"markdown","source":"Steps on my final model:\n1. I used vgg50 and it was too slow due to its complexity.\n2. I used vgg16 and freezed first 5 layers , the val_accuracy was approx.50%.\n3. I used vgg16 and freezed first 8 layers, the val_accuracy increased to approx.70%.\n4. I used vgg16 and freezed all convolution laye (dropout=0.3), val_the accuracy increased to 93%.\n5. I used vgg16 and freezed all convolution laye (dropout=0.4), val_the accuracy decreased to 92%.\n6. I used resnet18 and freezed all convolution layers, va_accuracy remained same 92%.\nTherefore, I have decided to submit 4th approach in the competition.\n(I did try basic segmentation on the images and fed those images into the model but, there wasn't much effect. so, I decided to drop it because it was occupying additional space in my kaggle.)","metadata":{}},{"cell_type":"code","source":"class CustomVGG16(nn.Module):\n    def __init__(self):\n        super().__init__()\n\n        # Using the pre-trained VGG16 convolutional layers\n        vgg16 = models.vgg16(pretrained=True)\n        self.conv_base = vgg16.features  # Only convolutional layers from VGG16\n        \n        # Freeze all layers\n        for param in self.conv_base.parameters():\n            param.requires_grad = False\n\n        # Adaptive pooling to get a fixed output size regardless of input dimensions\n        self.adaptive_pool = nn.AdaptiveAvgPool2d((5, 5))  # Output size (5, 5)\n\n        # Custom classifier for binary classification\n        self.classifier = nn.Sequential(\n            nn.Flatten(),                        # Flatten the feature map to a 1D vector\n            nn.Linear(512 * 5 * 5, 256),         # Adjust dimensions to match the pooled output size\n            nn.ReLU(),\n            nn.Dropout(0.3),\n            nn.Linear(256, 1),                   # Output layer: 1 unit for binary classification\n        )\n\n    def forward(self, x):\n        x = self.conv_base(x)  # Pass through convolutional layers\n        x = self.adaptive_pool(x)  # Adaptive pooling to ensure fixed output size\n        x = self.classifier(x)  # Pass through the custom classifier\n        return x\n\n# Instantiate the model\nmodel = CustomVGG16()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-11-25T02:48:00.282712Z","iopub.execute_input":"2024-11-25T02:48:00.283046Z","iopub.status.idle":"2024-11-25T02:48:01.949766Z","shell.execute_reply.started":"2024-11-25T02:48:00.283016Z","shell.execute_reply":"2024-11-25T02:48:01.948776Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"I wanted to check if the output is as expected to avoid issue while processing loss.","metadata":{}},{"cell_type":"code","source":"dummy_input = torch.randn(64, 3, 128, 128)\n\n# Pass the dummy input through the model\noutput = model(dummy_input)\n\n# Check the output\nprint(output.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-11-25T02:50:30.006832Z","iopub.execute_input":"2024-11-25T02:50:30.007217Z","iopub.status.idle":"2024-11-25T02:50:34.581704Z","shell.execute_reply.started":"2024-11-25T02:50:30.007186Z","shell.execute_reply":"2024-11-25T02:50:34.580776Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"It is very clear from the summary that all the parameters in the convolution layers are freezed and used as feature extractor. Also, Pooling and activation function(ReLU)  does not contribute to parameters. We are using maxpooling to reduce the dimension by half and ReLU to introduce non-linearity.","metadata":{}},{"cell_type":"code","source":"from torchsummary import summary\n# Display the summary of the model for an input size of 224x224 (as VGG16 expects this input size)\nsummary(model, input_size=(3, 128, 128))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-11-25T02:37:25.736474Z","iopub.execute_input":"2024-11-25T02:37:25.737488Z","iopub.status.idle":"2024-11-25T02:37:25.769143Z","shell.execute_reply.started":"2024-11-25T02:37:25.737448Z","shell.execute_reply":"2024-11-25T02:37:25.768327Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(f\"Using device: {device}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-11-24T20:13:00.680718Z","iopub.execute_input":"2024-11-24T20:13:00.6811Z","iopub.status.idle":"2024-11-24T20:13:00.745085Z","shell.execute_reply.started":"2024-11-24T20:13:00.681043Z","shell.execute_reply":"2024-11-24T20:13:00.744383Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**Training**","metadata":{}},{"cell_type":"markdown","source":"1. The loss function suitable for this task was BCEWithLogitsLoss() because this is a binary classification task. It combines Binary Cross Entropy and sigmoid function.\n2. I used AdamW as an optimizer due to its preference over Adam incase of deploying pretrained model.\n3. I used very less epochs and patience becasue of the time constraints, also checkpoints to resume incase of interruption.\n4.  model.train() to update the parameters during training.\n5.  model.eval() to undo parameters update during evaluation.\n6.  I used early stopping to save time on unnecessary training after there is no more learning left for the model.","metadata":{}},{"cell_type":"code","source":"# Checkpoint file path\ncheckpoint_file = 'best_model_checkpoint.pth'\n\n# Move the model to GPU if available\nmodel.to(device)\n\n# Loss function and optimizer\ncriterion = nn.BCEWithLogitsLoss()\noptimizer = optim.AdamW(model.parameters(), lr=0.001)\n\n# Training configuration\nepochs = 10\npatience = 3\npatience_counter = 0\n\n# Lists to store loss values for plotting\ntrain_losses = []\nval_losses = []\ntrain_accuracies = []\nval_accuracies = []\n\n# Load checkpoint if exists\nstart_epoch = 0\nif os.path.exists(checkpoint_file):\n    checkpoint = torch.load(checkpoint_file)\n    model.load_state_dict(checkpoint['model_state_dict'])\n    optimizer.load_state_dict(checkpoint['optimizer_state_dict'])\n    start_epoch = checkpoint['epoch'] + 1\n    best_val_loss = checkpoint['val_loss'][-1]  # Load last best validation loss\n    train_losses = checkpoint['train_loss']\n    train_accuracies = checkpoint['train_accuracy']\n    val_losses = checkpoint['val_loss']\n    val_accuracies = checkpoint['val_accuracy']\n    print(f\"Resuming training from epoch {start_epoch}\")\nelse:\n    best_val_loss = float('inf')\n\n# Training loop\nfor epoch in range(start_epoch, epochs):\n    print(f'Epoch {epoch + 1}/{epochs}')\n    \n    # Training phase\n    model.train()\n    running_train_loss = 0.0\n    train_correct = 0\n    train_total = 0\n    \n    for images, labels in tqdm(train_loader, desc=\"Training\"):\n        images, labels = images.to(device), labels.float().to(device)\n\n        # Zero gradients\n        optimizer.zero_grad()\n\n        # Forward pass\n        outputs = model(images)\n        loss = criterion(outputs.squeeze(), labels)\n        running_train_loss += loss.item()\n\n        # Backward pass and optimization\n        loss.backward()\n        optimizer.step()\n\n        # Calculate training accuracy\n        predictions = torch.sigmoid(outputs).squeeze() > 0.5\n        train_correct += (predictions == labels).sum().item()\n        train_total += labels.size(0)\n\n    avg_train_loss = running_train_loss / len(train_loader)\n    train_losses.append(avg_train_loss)\n    train_accuracy = 100 * train_correct / train_total\n    train_accuracies.append(train_accuracy)\n    print(f'Training loss: {avg_train_loss:.4f}, Training accuracy: {train_accuracy:.2f}%')\n\n\n    # Validation phase\n    model.eval()\n    running_val_loss = 0.0\n    val_correct = 0\n    val_total = 0\n    \n    with torch.no_grad():\n        for images, labels in tqdm(val_loader, desc=\"Validation\"):\n            images, labels = images.to(device), labels.float().to(device)\n\n            outputs = model(images)\n            loss = criterion(outputs.squeeze(), labels)\n            running_val_loss += loss.item()\n\n            # Calculate validation accuracy\n            predictions = torch.sigmoid(outputs).squeeze() > 0.5\n            val_correct += (predictions == labels).sum().item()\n            val_total += labels.size(0)\n\n    avg_val_loss = running_val_loss / len(val_loader)\n    val_losses.append(avg_val_loss)\n    val_accuracy = 100 * val_correct / val_total\n    val_accuracies.append(val_accuracy)\n    print(f'Validation loss: {avg_val_loss:.4f}, Validation accuracy: {val_accuracy:.2f}%')\n\n\n    # Early stopping and checkpoint saving\n    if avg_val_loss < best_val_loss:\n        best_val_loss = avg_val_loss\n        patience_counter = 0\n        checkpoint = {\n            'epoch': epoch,\n            'model_state_dict': model.state_dict(),\n            'optimizer_state_dict': optimizer.state_dict(),\n            'val_loss': val_losses,\n            'val_accuracy': val_accuracies,\n            'train_loss': train_losses,\n            'train_accuracy': train_accuracies\n        }\n        torch.save(checkpoint, checkpoint_file)\n        print(\"Best model updated\")\n    else:\n        patience_counter += 1\n        if patience_counter >= patience:\n            print(\"Early stopping triggered\")\n            break\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-11-24T20:13:00.746209Z","iopub.execute_input":"2024-11-24T20:13:00.746565Z","iopub.status.idle":"2024-11-25T01:09:19.497319Z","shell.execute_reply.started":"2024-11-24T20:13:00.746528Z","shell.execute_reply":"2024-11-25T01:09:19.496223Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**Plot**","metadata":{}},{"cell_type":"markdown","source":"Visualize the results.","metadata":{}},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n# Plotting the loss and accuracy\nplt.figure(figsize=(12, 5))\nplt.subplot(1, 2, 1)\nplt.plot(train_losses, label='Train Loss')\nplt.plot(val_losses, label='Validation Loss')\nplt.xlabel('Epochs')\nplt.ylabel('Loss')\nplt.title('Loss vs. Epochs')\nplt.legend()\n\nplt.subplot(1, 2, 2)\nplt.plot(train_accuracies, label='Train Accuracy')\nplt.plot(val_accuracies, label='Validation Accuracy')\nplt.xlabel('Epochs')\nplt.ylabel('Accuracy (%)')\nplt.title('Accuracy vs. Epochs')\nplt.legend()\n\nplt.tight_layout()\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-11-25T01:10:35.448699Z","iopub.execute_input":"2024-11-25T01:10:35.449727Z","iopub.status.idle":"2024-11-25T01:10:35.960885Z","shell.execute_reply.started":"2024-11-25T01:10:35.449682Z","shell.execute_reply":"2024-11-25T01:10:35.96013Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Finally, After training the model, this is where I use my test data to see if the model is working as expected. Most importantly, we have to set the model to eval() state to avoid further parameters upgrade. ","metadata":{}},{"cell_type":"code","source":"\n# Define the model architecture (same as during training)\nmodel = CustomVGG16()  # Replace MyModel with your actual model class\n\n# Load the checkpoint\ncheckpoint = torch.load(checkpoint_file)\n\n# Load the model state dictionary\nmodel.load_state_dict(checkpoint['model_state_dict'])\nmodel.to(device)\nmodel.eval()  # Set the model to evaluation mode\n\n# Get predictions and corresponding images\npredictions = []\nimages_to_display = []\n\nwith torch.no_grad():\n    for images in test_loader:  # Only images in the test loader\n        images = images.to(device)\n        outputs = model(images)\n        \n        # Convert logits to binary predictions (Healthy/Diseased)\n        preds = (torch.sigmoid(outputs).squeeze() > 0.5).cpu().numpy()\n        preds_mapped = [\"Diseased\" if pred else \"Healthy\" for pred in preds]\n        predictions.extend(preds_mapped)\n\n        images_to_display.extend(images.cpu().numpy())\n        if len(predictions) >= 10:  # Stop after getting 10 predictions\n            break\n\n# Visualize the first 10 images with their predictions\nfig, axes = plt.subplots(1, 10, figsize=(20, 5))\nfor idx, ax in enumerate(axes):\n    # Denormalize images for display if normalized during preprocessing\n    img = images_to_display[idx].transpose(1, 2, 0)  # Convert CHW to HWC\n    img = (img - img.min()) / (img.max() - img.min())  # Scale to [0, 1] range\n    ax.imshow(img)\n    ax.axis('off')\n    ax.set_title(predictions[idx], fontsize=10)\nplt.tight_layout()\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-11-25T01:10:35.962134Z","iopub.execute_input":"2024-11-25T01:10:35.962591Z","iopub.status.idle":"2024-11-25T01:11:51.121397Z","shell.execute_reply.started":"2024-11-25T01:10:35.962549Z","shell.execute_reply":"2024-11-25T01:11:51.120502Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**Inference Pipeline**","metadata":{}},{"cell_type":"markdown","source":"1. Download some images from google.\n2. Load the images and perform the same transformation on the new images as we did in CustomDataset.\n3. Load the saved model.\n4. Set the model to evaluation mode.\n5. Input the images into the model and predict the class.\n6. Finally, visualize the images with classification.\n","metadata":{}},{"cell_type":"code","source":"transform = transforms.Compose([\n    transforms.Resize((128, 128)),\n    transforms.ToTensor(),\n    transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))  # Adjust values based on your training\n])\n\n# Define the folder where your images are stored\nimage_folder = '/kaggle/input/test-images'\n\n# Get all image files in the folder (you can filter by extension if needed)\nimage_paths = [os.path.join(image_folder, filename) for filename in os.listdir(image_folder) \n               if filename.lower().endswith(('.png', '.jpg', '.jpeg'))]\n\n# Initialize the model (replace with your actual model)\nmodel = CustomVGG16()  # Use the same model as you used during training\ncheckpoint_file = '/kaggle/working/best_model_checkpoint.pth'\n\n# Load the checkpoint and model weights\ncheckpoint = torch.load(checkpoint_file)\nmodel.load_state_dict(checkpoint['model_state_dict'])\nmodel.to(device)\nmodel.eval()  # Set the model to evaluation mode\n\n# Make predictions on each image\npredictions = []\nimages_to_display = []\n\nwith torch.no_grad():\n    for img_path in image_paths:\n        # Open the image\n        image = Image.open(img_path)\n        \n        # Apply transformations\n        image = transform(image).unsqueeze(0).to(device)  # Add batch dimension\n        \n        # Get the model's output\n        output = model(image)\n        \n        # Apply sigmoid and convert to binary prediction (Healthy/Diseased)\n        pred = (torch.sigmoid(output).squeeze() > 0.5).cpu().numpy()\n        predictions.append('Diseased' if pred else 'Healthy')\n        \n        \n        images_to_display.append(image.cpu().numpy().squeeze().transpose(1, 2, 0))  # Convert from tensor to numpy\n\n# Visualize the images with their predictions\nfig, axes = plt.subplots(1, len(predictions), figsize=(15, 5))\n\nfor idx, ax in enumerate(axes):\n    img = images_to_display[idx]\n    img = (img - img.min()) / (img.max() - img.min())  # Normalize to [0, 1] for display\n    ax.imshow(img)\n    ax.axis('off')\n    ax.set_title(f'Prediction: {predictions[idx]}')\n\nplt.tight_layout()\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-11-25T01:11:51.122977Z","iopub.execute_input":"2024-11-25T01:11:51.123233Z","iopub.status.idle":"2024-11-25T01:11:53.67466Z","shell.execute_reply.started":"2024-11-25T01:11:51.123208Z","shell.execute_reply":"2024-11-25T01:11:53.673746Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**The model is able to classify as healthy and diseased as long as the images are clear.**","metadata":{}},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}