{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.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":13836,"databundleVersionId":1718836,"sourceType":"competition"}],"dockerImageVersionId":30627,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import pandas as pd\nimport cv2\nimport os \nimport torch.nn as nn\nfrom torch.utils.data import Dataset ,DataLoader, Subset\nimport torch.optim as optim\nimport numpy as np\nimport torch\nfrom torchvision import transforms\nimport matplotlib.pyplot as plt\nfrom PIL import Image\nfrom torch.utils.data import random_split\nfrom tqdm import tqdm","metadata":{"execution":{"iopub.status.busy":"2024-01-08T09:29:05.264392Z","iopub.execute_input":"2024-01-08T09:29:05.264774Z","iopub.status.idle":"2024-01-08T09:29:05.271068Z","shell.execute_reply.started":"2024-01-08T09:29:05.264745Z","shell.execute_reply":"2024-01-08T09:29:05.270184Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"data_dir = '/kaggle/input/cassava-leaf-disease-classification'\nbatch_size = 32\nepochs = 30\nnum_classes = 5\nlr_rate = 0.05\n\ndevice = torch.device(\"cuda:0\" if torch.cuda.is_available() else \"cpu\")","metadata":{"execution":{"iopub.status.busy":"2024-01-08T09:29:05.273052Z","iopub.execute_input":"2024-01-08T09:29:05.273318Z","iopub.status.idle":"2024-01-08T09:29:05.289666Z","shell.execute_reply.started":"2024-01-08T09:29:05.273294Z","shell.execute_reply":"2024-01-08T09:29:05.288694Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"os.listdir(data_dir)\nprint('Train images: %d' %len(os.listdir(os.path.join(data_dir, \"train_images\"))))","metadata":{"execution":{"iopub.status.busy":"2024-01-08T09:29:05.290964Z","iopub.execute_input":"2024-01-08T09:29:05.291835Z","iopub.status.idle":"2024-01-08T09:29:05.310630Z","shell.execute_reply.started":"2024-01-08T09:29:05.291808Z","shell.execute_reply":"2024-01-08T09:29:05.309786Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_labels = pd.read_csv(os.path.join(data_dir, \"train.csv\"))\ntrain_labels.head()","metadata":{"execution":{"iopub.status.busy":"2024-01-08T09:29:05.311739Z","iopub.execute_input":"2024-01-08T09:29:05.312000Z","iopub.status.idle":"2024-01-08T09:29:05.333791Z","shell.execute_reply.started":"2024-01-08T09:29:05.311978Z","shell.execute_reply":"2024-01-08T09:29:05.332973Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Design a specific Dataset to read local data\nclass CustomDataset(Dataset):\n    # Initialize the dataset with the CSV file, root directory, and optional transform\n    def __init__(self, csv_file, root_dir, transform=None):\n        self.data_frame = pd.read_csv(csv_file)\n        self.root_dir = root_dir\n        self.transform = transform\n        \n    # Return the number of samples in the dataset\n    def __len__(self):\n        return len(self.data_frame)\n\n    # Get the file name and label for the specified index\n    def __getitem__(self, idx):\n        img_name = self.data_frame.iloc[idx, 0]\n        img_path = os.path.join(self.root_dir, img_name)\n        image = Image.open(img_path).convert('RGB')\n\n        label = int(self.data_frame.iloc[idx, 1])\n        # Apply the specified transform to the image if it exists\n        if self.transform:\n            image = self.transform(image)\n\n        return image, label\n\n# Define data transform\ntransform = transforms.Compose([\n    transforms.Resize((224, 224)),\n    transforms.ToTensor(),\n])\n# Read images from local file\ndataset = CustomDataset(csv_file=os.path.join(data_dir, \"train.csv\"), root_dir=os.path.join(data_dir, \"train_images\"), transform=transform)\n\n# Calculate the dataset size\ndataset_size = len(dataset)\n\n# Define the ratio\ntrain_ratio = 0.8\nval_ratio = 0.1\ntest_ratio = 0.1\n\n# Calculate the size of every dataset\ntrain_size = int(train_ratio * dataset_size)\nval_size = int(val_ratio * dataset_size)\ntest_size = dataset_size - train_size - val_size\n\n# Split the dataset\ntrain_dataset, val_dataset, test_dataset = random_split(dataset, [train_size, val_size, test_size])\n\n# Use DataLoader to load data\ntrain_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)\nval_loader = DataLoader(val_dataset, batch_size=batch_size, shuffle=False)\ntest_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)\n\n# Print the size of dataset\nprint(f'Train dataset size: {len(train_dataset)}')\nprint(f'Validation dataset size: {len(val_dataset)}')\nprint(f'Test dataset size: {len(test_dataset)}')","metadata":{"execution":{"iopub.status.busy":"2024-01-08T09:29:05.335817Z","iopub.execute_input":"2024-01-08T09:29:05.336072Z","iopub.status.idle":"2024-01-08T09:29:05.363681Z","shell.execute_reply.started":"2024-01-08T09:29:05.336050Z","shell.execute_reply":"2024-01-08T09:29:05.362844Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Define a simple Convolutional Neural Network (CNN) class\nclass SimpleCNN(nn.Module):\n    # init()：Start initialize\n    def __init__(self, num_classes):\n        super(SimpleCNN, self).__init__()\n        # First convolutional layer\n        self.conv1 = nn.Conv2d(3, 32, kernel_size=3, padding=1)\n        # Use ReLU as the activation function\n        self.relu = nn.ReLU(inplace=True)\n        self.pool = nn.MaxPool2d(kernel_size=2, stride=2)\n        # Second convolutional layer\n        self.conv2 = nn.Conv2d(32, 64, kernel_size=3, padding=1)\n        # Fully connected layers for classification\n        self.fc1 = nn.Linear(64 * 56 * 56, 64)\n        self.fc2 = nn.Linear(64, num_classes)\n\n    def forward(self, x):\n        # Move the input tensor to the device (GPU or CPU)\n        x = x.to(device)\n        # First convolutional layer\n        x = self.conv1(x)\n        x = self.relu(x)\n        x = self.pool(x)\n        # Second convolutional layer\n        x = self.conv2(x)\n        x = self.relu(x)\n        x = self.pool(x)\n        # Flatten the output for the fully connected layers\n        x = x.view(x.size(0), -1)\n        x = self.fc1(x)\n        x = self.fc2(x)\n        return x","metadata":{"execution":{"iopub.status.busy":"2024-01-08T09:29:05.364710Z","iopub.execute_input":"2024-01-08T09:29:05.364976Z","iopub.status.idle":"2024-01-08T09:29:05.374369Z","shell.execute_reply.started":"2024-01-08T09:29:05.364953Z","shell.execute_reply":"2024-01-08T09:29:05.373238Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Design ResNet\nclass BasicBlock(nn.Module):\n    # expansion refers to the multiple of decreasing the scale to increase the dimension in each small residual block\n    expansion = 1\n \n    # init()：Start initialize\n    def __init__(self, in_channel, out_channel, stride=1, downsample=None, **kwargs):\n        super(BasicBlock, self).__init__()\n        self.conv1 = nn.Conv2d(in_channels=in_channel, out_channels=out_channel,\n                               kernel_size=3, stride=stride, padding=1, bias=False)\n        # Use batch normalization\n        self.bn1 = nn.BatchNorm2d(out_channel)\n        # Use ReLU as the activation function\n        self.relu = nn.ReLU()\n        self.conv2 = nn.Conv2d(in_channels=out_channel, out_channels=out_channel,\n                               kernel_size=3, stride=1, padding=1, bias=False)\n        self.bn2 = nn.BatchNorm2d(out_channel)\n        self.downsample = downsample\n \n    # forward()：The forward propagation process is defined and the connections between the layers are described\n    def forward(self, x):\n        # The residual block retains the original input\n        identity = x\n        # In the case of a dashed residual structure, downsampling is performed\n        if self.downsample is not None:\n            identity = self.downsample(x)\n \n        out = self.conv1(x)\n        out = self.bn1(out)\n        out = self.relu(out)\n        # -----------------------------------------\n        out = self.conv2(out)\n        out = self.bn2(out)\n        # The main branch and the shortcut branch data are added\n        out += identity\n        out = self.relu(out)\n \n        return out\n \n \n# Define the residual structure of ResNet50/101/152\nclass Bottleneck(nn.Module):\n    # expansion refers to the multiple of decreasing the scale to increase the dimension in each small residual block\n    expansion = 4\n \n    # init()：Start initialize\n    def __init__(self, in_channel, out_channel, stride=1, downsample=None,\n                 groups=1, width_per_group=64):\n        super(Bottleneck, self).__init__()\n \n        width = int(out_channel * (width_per_group / 64.)) * groups\n \n        self.conv1 = nn.Conv2d(in_channels=in_channel, out_channels=width,\n                               kernel_size=1, stride=1, bias=False)\n        # Use batch normalization\n        self.bn1 = nn.BatchNorm2d(width)\n        # -----------------------------------------\n        self.conv2 = nn.Conv2d(in_channels=width, out_channels=width, groups=groups,\n                               kernel_size=3, stride=stride, bias=False, padding=1)\n        self.bn2 = nn.BatchNorm2d(width)\n        # -----------------------------------------\n        self.conv3 = nn.Conv2d(in_channels=width, out_channels=out_channel * self.expansion,\n                               kernel_size=1, stride=1, bias=False)\n        self.bn3 = nn.BatchNorm2d(out_channel * self.expansion)\n        # Use ReLU as the activation function\n        self.relu = nn.ReLU(inplace=True)\n        self.downsample = downsample\n \n    # forward()：The forward propagation process is defined and the connections between the layers are described\n    def forward(self, x):\n        # The residual block retains the original input\n        identity = x\n        # In the case of a dashed residual structure, downsampling is performed\n        if self.downsample is not None:\n            identity = self.downsample(x)\n \n        out = self.conv1(x)\n        out = self.bn1(out)\n        out = self.relu(out)\n \n        out = self.conv2(out)\n        out = self.bn2(out)\n        out = self.relu(out)\n \n        out = self.conv3(out)\n        out = self.bn3(out)\n        # The main branch and the shortcut branch data are added\n        out += identity\n        out = self.relu(out)\n \n        return out\n \n \n# Define ResNet class\nclass ResNet(nn.Module):\n    # initialize the function\n    def __init__(self,\n                 block,\n                 blocks_num,\n                 num_classes=1000,\n                 include_top=True,\n                 groups=1,\n                 width_per_group=64):\n        super(ResNet, self).__init__()\n        self.include_top = include_top\n        # The maxpool has 64 output channels and 64 residual structure input channels\n        self.in_channel = 64\n \n        self.groups = groups\n        self.width_per_group = width_per_group\n \n        self.conv1 = nn.Conv2d(3, self.in_channel, kernel_size=7, stride=2,\n                               padding=3, bias=False)\n        self.bn1 = nn.BatchNorm2d(self.in_channel)\n        self.relu = nn.ReLU(inplace=True)\n        self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)\n        # Shallow stride=1, deep stride=2\n        # block：Two types of residual modules are defined\n        # block_num：The number of residual blocks in the module\n        self.layer1 = self._make_layer(block, 64, blocks_num[0])\n        self.layer2 = self._make_layer(block, 128, blocks_num[1], stride=2)\n        self.layer3 = self._make_layer(block, 256, blocks_num[2], stride=2)\n        self.layer4 = self._make_layer(block, 512, blocks_num[3], stride=2)\n        if self.include_top:\n            # Adaptive average pooling, with specified outputs (H, W) and no change in the number of channels\n            self.avgpool = nn.AdaptiveAvgPool2d((1, 1))\n            # Fully connected layer\n            self.fc = nn.Linear(512 * block.expansion, num_classes)\n        # Inherit nn. Module class, self.modules(), which returns all modules in the network\n        for m in self.modules():\n            # if convolution layer\n            if isinstance(m, nn.Conv2d):\n                nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')\n \n    # Define the Residuals module, which consists of several residual blocks\n    def _make_layer(self, block, channel, block_num, stride=1):\n        downsample = None\n        if stride != 1 or self.in_channel != channel * block.expansion:\n            downsample = nn.Sequential(\n                nn.Conv2d(self.in_channel, channel * block.expansion, kernel_size=1, stride=stride, bias=False),\n                nn.BatchNorm2d(channel * block.expansion))\n \n        layers = []\n        layers.append(block(self.in_channel,\n                            channel,\n                            downsample=downsample,\n                            stride=stride,\n                            groups=self.groups,\n                            width_per_group=self.width_per_group))\n        self.in_channel = channel * block.expansion\n \n        for _ in range(1, block_num):\n            layers.append(block(self.in_channel,\n                                channel,\n                                groups=self.groups,\n                                width_per_group=self.width_per_group))\n        # Sequential：Custom sequences are connected into models to generate network structures\n        return nn.Sequential(*layers)\n \n    # forward()：The forward propagation process is defined and the connections between the layers are described\n    def forward(self, x):\n        # Static layer\n        x = self.conv1(x)\n        x = self.bn1(x)\n        x = self.relu(x)\n        x = self.maxpool(x)\n        # Dynamic layers\n        x = self.layer1(x)\n        x = self.layer2(x)\n        x = self.layer3(x)\n        x = self.layer4(x)\n \n        if self.include_top:\n            x = self.avgpool(x)\n            x = torch.flatten(x, 1)\n            x = self.fc(x)\n \n        return x\n\n# ResNet50\ndef resnet50(num_classes=num_classes, include_top=True):\n    return ResNet(Bottleneck, [3, 4, 6, 3], num_classes=num_classes, include_top=include_top)\n\n# ResNet101\ndef resnet101(num_classes=num_classes, include_top=True):\n    return ResNet(Bottleneck, [3, 4, 23, 3], num_classes=num_classes, include_top=include_top)\n\n# ResNet152\ndef resnet152(num_classes=num_classes, include_top=True):\n    return ResNet(Bottleneck, [3, 8, 36, 3], num_classes=num_classes, include_top=include_top)","metadata":{"execution":{"iopub.status.busy":"2024-01-08T09:29:05.381909Z","iopub.execute_input":"2024-01-08T09:29:05.382205Z","iopub.status.idle":"2024-01-08T09:29:05.415532Z","shell.execute_reply.started":"2024-01-08T09:29:05.382168Z","shell.execute_reply":"2024-01-08T09:29:05.414749Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# choose the model you want\nmodel = resnet50(num_classes).to(device)\n# design the optimizers\noptimizer1 = optim.Adam(model.parameters(), lr=lr_rate)\noptimizer2 = optim.SGD(model.parameters(), lr=lr_rate, momentum=0.9)\n\n# Validate step\ndef validate(model, val_loader):\n    # Set the model to evaluation mode\n    model.eval()\n    correct = 0\n    total = 0\n    # Use tqdm for a progress bar during training\n    progress_bar = tqdm(enumerate(val_loader), total=len(val_loader))\n    with torch.no_grad():\n        for batch_idx, (images, labels) in progress_bar:\n            images = images.to(device)\n            labels = labels.to(device)\n            outputs = model(images)\n            _, predicted = torch.max(outputs, 1)\n            total += labels.size(0)\n            correct += (predicted == labels).sum().item()\n            \n    # Calculate accuracy and print the result\n    accuracy = correct / total\n    print(\"\\n Evaluation accuracy: {}\".format(accuracy))\n    return accuracy\n\n# Test step\ndef test(model, test_loader):\n    # Set the model to evaluation mode\n    model.eval()\n    correct = 0\n    total = 0\n\n    with torch.no_grad():\n        for images, labels in test_loader:\n            images = images.to(device)\n            labels = labels.to(device)\n            outputs = model(images)\n            _, predicted = torch.max(outputs, 1)\n            total += labels.size(0)\n            correct += (predicted == labels).sum().item()\n            \n    # Calculate accuracy and print the result\n    accuracy = correct / total\n    print(f'Test Accuracy: {accuracy * 100:.2f}%')\n\n# Train step\ndef train(model, train_loader, val_loader, test_loader, num_epochs=5, optim = optimizer1):\n    criterion = nn.CrossEntropyLoss()\n    optimizer = optim\n    # Use a cyclic learning rate scheduler for adaptive learning rates\n    scheduler = torch.optim.lr_scheduler.CyclicLR(optimizer, base_lr=0.005, max_lr=0.05)\n    trn_acc_hist = []\n    val_acc_hist = []\n    \n    for epoch in range(num_epochs):\n        total_loss = 0.0\n        # Use tqdm for a progress bar during training\n        progress_bar = tqdm(enumerate(train_loader), total=len(train_loader), desc=f'Epoch {epoch+1}/{num_epochs}')\n        for batch_idx, (images, labels) in progress_bar:\n            images = images.to(device)\n            labels = labels.to(device)\n            optimizer.zero_grad()\n            outputs = model(images)\n            loss = criterion(outputs, labels)\n            loss.backward()\n            optimizer.step()\n            scheduler.step()\n            total_loss += loss.item()\n            progress_bar.set_postfix({'Loss': total_loss / (batch_idx + 1)})\n        \n        # Print average loss at the end of each epoch\n        print(f'Epoch {epoch+1}/{num_epochs}, Average Loss: {total_loss/len(train_loader)}')\n        # Record training and validation accuracy history\n        trn_acc_hist.append(validate(model, train_loader))\n        # Validation is performed at the end of each epoch\n        print(\"\\n Evaluate on validation set...\")\n        val_acc_hist.append(validate(model, val_loader))\n\n    # Test at the end of the training session\n    print(\"\\nTraining completed. Starting test evaluation.\")\n    test(model, test_loader)\n    return trn_acc_hist, val_acc_hist\n\n# Start training\nprint(\"Training started...\")\ntrn_acc_hist, val_acc_hist = train(model, train_loader, val_loader, test_loader, epochs, optimizer2)","metadata":{"execution":{"iopub.status.busy":"2024-01-08T09:29:05.416986Z","iopub.execute_input":"2024-01-08T09:29:05.417249Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Svae the trained model\ntorch.save(model.state_dict(),'./model_best.pth')","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Plot the training and validation accuracy histories\nx = np.arange(epochs)\nplt.figure()\nplt.plot(x, trn_acc_hist)\nplt.plot(x, val_acc_hist)\nplt.legend(['Training', 'Validation'])\nplt.xticks(x)\nplt.xlabel('Epoch')\nplt.ylabel('Accuracy')\nplt.title('Classification')\nplt.gcf().set_size_inches(10, 5)\n# Save the plot as an image (PNG) with a specified DPI (dots per inch)\nplt.savefig('classify.png', dpi=300)\nplt.show()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Get an enumeration of the test loader\nexamples = enumerate(test_loader)\nbatch_idx, (example_data, example_targets) = next(examples)\n# Get predictions through the model\nwith torch.no_grad():\n    example_data = example_data.to(device)\n    output = model(example_data)\nfig = plt.figure()\n\n# Iterate over the first 9 examples in the batch\nfor i in range(9):\n  plt.subplot(3,3,i+1)\n  plt.tight_layout()\n  plt.imshow(example_data[i][0].cpu())\n  plt.title(\"Prediction: {}\".format(output.data.max(1, keepdim=True)[1][i].item()))\n  plt.xticks([])\n  plt.yticks([])\nplt.show()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Add your image to predict the class\nimg = Image.open('xx.jpg').convert('RGB')\n# Transform the image to fit the input type\nx = transform(img).to(device)\npred_class = torch.argmax(model(x)).item()\nplt.figure(figsize=(6,8))\nplt.imshow(img)\nplt.title('Predicted Class: %s' %pred_class)\nplt.axis('off')\nplt.show()","metadata":{"trusted":true},"execution_count":null,"outputs":[]}]}