{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "fqNIt28_pKnS"
      },
      "source": [
        "1. Persiapan Lingkungan"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {
        "id": "n6mBf6uO1VS6"
      },
      "outputs": [],
      "source": [
        "!pip install -q split-folders"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "dNC9QMXh1mWq",
        "outputId": "132f1b20-f2b3-4acc-a0f5-7464e74ca613"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "TensorFlow Version: 2.20.0\n"
          ]
        }
      ],
      "source": [
        "import os\n",
        "import shutil\n",
        "import numpy as np\n",
        "import matplotlib.pyplot as plt\n",
        "import seaborn as sns\n",
        "import splitfolders\n",
        "import tarfile\n",
        "import zipfile\n",
        "from PIL import Image\n",
        "\n",
        "from pathlib import Path\n",
        "from PIL import Image\n",
        "from google.colab import drive\n",
        "\n",
        "\n",
        "import tensorflow as tf\n",
        "from tensorflow.keras import layers, models, callbacks\n",
        "from tensorflow.keras.applications import EfficientNetV2B0\n",
        "from sklearn.metrics import classification_report, confusion_matrix\n",
        "\n",
        "print(f\"TensorFlow Version: {tf.__version__}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "A6j2Vv8ypnrd"
      },
      "source": [
        "2. Download dan Ekstraksi Dataset"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "bsGJEEFs7Flg",
        "outputId": "30228ce8-1b64-47f5-93f7-30632b2af1c5"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Mounted at /content/drive\n",
            "Moving ZIP file to /content...\n",
            "File successfully moved.\n",
            "Extracting dataset...\n",
            "Extraction completed!\n"
          ]
        }
      ],
      "source": [
        "drive.mount('/content/drive')\n",
        "\n",
        "archive_path = '/content/drive/MyDrive/jagung/dataset_jagung.zip'\n",
        "destination_path = '/content/dataset_jagung.zip'\n",
        "extract_path = '/content/dataset'\n",
        "\n",
        "if os.path.exists(archive_path):\n",
        "    print(\"Moving ZIP file to /content...\")\n",
        "    shutil.copy(archive_path, destination_path)\n",
        "    print(\"File successfully moved.\")\n",
        "else:\n",
        "    print(f\"Error: File not found at {archive_path}\")\n",
        "\n",
        "if os.path.exists(destination_path):\n",
        "    if not os.path.exists(extract_path) or len(os.listdir(extract_path)) == 0:\n",
        "        os.makedirs(extract_path, exist_ok=True)\n",
        "        print(\"Extracting dataset...\")\n",
        "        try:\n",
        "            with zipfile.ZipFile(destination_path, 'r') as zip_ref:\n",
        "                zip_ref.extractall(path=extract_path)\n",
        "            print(\"Extraction completed!\")\n",
        "        except Exception as e:\n",
        "            print(f\"Extraction failed: {e}\")\n",
        "    else:\n",
        "        print(\"Dataset is already extracted and ready for use.\")\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "148LNQ7Ips01"
      },
      "source": [
        "4. Data Cleaning dan Validasi Gambat (RGB)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "bIPb9DIX1spd",
        "outputId": "422957b6-c1e7-4f65-8834-14673b50a70e"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Starting data cleanup process...\n",
            "Cleanup completed. Total of 0 problematic files removed.\n"
          ]
        }
      ],
      "source": [
        "dataset_path = \"/content/dataset/dataset_jagung_v1\"\n",
        "\n",
        "def clean_image_data(directory):\n",
        "    removed_count = 0\n",
        "    for root, dirs, files in os.walk(directory):\n",
        "        for file in files:\n",
        "            file_path = os.path.join(root, file)\n",
        "            try:\n",
        "                img = Image.open(file_path)\n",
        "                img.verify()\n",
        "\n",
        "                img = Image.open(file_path)\n",
        "                if img.mode != 'RGB':\n",
        "                    img = img.convert('RGB')\n",
        "                    img.save(file_path)\n",
        "            except Exception as e:\n",
        "                print(f\"Removing corrupt/invalid file: {file_path}\")\n",
        "                os.remove(file_path)\n",
        "                removed_count += 1\n",
        "    return removed_count\n",
        "\n",
        "print(\"Starting data cleanup process...\")\n",
        "removed = clean_image_data(dataset_path)\n",
        "print(f\"Cleanup completed. Total of {removed} problematic files removed.\")\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "YSQX48hTp3vW"
      },
      "source": [
        "5. Data Spliting (70/15/15/)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 5,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "8Zg2hJw4a3RJ",
        "outputId": "3daeef44-6e99-48d4-da8e-53f7f431a550"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Splitting dataset into Train, Validation, and Test...\n"
          ]
        },
        {
          "name": "stderr",
          "output_type": "stream",
          "text": [
            "Copying files: 7106 files [01:11, 99.29 files/s]  "
          ]
        },
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Dataset splitting completed.\n"
          ]
        },
        {
          "name": "stderr",
          "output_type": "stream",
          "text": [
            "\n"
          ]
        }
      ],
      "source": [
        "output_dir = \"/content/dataset_split\"\n",
        "\n",
        "if os.path.exists(output_dir):\n",
        "    shutil.rmtree(output_dir)\n",
        "\n",
        "print(\"Splitting dataset into Train, Validation, and Test...\")\n",
        "splitfolders.ratio(dataset_path, output=output_dir,\n",
        "                   seed=42, ratio=(0.7, 0.15, 0.15),\n",
        "                   group_prefix=None, move=False)\n",
        "print(\"Dataset splitting completed.\")\n",
        "\n",
        "train_dir = os.path.join(output_dir, 'train')\n",
        "val_dir = os.path.join(output_dir, 'val')\n",
        "test_dir = os.path.join(output_dir, 'test')"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "F0q8ELD5qPAY"
      },
      "source": [
        "6. Data Loader dan Augmentasi"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 6,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "rNU4YGv61wFX",
        "outputId": "beda9f63-d52a-46dd-a28c-6d1ac17e7169"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Found 4973 files belonging to 4 classes.\n",
            "Found 1063 files belonging to 4 classes.\n",
            "Found 1070 files belonging to 4 classes.\n",
            "Number of classes: 4\n",
            "Class names: ['Bercak Daun', 'Daun Sehat', 'Karat Daun', 'hawar daun']\n"
          ]
        }
      ],
      "source": [
        "BATCH_SIZE = 32\n",
        "IMG_SIZE = (224, 224)\n",
        "\n",
        "train_ds = tf.keras.utils.image_dataset_from_directory(\n",
        "    train_dir, shuffle=True, batch_size=BATCH_SIZE, image_size=IMG_SIZE)\n",
        "\n",
        "val_ds = tf.keras.utils.image_dataset_from_directory(\n",
        "    val_dir, shuffle=True, batch_size=BATCH_SIZE, image_size=IMG_SIZE)\n",
        "\n",
        "test_ds = tf.keras.utils.image_dataset_from_directory(\n",
        "    test_dir, shuffle=False, batch_size=BATCH_SIZE, image_size=IMG_SIZE)\n",
        "\n",
        "class_names = train_ds.class_names\n",
        "NUM_CLASSES = len(class_names)\n",
        "print(f\"Number of classes: {NUM_CLASSES}\")\n",
        "print(f\"Class names: {class_names}\")\n",
        "\n",
        "def to_one_hot(image, label):\n",
        "    return image, tf.one_hot(label, NUM_CLASSES)\n",
        "\n",
        "train_ds = train_ds.map(to_one_hot, num_parallel_calls=tf.data.AUTOTUNE)\n",
        "val_ds = val_ds.map(to_one_hot, num_parallel_calls=tf.data.AUTOTUNE)\n",
        "test_ds = test_ds.map(to_one_hot, num_parallel_calls=tf.data.AUTOTUNE)\n",
        "\n",
        "AUTOTUNE = tf.data.AUTOTUNE\n",
        "train_ds = train_ds.prefetch(buffer_size=AUTOTUNE)\n",
        "val_ds = val_ds.prefetch(buffer_size=AUTOTUNE)\n",
        "test_ds = test_ds.prefetch(buffer_size=AUTOTUNE)\n",
        "\n",
        "data_augmentation = tf.keras.Sequential([\n",
        "    layers.RandomFlip(\"horizontal_and_vertical\"),\n",
        "    layers.RandomRotation(0.2),\n",
        "    layers.RandomZoom(0.2),\n",
        "    layers.RandomTranslation(0.1, 0.1),\n",
        "    layers.RandomContrast(0.2),\n",
        "    layers.RandomBrightness(0.2)\n",
        "], name=\"data_augmentation\")\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "BwSHXm-WqXeE"
      },
      "source": [
        "7. Visualisasi Sample dan Hasil Augmentasi"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 7,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 610
        },
        "id": "tc8SYmZH3lGS",
        "outputId": "44f07316-12d9-41a7-8069-5b108a335a18"
      },
      "outputs": [
        {
          "data": {
            "image/png": "[encoded data, 1341152 characters]",
            "text/plain": [
              "<Figure size 1200x600 with 8 Axes>"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "plt.figure(figsize=(12, 6))\n",
        "for images, labels in train_ds.take(1):\n",
        "    for i in range(4):\n",
        "        ax = plt.subplot(2, 4, i + 1)\n",
        "        plt.imshow(images[i].numpy().astype(\"uint8\"))\n",
        "        orig_class_idx = np.argmax(labels[i].numpy())\n",
        "\n",
        "        plt.title(f\"Ori: {class_names[orig_class_idx]}\")\n",
        "        plt.axis(\"off\")\n",
        "\n",
        "    augmented_images = data_augmentation(images, training=True)\n",
        "    for i in range(4):\n",
        "        ax = plt.subplot(2, 4, i + 5)\n",
        "        plt.imshow(augmented_images[i].numpy().astype(\"uint8\"))\n",
        "        orig_class_idx = np.argmax(labels[i].numpy())\n",
        "\n",
        "        plt.title(f\"Aug: {class_names[orig_class_idx]}\")\n",
        "        plt.axis(\"off\")\n",
        "plt.tight_layout()\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "vQefwIFlqhU0"
      },
      "source": [
        "8. Arsitektur Model CNN (EfficientNetv2B0)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 8,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 737
        },
        "id": "iswHWuZb3pZj",
        "outputId": "a83c5bc3-e512-4e01-c359-0766daaa608c"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "No checkpoint found.\n",
            "Downloading data from https://storage.googleapis.com/tensorflow/keras-applications/efficientnet_v2/efficientnetv2-b0_notop.h5\n",
            "\u001b[1m24274472/24274472\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m0s\u001b[0m 0us/step\n"
          ]
        },
        {
          "data": {
            "text/html": [
              "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"font-weight: bold\">Model: \"functional_1\"</span>\n",
              "</pre>\n"
            ],
            "text/plain": [
              "\u001b[1mModel: \"functional_1\"\u001b[0m\n"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        },
        {
          "data": {
            "text/html": [
              "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\">┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓\n",
              "┃<span style=\"font-weight: bold\"> Layer (type)                    </span>┃<span style=\"font-weight: bold\"> Output Shape           </span>┃<span style=\"font-weight: bold\">       Param # </span>┃\n",
              "┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩\n",
              "│ input_layer_2 (<span style=\"color: #0087ff; text-decoration-color: #0087ff\">InputLayer</span>)      │ (<span style=\"color: #00d7ff; text-decoration-color: #00d7ff\">None</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">224</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">224</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">3</span>)    │             <span style=\"color: #00af00; text-decoration-color: #00af00\">0</span> │\n",
              "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
              "│ data_augmentation (<span style=\"color: #0087ff; text-decoration-color: #0087ff\">Sequential</span>)  │ (<span style=\"color: #00d7ff; text-decoration-color: #00d7ff\">None</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">224</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">224</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">3</span>)    │             <span style=\"color: #00af00; text-decoration-color: #00af00\">0</span> │\n",
              "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
              "│ efficientnetv2-b0 (<span style=\"color: #0087ff; text-decoration-color: #0087ff\">Functional</span>)  │ (<span style=\"color: #00d7ff; text-decoration-color: #00d7ff\">None</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">7</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">7</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">1280</span>)     │     <span style=\"color: #00af00; text-decoration-color: #00af00\">5,919,312</span> │\n",
              "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
              "│ conv2d (<span style=\"color: #0087ff; text-decoration-color: #0087ff\">Conv2D</span>)                 │ (<span style=\"color: #00d7ff; text-decoration-color: #00d7ff\">None</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">7</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">7</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">512</span>)      │     <span style=\"color: #00af00; text-decoration-color: #00af00\">5,898,752</span> │\n",
              "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
              "│ batch_normalization             │ (<span style=\"color: #00d7ff; text-decoration-color: #00d7ff\">None</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">7</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">7</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">512</span>)      │         <span style=\"color: #00af00; text-decoration-color: #00af00\">2,048</span> │\n",
              "│ (<span style=\"color: #0087ff; text-decoration-color: #0087ff\">BatchNormalization</span>)            │                        │               │\n",
              "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
              "│ max_pooling2d (<span style=\"color: #0087ff; text-decoration-color: #0087ff\">MaxPooling2D</span>)    │ (<span style=\"color: #00d7ff; text-decoration-color: #00d7ff\">None</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">3</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">3</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">512</span>)      │             <span style=\"color: #00af00; text-decoration-color: #00af00\">0</span> │\n",
              "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
              "│ dropout (<span style=\"color: #0087ff; text-decoration-color: #0087ff\">Dropout</span>)               │ (<span style=\"color: #00d7ff; text-decoration-color: #00d7ff\">None</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">3</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">3</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">512</span>)      │             <span style=\"color: #00af00; text-decoration-color: #00af00\">0</span> │\n",
              "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
              "│ conv2d_1 (<span style=\"color: #0087ff; text-decoration-color: #0087ff\">Conv2D</span>)               │ (<span style=\"color: #00d7ff; text-decoration-color: #00d7ff\">None</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">3</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">3</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">256</span>)      │     <span style=\"color: #00af00; text-decoration-color: #00af00\">1,179,904</span> │\n",
              "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
              "│ batch_normalization_1           │ (<span style=\"color: #00d7ff; text-decoration-color: #00d7ff\">None</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">3</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">3</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">256</span>)      │         <span style=\"color: #00af00; text-decoration-color: #00af00\">1,024</span> │\n",
              "│ (<span style=\"color: #0087ff; text-decoration-color: #0087ff\">BatchNormalization</span>)            │                        │               │\n",
              "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
              "│ global_average_pooling2d        │ (<span style=\"color: #00d7ff; text-decoration-color: #00d7ff\">None</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">256</span>)            │             <span style=\"color: #00af00; text-decoration-color: #00af00\">0</span> │\n",
              "│ (<span style=\"color: #0087ff; text-decoration-color: #0087ff\">GlobalAveragePooling2D</span>)        │                        │               │\n",
              "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
              "│ dropout_1 (<span style=\"color: #0087ff; text-decoration-color: #0087ff\">Dropout</span>)             │ (<span style=\"color: #00d7ff; text-decoration-color: #00d7ff\">None</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">256</span>)            │             <span style=\"color: #00af00; text-decoration-color: #00af00\">0</span> │\n",
              "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
              "│ dense (<span style=\"color: #0087ff; text-decoration-color: #0087ff\">Dense</span>)                   │ (<span style=\"color: #00d7ff; text-decoration-color: #00d7ff\">None</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">1024</span>)           │       <span style=\"color: #00af00; text-decoration-color: #00af00\">263,168</span> │\n",
              "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
              "│ batch_normalization_2           │ (<span style=\"color: #00d7ff; text-decoration-color: #00d7ff\">None</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">1024</span>)           │         <span style=\"color: #00af00; text-decoration-color: #00af00\">4,096</span> │\n",
              "│ (<span style=\"color: #0087ff; text-decoration-color: #0087ff\">BatchNormalization</span>)            │                        │               │\n",
              "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
              "│ dropout_2 (<span style=\"color: #0087ff; text-decoration-color: #0087ff\">Dropout</span>)             │ (<span style=\"color: #00d7ff; text-decoration-color: #00d7ff\">None</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">1024</span>)           │             <span style=\"color: #00af00; text-decoration-color: #00af00\">0</span> │\n",
              "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
              "│ dense_1 (<span style=\"color: #0087ff; text-decoration-color: #0087ff\">Dense</span>)                 │ (<span style=\"color: #00d7ff; text-decoration-color: #00d7ff\">None</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">4</span>)              │         <span style=\"color: #00af00; text-decoration-color: #00af00\">4,100</span> │\n",
              "└─────────────────────────────────┴────────────────────────┴───────────────┘\n",
              "</pre>\n"
            ],
            "text/plain": [
              "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓\n",
              "┃\u001b[1m \u001b[0m\u001b[1mLayer (type)                   \u001b[0m\u001b[1m \u001b[0m┃\u001b[1m \u001b[0m\u001b[1mOutput Shape          \u001b[0m\u001b[1m \u001b[0m┃\u001b[1m \u001b[0m\u001b[1m      Param #\u001b[0m\u001b[1m \u001b[0m┃\n",
              "┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩\n",
              "│ input_layer_2 (\u001b[38;5;33mInputLayer\u001b[0m)      │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m224\u001b[0m, \u001b[38;5;34m224\u001b[0m, \u001b[38;5;34m3\u001b[0m)    │             \u001b[38;5;34m0\u001b[0m │\n",
              "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
              "│ data_augmentation (\u001b[38;5;33mSequential\u001b[0m)  │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m224\u001b[0m, \u001b[38;5;34m224\u001b[0m, \u001b[38;5;34m3\u001b[0m)    │             \u001b[38;5;34m0\u001b[0m │\n",
              "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
              "│ efficientnetv2-b0 (\u001b[38;5;33mFunctional\u001b[0m)  │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m7\u001b[0m, \u001b[38;5;34m7\u001b[0m, \u001b[38;5;34m1280\u001b[0m)     │     \u001b[38;5;34m5,919,312\u001b[0m │\n",
              "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
              "│ conv2d (\u001b[38;5;33mConv2D\u001b[0m)                 │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m7\u001b[0m, \u001b[38;5;34m7\u001b[0m, \u001b[38;5;34m512\u001b[0m)      │     \u001b[38;5;34m5,898,752\u001b[0m │\n",
              "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
              "│ batch_normalization             │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m7\u001b[0m, \u001b[38;5;34m7\u001b[0m, \u001b[38;5;34m512\u001b[0m)      │         \u001b[38;5;34m2,048\u001b[0m │\n",
              "│ (\u001b[38;5;33mBatchNormalization\u001b[0m)            │                        │               │\n",
              "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
              "│ max_pooling2d (\u001b[38;5;33mMaxPooling2D\u001b[0m)    │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m3\u001b[0m, \u001b[38;5;34m3\u001b[0m, \u001b[38;5;34m512\u001b[0m)      │             \u001b[38;5;34m0\u001b[0m │\n",
              "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
              "│ dropout (\u001b[38;5;33mDropout\u001b[0m)               │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m3\u001b[0m, \u001b[38;5;34m3\u001b[0m, \u001b[38;5;34m512\u001b[0m)      │             \u001b[38;5;34m0\u001b[0m │\n",
              "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
              "│ conv2d_1 (\u001b[38;5;33mConv2D\u001b[0m)               │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m3\u001b[0m, \u001b[38;5;34m3\u001b[0m, \u001b[38;5;34m256\u001b[0m)      │     \u001b[38;5;34m1,179,904\u001b[0m │\n",
              "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
              "│ batch_normalization_1           │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m3\u001b[0m, \u001b[38;5;34m3\u001b[0m, \u001b[38;5;34m256\u001b[0m)      │         \u001b[38;5;34m1,024\u001b[0m │\n",
              "│ (\u001b[38;5;33mBatchNormalization\u001b[0m)            │                        │               │\n",
              "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
              "│ global_average_pooling2d        │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m256\u001b[0m)            │             \u001b[38;5;34m0\u001b[0m │\n",
              "│ (\u001b[38;5;33mGlobalAveragePooling2D\u001b[0m)        │                        │               │\n",
              "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
              "│ dropout_1 (\u001b[38;5;33mDropout\u001b[0m)             │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m256\u001b[0m)            │             \u001b[38;5;34m0\u001b[0m │\n",
              "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
              "│ dense (\u001b[38;5;33mDense\u001b[0m)                   │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m1024\u001b[0m)           │       \u001b[38;5;34m263,168\u001b[0m │\n",
              "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
              "│ batch_normalization_2           │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m1024\u001b[0m)           │         \u001b[38;5;34m4,096\u001b[0m │\n",
              "│ (\u001b[38;5;33mBatchNormalization\u001b[0m)            │                        │               │\n",
              "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
              "│ dropout_2 (\u001b[38;5;33mDropout\u001b[0m)             │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m1024\u001b[0m)           │             \u001b[38;5;34m0\u001b[0m │\n",
              "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
              "│ dense_1 (\u001b[38;5;33mDense\u001b[0m)                 │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m4\u001b[0m)              │         \u001b[38;5;34m4,100\u001b[0m │\n",
              "└─────────────────────────────────┴────────────────────────┴───────────────┘\n"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        },
        {
          "data": {
            "text/html": [
              "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"font-weight: bold\"> Total params: </span><span style=\"color: #00af00; text-decoration-color: #00af00\">13,272,404</span> (50.63 MB)\n",
              "</pre>\n"
            ],
            "text/plain": [
              "\u001b[1m Total params: \u001b[0m\u001b[38;5;34m13,272,404\u001b[0m (50.63 MB)\n"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        },
        {
          "data": {
            "text/html": [
              "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"font-weight: bold\"> Trainable params: </span><span style=\"color: #00af00; text-decoration-color: #00af00\">7,349,508</span> (28.04 MB)\n",
              "</pre>\n"
            ],
            "text/plain": [
              "\u001b[1m Trainable params: \u001b[0m\u001b[38;5;34m7,349,508\u001b[0m (28.04 MB)\n"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        },
        {
          "data": {
            "text/html": [
              "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"font-weight: bold\"> Non-trainable params: </span><span style=\"color: #00af00; text-decoration-color: #00af00\">5,922,896</span> (22.59 MB)\n",
              "</pre>\n"
            ],
            "text/plain": [
              "\u001b[1m Non-trainable params: \u001b[0m\u001b[38;5;34m5,922,896\u001b[0m (22.59 MB)\n"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "def build_model(num_classes):\n",
        "    base_model = EfficientNetV2B0(\n",
        "        input_shape=IMG_SIZE + (3,),\n",
        "        include_top=False,\n",
        "        weights='imagenet',\n",
        "    )\n",
        "    base_model.trainable = False\n",
        "\n",
        "    inputs = tf.keras.Input(shape=IMG_SIZE + (3,))\n",
        "\n",
        "    x = data_augmentation(inputs)\n",
        "    x = base_model(x, training=False)\n",
        "\n",
        "    x = layers.Conv2D(512, (3, 3), padding='same', activation='swish')(x)\n",
        "    x = layers.BatchNormalization()(x)\n",
        "    x = layers.MaxPooling2D((2, 2))(x)\n",
        "    x = layers.Dropout(0.2)(x)\n",
        "\n",
        "    x = layers.Conv2D(256, (3, 3), padding='same', activation='swish')(x)\n",
        "    x = layers.BatchNormalization()(x)\n",
        "\n",
        "    x = layers.GlobalAveragePooling2D()(x)\n",
        "    x = layers.Dropout(0.3)(x)\n",
        "\n",
        "    x = layers.Dense(1024, activation='swish')(x)\n",
        "    x = layers.BatchNormalization()(x)\n",
        "    x = layers.Dropout(0.4)(x)\n",
        "\n",
        "    outputs = layers.Dense(num_classes, activation='sigmoid', dtype='float32')(x)\n",
        "\n",
        "    model = models.Model(inputs, outputs)\n",
        "    return model, base_model\n",
        "\n",
        "checkpoint_path = '/content/best_model/best_model.keras'\n",
        "\n",
        "if os.path.exists(checkpoint_path):\n",
        "    print(f\"Loading weights from model checkpoint: {checkpoint_path}...\")\n",
        "    try:\n",
        "        model = models.load_model(checkpoint_path)\n",
        "    except Exception as e:\n",
        "        print(f\"Failed to load checkpoint model: {e}.\")\n",
        "        model, base_model = build_model(NUM_CLASSES)\n",
        "else:\n",
        "    print(\"No checkpoint found.\")\n",
        "    model, base_model = build_model(NUM_CLASSES)\n",
        "\n",
        "model.summary()\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "yik1l5ngqt_1"
      },
      "source": [
        "9. Implementasi Callbacks"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 9,
      "metadata": {
        "id": "s4Trlvno3wny"
      },
      "outputs": [],
      "source": [
        "checkpoint_cb = callbacks.ModelCheckpoint(\n",
        "    \"best_model/best_model.keras\",\n",
        "    save_best_only=True,\n",
        "    monitor=\"val_loss\",\n",
        "    mode=\"min\"\n",
        ")\n",
        "\n",
        "early_stopping_cb = callbacks.EarlyStopping(\n",
        "    monitor=\"val_loss\",\n",
        "    patience=7,\n",
        "    restore_best_weights=True,\n",
        "    mode=\"min\"\n",
        ")\n",
        "\n",
        "reduce_lr_cb = callbacks.ReduceLROnPlateau(\n",
        "    monitor='val_loss',\n",
        "    factor=0.2,\n",
        "    patience=3,\n",
        "    min_lr=1e-6,\n",
        "    verbose=1,\n",
        "    mode=\"min\"\n",
        ")\n",
        "\n",
        "callbacks_list = [checkpoint_cb, early_stopping_cb, reduce_lr_cb]\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "UKyFNmVvqyHx"
      },
      "source": [
        "10. Training Model"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 10,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "cq5rllO63zXA",
        "outputId": "0d3c03c8-d354-4b8b-befb-ecbb68b18735"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Training...\n",
            "Epoch 1/15\n",
            "\u001b[1m156/156\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m207s\u001b[0m 1s/step - accuracy: 0.9153 - loss: 0.2220 - val_accuracy: 0.9459 - val_loss: 0.1425 - learning_rate: 0.0010\n",
            "Epoch 2/15\n",
            "\u001b[1m156/156\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m186s\u001b[0m 1s/step - accuracy: 0.9512 - loss: 0.1323 - val_accuracy: 0.9781 - val_loss: 0.0640 - learning_rate: 0.0010\n",
            "Epoch 3/15\n",
            "\u001b[1m156/156\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m191s\u001b[0m 1s/step - accuracy: 0.9649 - loss: 0.0948 - val_accuracy: 0.9730 - val_loss: 0.0645 - learning_rate: 0.0010\n",
            "Epoch 4/15\n",
            "\u001b[1m156/156\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m180s\u001b[0m 1s/step - accuracy: 0.9707 - loss: 0.0796 - val_accuracy: 0.9748 - val_loss: 0.0763 - learning_rate: 0.0010\n",
            "Epoch 5/15\n",
            "\u001b[1m156/156\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m0s\u001b[0m 958ms/step - accuracy: 0.9725 - loss: 0.0770\n",
            "Epoch 5: ReduceLROnPlateau reducing learning rate to 0.00020000000949949026.\n",
            "\u001b[1m156/156\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m191s\u001b[0m 1s/step - accuracy: 0.9733 - loss: 0.0757 - val_accuracy: 0.9661 - val_loss: 0.0838 - learning_rate: 0.0010\n",
            "Epoch 6/15\n",
            "\u001b[1m156/156\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m185s\u001b[0m 1s/step - accuracy: 0.9778 - loss: 0.0564 - val_accuracy: 0.9824 - val_loss: 0.0445 - learning_rate: 2.0000e-04\n",
            "Epoch 7/15\n",
            "\u001b[1m156/156\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m179s\u001b[0m 1s/step - accuracy: 0.9786 - loss: 0.0557 - val_accuracy: 0.9857 - val_loss: 0.0419 - learning_rate: 2.0000e-04\n",
            "Epoch 8/15\n",
            "\u001b[1m156/156\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m167s\u001b[0m 1s/step - accuracy: 0.9804 - loss: 0.0534 - val_accuracy: 0.9880 - val_loss: 0.0354 - learning_rate: 2.0000e-04\n",
            "Epoch 9/15\n",
            "\u001b[1m156/156\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m171s\u001b[0m 1s/step - accuracy: 0.9807 - loss: 0.0495 - val_accuracy: 0.9882 - val_loss: 0.0317 - learning_rate: 2.0000e-04\n",
            "Epoch 10/15\n",
            "\u001b[1m156/156\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m179s\u001b[0m 1s/step - accuracy: 0.9842 - loss: 0.0427 - val_accuracy: 0.9894 - val_loss: 0.0319 - learning_rate: 2.0000e-04\n",
            "Epoch 11/15\n",
            "\u001b[1m156/156\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m173s\u001b[0m 1s/step - accuracy: 0.9833 - loss: 0.0452 - val_accuracy: 0.9892 - val_loss: 0.0293 - learning_rate: 2.0000e-04\n",
            "Epoch 12/15\n",
            "\u001b[1m156/156\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m214s\u001b[0m 1s/step - accuracy: 0.9839 - loss: 0.0413 - val_accuracy: 0.9889 - val_loss: 0.0320 - learning_rate: 2.0000e-04\n",
            "Epoch 13/15\n",
            "\u001b[1m156/156\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m181s\u001b[0m 1s/step - accuracy: 0.9864 - loss: 0.0393 - val_accuracy: 0.9880 - val_loss: 0.0329 - learning_rate: 2.0000e-04\n",
            "Epoch 14/15\n",
            "\u001b[1m156/156\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m0s\u001b[0m 1s/step - accuracy: 0.9878 - loss: 0.0355\n",
            "Epoch 14: ReduceLROnPlateau reducing learning rate to 4.0000001899898055e-05.\n",
            "\u001b[1m156/156\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m246s\u001b[0m 1s/step - accuracy: 0.9877 - loss: 0.0358 - val_accuracy: 0.9885 - val_loss: 0.0340 - learning_rate: 2.0000e-04\n",
            "Epoch 15/15\n",
            "\u001b[1m156/156\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m176s\u001b[0m 1s/step - accuracy: 0.9866 - loss: 0.0358 - val_accuracy: 0.9897 - val_loss: 0.0326 - learning_rate: 4.0000e-05\n"
          ]
        }
      ],
      "source": [
        "model.compile(\n",
        "    optimizer=tf.keras.optimizers.Adam(learning_rate=1e-3),\n",
        "    loss=tf.keras.losses.BinaryCrossentropy(),\n",
        "    metrics=[tf.keras.metrics.BinaryAccuracy(name='accuracy')]\n",
        ")\n",
        "\n",
        "print(\"Training...\")\n",
        "EPOCHS_PHASE_1 = 15\n",
        "\n",
        "history_1 = model.fit(\n",
        "    train_ds,\n",
        "    validation_data=val_ds,\n",
        "    epochs=EPOCHS_PHASE_1,\n",
        "    callbacks=callbacks_list\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Fq0qppJ_rGDE"
      },
      "source": [
        "11. Plot History"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 11,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 545
        },
        "id": "wOW7f13UD4wy",
        "outputId": "eff4a3d7-9ddc-4992-da43-0ab09631e47d"
      },
      "outputs": [
        {
          "data": {
            "image/png": "[encoded data, 89404 characters]",
            "text/plain": [
              "<Figure size 1600x600 with 2 Axes>"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "def plot_history(history_1, initial_epochs):\n",
        "    acc = history_1.history['accuracy']\n",
        "    val_acc = history_1.history['val_accuracy']\n",
        "    loss = history_1.history['loss']\n",
        "    val_loss = history_1.history['val_loss']\n",
        "\n",
        "    plt.figure(figsize=(16, 6))\n",
        "\n",
        "    plt.subplot(1, 2, 1)\n",
        "    plt.plot(acc, label='Training Accuracy')\n",
        "    plt.plot(val_acc, label='Validation Accuracy')\n",
        "    plt.legend(loc='lower right')\n",
        "    plt.title('Training and Validation Accuracy')\n",
        "\n",
        "    plt.subplot(1, 2, 2)\n",
        "    plt.plot(loss, label='Training Loss')\n",
        "    plt.plot(val_loss, label='Validation Loss')\n",
        "    plt.legend(loc='upper right')\n",
        "    plt.title('Training and Validation Loss')\n",
        "    plt.show()\n",
        "\n",
        "plot_history(history_1, EPOCHS_PHASE_1)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "TxXzQ8ZrrmiS"
      },
      "source": [
        "12. Test Time Augmentation"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 12,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "5vnDCrqLD5hT",
        "outputId": "b4e04c95-c570-4817-cf1f-37321560c89c"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Evaluating model using TTA (Test-Time Augmentation) for Multi-Label...\n",
            "TTA Step 1/5\n",
            "TTA Step 2/5\n",
            "TTA Step 3/5\n",
            "TTA Step 4/5\n",
            "TTA Step 5/5\n",
            "\n",
            "Subset Accuracy (Exact Match) on Test Set (with TTA): 97.20%\n",
            "Binary Accuracy (Per-Class Average) on Test Set (with TTA): 98.86%\n"
          ]
        }
      ],
      "source": [
        "print(\"Evaluating model using TTA (Test-Time Augmentation) for Multi-Label...\")\n",
        "\n",
        "test_images = []\n",
        "test_labels_true = []\n",
        "\n",
        "for images, labels in test_ds.unbatch():\n",
        "    test_images.append(images.numpy())\n",
        "    test_labels_true.append(labels.numpy())\n",
        "\n",
        "test_images = np.array(test_images)\n",
        "test_labels_true = np.array(test_labels_true)\n",
        "\n",
        "TTA_STEPS = 5\n",
        "tta_predictions = []\n",
        "\n",
        "for i in range(TTA_STEPS):\n",
        "    print(f\"TTA Step {i+1}/{TTA_STEPS}\")\n",
        "    aug_images = data_augmentation(test_images, training=True)\n",
        "    preds = model.predict(aug_images, batch_size=BATCH_SIZE, verbose=0)\n",
        "    tta_predictions.append(preds)\n",
        "\n",
        "mean_tta_preds = np.mean(tta_predictions, axis=0)\n",
        "\n",
        "THRESHOLD = 0.5\n",
        "test_preds_binary = (mean_tta_preds > THRESHOLD).astype(int)\n",
        "\n",
        "correct_predictions = np.equal(test_preds_binary, test_labels_true.astype(int))\n",
        "tta_accuracy = np.mean(correct_predictions)\n",
        "\n",
        "print(f\"\\nSubset Accuracy (Exact Match) on Test Set (with TTA): {np.mean(np.all(correct_predictions, axis=1)) * 100:.2f}%\")\n",
        "print(f\"Binary Accuracy (Per-Class Average) on Test Set (with TTA): {tta_accuracy * 100:.2f}%\")\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "y-ADQvPIr1Wo"
      },
      "source": [
        "13. Metrik Evaluasi"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 13,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 1000
        },
        "id": "4ea9-S50EtQ6",
        "outputId": "f1632cab-a233-4072-d0ec-70f141f83757"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "\n",
            "Classification Report (Multi-Label):\n",
            "\n",
            "              precision    recall  f1-score   support\n",
            "\n",
            " Bercak Daun       0.96      0.94      0.95       224\n",
            "  Daun Sehat       0.99      1.00      1.00       345\n",
            "  Karat Daun       0.97      0.98      0.98       259\n",
            "  hawar daun       0.98      0.97      0.98       242\n",
            "\n",
            "   micro avg       0.98      0.98      0.98      1070\n",
            "   macro avg       0.98      0.97      0.97      1070\n",
            "weighted avg       0.98      0.98      0.98      1070\n",
            " samples avg       0.97      0.98      0.98      1070\n",
            "\n"
          ]
        },
        {
          "name": "stderr",
          "output_type": "stream",
          "text": [
            "/usr/local/lib/python3.12/dist-packages/sklearn/metrics/_classification.py:1565: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 in samples with no predicted labels. Use `zero_division` parameter to control this behavior.\n",
            "  _warn_prf(average, modifier, f\"{metric.capitalize()} is\", len(result))\n"
          ]
        },
        {
          "data": {
            "image/png": "[encoded data, 58432 characters]",
            "text/plain": [
              "<Figure size 1200x1000 with 4 Axes>"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "from sklearn.metrics import multilabel_confusion_matrix\n",
        "\n",
        "print(\"\\nClassification Report (Multi-Label):\\n\")\n",
        "print(classification_report(test_labels_true, test_preds_binary, target_names=class_names))\n",
        "\n",
        "mcm = multilabel_confusion_matrix(test_labels_true, test_preds_binary)\n",
        "\n",
        "fig, axes = plt.subplots(2, 2, figsize=(12, 10))\n",
        "axes = axes.ravel()\n",
        "\n",
        "for i, class_name in enumerate(class_names):\n",
        "    sns.heatmap(mcm[i], annot=True, fmt='d', cmap='Blues', ax=axes[i], cbar=False,\n",
        "                xticklabels=['Negative', 'Positive'], yticklabels=['Negative', 'Positive'])\n",
        "    axes[i].set_title(f'Confusion Matrix: {class_name}')\n",
        "    axes[i].set_xlabel('Predicted')\n",
        "    axes[i].set_ylabel('True')\n",
        "\n",
        "plt.tight_layout()\n",
        "plt.show()"
      ]
    }
  ],
  "metadata": {
    "accelerator": "GPU",
    "colab": {
      "gpuType": "T4",
      "provenance": []
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    },
    "language_info": {
      "name": "python"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
