{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[],"dockerImageVersionId":28755,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# ============================================================\n# SAMPLE\n# Persiapan Library dan Reproducibility\n# ============================================================\n\n# Mengimpor library untuk pengolahan sistem dan file\nimport os\nimport random\n\n# Mengimpor library untuk pengolahan citra dan array\nimport cv2\nimport numpy as np\n\n# Mengimpor library untuk pengolahan dan analisis data\nimport pandas as pd\n\n# Mengimpor library untuk visualisasi data\nimport matplotlib.pyplot as plt\nimport seaborn as sns\n\n# Mengimpor fungsi untuk pembagian dan validasi data\nfrom sklearn.model_selection import (\n    train_test_split,\n    StratifiedKFold\n)\n\n# Mengimpor fungsi untuk melakukan oversampling\nfrom sklearn.utils import resample\n\n# Mengimpor TensorFlow untuk proses pembangunan dan pelatihan model\nimport tensorflow as tf\n\n\n# Menentukan nilai seed untuk menjaga hasil eksperimen tetap konsisten\nSEED = 42\n\n# Mengatur seed pada library yang digunakan\nrandom.seed(SEED)\nnp.random.seed(SEED)\ntf.random.set_seed(SEED)","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2026-08-21T06:34:44.556503Z","iopub.execute_input":"2026-08-21T06:34:44.556992Z","iopub.status.idle":"2026-08-21T06:35:01.293074Z","shell.execute_reply.started":"2026-08-21T06:34:44.556955Z","shell.execute_reply":"2026-08-21T06:35:01.292037Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Sample","metadata":{}},{"cell_type":"code","source":"import os\n\n# Menentukan direktori utama dataset Messidor\nbase_path = \"/kaggle/input/datasets/asepshantikaramadhan/diabetikk/Messidor\"\n\n# Menentukan direktori citra untuk data training dan testing\ntrain_dir = os.path.join(base_path, \"train\")\ntest_dir  = os.path.join(base_path, \"test\")\n\n# Menentukan lokasi file CSV untuk data training dan testing\ntrain_csv_path = os.path.join(base_path, \"train.csv\")\ntest_csv_path  = os.path.join(base_path, \"test.csv\")\n\n# Membaca data training dari file CSV\ntrain_df = pd.read_csv(train_csv_path)\n\n# Membaca data testing dari file CSV\ntest_df = pd.read_csv(test_csv_path)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-21T06:35:01.295312Z","iopub.execute_input":"2026-08-21T06:35:01.295878Z","iopub.status.idle":"2026-08-21T06:35:01.373719Z","shell.execute_reply.started":"2026-08-21T06:35:01.295848Z","shell.execute_reply":"2026-08-21T06:35:01.372764Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ======== Pemeriksaan Awal Dataset ========\n\n# Menampilkan lima baris pertama data training\nprint(train_df.head())\n\nprint()\n\n# Menampilkan ukuran dataset training\nprint(train_df.shape)\n\n# Menampilkan ukuran dataset testing\nprint(test_df.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-21T06:35:01.374693Z","iopub.execute_input":"2026-08-21T06:35:01.374955Z","iopub.status.idle":"2026-08-21T06:35:01.393385Z","shell.execute_reply.started":"2026-08-21T06:35:01.374930Z","shell.execute_reply":"2026-08-21T06:35:01.392476Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ======== Pemeriksaan Jumlah Data dan Distribusi Identitas Dataset ========\n\n# Menampilkan jumlah data pada dataset training\nprint(\"Jumlah data Train :\", len(train_df))\n\n# Menampilkan jumlah data pada dataset testing\nprint(\"Jumlah data Test  :\", len(test_df))\n\nprint()\n\n# Menampilkan jumlah kemunculan setiap nilai Id pada data training\nprint(train_df[\"Id\"].value_counts().sort_index())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-21T06:35:01.394457Z","iopub.execute_input":"2026-08-21T06:35:01.395224Z","iopub.status.idle":"2026-08-21T06:35:01.414067Z","shell.execute_reply.started":"2026-08-21T06:35:01.395198Z","shell.execute_reply":"2026-08-21T06:35:01.413387Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ======== Informasi Struktur Dataset ========\n\nprint(train_df.info())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-21T06:35:01.414924Z","iopub.execute_input":"2026-08-21T06:35:01.415490Z","iopub.status.idle":"2026-08-21T06:35:01.428812Z","shell.execute_reply.started":"2026-08-21T06:35:01.415455Z","shell.execute_reply":"2026-08-21T06:35:01.428142Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ======== Pemeriksaan Nilai Kosong pada Dataset ========\n\n# Memeriksa jumlah nilai kosong pada setiap kolom\nprint(train_df.isnull().sum())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-21T06:35:01.429934Z","iopub.execute_input":"2026-08-21T06:35:01.430376Z","iopub.status.idle":"2026-08-21T06:35:01.442271Z","shell.execute_reply.started":"2026-08-21T06:35:01.430290Z","shell.execute_reply":"2026-08-21T06:35:01.441420Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Explore","metadata":{}},{"cell_type":"code","source":"# ======== Visualisasi Distribusi Kelas Data Training ========\n\n# Membuat area gambar untuk visualisasi distribusi kelas\nplt.figure(figsize=(7,5))\n\n# Membuat grafik jumlah citra pada setiap kelas\nsns.countplot(\n    data=train_df,\n    x=\"Id\"\n)\n\n# Memberikan judul pada grafik\nplt.title(\"Distribusi Kelas Training Dataset\")\n\n# Memberikan label pada sumbu x\nplt.xlabel(\"Class\")\n\n# Memberikan label pada sumbu y\nplt.ylabel(\"Jumlah Citra\")\n\n# Menampilkan grafik\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-21T06:35:01.443250Z","iopub.execute_input":"2026-08-21T06:35:01.443610Z","iopub.status.idle":"2026-08-21T06:35:01.657504Z","shell.execute_reply.started":"2026-08-21T06:35:01.443571Z","shell.execute_reply":"2026-08-21T06:35:01.656621Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ======== Persentase Distribusi Kelas Data Training ========\n\n# Menghitung persentase jumlah data pada setiap kelas\nclass_dist = (\n    train_df[\"Id\"]\n    .value_counts(normalize=True)\n    .sort_index()*100\n)\n\n# Menampilkan persentase distribusi setiap kelas\nprint(class_dist.round(2))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-21T06:35:01.658567Z","iopub.execute_input":"2026-08-21T06:35:01.658950Z","iopub.status.idle":"2026-08-21T06:35:01.665822Z","shell.execute_reply.started":"2026-08-21T06:35:01.658917Z","shell.execute_reply":"2026-08-21T06:35:01.665023Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ======== Visualisasi Contoh Citra Fundus Berdasarkan Kelas ========\n\n# Menentukan nama untuk setiap kelas retinopati diabetik\nlabel_name = {\n    0: \"Normal\",\n    1: \"Mild\",\n    2: \"Moderate\",\n    3: \"Severe\"\n}\n\n# Menentukan ukuran area visualisasi\nplt.figure(figsize=(12,8))\n\n# Melakukan iterasi pada setiap kelas yang terdapat dalam dataset\nfor i, cls in enumerate(sorted(train_df[\"Id\"].unique())):\n\n    # Mengambil satu citra sebagai contoh dari setiap kelas\n    sample = train_df[\n        train_df[\"Id\"] == cls\n    ].iloc[0]\n\n    # Menentukan lokasi file citra\n    img_path = os.path.join(\n        train_dir,\n        sample[\"Image\"]\n    )\n\n    # Membaca citra fundus\n    image = cv2.imread(img_path)\n\n    # Mengubah format warna citra dari BGR menjadi RGB\n    image = cv2.cvtColor(\n        image,\n        cv2.COLOR_BGR2RGB\n    )\n\n     # Menempatkan citra pada susunan 2 baris dan 2 kolom\n    plt.subplot(2,2,i+1)\n\n     # Menampilkan citra\n    plt.imshow(image)\n\n    # Memberikan nama kelas pada setiap citra\n    plt.title(label_name[cls])\n\n    # Menghilangkan sumbu koordinat\n    plt.axis(\"off\")\n\n# Mengatur tata letak agar setiap citra tidak saling bertumpuk\nplt.tight_layout()\n\n# Menampilkan seluruh citra\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-21T06:35:01.666965Z","iopub.execute_input":"2026-08-21T06:35:01.667682Z","iopub.status.idle":"2026-08-21T06:35:02.499898Z","shell.execute_reply.started":"2026-08-21T06:35:01.667641Z","shell.execute_reply":"2026-08-21T06:35:02.499131Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ======== Analisis Ukuran Citra Fundus ========\n\n# Menyiapkan list untuk menyimpan ukuran setiap citra\nsizes = []\n\n# Melakukan iterasi pada seluruh nama citra dalam dataset training\nfor img_name in train_df[\"Image\"]:\n\n    # Menentukan lokasi file citra\n    img_path = os.path.join(\n        train_dir,\n        img_name\n    )\n\n    # Membaca citra fundus\n    img = cv2.imread(img_path)\n\n    # Mengambil ukuran tinggi dan lebar citra\n    h, w = img.shape[:2]\n\n    # Menyimpan ukuran lebar dan tinggi citra\n    sizes.append((w,h))\n\n# Mengubah data ukuran citra menjadi DataFrame\nsizes = pd.DataFrame(\n    sizes,\n    columns=[\"Width\",\"Height\"]\n)\n\n# Menampilkan statistik deskriptif ukuran citra\nprint(sizes.describe())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-21T06:35:02.501518Z","iopub.execute_input":"2026-08-21T06:35:02.501859Z","iopub.status.idle":"2026-08-21T06:35:23.625534Z","shell.execute_reply.started":"2026-08-21T06:35:02.501826Z","shell.execute_reply":"2026-08-21T06:35:23.624438Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ======== Visualisasi Distribusi Ukuran Citra ========\n\n# Membuat area untuk menampilkan grafik\nplt.figure(figsize=(7,5))\n\n# Menampilkan hubungan antara lebar dan tinggi citra\nplt.scatter(\n    sizes[\"Width\"],\n    sizes[\"Height\"],\n    alpha=0.5\n)\n\n# Memberikan label pada sumbu horizontal\nplt.xlabel(\"Width\")\n\n# Memberikan label pada sumbu vertikal\nplt.ylabel(\"Height\")\n\n# Memberikan judul pada grafik\nplt.title(\"Distribusi Ukuran Citra\")\n\n# Menampilkan grafik\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-21T06:35:23.627068Z","iopub.execute_input":"2026-08-21T06:35:23.627980Z","iopub.status.idle":"2026-08-21T06:35:23.787866Z","shell.execute_reply.started":"2026-08-21T06:35:23.627949Z","shell.execute_reply":"2026-08-21T06:35:23.786972Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Modify","metadata":{}},{"cell_type":"code","source":"# ======== Persiapan Stratified 5 Fold Cross Validation ========\n\n# Mengimpor StratifiedKFold untuk pembagian data\n# training dan validation dengan mempertahankan proporsi kelas\nfrom sklearn.model_selection import StratifiedKFold\n\n# Menentukan jumlah fold yang digunakan\nNUM_FOLDS = 5\n\n# Membuat objek Stratified 5 Fold Cross Validation\nskf = StratifiedKFold(\n    n_splits=NUM_FOLDS,\n    shuffle=True,\n    random_state=42\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-21T06:35:23.788972Z","iopub.execute_input":"2026-08-21T06:35:23.789417Z","iopub.status.idle":"2026-08-21T06:35:23.794969Z","shell.execute_reply.started":"2026-08-21T06:35:23.789378Z","shell.execute_reply":"2026-08-21T06:35:23.794040Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ======== Random Oversampling Data Training ========\n\n# Membuat fungsi untuk melakukan Random Oversampling\n# pada data training berdasarkan kelas\ndef random_oversampling(df):\n\n    # Menentukan jumlah data dari kelas dengan jumlah terbanyak\n    max_count = df[\"Id\"].value_counts().max()\n\n    # Menyiapkan list untuk menyimpan data hasil oversampling\n    oversampled = []\n\n    # Melakukan proses oversampling pada setiap kelas\n    for cls in sorted(df[\"Id\"].unique()):\n\n        # Mengambil data berdasarkan kelas yang sedang diproses\n        cls_df = df[\n            df[\"Id\"] == cls\n        ]\n\n        # Melakukan pengambilan sampel secara acak dengan pengembalian\n        # hingga jumlah data setiap kelas sama dengan kelas terbanyak\n        cls_df_over = resample(\n\n            cls_df,\n\n            replace=True,\n\n            n_samples=max_count,\n\n            random_state=42\n\n        )\n\n         # Menambahkan hasil oversampling ke dalam list\n        oversampled.append(\n            cls_df_over\n        )\n\n     # Menggabungkan seluruh data dari setiap kelas\n    df_balanced = pd.concat(\n        oversampled\n    )\n\n    # Mengacak urutan data dan mengatur kembali indeks\n    df_balanced = df_balanced.sample(\n        frac=1,\n        random_state=42\n    ).reset_index(drop=True)\n    \n\n    # Mengembalikan dataset training yang telah diseimbangkan\n    return df_balanced","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-21T06:35:23.799100Z","iopub.execute_input":"2026-08-21T06:35:23.799482Z","iopub.status.idle":"2026-08-21T06:35:23.809448Z","shell.execute_reply.started":"2026-08-21T06:35:23.799456Z","shell.execute_reply":"2026-08-21T06:35:23.808672Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ======== Pembagian Data dan Penerapan Random Oversampling pada Training Fold ========\n\n# Membagi dataset training menjadi 5 fold secara stratified\n# dengan mempertahankan proporsi setiap kelas\nfor fold, (train_idx, val_idx) in enumerate(\n\n    skf.split(\n        train_df,\n        train_df[\"Id\"]\n    )\n\n):\n\n    # Mengambil data berdasarkan indeks untuk training fold\n    train_fold = train_df.iloc[train_idx]\n\n    # Mengambil data berdasarkan indeks untuk validation fold\n    val_fold = train_df.iloc[val_idx]\n\n    # Menyimpan salinan training fold sebelum Random Oversampling\n    train_fold_before_ros = train_fold.copy()\n\n    # Menampilkan informasi jumlah data pada setiap fold\n    print(\"=\" * 40)\n    print(f\"Fold {fold+1}\")\n    print(\"Training :\", train_fold.shape)\n    print(\"Validation :\", val_fold.shape)\n\n    # Menampilkan distribusi kelas sebelum Random Oversampling\n    print(\"\\nDistribusi sebelum Random Oversampling\")\n    print(train_fold_before_ros[\"Id\"].value_counts().sort_index())\n\n    # Menerapkan Random Oversampling hanya pada data training fold\n    train_fold = random_oversampling(train_fold)\n\n    # Menampilkan distribusi kelas setelah Random Oversampling\n    print(\"\\nDistribusi setelah Random Oversampling\")\n    print(train_fold[\"Id\"].value_counts().sort_index())\n\n    # Menghentikan proses setelah satu fold untuk melihat hasilnya\n    break","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-21T06:35:23.810465Z","iopub.execute_input":"2026-08-21T06:35:23.810827Z","iopub.status.idle":"2026-08-21T06:35:23.839424Z","shell.execute_reply.started":"2026-08-21T06:35:23.810792Z","shell.execute_reply":"2026-08-21T06:35:23.838523Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ======== Distribusi Data Training Sebelum Random Oversampling ========\n\n# Menentukan nama untuk setiap kelas\nlabel_name = {\n    0:\"Normal\",\n    1:\"Mild\",\n    2:\"Moderate\",\n    3:\"Severe\"\n}\n\n# Menghitung jumlah data pada setiap kelas sebelum Random Oversampling\ncounts = train_fold_before_ros[\"Id\"].value_counts().sort_index()\n\n# Membuat area untuk visualisasi\nplt.figure(figsize=(8,5))\n\n# Membuat grafik batang distribusi kelas\nbars = plt.bar(\n    [label_name[i] for i in counts.index],\n    counts.values\n)\n\n# Menampilkan jumlah data pada setiap batang\nfor bar in bars:\n    h = bar.get_height()\n    plt.text(\n        bar.get_x()+bar.get_width()/2,\n        h+3,\n        str(h),\n        ha=\"center\"\n    )\n\n# Memberikan judul pada grafik\nplt.title(\"Distribusi Data Training pada Fold Pertama Sebelum Random Oversampling\")\n\n# Memberikan label pada sumbu x\nplt.xlabel(\"Kelas\")\n\n# Memberikan label pada sumbu y\nplt.ylabel(\"Jumlah Citra\")\n\n# Menampilkan grafik\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-21T06:35:23.840434Z","iopub.execute_input":"2026-08-21T06:35:23.840719Z","iopub.status.idle":"2026-08-21T06:35:23.982517Z","shell.execute_reply.started":"2026-08-21T06:35:23.840692Z","shell.execute_reply":"2026-08-21T06:35:23.981431Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ======== Visualisasi Distribusi Training Fold Sebelum Random Oversampling =======\n\n# Menentukan nama untuk setiap kelas\nlabel_name = {\n    0: \"Normal\",\n    1: \"Mild\",\n    2: \"Moderate\",\n    3: \"Severe\"\n}\n\n# Menghitung jumlah data pada setiap kelas sebelum\n# Random Oversampling\ncounts = train_fold[\"Id\"].value_counts().sort_index()\n\n# Membuat area untuk visualisasi\nplt.figure(figsize=(8,5))\n\n# Membuat grafik batang distribusi kelas\nbars = plt.bar(\n    [label_name[i] for i in counts.index],\n    counts.values\n)\n\n# Menampilkan jumlah data di atas setiap batang\nfor bar in bars:\n    h = bar.get_height()\n    plt.text(\n        bar.get_x()+bar.get_width()/2,\n        h+3,\n        str(h),\n        ha=\"center\"\n    )\n\n# Memberikan judul pada grafik\nplt.title(\"Distribusi Data Training pada Fold Pertama Setelah Random Oversampling\")\n\n# Memberikan label pada sumbu x\nplt.xlabel(\"Kelas\")\n\n# Memberikan label pada sumbu y\nplt.ylabel(\"Jumlah Citra\")\n\n# Menampilkan grafik\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-21T06:35:23.983767Z","iopub.execute_input":"2026-08-21T06:35:23.984136Z","iopub.status.idle":"2026-08-21T06:35:24.117826Z","shell.execute_reply.started":"2026-08-21T06:35:23.984092Z","shell.execute_reply":"2026-08-21T06:35:24.116975Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ======= Pemeriksaan Distribusi Training Fold Setelah Random Oversampling ========\n\n# Menampilkan jumlah data pada setiap kelas setelah Random Oversampling\n\nprint(train_fold[\"Id\"].value_counts().sort_index())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-21T06:35:24.119822Z","iopub.execute_input":"2026-08-21T06:35:24.120125Z","iopub.status.idle":"2026-08-21T06:35:24.125917Z","shell.execute_reply.started":"2026-08-21T06:35:24.120102Z","shell.execute_reply":"2026-08-21T06:35:24.125011Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ======= Konfigurasi Parameter Dataset =======\n\n# Menentukan ukuran citra yang digunakan sebagai input model\nIMG_SIZE = 380\n\n# Menentukan jumlah citra dalam setiap batch\nBATCH_SIZE = 16\n\n# Menentukan jumlah kelas yang digunakan dalam klasifikasi\nNUM_CLASSES = 4\n\n# Mengaktifkan optimasi otomatis pada pipeline TensorFlow\nAUTOTUNE = tf.data.AUTOTUNE","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-21T06:35:24.126804Z","iopub.execute_input":"2026-08-21T06:35:24.127138Z","iopub.status.idle":"2026-08-21T06:35:24.141522Z","shell.execute_reply.started":"2026-08-21T06:35:24.127100Z","shell.execute_reply":"2026-08-21T06:35:24.140637Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ======== Preprocessing dan Pemuatan Citra =======\n\n# Membuat fungsi untuk membaca dan melakukan preprocessing citra\ndef load_image(path, label):\n\n    # Mengubah path dari TensorFlow menjadi string\n    path = path.numpy().decode(\"utf-8\")\n\n    # Membaca citra dari lokasi file\n    image = cv2.imread(path)\n\n    # Mengubah format warna citra dari BGR menjadi RGB\n    image = cv2.cvtColor(\n        image,\n        cv2.COLOR_BGR2RGB\n    )\n\n    # Mengubah ukuran citra menjadi 380 x 380 piksel\n    image = cv2.resize(\n        image,\n        (\n            IMG_SIZE,\n            IMG_SIZE\n        )\n    )\n\n     # Mengubah tipe data citra menjadi float32\n    image = image.astype(np.float32)\n\n    # Melakukan preprocessing citra sesuai dengan\n    # kebutuhan arsitektur EfficientNet\n    image = tf.keras.applications.efficientnet.preprocess_input(\n        image\n    )\n\n    # Mengubah label kelas menjadi bentuk one-hot encoding\n    label = np.eye(\n        NUM_CLASSES,\n        dtype=np.float32\n    )[int(label)]\n\n    # Mengembalikan citra dan label hasil preprocessing\n    return image, label","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-21T06:35:24.142565Z","iopub.execute_input":"2026-08-21T06:35:24.142869Z","iopub.status.idle":"2026-08-21T06:35:24.154929Z","shell.execute_reply.started":"2026-08-21T06:35:24.142833Z","shell.execute_reply":"2026-08-21T06:35:24.154118Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ======= Visualisasi Perubahan Ukuran Citra =======\n\n# Mengambil nama citra pertama dari dataset training\nsample_name = train_df.iloc[0][\"Image\"]\n\n# Menentukan lokasi file citra\nsample_path = os.path.join(\n    train_dir,\n    sample_name\n)\n\n# Membaca citra asli\nimage = cv2.imread(sample_path)\n\n# Mengubah format warna citra dari BGR menjadi RGB\nimage = cv2.cvtColor(\n    image,\n    cv2.COLOR_BGR2RGB\n)\n\n# Mengubah ukuran citra menjadi 380 x 380 piksel\nresize_image = cv2.resize(\n    image,\n    (\n        IMG_SIZE,\n        IMG_SIZE\n    )\n)\n\n# Membuat area untuk membandingkan citra asli dan citra hasil resize\nplt.figure(figsize=(10,5))\n\n# Menampilkan citra asli\nplt.subplot(1,2,1)\n\nplt.imshow(image)\n\n# Menampilkan ukuran asli citra pada judul\nplt.title(\n    f\"Original\\n{image.shape[1]} x {image.shape[0]}\"\n)\n\nplt.axis(\"off\")\n\n# Menampilkan citra setelah proses resize\nplt.subplot(1,2,2)\n\nplt.imshow(resize_image)\n\n# Menampilkan ukuran citra setelah resize\nplt.title(\n    f\"Resize\\n{IMG_SIZE} x {IMG_SIZE}\"\n)\n\nplt.axis(\"off\")\n\n# Mengatur tata letak visualisasi\nplt.tight_layout()\n\n# Menampilkan hasil visualisasi\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-21T06:35:24.155859Z","iopub.execute_input":"2026-08-21T06:35:24.156439Z","iopub.status.idle":"2026-08-21T06:35:24.581644Z","shell.execute_reply.started":"2026-08-21T06:35:24.156403Z","shell.execute_reply":"2026-08-21T06:35:24.581005Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ======= Integrasi Preprocessing Citra dengan TensorFlow Dataset =======\n\n# Membuat fungsi untuk mengintegrasikan proses preprocessing\n# citra ke dalam pipeline TensorFlow\ndef tf_load_image(path, label):\n\n    # Menjalankan fungsi load_image menggunakan tf.py_function\n    image, label = tf.py_function(\n\n        load_image,\n\n        [path, label],\n\n        [tf.float32, tf.float32]\n\n    )\n\n     # Menentukan bentuk tensor citra setelah preprocessing\n    image.set_shape(\n        (\n            IMG_SIZE,\n            IMG_SIZE,\n            3\n        )\n    )\n\n    # Menentukan bentuk tensor label sesuai jumlah kelas\n    label.set_shape(\n        (\n            NUM_CLASSES,\n        )\n    )\n\n    # Mengembalikan citra dan label yang telah diproses\n    return image, label","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-21T06:35:24.582688Z","iopub.execute_input":"2026-08-21T06:35:24.583037Z","iopub.status.idle":"2026-08-21T06:35:24.588234Z","shell.execute_reply.started":"2026-08-21T06:35:24.583008Z","shell.execute_reply":"2026-08-21T06:35:24.587663Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ======== Pembentukan Pipeline Dataset TensorFlow =======\n\n# Membuat fungsi untuk membentuk dataset TensorFlow\n# dari data citra dan label\ndef create_dataset(\n    df,\n    image_dir,\n    training=False\n):\n\n    # Membuat daftar path lengkap setiap citra\n    image_paths = [\n\n        os.path.join(\n            image_dir,\n            img\n        )\n\n        for img in df[\"Image\"]\n\n    ]\n\n    # Mengambil label kelas dan mengubahnya menjadi integer\n    labels = df[\"Id\"].values.astype(\n        np.int32\n    )\n\n    # Membuat TensorFlow Dataset dari path citra dan label\n    ds = tf.data.Dataset.from_tensor_slices(\n\n        (\n            image_paths,\n            labels\n        )\n\n    )\n\n    # Menerapkan fungsi preprocessing pada setiap citra\n    ds = ds.map(\n\n        tf_load_image,\n\n        num_parallel_calls=AUTOTUNE\n\n    )\n\n    # Mengacak data hanya ketika dataset digunakan untuk training\n    if training:\n\n        ds = ds.shuffle(\n\n            len(df),\n\n            seed=42\n\n        )\n\n    # Mengelompokkan data ke dalam batch\n    ds = ds.batch(\n        BATCH_SIZE\n    )\n\n     # Mengoptimalkan proses pemuatan data berikutnya\n    ds = ds.prefetch(\n        AUTOTUNE\n    )\n\n    # Mengembalikan dataset yang telah diproses\n    return ds","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-21T06:35:24.589142Z","iopub.execute_input":"2026-08-21T06:35:24.589463Z","iopub.status.idle":"2026-08-21T06:35:24.601906Z","shell.execute_reply.started":"2026-08-21T06:35:24.589423Z","shell.execute_reply":"2026-08-21T06:35:24.601079Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ======= Pembentukan Dataset Training dan Validation pada Setiap Fold =======\n\n# Melakukan pembagian dataset training menggunakan\n# Stratified 5 Fold Cross Validation\nfor fold, (train_idx, val_idx) in enumerate(\n\n    skf.split(\n        train_df,\n        train_df[\"Id\"]\n    )\n\n):\n\n    # Menampilkan pembatas untuk setiap fold\n    print(\"=\"*50)\n\n    # Menampilkan nomor fold yang sedang diproses\n    print(f\"Fold {fold+1}\")\n\n     # Mengambil data training berdasarkan indeks fold\n    train_fold = train_df.iloc[\n        train_idx\n    ]\n\n    # Mengambil data validation berdasarkan indeks fold\n    val_fold = train_df.iloc[\n        val_idx\n    ]\n\n     # Menerapkan Random Oversampling hanya pada data training\n    train_fold = random_oversampling(\n        train_fold\n    )\n\n     # Membentuk TensorFlow Dataset untuk data training\n    train_ds = create_dataset(\n\n        train_fold,\n\n        image_dir=train_dir,\n\n        training=True\n\n    )\n\n     # Membentuk TensorFlow Dataset untuk data validation\n    # tanpa menerapkan Random Oversampling\n    val_ds = create_dataset(\n\n        val_fold,\n\n        image_dir=train_dir,\n\n        training=False\n\n    )\n\n     # Menampilkan jumlah data training setelah oversampling\n    print(train_fold.shape)\n\n     # Menampilkan jumlah data validation\n    print(val_fold.shape)\n\n     # Menghentikan proses setelah satu fold\n    break","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-21T06:35:24.603008Z","iopub.execute_input":"2026-08-21T06:35:24.603478Z","iopub.status.idle":"2026-08-21T06:35:28.130851Z","shell.execute_reply.started":"2026-08-21T06:35:24.603444Z","shell.execute_reply":"2026-08-21T06:35:28.129912Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ====== Pembentukan Dataset Testing =======\n\n# Membentuk TensorFlow Dataset untuk data testing\n# tanpa melakukan pengacakan data\ntest_ds = create_dataset(\n    test_df,\n    image_dir=test_dir,\n    training=False\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-21T06:35:28.131977Z","iopub.execute_input":"2026-08-21T06:35:28.132402Z","iopub.status.idle":"2026-08-21T06:35:28.148933Z","shell.execute_reply.started":"2026-08-21T06:35:28.132355Z","shell.execute_reply":"2026-08-21T06:35:28.148409Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ======== Pemeriksaan Dataset Testing ========\n\n# Menampilkan struktur TensorFlow Dataset untuk data testing\nprint(test_ds)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-21T06:35:28.149855Z","iopub.execute_input":"2026-08-21T06:35:28.150076Z","iopub.status.idle":"2026-08-21T06:35:28.154451Z","shell.execute_reply.started":"2026-08-21T06:35:28.150036Z","shell.execute_reply":"2026-08-21T06:35:28.153439Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Model","metadata":{}},{"cell_type":"code","source":"# ======== Konfigurasi Focal Loss ========\n\n# Mengimpor TensorFlow\nimport tensorflow as tf\n\n# Menentukan fungsi Focal Loss untuk klasifikasi multiclass\nfocal_loss = tf.keras.losses.CategoricalFocalCrossentropy(\n    alpha=0.25,\n    gamma=2.0\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-21T06:35:28.155402Z","iopub.execute_input":"2026-08-21T06:35:28.155777Z","iopub.status.idle":"2026-08-21T06:35:28.168258Z","shell.execute_reply.started":"2026-08-21T06:35:28.155730Z","shell.execute_reply":"2026-08-21T06:35:28.167280Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ======== Pembangunan Model EfficientNetB4 dengan Transfer Learning ========\n\n# Mengimpor layer, Model, dan arsitektur EfficientNetB4\nfrom tensorflow.keras import layers\nfrom tensorflow.keras.models import Model\nfrom tensorflow.keras.applications import EfficientNetB4\n\n# Membuat fungsi untuk membangun arsitektur model\ndef build_model():\n\n    # Memuat EfficientNetB4 dengan bobot ImageNet\n    # tanpa menggunakan bagian klasifikasi bawaan\n    base_model = EfficientNetB4(\n        include_top=False,\n        weights=\"imagenet\",\n        input_shape=(IMG_SIZE, IMG_SIZE, 3)\n    )\n\n    # Membekukan bobot base model pada tahap transfer learning\n    base_model.trainable = False\n\n    # Menentukan bentuk input citra untuk model\n    inputs = layers.Input(\n        shape=(IMG_SIZE, IMG_SIZE,3)\n    )\n\n     # Memproses citra menggunakan base model\n    x = base_model(\n        inputs,\n        training=False\n    )\n\n    # Mengubah feature map menjadi satu vektor fitur\n    x = layers.GlobalAveragePooling2D()(x)\n\n    # Mengurangi risiko overfitting melalui dropout\n    x = layers.Dropout(0.3)(x)\n\n    # Membuat layer output dengan empat kelas klasifikasi\n    outputs = layers.Dense(\n        NUM_CLASSES,\n        activation=\"softmax\"\n    )(x)\n\n     # Menggabungkan input dan output menjadi sebuah model\n    model = Model(\n        inputs,\n        outputs\n    )\n\n    # Mengatur konfigurasi proses pelatihan model\n    model.compile(\n\n         # Menggunakan optimizer Adam dengan learning rate 0.0001\n        optimizer=tf.keras.optimizers.Adam(\n            learning_rate=1e-4\n        ),\n\n        # Menggunakan Focal Loss sebagai fungsi loss\n        loss=focal_loss,\n\n        # Menggunakan accuracy sebagai metrik pelatihan\n        metrics=[\n            \"accuracy\"\n        ]\n\n    )\n\n    # Mengembalikan model yang telah dikonfigurasi\n    return model","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-21T06:35:28.169242Z","iopub.execute_input":"2026-08-21T06:35:28.169911Z","iopub.status.idle":"2026-08-21T06:35:28.188104Z","shell.execute_reply.started":"2026-08-21T06:35:28.169869Z","shell.execute_reply":"2026-08-21T06:35:28.187510Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ======= Konfigurasi Callbacks Pelatihan Model ========\n\n# Mengimpor callback untuk mengatur proses pelatihan model\nfrom tensorflow.keras.callbacks import (\n    EarlyStopping,\n    ReduceLROnPlateau,\n    ModelCheckpoint\n)\n\n# Membuat fungsi callback berdasarkan nomor fold\ndef get_callbacks(fold):\n\n    # Menyiapkan daftar callback yang digunakan selama training\n    callbacks = [\n\n        # Menyimpan model dengan performa validation terbaik\n        ModelCheckpoint(\n\n        f\"best_model_fold{fold+1}.keras\",\n\n        monitor=\"val_accuracy\",\n\n        save_best_only=True,\n\n        mode=\"max\",\n\n        verbose=1\n\n),\n        # Menghentikan training jika validation accuracy\n        # tidak mengalami peningkatan dalam beberapa epoch\n        EarlyStopping(\n\n            monitor=\"val_accuracy\",\n\n            patience=6,\n\n            restore_best_weights=True,\n\n            verbose=1\n\n        ),\n\n        # Mengurangi learning rate ketika validation loss\n        # tidak mengalami perbaikan\n        ReduceLROnPlateau(\n\n            monitor=\"val_loss\",\n\n            factor=0.5,\n\n            patience=3,\n\n            verbose=1\n\n        )\n\n    ]\n\n    # Mengembalikan daftar callback\n    return callbacks","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-21T06:35:28.189026Z","iopub.execute_input":"2026-08-21T06:35:28.189290Z","iopub.status.idle":"2026-08-21T06:35:28.205304Z","shell.execute_reply.started":"2026-08-21T06:35:28.189267Z","shell.execute_reply":"2026-08-21T06:35:28.204412Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ======== Inisialisasi Penyimpanan Hasil Pelatihan Setiap Fold ========\n\n# Menyiapkan list untuk menyimpan nilai accuracy setiap fold\nfold_accuracy = []\n\n# Menyiapkan list untuk menyimpan nilai loss setiap fold\nfold_loss = []\n\n# Menyiapkan list untuk menyimpan model terbaik setiap fold\nbest_models = []\n\n# Menyiapkan list untuk menyimpan riwayat proses training\nhistories = []","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-21T06:35:28.206390Z","iopub.execute_input":"2026-08-21T06:35:28.206679Z","iopub.status.idle":"2026-08-21T06:35:28.219253Z","shell.execute_reply.started":"2026-08-21T06:35:28.206646Z","shell.execute_reply":"2026-08-21T06:35:28.218522Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ======== Pelatihan EfficientNetB4 dengan Stratified 5 Fold Cross Validation ========\n\nimport time # Mengimpor modul time untuk mengukur durasi pelatihan.\nwaktu_mulai = time.time() # Mencatat waktu saat pelatihan dimulai.\n\n# Melakukan pembagian dataset training menjadi 5 fold\n# dengan mempertahankan proporsi setiap kelas\nfor fold, (train_idx, val_idx) in enumerate(\n\n    skf.split(\n        train_df,\n        train_df[\"Id\"]\n    )\n\n):\n\n    # Menampilkan pembatas dan nomor fold yang sedang dilatih\n    print(\"=\"*60)\n\n    print(f\"TRAINING FOLD {fold+1}\")\n\n    # Mengambil data training berdasarkan indeks fold\n    train_fold = train_df.iloc[\n        train_idx\n    ]\n\n    # Mengambil data validation berdasarkan indeks fold\n    val_fold = train_df.iloc[\n        val_idx\n    ]\n\n    # Melakukan Random Oversampling hanya pada data training\n    train_fold = random_oversampling(\n        train_fold\n    )\n\n    # Membentuk dataset TensorFlow untuk training\n    train_ds = create_dataset(\n\n        train_fold,\n\n        image_dir=train_dir,\n\n        training=True\n\n    )\n\n    # Membentuk dataset TensorFlow untuk validation\n    # tanpa melakukan oversampling\n    val_ds = create_dataset(\n\n        val_fold,\n\n        image_dir=train_dir,\n\n        training=False\n\n    )\n\n    # Membangun model EfficientNetB4\n    model = build_model()\n\n    history = model.fit(\n\n        train_ds,\n\n        validation_data=val_ds,\n\n        epochs=30,\n\n        # Menggunakan callback untuk menyimpan model terbaik,\n        # menghentikan training, dan menyesuaikan learning rate\n        callbacks=get_callbacks(fold),\n\n        verbose=1\n\n    )\n\n    # Menyimpan riwayat proses training setiap fold\n    histories.append(history)\n\n     # Memuat kembali model terbaik yang telah disimpan\n    best_model = tf.keras.models.load_model(\n        f\"best_model_fold{fold+1}.keras\",\n\n        # Mendefinisikan fungsi loss yang digunakan saat training\n        custom_objects={\n        \"CategoricalFocalCrossentropy\":\n        tf.keras.losses.CategoricalFocalCrossentropy\n    }\n    )\n\n    # Mengevaluasi model terbaik pada data validation\n    score = best_model.evaluate(\n        val_ds,\n        verbose=0\n)\n\n    # Menyimpan nilai accuracy dari setiap fold\n    fold_accuracy.append(\n        score[1]\n    )\n\n    # Menyimpan nilai loss dari setiap fold\n    fold_loss.append(score[0])\n\n     # Menyimpan nama file model terbaik setiap fold\n    best_models.append(\n    f\"best_model_fold{fold+1}.keras\"\n)\n\n    # Memberikan jarak pada output\n    print()\n\n    # Menampilkan nilai accuracy validation setiap fold\n    print(f\"Fold {fold+1} Accuracy : {score[1]:.4f}\")\n\nselesai = time.time() # Mencatat waktu saat pelatihan selesai.\nprint(f\"\\nWaktu berlangsung : {round((selesai - waktu_mulai) / 60, 2)} menit\") # Mencetak total durasi pelatihan dalam menit.","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-21T06:35:28.220378Z","iopub.execute_input":"2026-08-21T06:35:28.220743Z","iopub.status.idle":"2026-08-21T07:13:54.114950Z","shell.execute_reply.started":"2026-08-21T06:35:28.220695Z","shell.execute_reply":"2026-08-21T07:13:54.114206Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Assess","metadata":{}},{"cell_type":"code","source":"# ======== Visualisasi Accuracy Training dan Validation Setiap Fold ========\n\n# Mengimpor library untuk membuat visualisasi\nimport matplotlib.pyplot as plt\n\n# Membuat area grafik\nplt.figure(figsize=(12,6))\n\n# Menampilkan riwayat training dari setiap fold\nfor i, history in enumerate(histories):\n\n    # Menampilkan accuracy pada data training\n    plt.plot(\n        history.history[\"accuracy\"],\n        label=f\"Train Fold {i+1}\"\n    )\n\n    # Menampilkan validation accuracy\n    # dengan garis putus-putus untuk membedakannya\n    plt.plot(\n        history.history[\"val_accuracy\"],\n        linestyle=\"--\",\n        label=f\"Validation Fold {i+1}\"\n    )\n\n# Memberikan judul grafik\nplt.title(\"Training and Validation Accuracy for Each Fold\")\n\n# Memberikan label sumbu X\nplt.xlabel(\"Epoch\")\n\n# Memberikan label sumbu Y\nplt.ylabel(\"Accuracy\")\n\n# Menampilkan legenda untuk setiap fold\nplt.legend(\n    bbox_to_anchor=(1.05,1),\n    loc=\"upper left\"\n)\n\n# Menampilkan grid untuk mempermudah pembacaan grafik\nplt.grid(True)\n\n# Mengatur tata letak grafik\nplt.tight_layout()\n\n# Menampilkan grafik\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-21T07:13:54.116013Z","iopub.execute_input":"2026-08-21T07:13:54.116317Z","iopub.status.idle":"2026-08-21T07:13:54.385765Z","shell.execute_reply.started":"2026-08-21T07:13:54.116292Z","shell.execute_reply":"2026-08-21T07:13:54.384904Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ======== Visualisasi Loss Training dan Validation Setiap Fold ========\n\n# Membuat area grafik\nplt.figure(figsize=(12,6))\n\n# Menampilkan riwayat training dari setiap fold\nfor i, history in enumerate(histories):\n\n    # Menampilkan loss pada data training\n    plt.plot(\n        history.history[\"loss\"],\n        label=f\"Train Fold {i+1}\"\n    )\n\n    # Menampilkan validation loss\n    # menggunakan garis putus-putus\n    plt.plot(\n        history.history[\"val_loss\"],\n        linestyle=\"--\",\n        label=f\"Validation Fold {i+1}\"\n    )\n\n# Memberikan judul grafik\nplt.title(\"Training and Validation Loss for Each Fold\")\n\n# Memberikan label sumbu X\nplt.xlabel(\"Epoch\")\n\n# Memberikan label sumbu Y\nplt.ylabel(\"Loss\")\n\n# Menampilkan legenda untuk setiap fold\nplt.legend(\n    bbox_to_anchor=(1.05,1),\n    loc=\"upper left\"\n)\n\n# Menampilkan grid pada grafik\nplt.grid(True)\n\n# Mengatur tata letak grafik\nplt.tight_layout()\n\n# Menampilkan grafik\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-21T07:13:54.386832Z","iopub.execute_input":"2026-08-21T07:13:54.387220Z","iopub.status.idle":"2026-08-21T07:13:54.657204Z","shell.execute_reply.started":"2026-08-21T07:13:54.387194Z","shell.execute_reply":"2026-08-21T07:13:54.656409Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ======= Analisis Rata Rata Accuracy pada 5 Fold ========\n\n# Mengimpor NumPy untuk pengolahan data numerik\nimport numpy as np\n\n# Menentukan jumlah epoch maksimum dari seluruh fold\nmax_epoch = max(\n    len(h.history[\"accuracy\"])\n    for h in histories\n)\n\n# Membuat array untuk menyimpan training accuracy\n# dari seluruh fold\ntrain_acc = np.full(\n    (len(histories), max_epoch),\n    np.nan\n)\n\n# Membuat array untuk menyimpan validation accuracy\n# dari seluruh fold\nval_acc = np.full(\n    (len(histories), max_epoch),\n    np.nan\n)\n\n# Memasukkan nilai accuracy dari setiap fold\nfor i, h in enumerate(histories):\n\n    # Menyimpan training accuracy setiap fold\n    train_acc[i,:len(h.history[\"accuracy\"])] = h.history[\"accuracy\"]\n\n     # Menyimpan validation accuracy setiap fold\n    val_acc[i,:len(h.history[\"val_accuracy\"])] = h.history[\"val_accuracy\"]\n\n# Menghitung rata rata training accuracy\n# pada setiap epoch\nmean_train_acc = np.nanmean(\n    train_acc,\n    axis=0\n)\n\n# Menghitung rata rata validation accuracy\n# pada setiap epoch\nmean_val_acc = np.nanmean(\n    val_acc,\n    axis=0\n)\n\n# Membuat visualisasi rata rata accuracy\nplt.figure(figsize=(10,6))\n\n# Menampilkan rata rata training accuracy\nplt.plot(\n    mean_train_acc,\n    linewidth=3,\n    label=\"Mean Training Accuracy\"\n)\n\n# Menampilkan rata rata validation accuracy\nplt.plot(\n    mean_val_acc,\n    linewidth=3,\n    linestyle=\"--\",\n    label=\"Mean Validation Accuracy\"\n)\n\n# Memberikan judul grafik\nplt.title(\"Mean Accuracy Across 5 Folds\")\n\n# Memberikan label sumbu X\nplt.xlabel(\"Epoch\")\n\n# Memberikan label sumbu Y\nplt.ylabel(\"Accuracy\")\n\n# Menampilkan legenda\nplt.legend()\n\n# Menampilkan grid\nplt.grid(True)\n\n# Mengatur tata letak grafik\nplt.tight_layout()\n\n# Menampilkan grafik\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-21T07:13:54.658321Z","iopub.execute_input":"2026-08-21T07:13:54.658645Z","iopub.status.idle":"2026-08-21T07:13:54.850955Z","shell.execute_reply.started":"2026-08-21T07:13:54.658621Z","shell.execute_reply":"2026-08-21T07:13:54.850032Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ======= Analisis Rata-Rata Loss pada 5 Fold ========\n\n# Membuat array untuk menyimpan training loss\n# dari seluruh fold\ntrain_loss = np.full(\n    (len(histories), max_epoch),\n    np.nan\n)\n\n# Membuat array untuk menyimpan validation loss\n# dari seluruh fold\nval_loss = np.full(\n    (len(histories), max_epoch),\n    np.nan\n)\n\n# Memasukkan nilai loss dari setiap fold\nfor i, h in enumerate(histories):\n\n    # Menyimpan training loss setiap fold\n    train_loss[i,:len(h.history[\"loss\"])] = h.history[\"loss\"]\n\n    # Menyimpan validation loss setiap fold\n    val_loss[i,:len(h.history[\"val_loss\"])] = h.history[\"val_loss\"]\n\n# Menghitung rata-rata training loss\n# pada setiap epoch\nmean_train_loss = np.nanmean(\n    train_loss,\n    axis=0\n)\n\n# Menghitung rata-rata validation loss\n# pada setiap epoch\nmean_val_loss = np.nanmean(\n    val_loss,\n    axis=0\n)\n\n# Membuat visualisasi rata-rata loss\nplt.figure(figsize=(10,6))\n\n# Menampilkan rata-rata training loss\nplt.plot(\n    mean_train_loss,\n    linewidth=3,\n    label=\"Mean Training Loss\"\n)\n\n# Menampilkan rata-rata validation loss\nplt.plot(\n    mean_val_loss,\n    linewidth=3,\n    linestyle=\"--\",\n    label=\"Mean Validation Loss\"\n)\n\n# Memberikan judul grafik\nplt.title(\"Mean Loss Across 5 Folds\")\n\n# Memberikan label sumbu X\nplt.xlabel(\"Epoch\")\n\n# Memberikan label sumbu Y\nplt.ylabel(\"Loss\")\n\n# Menampilkan legenda\nplt.legend()\n\n# Menampilkan grid\nplt.grid(True)\n\n# Mengatur tata letak grafik\nplt.tight_layout()\n\n# Menampilkan grafik\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-21T07:13:54.851980Z","iopub.execute_input":"2026-08-21T07:13:54.852335Z","iopub.status.idle":"2026-08-21T07:13:55.059011Z","shell.execute_reply.started":"2026-08-21T07:13:54.852310Z","shell.execute_reply":"2026-08-21T07:13:55.058113Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ======= Rekapitulasi Hasil Stratified 5 Fold Cross Validation =======\n\n# Mengimpor Pandas dan NumPy untuk pengolahan data\nimport pandas as pd\nimport numpy as np\n\n# Menampilkan judul hasil evaluasi\nprint(\"=\" * 60)\n\nprint(\"HASIL 5-FOLD CROSS VALIDATION\")\n\n# Menampilkan nilai accuracy dan loss pada setiap fold\nfor i in range(len(fold_accuracy)):\n\n    print(\n        f\"Fold {i+1}\"\n        f\" | Accuracy = {fold_accuracy[i]:.4f}\"\n        f\" | Loss = {fold_loss[i]:.4f}\"\n    )\n\n# Menampilkan pembatas hasil\nprint(\"=\" * 60)\n\n# Menghitung dan menampilkan rata-rata accuracy\nprint(\n    f\"Mean Accuracy : {np.mean(fold_accuracy):.4f}\"\n)\n\n# Menghitung dan menampilkan standar deviasi accuracy\nprint(\n    f\"Std Accuracy  : {np.std(fold_accuracy):.4f}\"\n)\n\n# Menghitung dan menampilkan rata-rata loss\nprint(\n    f\"Mean Loss     : {np.mean(fold_loss):.4f}\"\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-21T07:13:55.061004Z","iopub.execute_input":"2026-08-21T07:13:55.061699Z","iopub.status.idle":"2026-08-21T07:13:55.067652Z","shell.execute_reply.started":"2026-08-21T07:13:55.061670Z","shell.execute_reply":"2026-08-21T07:13:55.066616Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===== Penentuan Fold dengan Performa Terbaik =====\n\n# Menentukan indeks fold dengan nilai accuracy tertinggi\nbest_fold = np.argmax(fold_accuracy)\n\n# Menampilkan nomor fold dengan performa terbaik\nprint(\n    f\"Best Fold : Fold {best_fold+1}\"\n)\n\n# Menampilkan nilai accuracy dari fold terbaik\nprint(\n    f\"Accuracy : {fold_accuracy[best_fold]:.4f}\"\n)\n\n# Menampilkan nilai loss dari fold terbaik\nprint(\n    f\"Loss : {fold_loss[best_fold]:.4f}\"\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-21T07:13:55.068695Z","iopub.execute_input":"2026-08-21T07:13:55.069009Z","iopub.status.idle":"2026-08-21T07:13:55.083556Z","shell.execute_reply.started":"2026-08-21T07:13:55.068963Z","shell.execute_reply":"2026-08-21T07:13:55.082850Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===== Memuat Model Terbaik untuk Evaluasi =====\n\n# Memuat kembali model dari fold dengan validation accuracy tertinggi\nbest_model = tf.keras.models.load_model(\n\n    best_models[best_fold],\n\n       # Mendefinisikan fungsi loss yang digunakan saat model dibangun dan dilatih\n    custom_objects={\n\n        \"CategoricalFocalCrossentropy\":\n\n        tf.keras.losses.CategoricalFocalCrossentropy\n\n    }\n\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-21T07:13:55.084642Z","iopub.execute_input":"2026-08-21T07:13:55.085171Z","iopub.status.idle":"2026-08-21T07:13:57.644927Z","shell.execute_reply.started":"2026-08-21T07:13:55.085146Z","shell.execute_reply":"2026-08-21T07:13:57.644322Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===== Evaluasi Model Terbaik pada Data Testing =====\n\n# Mengevaluasi model terbaik menggunakan data testing\ntest_loss, test_accuracy = best_model.evaluate(\n\n    test_ds,\n\n    verbose=1\n\n)\n\n# Memberikan jarak sebelum menampilkan hasil\nprint()\n\n# Menampilkan pembatas hasil evaluasi\nprint(\"=\" * 60)\n\n# Menampilkan nilai accuracy pada data testing\nprint(f\"Test Accuracy : {test_accuracy:.4f}\")\n\n# Menampilkan nilai loss pada data testing\nprint(f\"Test Loss     : {test_loss:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-21T07:13:57.645788Z","iopub.execute_input":"2026-08-21T07:13:57.646087Z","iopub.status.idle":"2026-08-21T07:14:17.583563Z","shell.execute_reply.started":"2026-08-21T07:13:57.646063Z","shell.execute_reply":"2026-08-21T07:14:17.582704Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===== Prediksi Kelas pada Data Testing =====\n\n# Mengimpor NumPy untuk pengolahan array hasil prediksi\nimport numpy as np\n\n# Menyiapkan list untuk menyimpan label sebenarnya\ny_true = []\n\n# Menyiapkan list untuk menyimpan hasil prediksi model\ny_pred = []\n\n# Melakukan prediksi pada setiap batch data testing\nfor images, labels in test_ds:\n\n    # Menghasilkan probabilitas prediksi dari model terbaik\n    pred = best_model.predict(\n\n        images,\n\n        verbose=0\n\n    )\n\n     # Mengambil indeks kelas dengan probabilitas tertinggi sebagai label sebenarnya\n    y_true.extend(\n\n        np.argmax(\n\n            labels,\n\n            axis=1\n\n        )\n\n    )\n\n    # Mengambil indeks kelas dengan probabilitas tertinggi sebagai hasil prediksi model\n    y_pred.extend(\n\n        np.argmax(\n\n            pred,\n\n            axis=1\n\n        )\n\n    )\n\n# Mengubah label sebenarnya menjadi array NumPy\ny_true = np.array(y_true)\n\n# Mengubah hasil prediksi menjadi array NumPy\ny_pred = np.array(y_pred)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-21T07:14:17.584692Z","iopub.execute_input":"2026-08-21T07:14:17.585039Z","iopub.status.idle":"2026-08-21T07:14:35.163108Z","shell.execute_reply.started":"2026-08-21T07:14:17.585015Z","shell.execute_reply":"2026-08-21T07:14:35.162436Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===== Classification Report Model EfficientNetB4 =====\n\n# Mengimpor classification_report untuk menghitung metrik evaluasi berdasarkan hasil prediksi model\nfrom sklearn.metrics import classification_report\n\n# Menampilkan classification report berdasarkan label sebenarnya dan hasil prediksi\nprint(\n\n    classification_report(\n\n        y_true,\n\n        y_pred,\n\n        # Menampilkan hasil metrik dengan 4 angka desimal\n        digits=4\n\n    )\n\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-21T07:14:35.164331Z","iopub.execute_input":"2026-08-21T07:14:35.164716Z","iopub.status.idle":"2026-08-21T07:14:35.179418Z","shell.execute_reply.started":"2026-08-21T07:14:35.164688Z","shell.execute_reply":"2026-08-21T07:14:35.178498Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===== Perhitungan Accuracy pada Data Testing =====\n\n# Mengimpor fungsi accuracy_score\nfrom sklearn.metrics import accuracy_score\n\n# Menghitung accuracy berdasarkan label sebenarnya dan hasil prediksi model\nacc = accuracy_score(\n\n    y_true,\n\n    y_pred\n\n)\n\n# Memberikan jarak sebelum menampilkan hasil\nprint()\n\n# Menampilkan nilai accuracy dengan 4 angka desimal\nprint(\n\n    f\"Accuracy : {acc:.4f}\"\n\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-21T07:14:35.180526Z","iopub.execute_input":"2026-08-21T07:14:35.180933Z","iopub.status.idle":"2026-08-21T07:14:35.191973Z","shell.execute_reply.started":"2026-08-21T07:14:35.180908Z","shell.execute_reply":"2026-08-21T07:14:35.191205Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ====== Perhitungan Precision, Recall, dan F1-Score ======\n\n# Mengimpor metrik evaluasi precision, recall, dan F1-score\nfrom sklearn.metrics import (\n\n    precision_score,\n\n    recall_score,\n\n    f1_score\n\n)\n\n# Menghitung precision dengan rata-rata weighted\nprecision = precision_score(\n\n    y_true,\n\n    y_pred,\n\n    average=\"weighted\"\n\n)\n\n# Menghitung recall dengan rata-rata weighted\nrecall = recall_score(\n\n    y_true,\n\n    y_pred,\n\n    average=\"weighted\"\n\n)\n\n# Menghitung F1-score dengan rata-rata weighted\nf1 = f1_score(\n\n    y_true,\n\n    y_pred,\n\n    average=\"weighted\"\n\n)\n\n# Memberikan jarak sebelum menampilkan hasil\nprint()\n\n# Menampilkan nilai precision\nprint(\n\n    f\"Precision : {precision:.4f}\"\n\n)\n\n# Menampilkan nilai recall\nprint(\n\n    f\"Recall    : {recall:.4f}\"\n\n)\n\n# Menampilkan nilai F1-score\nprint(\n\n    f\"F1 Score  : {f1:.4f}\"\n\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-21T07:14:35.193112Z","iopub.execute_input":"2026-08-21T07:14:35.193712Z","iopub.status.idle":"2026-08-21T07:14:35.213226Z","shell.execute_reply.started":"2026-08-21T07:14:35.193683Z","shell.execute_reply":"2026-08-21T07:14:35.212492Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===== Visualisasi Confusion Matrix EfficientNetB4 =====\n\n# Mengimpor confusion_matrix untuk menghitung\n# jumlah prediksi benar dan salah pada setiap kelas\nfrom sklearn.metrics import confusion_matrix\n\n# Mengimpor Seaborn untuk membuat visualisasi heatmap\nimport seaborn as sns\n\n# Mengimpor Matplotlib untuk menampilkan grafik\nimport matplotlib.pyplot as plt\n\n# Menentukan nama empat kelas retinopati diabetik\nlabels = [\n    \"Normal\",\n    \"Mild\",\n    \"Moderate\",\n    \"Severe\"\n]\n\n# Menghitung confusion matrix berdasarkan\n# label aktual dan hasil prediksi\ncm = confusion_matrix(\n    y_true,\n    y_pred\n)\n\n# Membuat area untuk visualisasi confusion matrix\nplt.figure(figsize=(7,6))\n\n# Menampilkan confusion matrix dalam bentuk heatmap\nsns.heatmap(\n    cm,\n\n    # Menampilkan nilai jumlah data pada setiap sel\n    annot=True,\n\n    # Menampilkan nilai dalam bentuk bilangan bulat\n    fmt=\"d\",\n\n    # Menggunakan gradasi warna untuk menunjukkan jumlah data\n    cmap=\"Blues\",\n\n     # Memberikan nama kelas pada sumbu X\n    xticklabels=labels,\n\n    # Memberikan nama kelas pada sumbu Y\n    yticklabels=labels\n)\n\n# Memberikan label pada sumbu X\nplt.xlabel(\"Prediksi\")\n\n# Memberikan label pada sumbu Y\nplt.ylabel(\"Aktual\")\n\n# Memberikan judul confusion matrix\nplt.title(\"Confusion Matrix EfficientNetB4\")\n\n# Mengatur tata letak agar elemen grafik tidak terpotong\nplt.tight_layout()\n\n# Menampilkan confusion matrix\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-21T07:14:35.214447Z","iopub.execute_input":"2026-08-21T07:14:35.214762Z","iopub.status.idle":"2026-08-21T07:14:35.430979Z","shell.execute_reply.started":"2026-08-21T07:14:35.214727Z","shell.execute_reply":"2026-08-21T07:14:35.430184Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===== Menghasilkan Probabilitas Prediksi pada Data Testing =====\n\n# Mengimpor NumPy untuk melakukan operasi\n# penggabungan dan pengolahan array\nimport numpy as np\n\n# Menyiapkan list untuk menyimpan label aktual\n# dari seluruh data testing\ny_true = []\n\n# Menyiapkan list untuk menyimpan probabilitas prediksi\n# dari seluruh data testing\ny_prob = []\n\n# Melakukan iterasi pada seluruh batch data testing\nfor images, labels in test_ds:\n\n    # Menghasilkan probabilitas prediksi dari model terbaik\n    # berdasarkan gambar pada setiap batch\n    pred = best_model.predict(\n        images,\n        verbose=0\n    )\n\n    # Menyimpan label aktual dari setiap batch\n    # ke dalam list y_true\n    y_true.append(labels.numpy())\n\n    # Menyimpan hasil probabilitas prediksi dari setiap batch\n    # ke dalam list y_prob\n    y_prob.append(pred)\n\n# Menggabungkan seluruh label aktual dari setiap batch\n# menjadi satu array\ny_true = np.concatenate(\n    y_true,\n    axis=0\n)\n\n# Menggabungkan seluruh probabilitas prediksi dari setiap batch\n# menjadi satu array\ny_prob = np.concatenate(\n    y_prob,\n    axis=0\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-21T07:14:35.431862Z","iopub.execute_input":"2026-08-21T07:14:35.432191Z","iopub.status.idle":"2026-08-21T07:14:40.055685Z","shell.execute_reply.started":"2026-08-21T07:14:35.432153Z","shell.execute_reply":"2026-08-21T07:14:40.055012Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===== Menghitung ROC Curve dan AUC Setiap Kelas =====\n\n# Mengimpor roc_curve untuk menghitung nilai\n# False Positive Rate (FPR) dan True Positive Rate (TPR)\n# serta auc untuk menghitung luas area di bawah kurva ROC\nfrom sklearn.metrics import (\n    roc_curve,\n    auc\n)\n\n# Menyiapkan dictionary untuk menyimpan\n# nilai False Positive Rate (FPR) setiap kelas\nfpr = {}\n\n# Menyiapkan dictionary untuk menyimpan\n# nilai True Positive Rate (TPR) setiap kelas\ntpr = {}\n\n# Menyiapkan dictionary untuk menyimpan\n# nilai Area Under the Curve (AUC) setiap kelas\nroc_auc = {}\n\n# Melakukan perulangan untuk setiap kelas\n# berdasarkan jumlah kelas yang telah ditentukan\nfor i in range(NUM_CLASSES):\n\n    # Menghitung nilai False Positive Rate (FPR),\n    # True Positive Rate (TPR), dan threshold\n    # berdasarkan label aktual dan probabilitas prediksi\n    fpr[i], tpr[i], _ = roc_curve(\n\n        y_true[:, i],\n\n        y_prob[:, i]\n\n    )\n\n    # Menghitung nilai AUC berdasarkan\n    # FPR dan TPR pada setiap kelas\n    roc_auc[i] = auc(\n\n        fpr[i],\n\n        tpr[i]\n\n    )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-21T07:14:40.056683Z","iopub.execute_input":"2026-08-21T07:14:40.057002Z","iopub.status.idle":"2026-08-21T07:14:40.070883Z","shell.execute_reply.started":"2026-08-21T07:14:40.056972Z","shell.execute_reply":"2026-08-21T07:14:40.070041Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===== Menghitung Macro AUC =====\n\n# Menghitung rata-rata nilai AUC dari seluruh kelas\n# untuk mendapatkan nilai Macro AUC\nmacro_auc = np.mean(\n\n    list(roc_auc.values())\n\n)\n\n# Menampilkan nilai Macro AUC\n# dengan empat angka di belakang koma\nprint()\n\nprint(f\"Macro AUC : {macro_auc:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-21T07:14:40.076391Z","iopub.execute_input":"2026-08-21T07:14:40.076683Z","iopub.status.idle":"2026-08-21T07:14:40.081537Z","shell.execute_reply.started":"2026-08-21T07:14:40.076659Z","shell.execute_reply":"2026-08-21T07:14:40.080847Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===== Visualisasi ROC Curve Multiclass =====\n\n# Mengimpor Matplotlib untuk membuat\n# dan menampilkan grafik ROC Curve\nimport matplotlib.pyplot as plt\n\n# Menentukan nama empat kelas retinopati diabetik\nclass_names = [\n\n    \"Normal\",\n\n    \"Mild\",\n\n    \"Moderate\",\n\n    \"Severe\"\n\n]\n\n# Membuat area untuk visualisasi ROC Curve\nplt.figure(figsize=(8,6))\n\n# Melakukan perulangan untuk menampilkan\n# ROC Curve dari setiap kelas\nfor i in range(NUM_CLASSES):\n\n    # Menggambar ROC Curve berdasarkan\n    # nilai False Positive Rate (FPR) dan\n    # True Positive Rate (TPR) setiap kelas\n    plt.plot(\n\n        fpr[i],\n\n        tpr[i],\n\n         # Menentukan ketebalan garis ROC Curve\n        lw=2,\n\n        # Menampilkan nama kelas dan nilai AUC\n        # pada legenda grafik\n        label=f\"{class_names[i]} (AUC={roc_auc[i]:.3f})\"\n\n    )\n\n# Menampilkan garis diagonal sebagai garis referensi\n# yang menunjukkan performa klasifikasi secara acak\nplt.plot(\n\n    [0,1],\n\n    [0,1],\n\n    \"k--\"\n\n)\n\n# Mengatur batas nilai pada sumbu X\nplt.xlim([0,1])\n\n# Mengatur batas nilai pada sumbu Y\nplt.ylim([0,1.05])\n\n# Memberikan label pada sumbu X\n# yaitu False Positive Rate\nplt.xlabel(\"False Positive Rate\")\n\n# Memberikan label pada sumbu Y\n# yaitu True Positive Rate\nplt.ylabel(\"True Positive Rate\")\n\n# Memberikan judul pada grafik ROC Curve\nplt.title(\"ROC Curve Multiclass\")\n\n# Menampilkan keterangan nama kelas\n# dan nilai AUC pada grafik\nplt.legend()\n\n# Menampilkan grid untuk mempermudah\n# pembacaan nilai pada grafik\nplt.grid(True)\n\n# Menampilkan grafik ROC Curve\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-21T07:14:40.082484Z","iopub.execute_input":"2026-08-21T07:14:40.082727Z","iopub.status.idle":"2026-08-21T07:14:40.258318Z","shell.execute_reply.started":"2026-08-21T07:14:40.082705Z","shell.execute_reply":"2026-08-21T07:14:40.257680Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===== Menampilkan Nilai AUC Setiap Kelas dan Macro Average AUC ====\n\n# Menampilkan garis pembatas untuk memperjelas\n# bagian hasil evaluasi\nprint(\"=\"*50)\n\n# Melakukan perulangan untuk menampilkan\n# nilai AUC dari setiap kelas\nfor i in range(NUM_CLASSES):\n\n     # Menampilkan nama kelas dan nilai AUC\n    # dengan empat angka di belakang koma\n    print(\n\n        f\"{class_names[i]} : {roc_auc[i]:.4f}\"\n\n    )\n\n# Menampilkan kembali garis pembatas\n# setelah daftar nilai AUC setiap kelas\nprint(\"=\"*50)\n\n# Menampilkan nilai rata-rata AUC\n# dari seluruh kelas atau Macro Average AUC\nprint(\n\n    f\"Macro Average AUC : {macro_auc:.4f}\"\n\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-21T07:14:40.259203Z","iopub.execute_input":"2026-08-21T07:14:40.259512Z","iopub.status.idle":"2026-08-21T07:14:40.265288Z","shell.execute_reply.started":"2026-08-21T07:14:40.259480Z","shell.execute_reply":"2026-08-21T07:14:40.264374Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===== Visualisasi Accuracy Curve Setiap Fold =====\n\n# Mengimpor Matplotlib untuk membuat\n# dan menampilkan grafik accuracy\nimport matplotlib.pyplot as plt\n\n# Melakukan perulangan pada seluruh history\n# hasil proses training setiap fold\nfor i, history in enumerate(histories):\n\n    # Membuat area untuk visualisasi\n    # accuracy training dan validation\n    plt.figure(figsize=(8,5))\n\n    # Menampilkan nilai akurasi pada data training\n    # untuk setiap epoch\n    plt.plot(\n        history.history[\"accuracy\"],\n        label=\"Training Accuracy\"\n    )\n\n     # Menampilkan nilai akurasi pada data validation\n    # untuk setiap epoch\n    plt.plot(\n        history.history[\"val_accuracy\"],\n        label=\"Validation Accuracy\"\n    )\n\n    # Memberikan judul grafik berdasarkan\n    # nomor fold yang sedang ditampilkan\n    plt.title(\n        f\"Accuracy Curve - Fold {i+1}\"\n    )\n\n    # Memberikan label pada sumbu X\n    # yang menunjukkan jumlah epoch\n    plt.xlabel(\"Epoch\")\n\n    # Memberikan label pada sumbu Y\n    # yang menunjukkan nilai accuracy\n    plt.ylabel(\"Accuracy\")\n\n    # Menampilkan keterangan untuk membedakan\n    # training accuracy dan validation accuracy\n    plt.legend()\n\n    # Menampilkan grid untuk mempermudah\n    # pembacaan nilai pada grafik\n    plt.grid(True)\n\n    # Menampilkan grafik accuracy\n    plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-21T07:14:40.266228Z","iopub.execute_input":"2026-08-21T07:14:40.266601Z","iopub.status.idle":"2026-08-21T07:14:41.013142Z","shell.execute_reply.started":"2026-08-21T07:14:40.266568Z","shell.execute_reply":"2026-08-21T07:14:41.012329Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===== Visualisasi Loss Curve Setiap Fold ======\n\n# Mengimpor Matplotlib untuk membuat\n# dan menampilkan grafik loss\nimport matplotlib.pyplot as plt\n\n# Melakukan perulangan pada seluruh history\n# hasil proses training setiap fold\nfor i, history in enumerate(histories):\n\n     # Membuat area untuk visualisasi\n    # loss training dan validation\n    plt.figure(figsize=(8,5))\n\n     # Menampilkan nilai loss pada data training\n    # untuk setiap epoch\n    plt.plot(\n        history.history[\"loss\"],\n        label=\"Training Loss\"\n    )\n\n    # Menampilkan nilai loss pada data validation\n    # untuk setiap epoch\n    plt.plot(\n        history.history[\"val_loss\"],\n        label=\"Validation Loss\"\n    )\n\n    # Memberikan judul grafik berdasarkan\n    # nomor fold yang sedang ditampilkan\n    plt.title(\n        f\"Loss Curve - Fold {i+1}\"\n    )\n\n    # Memberikan label pada sumbu X\n    # yang menunjukkan jumlah epoch\n    plt.xlabel(\"Epoch\")\n\n    # Memberikan label pada sumbu Y\n    # yang menunjukkan nilai loss\n    plt.ylabel(\"Loss\")\n\n    # Menampilkan keterangan untuk membedakan\n    # training loss dan validation loss\n    plt.legend()\n\n    # Menampilkan grid untuk mempermudah\n    # pembacaan nilai pada grafik\n    plt.grid(True)\n\n     # Menampilkan grafik loss\n    plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-21T07:14:41.014196Z","iopub.execute_input":"2026-08-21T07:14:41.014656Z","iopub.status.idle":"2026-08-21T07:14:41.779187Z","shell.execute_reply.started":"2026-08-21T07:14:41.014608Z","shell.execute_reply":"2026-08-21T07:14:41.778568Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===== Visualisasi Accuracy dan Loss Setiap Fold =====\n\n# Melakukan perulangan pada seluruh history\n# hasil proses training setiap fold\nfor i, history in enumerate(histories):\n\n    # Membuat satu figure dengan dua area grafik\n    # untuk menampilkan accuracy dan loss\n    fig, ax = plt.subplots(\n        1,\n        2,\n        figsize=(14,5)\n    )\n\n    # Menampilkan nilai accuracy pada data training\n    # untuk setiap epoch\n    ax[0].plot(\n        history.history[\"accuracy\"],\n        label=\"Training\"\n    )\n\n    # Menampilkan nilai accuracy pada data validation\n    # untuk setiap epoch\n    ax[0].plot(\n        history.history[\"val_accuracy\"],\n        label=\"Validation\"\n    )\n\n    # Memberikan judul grafik accuracy\n    # berdasarkan nomor fold\n    ax[0].set_title(\n        f\"Accuracy Fold {i+1}\"\n    )\n\n    # Memberikan label pada sumbu X\n    # yang menunjukkan jumlah epoch\n    ax[0].set_xlabel(\"Epoch\")\n\n    # Memberikan label pada sumbu Y\n    # yang menunjukkan nilai accuracy\n    ax[0].set_ylabel(\"Accuracy\")\n\n    # Menampilkan keterangan untuk membedakan\n    # training dan validation accuracy\n    ax[0].legend()\n\n    # Menampilkan grid pada grafik accuracy\n    # untuk mempermudah pembacaan grafik\n    ax[0].grid(True)\n\n    # Menampilkan nilai loss pada data training\n    # untuk setiap epoch\n    ax[1].plot(\n        history.history[\"loss\"],\n        label=\"Training\"\n    )\n\n    # Menampilkan nilai loss pada data validation\n    # untuk setiap epoch\n    ax[1].plot(\n        history.history[\"val_loss\"],\n        label=\"Validation\"\n    )\n\n     # Memberikan judul grafik loss\n    # berdasarkan nomor fold\n    ax[1].set_title(\n        f\"Loss Fold {i+1}\"\n    )\n\n    # Memberikan label pada sumbu X\n    # yang menunjukkan jumlah epoch\n    ax[1].set_xlabel(\"Epoch\")\n\n    # Memberikan label pada sumbu Y\n    # yang menunjukkan nilai loss\n    ax[1].set_ylabel(\"Loss\")\n\n    # Menampilkan keterangan untuk membedakan\n    # training dan validation loss\n    ax[1].legend()\n\n    # Menampilkan grid pada grafik loss\n    # untuk mempermudah pembacaan grafik\n    ax[1].grid(True)\n\n    # Mengatur tata letak kedua grafik\n    # agar tidak saling bertumpuk atau terpotong\n    plt.tight_layout()\n\n    # Menampilkan grafik accuracy dan loss\n    plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-21T07:14:41.780206Z","iopub.execute_input":"2026-08-21T07:14:41.780554Z","iopub.status.idle":"2026-08-21T07:14:43.431557Z","shell.execute_reply.started":"2026-08-21T07:14:41.780513Z","shell.execute_reply":"2026-08-21T07:14:43.430608Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===== Visualisasi Hasil Prediksi pada Data Testing =====\n\n# Mengimpor Matplotlib untuk menampilkan citra\n# dan NumPy untuk melakukan operasi pada array\nimport matplotlib.pyplot as plt\nimport numpy as np\n\n# Menentukan nama empat kelas retinopati diabetik\nclass_names = [\n    \"Normal\",\n    \"Mild\",\n    \"Moderate\",\n    \"Severe\"\n]\n\n# Mengambil satu batch citra beserta label\n# dari dataset testing\nimages, labels = next(iter(test_ds))\n\n# Melakukan prediksi menggunakan model terbaik\n# pada citra dari dataset testing\npredictions = best_model.predict(\n    images,\n    verbose=0\n)\n\n# Menentukan kelas hasil prediksi berdasarkan\n# nilai probabilitas tertinggi\npred_labels = np.argmax(\n    predictions,\n    axis=1\n)\n\n# Menentukan kelas aktual berdasarkan\n# label dengan nilai tertinggi\ntrue_labels = np.argmax(\n    labels.numpy(),\n    axis=1\n)\n\n# Mengambil nilai probabilitas tertinggi\n# sebagai confidence dari setiap prediksi\nconfidences = np.max(\n    predictions,\n    axis=1\n)\n\n# Menentukan jumlah citra yang akan ditampilkan\n# dengan maksimal sebanyak 9 citra\nnum_images = min(9, len(images))\n\n# Membuat area untuk menampilkan\n# seluruh citra hasil prediksi\nplt.figure(figsize=(15, 12))\n\n# Melakukan perulangan untuk setiap citra\n# yang akan ditampilkan\nfor i in range(num_images):\n\n    # Membuat posisi subplot dalam\n    # susunan 3 baris dan 3 kolom\n    plt.subplot(3,3,i+1)\n\n     # Mengambil citra dari batch dan mengubahnya\n    # menjadi array NumPy\n    img = images[i].numpy()\n\n    # Melakukan normalisasi nilai piksel\n    # agar citra dapat ditampilkan dengan baik\n    img = (img - img.min()) / (img.max() - img.min())\n\n     # Menampilkan citra\n    plt.imshow(img)\n\n    # Mengambil nama kelas aktual berdasarkan\n    # indeks label sebenarnya\n    actual = class_names[true_labels[i]]\n\n     # Mengambil nama kelas berdasarkan\n    # hasil prediksi model\n    pred = class_names[pred_labels[i]]\n\n    # Mengubah nilai confidence menjadi\n    # bentuk persentase\n    conf = confidences[i] * 100\n\n    # Memeriksa apakah hasil prediksi\n    # sesuai dengan label aktual\n    if true_labels[i] == pred_labels[i]:\n\n        # Menggunakan warna hijau untuk\n        # menunjukkan prediksi yang benar\n        color = \"green\"\n\n         # Menentukan status prediksi sebagai benar\n        status = \"✓ Benar\"\n    else:\n\n        # Menggunakan warna merah untuk\n        # menunjukkan prediksi yang salah\n        color = \"red\"\n\n        # Menentukan status prediksi sebagai salah\n        status = \"✗ Salah\"\n\n    # Menampilkan status, kelas aktual,\n    # kelas prediksi, dan nilai confidence\n    plt.title(\n        f\"{status}\\n\"\n        f\"Actual : {actual}\\n\"\n        f\"Prediction : {pred}\\n\"\n        f\"Confidence : {conf:.2f}%\",\n        fontsize=9,\n        color=color\n    )\n\n    # Menghilangkan sumbu pada setiap citra\n    # agar tampilan lebih bersih\n    plt.axis(\"off\")\n\n# Mengatur tata letak agar seluruh elemen grafik\n# tidak saling bertumpuk atau terpotong\nplt.tight_layout()\n\n# Menampilkan seluruh citra beserta\n# hasil prediksinya\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-21T07:14:43.432597Z","iopub.execute_input":"2026-08-21T07:14:43.432904Z","iopub.status.idle":"2026-08-21T07:14:45.073067Z","shell.execute_reply.started":"2026-08-21T07:14:43.432866Z","shell.execute_reply":"2026-08-21T07:14:45.072089Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===== Mengimpor Library untuk Pengolahan dan Visualisasi Citra =====\n\n# Mengimpor OpenCV untuk melakukan\n# pengolahan dan manipulasi citra\nimport cv2\n\n# Mengimpor NumPy untuk melakukan operasi\n# pada data dan array citra\nimport numpy as np\n\n# Mengimpor Matplotlib untuk menampilkan\n# dan memvisualisasikan citra\nimport matplotlib.pyplot as plt","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-21T07:14:45.074095Z","iopub.execute_input":"2026-08-21T07:14:45.074523Z","iopub.status.idle":"2026-08-21T07:14:45.078482Z","shell.execute_reply.started":"2026-08-21T07:14:45.074495Z","shell.execute_reply":"2026-08-21T07:14:45.077755Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===== Menentukan Nama Kelas Retinopati Diabetik ====\n\n# Menentukan nama empat kelas retinopati diabetik\n# yang digunakan dalam proses klasifikasi\nclass_names = [\n\n    \"Normal\",\n\n    \"Mild\",\n\n    \"Moderate\",\n\n    \"Severe\"\n\n]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-21T07:14:45.079534Z","iopub.execute_input":"2026-08-21T07:14:45.079858Z","iopub.status.idle":"2026-08-21T07:14:45.092933Z","shell.execute_reply.started":"2026-08-21T07:14:45.079836Z","shell.execute_reply":"2026-08-21T07:14:45.092103Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===== Fungsi Preprocessing Satu Citra untuk Prediksi =====\n\n# Mendefinisikan fungsi untuk melakukan preprocessing\n# pada satu citra sebelum digunakan oleh model\ndef preprocess_single_image(image_path):\n\n    # Membaca citra dari lokasi file yang diberikan\n    image = cv2.imread(image_path)\n\n    # Mengubah format warna citra dari BGR\n    # menjadi RGB agar sesuai dengan format yang digunakan\n    # pada proses visualisasi dan model\n    image = cv2.cvtColor(\n        image,\n        cv2.COLOR_BGR2RGB\n    )\n\n    # Mengubah ukuran citra sesuai dengan ukuran input\n    # yang dibutuhkan oleh model\n    image = cv2.resize(\n        image,\n        (IMG_SIZE, IMG_SIZE)\n    )\n\n    # Mengubah tipe data citra menjadi float32\n    # agar sesuai dengan kebutuhan proses preprocessing\n    image = image.astype(np.float32)\n\n    # Melakukan preprocessing citra menggunakan\n    # preprocessing input dari arsitektur EfficientNet\n    image = tf.keras.applications.efficientnet.preprocess_input(\n        image\n    )\n\n    # Menambahkan dimensi batch pada citra\n    # sehingga memiliki format yang sesuai untuk prediksi model\n    image = np.expand_dims(\n        image,\n        axis=0\n    )\n\n     # Mengembalikan citra yang telah melalui\n    # seluruh tahapan preprocessing\n    return image","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-21T07:14:45.093896Z","iopub.execute_input":"2026-08-21T07:14:45.094305Z","iopub.status.idle":"2026-08-21T07:14:45.106885Z","shell.execute_reply.started":"2026-08-21T07:14:45.094260Z","shell.execute_reply":"2026-08-21T07:14:45.106066Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===== Melakukan Prediksi pada Satu Citra =====\n\n# Menentukan lokasi file citra yang akan digunakan\n# sebagai data input untuk proses prediksi\nimage_path = \"/kaggle/input/datasets/asepshantikaramadhan/fundus4/proliferative1.tif\"\n\n# Melakukan preprocessing pada citra\n# agar sesuai dengan format input model\nimage = preprocess_single_image(\n    image_path\n)\n\n# Melakukan prediksi menggunakan model terbaik\n# untuk memperoleh probabilitas setiap kelas\nprobability = best_model.predict(\n    image,\n    verbose=0\n)\n\n# Menentukan indeks kelas dengan probabilitas\n# prediksi paling tinggi\npredicted_class = np.argmax(\n    probability\n)\n\n# Mengambil nilai probabilitas tertinggi\n# sebagai tingkat confidence prediksi\nconfidence = np.max(\n    probability\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-21T07:14:45.107943Z","iopub.execute_input":"2026-08-21T07:14:45.108155Z","iopub.status.idle":"2026-08-21T07:15:07.065156Z","shell.execute_reply.started":"2026-08-21T07:14:45.108133Z","shell.execute_reply":"2026-08-21T07:15:07.064452Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===== Visualisasi Citra dan Hasil Prediksi =====\n\n# Membaca kembali citra asli dari lokasi file\n# untuk ditampilkan dalam bentuk visual\noriginal = cv2.imread(image_path)\n\n# Mengubah format warna citra dari BGR\n# menjadi RGB agar dapat ditampilkan dengan benar\noriginal = cv2.cvtColor(\n    original,\n    cv2.COLOR_BGR2RGB\n)\n\n# Membuat area untuk menampilkan citra hasil prediksi\nplt.figure(figsize=(6,6))\n\n# Menampilkan citra asli\nplt.imshow(original)\n\n# Menghilangkan sumbu pada visualisasi\n# agar tampilan citra lebih bersih\nplt.axis(\"off\")\n\n# Menampilkan kelas hasil prediksi dan\n# tingkat confidence dari model\nplt.title(\n\n    f\"Prediction : {class_names[predicted_class]}\\n\"\n\n    f\"Confidence : {confidence*100:.2f}%\"\n\n)\n\n# Menampilkan citra beserta hasil prediksinya\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-21T07:15:07.066319Z","iopub.execute_input":"2026-08-21T07:15:07.066659Z","iopub.status.idle":"2026-08-21T07:15:07.319852Z","shell.execute_reply.started":"2026-08-21T07:15:07.066630Z","shell.execute_reply":"2026-08-21T07:15:07.319106Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===== Menampilkan Probabilitas Prediksi Setiap Kelas =====\n\n# Menampilkan garis pembatas sebagai pemisah\n# bagian hasil probabilitas prediksi\nprint(\"=\"*50)\n\n# Menampilkan judul hasil probabilitas prediksi\nprint(\"Prediction Probability\")\n\n# Menampilkan garis pembatas\nprint(\"=\"*50)\n\n# Melakukan perulangan untuk setiap kelas\n# untuk menampilkan probabilitas hasil prediksi\nfor i, cls in enumerate(class_names):\n\n    # Menampilkan nama kelas dan probabilitas prediksi\n    # dalam bentuk persentase dengan dua angka desimal\n    print(\n\n        f\"{cls:<15} : {probability[0][i]*100:.2f}%\"\n\n    )\n\n# Menampilkan garis pembatas setelah\n# seluruh probabilitas kelas ditampilkan\nprint(\"=\"*50)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-21T07:15:07.320879Z","iopub.execute_input":"2026-08-21T07:15:07.321656Z","iopub.status.idle":"2026-08-21T07:15:07.327383Z","shell.execute_reply.started":"2026-08-21T07:15:07.321628Z","shell.execute_reply":"2026-08-21T07:15:07.326444Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===== Visualisasi Probabilitas Prediksi Setiap Kelas =====\n\n# Membuat area untuk visualisasi\n# probabilitas prediksi setiap kelas\nplt.figure(figsize=(7,4))\n\n# Membuat grafik batang berdasarkan\n# nama kelas dan nilai probabilitas prediksi\nbars = plt.bar(\n\n    class_names,\n\n    probability[0]\n\n)\n\n# Mengatur batas nilai pada sumbu Y\n# dari 0 hingga 1\nplt.ylim(0,1)\n\n# Memberikan label pada sumbu Y\n# yang menunjukkan nilai probabilitas\nplt.ylabel(\"Probability\")\n\n# Memberikan judul pada grafik\nplt.title(\"Prediction Probability\")\n\n# Menambahkan nilai probabilitas\n# pada bagian atas setiap batang\nfor bar, value in zip(bars, probability[0]):\n\n    # Menampilkan nilai probabilitas\n    # dalam bentuk persentase\n    plt.text(\n\n        bar.get_x()+bar.get_width()/2,\n\n        value,\n\n        f\"{value*100:.1f}%\",\n\n        ha=\"center\",\n\n        va=\"bottom\"\n\n    )\n\n# Menampilkan grafik probabilitas prediksi\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-21T07:15:07.328565Z","iopub.execute_input":"2026-08-21T07:15:07.328978Z","iopub.status.idle":"2026-08-21T07:15:07.451851Z","shell.execute_reply.started":"2026-08-21T07:15:07.328939Z","shell.execute_reply":"2026-08-21T07:15:07.451216Z"}},"outputs":[],"execution_count":null}]}