{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "6f8f04f6-045f-40ce-a3bd-07ea72478e43",
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "import tensorflow as tf\n",
    "import zipfile\n",
    "import numpy as np\n",
    "import shutil\n",
    "from glob import glob\n",
    "from sklearn.model_selection import train_test_split\n",
    "from tensorflow.keras.preprocessing.image import ImageDataGenerator, img_to_array, load_img, save_img\n",
    "from tensorflow.keras.layers import GlobalAveragePooling2D, Dense, Dropout\n",
    "from tensorflow.keras.regularizers import l2\n",
    "from tensorflow.keras.models import Sequential\n",
    "from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint, ReduceLROnPlateau\n",
    "from tensorflow.keras.applications.mobilenet_v2 import preprocess_input\n",
    "from scipy.ndimage import rotate\n",
    "from skimage.exposure import adjust_gamma\n",
    "import matplotlib.pyplot as plt\n",
    "import seaborn as sns\n",
    "from sklearn.metrics import confusion_matrix, classification_report"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "71840f0b-ae56-436a-9065-925d3fd860d0",
   "metadata": {},
   "outputs": [],
   "source": [
    "# -------------------------------------------------\n",
    "# 1) Ekstrak dataset (jika belum ada folder 'dataset')\n",
    "# -------------------------------------------------\n",
    "dataset_path = 'C:/Users/FIKRI/Documents/Model/dataset.zip'\n",
    "extract_path = 'C:/Users/FIKRI/Documents/Model'\n",
    "\n",
    "if not os.path.exists(os.path.join(extract_path, 'dataset')):\n",
    "    with zipfile.ZipFile(dataset_path, 'r') as zip_ref:\n",
    "        zip_ref.extractall(extract_path)\n",
    "\n",
    "# Pastikan kita punya 3 folder kelas di:\n",
    "# [path]/dataset/anorganik\n",
    "# [path]/dataset/organik\n",
    "# [path]/dataset/bukansampah"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "91294283-684c-4294-ba11-94921326e0a3",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Jumlah gambar di kelas anorganik: 889\n",
      "Jumlah gambar di kelas bukansampah: 3008\n",
      "Jumlah gambar di kelas organik: 345\n"
     ]
    }
   ],
   "source": [
    "dataset_dir = os.path.join(extract_path, 'dataset')\n",
    "classes = ['anorganik', 'bukansampah', 'organik']\n",
    "\n",
    "# Cek jumlah data asli di setiap kelas\n",
    "for cls in classes:\n",
    "    class_path = os.path.join(dataset_dir, cls)\n",
    "    num_images = len(os.listdir(class_path))\n",
    "    print(f\"Jumlah gambar di kelas {cls}: {num_images}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "486d3551-f716-4504-aa9d-75abb753ec82",
   "metadata": {},
   "outputs": [],
   "source": [
    "# -------------------------------------------------\n",
    "# 2) Split data asli menjadi train & val (80:20)\n",
    "#    tanpa augmentasi apa pun\n",
    "# -------------------------------------------------\n",
    "split_dir = os.path.join(extract_path, 'splitted_data')\n",
    "train_original_dir = os.path.join(split_dir, 'train_original')\n",
    "val_original_dir   = os.path.join(split_dir, 'val_original')\n",
    "\n",
    "# Buat folder jika belum ada\n",
    "os.makedirs(train_original_dir, exist_ok=True)\n",
    "os.makedirs(val_original_dir, exist_ok=True)\n",
    "for cls in classes:\n",
    "    os.makedirs(os.path.join(train_original_dir, cls), exist_ok=True)\n",
    "    os.makedirs(os.path.join(val_original_dir, cls), exist_ok=True)\n",
    "\n",
    "# Lakukan split data\n",
    "for cls in classes:\n",
    "    input_class_dir = os.path.join(dataset_dir, cls)\n",
    "    images = os.listdir(input_class_dir)\n",
    "\n",
    "    # Split 80:20\n",
    "    train_imgs, val_imgs = train_test_split(images, test_size=0.2, random_state=42)\n",
    "\n",
    "    # Copy ke folder train_original\n",
    "    for img_name in train_imgs:\n",
    "        src = os.path.join(input_class_dir, img_name)\n",
    "        dst = os.path.join(train_original_dir, cls, img_name)\n",
    "        shutil.copy(src, dst)\n",
    "\n",
    "    # Copy ke folder val_original\n",
    "    for img_name in val_imgs:\n",
    "        src = os.path.join(input_class_dir, img_name)\n",
    "        dst = os.path.join(val_original_dir, cls, img_name)\n",
    "        shutil.copy(src, dst)\n",
    "\n",
    "print(\"Split data asli -> train_original & val_original selesai.\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "99c776a4-48e7-4df4-8715-e2bec5c7b19f",
   "metadata": {},
   "outputs": [],
   "source": [
    "# -------------------------------------------------\n",
    "# 3) Augmentasi hanya untuk data train\n",
    "#    (val dibiarkan apa adanya)\n",
    "# -------------------------------------------------\n",
    "\n",
    "# Folder output augmentasi train\n",
    "aug_train_dir = os.path.join(split_dir, 'train_aug')\n",
    "os.makedirs(aug_train_dir, exist_ok=True)\n",
    "for cls in classes:\n",
    "    os.makedirs(os.path.join(aug_train_dir, cls), exist_ok=True)\n",
    "\n",
    "# Fungsi rotasi\n",
    "def custom_rotation(img_array, rotation_angles):\n",
    "    augmented_images = []\n",
    "    for angle in rotation_angles:\n",
    "        # rotate dari scipy.ndimage\n",
    "        augmented_img = rotate(img_array, angle, reshape=False, mode='constant', cval=0)\n",
    "        augmented_img = np.clip(augmented_img, 0, 255).astype('uint8')\n",
    "        augmented_images.append(augmented_img)\n",
    "    return augmented_images\n",
    "\n",
    "# Fungsi mengatur kontras\n",
    "def adjust_contrast(img_array, gamma):\n",
    "    # adjust_gamma dari skimage.exposure\n",
    "    augmented_img = adjust_gamma(img_array, gamma=gamma)\n",
    "    augmented_img = np.clip(augmented_img, 0, 255).astype('uint8')\n",
    "    return augmented_img\n",
    "\n",
    "# Fungsi augmentasi lainnya (ImageDataGenerator)\n",
    "def apply_other_augmentations(img_array, datagen, num_augmented_images=1):\n",
    "    # Tambah dimensi batch\n",
    "    img_array = img_array.reshape((1,) + img_array.shape)\n",
    "    augmented_images = []\n",
    "    for _ in range(num_augmented_images):\n",
    "        augmented_img = next(datagen.flow(img_array, batch_size=1))[0]\n",
    "        augmented_img = np.clip(augmented_img, 0, 255).astype('uint8')\n",
    "        augmented_images.append(augmented_img)\n",
    "    return augmented_images\n",
    "\n",
    "# Definisikan ImageDataGenerator khusus untuk masing-masing kelas (opsional)\n",
    "datagen_anorganik = ImageDataGenerator(\n",
    "    width_shift_range=0.1,\n",
    "    height_shift_range=0.1,\n",
    "    zoom_range=0.2,\n",
    "    horizontal_flip=True,\n",
    "    vertical_flip=True,\n",
    "    fill_mode='constant'\n",
    ")\n",
    "\n",
    "datagen_organik = ImageDataGenerator(\n",
    "    width_shift_range=0.1,\n",
    "    height_shift_range=0.1,\n",
    "    shear_range=0.1,\n",
    "    brightness_range=(0.2, 1.0),\n",
    "    zoom_range=[0.8, 0.4],\n",
    "    horizontal_flip=True,\n",
    "    vertical_flip=True,\n",
    "    fill_mode='constant'\n",
    ")\n",
    "\n",
    "datagen_bukansampah = ImageDataGenerator(\n",
    "    rotation_range=10,\n",
    "    brightness_range=(0.9, 1.1),\n",
    "    horizontal_flip=True,\n",
    "    vertical_flip=True,\n",
    "    fill_mode='constant'\n",
    ")\n",
    "\n",
    "# Parameter augmentasi\n",
    "rotation_angles = [-20, 20, -30, 30]\n",
    "gamma_values = [0.5, 1.2]\n",
    "target_size = (600, 800)  # Ukuran resize saat augmentasi\n",
    "num_aug = 2               # Berapa kali augmentasi (ImageDataGenerator) per rotasi\n",
    "\n",
    "# Loop per kelas untuk augmentasi\n",
    "for cls in classes:\n",
    "    input_path = os.path.join(train_original_dir, cls)  # folder train_original\n",
    "    output_path = os.path.join(aug_train_dir, cls)      # folder train_aug\n",
    "\n",
    "    images = os.listdir(input_path)\n",
    "\n",
    "    # Pilih generator sesuai kelas\n",
    "    if cls == \"anorganik\":\n",
    "        datagen = datagen_anorganik\n",
    "    elif cls == \"organik\":\n",
    "        datagen = datagen_organik\n",
    "    else:\n",
    "        datagen = datagen_bukansampah\n",
    "\n",
    "    for img_file in images:\n",
    "        img_path = os.path.join(input_path, img_file)\n",
    "        img = load_img(img_path, target_size=target_size)\n",
    "        img_array = img_to_array(img)\n",
    "\n",
    "        # Augmentasi rotasi manual\n",
    "        rotated_images = custom_rotation(img_array, rotation_angles)\n",
    "\n",
    "        # Kumpulkan semua augmented\n",
    "        all_augmented_images = []\n",
    "        for rotated_img in rotated_images:\n",
    "            # Augmentasi pakai ImageDataGenerator\n",
    "            augmented_images = apply_other_augmentations(rotated_img, datagen, num_aug)\n",
    "            all_augmented_images.extend(augmented_images)\n",
    "\n",
    "            # Tambahkan variasi kontras\n",
    "            for gamma in gamma_values:\n",
    "                contrast_img = adjust_contrast(rotated_img, gamma)\n",
    "                all_augmented_images.append(contrast_img)\n",
    "\n",
    "        # Simpan hasil augmentasi\n",
    "        # Juga simpan original image agar train mencakup gambar aslinya\n",
    "        # (Jika mau menyimpan aslinya di folder augmented, boleh juga)\n",
    "        # save_img(os.path.join(output_path, img_file), img_array.astype('uint8'))\n",
    "\n",
    "        # Untuk menjaga agar data augmented tidak meledak jumlahnya,\n",
    "        # kita bisa batasi sesuai kebutuhan. Di sini disimpan semua:\n",
    "        for i, augmented_img in enumerate(all_augmented_images):\n",
    "            save_path = os.path.join(output_path, f'{cls}_{img_file.split(\".\")[0]}_aug_{i+1}.jpg')\n",
    "            save_img(save_path, augmented_img)\n",
    "\n",
    "print(\"Augmentasi train selesai.\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "99ac457f-8891-4d4d-ac3b-5b0eb9eb7058",
   "metadata": {},
   "outputs": [],
   "source": [
    "# -------------------------------------------------\n",
    "# 4) Siapkan folder final train & val\n",
    "#    (Kita akan gabung: data original train + data augmented)\n",
    "# -------------------------------------------------\n",
    "final_train_dir = os.path.join(extract_path, 'train_data')\n",
    "final_val_dir   = os.path.join(extract_path, 'val_data')\n",
    "\n",
    "# Bersihkan folder jika sudah ada\n",
    "if os.path.exists(final_train_dir):\n",
    "    shutil.rmtree(final_train_dir)\n",
    "if os.path.exists(final_val_dir):\n",
    "    shutil.rmtree(final_val_dir)\n",
    "\n",
    "os.makedirs(final_train_dir, exist_ok=True)\n",
    "os.makedirs(final_val_dir, exist_ok=True)\n",
    "\n",
    "for cls in classes:\n",
    "    os.makedirs(os.path.join(final_train_dir, cls), exist_ok=True)\n",
    "    os.makedirs(os.path.join(final_val_dir, cls), exist_ok=True)\n",
    "\n",
    "# Copy data original train ke final_train_dir\n",
    "for cls in classes:\n",
    "    src_path = os.path.join(train_original_dir, cls)\n",
    "    dst_path = os.path.join(final_train_dir, cls)\n",
    "    for img_file in os.listdir(src_path):\n",
    "        shutil.copy(os.path.join(src_path, img_file), os.path.join(dst_path, img_file))\n",
    "\n",
    "# Copy data augmented train ke final_train_dir\n",
    "for cls in classes:\n",
    "    src_path = os.path.join(aug_train_dir, cls)\n",
    "    dst_path = os.path.join(final_train_dir, cls)\n",
    "    for img_file in os.listdir(src_path):\n",
    "        shutil.copy(os.path.join(src_path, img_file), os.path.join(dst_path, img_file))\n",
    "\n",
    "# Copy data val_original ke final_val_dir\n",
    "for cls in classes:\n",
    "    src_path = os.path.join(val_original_dir, cls)\n",
    "    dst_path = os.path.join(final_val_dir, cls)\n",
    "    for img_file in os.listdir(src_path):\n",
    "        shutil.copy(os.path.join(src_path, img_file), os.path.join(dst_path, img_file))\n",
    "\n",
    "print(\"Folder train_data dan val_data siap (gabungan original + augmented).\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "40354bb4-20e0-4310-8310-eef8d79c6d2e",
   "metadata": {},
   "outputs": [],
   "source": [
    "final_train_dir = os.path.join(extract_path, 'train_data')\n",
    "final_val_dir   = os.path.join(extract_path, 'val_data')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "c66b99c0-53c3-40fe-950c-d2dbc1aca76e",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Found 29300 images belonging to 3 classes.\n",
      "Found 843 images belonging to 3 classes.\n"
     ]
    }
   ],
   "source": [
    "# -------------------------------------------------\n",
    "# 5) Buat ImageDataGenerator untuk training & validasi\n",
    "# -------------------------------------------------\n",
    "train_datagen = ImageDataGenerator(\n",
    "    preprocessing_function=preprocess_input\n",
    ")\n",
    "val_datagen = ImageDataGenerator(\n",
    "    preprocessing_function=preprocess_input\n",
    ")\n",
    "\n",
    "train_generator = train_datagen.flow_from_directory(\n",
    "    final_train_dir,\n",
    "    target_size=(224, 224),\n",
    "    batch_size=32,\n",
    "    class_mode='categorical',\n",
    "    shuffle=True\n",
    ")\n",
    "\n",
    "val_generator = val_datagen.flow_from_directory(\n",
    "    final_val_dir,\n",
    "    target_size=(224, 224),\n",
    "    batch_size=32,\n",
    "    class_mode='categorical',\n",
    "    shuffle=False\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "e7b6f9ff-43ab-4298-a101-66df72881472",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Model: \"sequential\"\n",
      "_________________________________________________________________\n",
      " Layer (type)                Output Shape              Param #   \n",
      "=================================================================\n",
      " mobilenetv2_1.00_224 (Funct  (None, 7, 7, 1280)       2257984   \n",
      " ional)                                                          \n",
      "                                                                 \n",
      " global_average_pooling2d (G  (None, 1280)             0         \n",
      " lobalAveragePooling2D)                                          \n",
      "                                                                 \n",
      " dense (Dense)               (None, 256)               327936    \n",
      "                                                                 \n",
      " dense_1 (Dense)             (None, 128)               32896     \n",
      "                                                                 \n",
      " dense_2 (Dense)             (None, 3)                 387       \n",
      "                                                                 \n",
      "=================================================================\n",
      "Total params: 2,619,203\n",
      "Trainable params: 1,567,299\n",
      "Non-trainable params: 1,051,904\n",
      "_________________________________________________________________\n",
      "Class Indices (Train): {'anorganik': 0, 'bukansampah': 1, 'organik': 2}\n"
     ]
    }
   ],
   "source": [
    "# -------------------------------------------------\n",
    "# 6) Definisikan Model (MobileNetV2 + Fine-Tuning)\n",
    "# -------------------------------------------------\n",
    "base_model = tf.keras.applications.MobileNetV2(\n",
    "    input_shape=(224, 224, 3),\n",
    "    include_top=False,\n",
    "    weights='imagenet'\n",
    ")\n",
    "\n",
    "# Fine-tuning: freeze layer kecuali 20 terakhir\n",
    "base_model.trainable = True\n",
    "for layer in base_model.layers[:-20]:\n",
    "    layer.trainable = False\n",
    "\n",
    "model = Sequential([\n",
    "    base_model,\n",
    "    GlobalAveragePooling2D(),\n",
    "    Dense(256, activation='relu'),\n",
    "    #Dropout(0.4),\n",
    "    Dense(128, activation='relu'),\n",
    "    #Dropout(0.4),\n",
    "    Dense(3, activation='softmax')\n",
    "])\n",
    "\n",
    "# Kompilasi model\n",
    "model.compile(\n",
    "    optimizer=tf.keras.optimizers.Adam(learning_rate=1e-5),\n",
    "    loss='categorical_crossentropy',\n",
    "    metrics=['accuracy']\n",
    ")\n",
    "\n",
    "model.summary()\n",
    "print(\"Class Indices (Train):\", train_generator.class_indices)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "60374dfc-8dde-4789-ba8a-d9c1c6887272",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 1/10\n",
      "916/916 [==============================] - 113s 117ms/step - loss: 0.1232 - accuracy: 0.9707 - val_loss: 0.0257 - val_accuracy: 0.9905 - lr: 1.0000e-05\n",
      "Epoch 2/10\n",
      "916/916 [==============================] - 91s 99ms/step - loss: 0.0136 - accuracy: 0.9967 - val_loss: 0.0048 - val_accuracy: 1.0000 - lr: 1.0000e-05\n",
      "Epoch 3/10\n",
      "916/916 [==============================] - 98s 106ms/step - loss: 0.0058 - accuracy: 0.9990 - val_loss: 0.0027 - val_accuracy: 1.0000 - lr: 1.0000e-05\n",
      "Epoch 4/10\n",
      "916/916 [==============================] - 103s 113ms/step - loss: 0.0025 - accuracy: 0.9997 - val_loss: 0.0030 - val_accuracy: 0.9988 - lr: 1.0000e-05\n",
      "Epoch 5/10\n",
      "916/916 [==============================] - 95s 104ms/step - loss: 0.0018 - accuracy: 0.9997 - val_loss: 0.0025 - val_accuracy: 0.9988 - lr: 1.0000e-05\n",
      "Epoch 6/10\n",
      "916/916 [==============================] - 92s 101ms/step - loss: 0.0016 - accuracy: 0.9996 - val_loss: 0.0017 - val_accuracy: 1.0000 - lr: 1.0000e-05\n",
      "Epoch 7/10\n",
      "916/916 [==============================] - 92s 100ms/step - loss: 8.7582e-04 - accuracy: 1.0000 - val_loss: 0.0019 - val_accuracy: 1.0000 - lr: 1.0000e-05\n",
      "Epoch 8/10\n",
      "916/916 [==============================] - 92s 100ms/step - loss: 6.9067e-04 - accuracy: 0.9999 - val_loss: 0.0023 - val_accuracy: 1.0000 - lr: 1.0000e-05\n",
      "Epoch 9/10\n",
      "916/916 [==============================] - 92s 101ms/step - loss: 5.1801e-04 - accuracy: 0.9999 - val_loss: 0.0019 - val_accuracy: 1.0000 - lr: 2.0000e-06\n",
      "Epoch 10/10\n",
      "916/916 [==============================] - 92s 100ms/step - loss: 2.4760e-04 - accuracy: 1.0000 - val_loss: 0.0018 - val_accuracy: 1.0000 - lr: 2.0000e-06\n"
     ]
    }
   ],
   "source": [
    "# -------------------------------------------------\n",
    "# 7) Training\n",
    "# -------------------------------------------------\n",
    "callbacks = [\n",
    "    EarlyStopping(monitor='val_loss', patience=5, restore_best_weights=True),\n",
    "    ModelCheckpoint('model_test.h5', save_best_only=True),\n",
    "    ReduceLROnPlateau(monitor='val_loss', factor=0.2, patience=2, min_lr=1e-6)\n",
    "]\n",
    "\n",
    "history = model.fit(\n",
    "    train_generator,\n",
    "    epochs=10,\n",
    "    validation_data=val_generator,\n",
    "    callbacks=callbacks\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "2237bfbd-872c-420d-99ab-33cc1149fc50",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "27/27 [==============================] - 4s 161ms/step - loss: 0.0018 - accuracy: 1.0000\n",
      "Akurasi pada data validasi: 1.00\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 34976 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "[encoded data, 28916 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "27/27 [==============================] - 5s 154ms/step\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 28576 characters]",
      "text/plain": [
       "<Figure size 600x500 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "Classification Report:\n",
      "              precision    recall  f1-score   support\n",
      "\n",
      "   anorganik       1.00      1.00      1.00       178\n",
      " bukansampah       1.00      1.00      1.00       602\n",
      "     organik       1.00      1.00      1.00        63\n",
      "\n",
      "    accuracy                           1.00       843\n",
      "   macro avg       1.00      1.00      1.00       843\n",
      "weighted avg       1.00      1.00      1.00       843\n",
      "\n"
     ]
    }
   ],
   "source": [
    "# -------------------------------------------------\n",
    "# 8) Evaluasi Model & Confusion Matrix\n",
    "# -------------------------------------------------\n",
    "loss, accuracy = model.evaluate(val_generator)\n",
    "print(f\"Akurasi pada data validasi: {accuracy:.2f}\")\n",
    "\n",
    "# Plot Akurasi\n",
    "plt.figure()\n",
    "plt.plot(history.history['accuracy'], label='Training Accuracy')\n",
    "plt.plot(history.history['val_accuracy'], label='Validation Accuracy')\n",
    "plt.xlabel('Epochs')\n",
    "plt.ylabel('Accuracy')\n",
    "plt.legend()\n",
    "plt.title('Training vs Validation Accuracy')\n",
    "plt.show()\n",
    "\n",
    "# Plot Loss\n",
    "plt.figure()\n",
    "plt.plot(history.history['loss'], label='Training Loss')\n",
    "plt.plot(history.history['val_loss'], label='Validation Loss')\n",
    "plt.xlabel('Epochs')\n",
    "plt.ylabel('Loss')\n",
    "plt.legend()\n",
    "plt.title('Training vs Validation Loss')\n",
    "plt.show()\n",
    "\n",
    "# Confusion Matrix\n",
    "Y_pred = model.predict(val_generator, steps=val_generator.samples // val_generator.batch_size + 1)\n",
    "y_pred = np.argmax(Y_pred, axis=1)\n",
    "y_true = val_generator.classes\n",
    "\n",
    "cm = confusion_matrix(y_true, y_pred)\n",
    "plt.figure(figsize=(6, 5))\n",
    "sns.heatmap(cm, annot=True, fmt=\"d\", cmap=\"Blues\",\n",
    "            xticklabels=list(val_generator.class_indices.keys()),\n",
    "            yticklabels=list(val_generator.class_indices.keys()))\n",
    "plt.xlabel(\"Prediksi\")\n",
    "plt.ylabel(\"Asli\")\n",
    "plt.title(\"Confusion Matrix\")\n",
    "plt.show()\n",
    "\n",
    "# Classification Report\n",
    "print(\"\\nClassification Report:\")\n",
    "print(classification_report(y_true, y_pred, target_names=val_generator.class_indices.keys()))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "dc41ecb1-39fd-40b7-a1c2-6ce531fc4b40",
   "metadata": {},
   "outputs": [],
   "source": [
    "# -------------------------------------------------\n",
    "# 9) Konversi model ke .onnx\n",
    "# -------------------------------------------------\n",
    "import tf2onnx\n",
    "onnx_model_path = \"mobilenetv2.onnx\"\n",
    "spec = (tf.TensorSpec((None, 224, 224, 3), tf.float32, name=\"input\"),)\n",
    "onnx_model, _ = tf2onnx.convert.from_keras(model, input_signature=spec, opset=13)\n",
    "with open(onnx_model_path, \"wb\") as f:\n",
    "    f.write(onnx_model.SerializeToString())\n",
    "\n",
    "print(f\"Model berhasil dikonversi ke ONNX: {onnx_model_path}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9a882142-7198-4cff-a479-31c832696d41",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
