{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":21154,"databundleVersionId":1243559,"sourceType":"competition"}],"dockerImageVersionId":31192,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-12-03T14:08:40.904064Z","iopub.execute_input":"2025-12-03T14:08:40.904842Z","iopub.status.idle":"2025-12-03T14:08:40.94981Z","shell.execute_reply.started":"2025-12-03T14:08:40.904818Z","shell.execute_reply":"2025-12-03T14:08:40.949063Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 1. Import Library yang Diperlukan","metadata":{}},{"cell_type":"code","source":"# Fix untuk kompatibilitas Protobuf di Kaggle\nimport os\nos.environ['PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION'] = 'python'\n\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nimport re\nimport math\n\n# TensorFlow dan Keras untuk MLP\nimport tensorflow as tf\nfrom tensorflow import keras\nfrom tensorflow.keras import layers, models, callbacks\nfrom tensorflow.keras.preprocessing import image\n\n# Scikit-learn untuk evaluasi dan preprocessing\nfrom sklearn.model_selection import train_test_split, KFold\nfrom sklearn.preprocessing import LabelEncoder, StandardScaler\nfrom sklearn.metrics import (f1_score, accuracy_score, classification_report, \n                             confusion_matrix, precision_score, recall_score, \n                             roc_auc_score)\n\nimport warnings\nwarnings.filterwarnings('ignore')\n\nprint(\"=\" * 60)\nprint(\"LIBRARY IMPORT BERHASIL!\")\nprint(\"=\" * 60)\nprint(f\"TensorFlow version: {tf.__version__}\")\nprint(f\"NumPy version: {np.__version__}\")\nprint(f\"Pandas version: {pd.__version__}\")\nprint(\"=\" * 60)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-03T14:08:40.95123Z","iopub.execute_input":"2025-12-03T14:08:40.951572Z","iopub.status.idle":"2025-12-03T14:08:40.958353Z","shell.execute_reply.started":"2025-12-03T14:08:40.951553Z","shell.execute_reply":"2025-12-03T14:08:40.957715Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 2. Setup TPU (Opsional untuk Kaggle)\n\nDeteksi dan inisialisasi TPU jika tersedia di environment Kaggle. Jika tidak, akan menggunakan GPU/CPU.","metadata":{}},{"cell_type":"code","source":"import os\n\nprint(\"🔍 Mencari akselerator terbaik...\")\nprint(f\"TPU_NAME environment: {os.environ.get('TPU_NAME', 'Not set')}\")\nprint(f\"COLAB_TPU_ADDR: {os.environ.get('COLAB_TPU_ADDR', 'Not set')}\")\n\n# Cek GPU yang tersedia\ngpus = tf.config.list_physical_devices('GPU')\nprint(f\"\\n🎮 GPU tersedia: {len(gpus)} device(s)\")\nif gpus:\n    for i, gpu in enumerate(gpus):\n        print(f\"  [{i}] {gpu.name}\")\n        # Get GPU details jika bisa\n        try:\n            gpu_details = tf.config.experimental.get_device_details(gpu)\n            if 'device_name' in gpu_details:\n                print(f\"      Model: {gpu_details['device_name']}\")\n        except:\n            pass\n\n# PRIORITAS 1: Coba koneksi TPU terlebih dahulu\ntpu_available = False\nprint(f\"\\n{'='*60}\")\nprint(\"PRIORITAS 1: Mencoba koneksi ke TPU...\")\nprint(f\"{'='*60}\")\n\ntry:\n    # Coba beberapa metode untuk menemukan TPU\n    tpu_address = None\n    \n    # Method 1: Environment variable TPU_NAME\n    if os.environ.get('TPU_NAME'):\n        tpu_address = os.environ['TPU_NAME']\n        print(f\"✓ Method 1 - TPU_NAME: {tpu_address}\")\n    \n    # Method 2: COLAB_TPU_ADDR (untuk compatibility)\n    elif os.environ.get('COLAB_TPU_ADDR'):\n        tpu_address = os.environ['COLAB_TPU_ADDR']\n        print(f\"✓ Method 2 - COLAB_TPU_ADDR: {tpu_address}\")\n    \n    # Method 3: Auto-detect dengan resolver\n    else:\n        print(\"⚡ Method 3 - Auto-detect TPU...\")\n        resolver = tf.distribute.cluster_resolver.TPUClusterResolver()\n        tpu_address = resolver.get_master()\n        print(f\"✓ Auto-detected: {tpu_address}\")\n    \n    # Jika alamat ditemukan, coba koneksi\n    if tpu_address and tpu_address != '':\n        resolver = tf.distribute.cluster_resolver.TPUClusterResolver(tpu=tpu_address)\n        tf.config.experimental_connect_to_cluster(resolver)\n        tf.tpu.experimental.initialize_tpu_system(resolver)\n        \n        print(f'✅ TPU berhasil terkoneksi!')\n        print(f'   Workers: {resolver.cluster_spec().as_dict()[\"worker\"]}')\n        \n        # Buat TPU Strategy\n        strategy = tf.distribute.TPUStrategy(resolver)\n        tpu_available = True\n        print(f\"✅ TPU Strategy aktif dengan {strategy.num_replicas_in_sync} replicas\")\n    else:\n        raise ValueError(\"TPU address kosong atau tidak valid\")\n    \nexcept Exception as e:\n    print(f\"❌ TPU tidak tersedia: {str(e)[:250]}\")\n    tpu_available = False\n\n# PRIORITAS 2: Jika TPU gagal, gunakan GPU T4 x2 atau fallback lainnya\nif not tpu_available:\n    print(f\"\\n{'='*60}\")\n    print(\"PRIORITAS 2: Fallback ke GPU Strategy...\")\n    print(f\"{'='*60}\")\n    \n    if len(gpus) >= 2:\n        # Multi-GPU dengan MirroredStrategy (ideal untuk T4 x2)\n        print(f\"✅ Menggunakan MirroredStrategy dengan {len(gpus)} GPU\")\n        print(f\"   Tipe: Multi-GPU parallelization\")\n        print(f\"   Kecepatan: ~{len(gpus)}x faster than single GPU\")\n        strategy = tf.distribute.MirroredStrategy()\n        \n        # Print detail setiap GPU\n        print(f\"\\n   GPU yang akan digunakan:\")\n        for i, gpu in enumerate(gpus):\n            print(f\"   - GPU {i}: {gpu.name}\")\n    \n    elif len(gpus) == 1:\n        # Single GPU\n        print(\"✅ Menggunakan Single GPU Strategy\")\n        print(f\"   GPU: {gpus[0].name}\")\n        strategy = tf.distribute.get_strategy()\n    \n    else:\n        # CPU fallback\n        print(\"⚠️  Tidak ada GPU/TPU tersedia\")\n        print(\"   Menggunakan CPU (akan sangat lambat untuk dataset besar)\")\n        strategy = tf.distribute.get_strategy()\n\n# Summary\nprint(f\"\\n{'='*60}\")\nprint(\"📊 KONFIGURASI AKHIR:\")\nprint(f\"{'='*60}\")\nprint(f\"Strategy Type: {strategy.__class__.__name__}\")\nprint(f\"Replicas/Devices: {strategy.num_replicas_in_sync}\")\n\nif tpu_available:\n    print(f\"Accelerator: TPU (8 cores)\")\n    print(f\"Kecepatan estimasi: ~3-5 menit untuk 20 epochs\")\nelif len(gpus) >= 2:\n    print(f\"Accelerator: Multi-GPU ({len(gpus)}x GPU)\")\n    print(f\"Kecepatan estimasi: ~5-8 menit untuk 20 epochs\")\nelif len(gpus) == 1:\n    print(f\"Accelerator: Single GPU\")\n    print(f\"Kecepatan estimasi: ~10-15 menit untuk 20 epochs\")\nelse:\n    print(f\"Accelerator: CPU\")\n    print(f\"Kecepatan estimasi: ~2-4 jam untuk 20 epochs (tidak disarankan)\")\n\nprint(f\"{'='*60}\\n\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-03T14:08:40.959228Z","iopub.execute_input":"2025-12-03T14:08:40.959539Z","iopub.status.idle":"2025-12-03T14:08:40.991491Z","shell.execute_reply.started":"2025-12-03T14:08:40.959521Z","shell.execute_reply":"2025-12-03T14:08:40.990652Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 3. Definisi Hyperparameter (Tuning Dasar)\n\nHyperparameter didefinisikan di awal untuk memudahkan tuning dan eksperimen.","metadata":{}},{"cell_type":"code","source":"# ============================================================================\n# HYPERPARAMETERS - Didefinisikan di awal untuk mudah tuning\n# ============================================================================\n\n# Ukuran gambar\nIMAGE_SIZE = [192, 192]  # Resolusi gambar input\nIMG_CHANNELS = 3  # RGB channels\nFLAT_DIM = IMAGE_SIZE[0] * IMAGE_SIZE[1] * IMG_CHANNELS  # 192*192*3 = 110,592 fitur\n\n# Jumlah kelas\nNUM_CLASSES = 104  # 104 jenis bunga\n\n# Training parameters\nBASE_BATCH_SIZE = 128  # Base batch size per replica\nBATCH_SIZE = BASE_BATCH_SIZE * strategy.num_replicas_in_sync  # Total batch size (global)\nEPOCHS = 10  # Maksimal epoch dengan early stopping\nLEARNING_RATE = 0.001  # Initial learning rate untuk Adam optimizer\nDROPOUT_RATE = 0.5  # Dropout rate untuk regularisasi\n\n# MLP Architecture\nHIDDEN_LAYERS = [2048, 1024, 512, 256]  # Neuron di setiap hidden layer\nACTIVATION = 'relu'  # Activation function untuk hidden layers\n\n# Validation\nVALIDATION_SPLIT = 0.2  # 20% data untuk validasi\nK_FOLDS = 5  # Jumlah folds untuk cross-validation (opsional)\n\nprint(\"=\" * 60)\nprint(\"HYPERPARAMETERS CONFIGURATION\")\nprint(\"=\" * 60)\nprint(f\"Image Size: {IMAGE_SIZE[0]}x{IMAGE_SIZE[1]}x{IMG_CHANNELS}\")\nprint(f\"Flattened Dimension: {FLAT_DIM}\")\nprint(f\"Base Batch Size (per replica): {BASE_BATCH_SIZE}\")\nprint(f\"Global Batch Size: {BATCH_SIZE}\")\nprint(f\"Learning Rate: {LEARNING_RATE}\")\nprint(f\"Dropout Rate: {DROPOUT_RATE}\")\nprint(f\"Hidden Layers: {HIDDEN_LAYERS}\")\nprint(f\"Activation Function: {ACTIVATION}\")\n\nprint(\"=\" * 60)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-03T14:08:41.065847Z","iopub.execute_input":"2025-12-03T14:08:41.066354Z","iopub.status.idle":"2025-12-03T14:08:41.073136Z","shell.execute_reply.started":"2025-12-03T14:08:41.06633Z","shell.execute_reply":"2025-12-03T14:08:41.072288Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 4. Preprocessing & Feature Engineering\n\nImplementasi preprocessing yang tepat untuk gambar:\n1. **Normalisasi**: Scaling pixel values ke range [0, 1]\n2. **Standardisasi**: StandardScaler untuk mean=0, std=1\n3. **Flattening**: Ubah gambar 3D menjadi 1D vector untuk input MLP","metadata":{}},{"cell_type":"code","source":"# Konfigurasi path dataset\nTRAIN_DIR = './train'  # Folder berisi gambar training\nTEST_DIR = './test'    # Folder berisi gambar test\n\n# Untuk Kaggle TPU competition\nGCS_PATH = '/kaggle/input/tpu-getting-started/tfrecords-jpeg-192x192'\nAUTOTUNE = tf.data.AUTOTUNE\n\nprint(f\"Konfigurasi Path:\")\nprint(f\"- Training Directory: {TRAIN_DIR}\")\nprint(f\"- Test Directory: {TEST_DIR}\")\nprint(f\"- Image Size: {IMAGE_SIZE[0]}x{IMAGE_SIZE[1]}\")\nprint(f\"- Channels: {IMG_CHANNELS}\")\nprint(f\"- Flattened Features: {FLAT_DIM}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-03T14:08:41.074343Z","iopub.execute_input":"2025-12-03T14:08:41.074592Z","iopub.status.idle":"2025-12-03T14:08:41.09202Z","shell.execute_reply.started":"2025-12-03T14:08:41.074576Z","shell.execute_reply":"2025-12-03T14:08:41.091304Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def load_and_preprocess_image(img_path, img_size=IMAGE_SIZE):\n    \"\"\"\n    Memuat dan memproses gambar dengan normalisasi dan flattening.\n    \n    Parameters:\n    -----------\n    img_path : str\n        Path ke file gambar\n    img_size : list\n        Ukuran target untuk resize gambar\n        \n    Returns:\n    --------\n    img_array : numpy array\n        Array gambar yang sudah diproses dan di-flatten\n    \"\"\"\n    try:\n        # Baca gambar\n        img = image.load_img(img_path, target_size=(img_size[0], img_size[1]))\n        \n        # Konversi ke array\n        img_array = image.img_to_array(img)\n        \n        # Normalisasi pixel values ke range [0, 1]\n        img_array = img_array / 255.0\n        \n        # Flatten array (ubah dari 3D ke 1D untuk MLP)\n        img_array = img_array.flatten()\n        \n        return img_array\n    except Exception as e:\n        print(f\"Error loading image {img_path}: {e}\")\n        return None\n\n# Fungsi untuk memuat data dari TFRecords (Kaggle TPU format)\ndef decode_image_tfrecord(image_data):\n    \"\"\"Decode JPEG dan normalize pixel ke [0,1].\"\"\"\n    image = tf.image.decode_jpeg(image_data, channels=3)\n    image = tf.cast(image, tf.float32) / 255.0\n    image = tf.reshape(image, [*IMAGE_SIZE, 3])\n    return image\n\ndef read_labeled_tfrecord(example):\n    \"\"\"Parse TFRecord untuk train/val (ada label).\"\"\"\n    LABELED_TFREC_FORMAT = {\n        \"image\": tf.io.FixedLenFeature([], tf.string),\n        \"class\": tf.io.FixedLenFeature([], tf.int64),\n    }\n    example = tf.io.parse_single_example(example, LABELED_TFREC_FORMAT)\n    image = decode_image_tfrecord(example['image'])\n    label = tf.cast(example['class'], tf.int32)\n    label = tf.one_hot(label, NUM_CLASSES)\n    image = tf.reshape(image, [FLAT_DIM])  # Flatten untuk MLP\n    return image, label\n\ndef read_unlabeled_tfrecord(example):\n    \"\"\"Parse TFRecord untuk test (tanpa label).\"\"\"\n    UNLABELED_TFREC_FORMAT = {\n        \"image\": tf.io.FixedLenFeature([], tf.string),\n        \"id\": tf.io.FixedLenFeature([], tf.string),\n    }\n    example = tf.io.parse_single_example(example, UNLABELED_TFREC_FORMAT)\n    image = decode_image_tfrecord(example['image'])\n    idnum = example['id']\n    image = tf.reshape(image, [FLAT_DIM])\n    return image, idnum\n\ndef load_tfrecord_dataset(filenames, labeled=True, ordered=False):\n    \"\"\"Buat tf.data.Dataset dari TFRecord files.\"\"\"\n    ignore_order = tf.data.Options()\n    if not ordered:\n        ignore_order.experimental_deterministic = False\n    dataset = tf.data.TFRecordDataset(filenames, num_parallel_reads=AUTOTUNE)\n    dataset = dataset.with_options(ignore_order)\n    dataset = dataset.map(\n        read_labeled_tfrecord if labeled else read_unlabeled_tfrecord,\n        num_parallel_calls=AUTOTUNE\n    )\n    return dataset\n\ndef count_data_items(filenames):\n    \"\"\"Hitung jumlah data dari nama file TFRecord.\"\"\"\n    n = [int(re.compile(r\"-([0-9]*)\\.\").search(filename).group(1)) \n         for filename in filenames]\n    return np.sum(n)\n\ndef load_dataset(data_dir, img_size=IMAGE_SIZE, max_samples=None):\n    \"\"\"\n    Memuat dataset gambar dari direktori lokal.\n    \n    Parameters:\n    -----------\n    data_dir : str\n        Path ke direktori data\n    img_size : list\n        Ukuran gambar\n    max_samples : int or None\n        Maksimal jumlah sampel yang dimuat\n        \n    Returns:\n    --------\n    X : numpy array\n        Fitur gambar\n    y : numpy array\n        Label\n    filenames : list\n        Nama file gambar\n    \"\"\"\n    X = []\n    y = []\n    filenames = []\n    \n    if not os.path.exists(data_dir):\n        print(f\"Warning: Direktori {data_dir} tidak ditemukan!\")\n        return np.array(X), np.array(y), filenames\n    \n    class_names = sorted([d for d in os.listdir(data_dir) \n                         if os.path.isdir(os.path.join(data_dir, d))])\n    \n    print(f\"Memuat data dari {data_dir}...\")\n    print(f\"Jumlah kelas: {len(class_names)}\")\n    \n    sample_count = 0\n    for class_idx, class_name in enumerate(class_names):\n        class_dir = os.path.join(data_dir, class_name)\n        image_files = [f for f in os.listdir(class_dir) \n                      if f.lower().endswith(('.png', '.jpg', '.jpeg'))]\n        \n        for img_file in image_files:\n            if max_samples and sample_count >= max_samples:\n                break\n                \n            img_path = os.path.join(class_dir, img_file)\n            img_array = load_and_preprocess_image(img_path, img_size)\n            \n            if img_array is not None:\n                X.append(img_array)\n                y.append(class_name)\n                filenames.append(img_file)\n                sample_count += 1\n        \n        if max_samples and sample_count >= max_samples:\n            break\n        \n        if (class_idx + 1) % 10 == 0:\n            print(f\"Processed {class_idx + 1}/{len(class_names)} kelas...\")\n    \n    print(f\"Total gambar dimuat: {len(X)}\")\n    \n    return np.array(X), np.array(y), filenames\n\nprint(\"✅ Fungsi preprocessing berhasil didefinisikan!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-03T14:08:41.092884Z","iopub.execute_input":"2025-12-03T14:08:41.093202Z","iopub.status.idle":"2025-12-03T14:08:41.116012Z","shell.execute_reply.started":"2025-12-03T14:08:41.093186Z","shell.execute_reply":"2025-12-03T14:08:41.115308Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 4.1 Memuat Data Training\n\nMemuat data dari TFRecords (Kaggle) atau dari folder lokal.","metadata":{}},{"cell_type":"code","source":"# Coba muat dari TFRecords (Kaggle environment)\ntry:\n    if os.path.exists(GCS_PATH):\n        print(\"📁 Dataset TFRecords ditemukan (Kaggle environment)\")\n        \n        TRAIN_FILES = tf.io.gfile.glob(GCS_PATH + '/train/*.tfrec')\n        VAL_FILES = tf.io.gfile.glob(GCS_PATH + '/val/*.tfrec')\n        TEST_FILES = tf.io.gfile.glob(GCS_PATH + '/test/*.tfrec')\n        \n        COUNT_TRAIN = count_data_items(TRAIN_FILES)\n        COUNT_VAL = count_data_items(VAL_FILES)\n        COUNT_TEST = count_data_items(TEST_FILES)\n        \n        print(f\"✅ Train samples: {COUNT_TRAIN}\")\n        print(f\"✅ Validation samples: {COUNT_VAL}\")\n        print(f\"✅ Test samples: {COUNT_TEST}\")\n        \n        # Buat datasets dengan drop_remainder untuk consistent batch size\n        train_dataset = load_tfrecord_dataset(TRAIN_FILES, labeled=True)\\\n            .shuffle(2048).repeat().batch(BASE_BATCH_SIZE, drop_remainder=True).prefetch(AUTOTUNE)\n        val_dataset = load_tfrecord_dataset(VAL_FILES, labeled=True)\\\n            .batch(BASE_BATCH_SIZE, drop_remainder=True).cache().prefetch(AUTOTUNE)\n        test_dataset = load_tfrecord_dataset(TEST_FILES, labeled=False, ordered=True)\\\n            .batch(BASE_BATCH_SIZE, drop_remainder=False).prefetch(AUTOTUNE)\n        \n        # Calculate steps with proper batch size per replica\n        STEPS_PER_EPOCH = COUNT_TRAIN // BATCH_SIZE\n        VALIDATION_STEPS = COUNT_VAL // BATCH_SIZE\n        \n        print(f\"\\n📊 Training Configuration:\")\n        print(f\"   - Base Batch Size (per replica): {BASE_BATCH_SIZE}\")\n        print(f\"   - Global Batch Size: {BATCH_SIZE}\")\n        print(f\"   - Steps per Epoch: {STEPS_PER_EPOCH}\")\n        print(f\"   - Validation Steps: {VALIDATION_STEPS}\")\n        \n        USE_TFRECORDS = True\n    else:\n        raise FileNotFoundError\n        \nexcept:\n    print(\"⚠️  Dataset TFRecords tidak ditemukan\")\n    print(\"📁 Mencoba memuat dari folder lokal...\")\n    \n    # Cek apakah dataset lokal ada\n    if os.path.exists(TRAIN_DIR):\n        print(f\"✅ Dataset lokal ditemukan di {TRAIN_DIR}\")\n        # Batasi sampel untuk demo (hapus max_samples untuk load semua)\n        X_train_full, y_train_full, train_filenames = load_dataset(TRAIN_DIR, max_samples=1000)\n        USE_TFRECORDS = False\n    else:\n        print(\"⚠️  Dataset lokal tidak ditemukan\")\n        print(\"🔧 Membuat dataset sintetis untuk demonstrasi...\")\n        \n        # Buat dataset sintetis\n        np.random.seed(42)\n        n_samples = 2000\n        n_classes = NUM_CLASSES\n        \n        # Generate data acak dengan distribusi yang realistis\n        X_train_full = np.random.rand(n_samples, FLAT_DIM)\n        y_train_full = np.array([f'class_{i % n_classes}' for i in range(n_samples)])\n        \n        print(f\"✅ Dataset sintetis: {n_samples} sampel, {n_classes} kelas\")\n        USE_TFRECORDS = False\n    \n    print(f\"- Contoh label: {np.unique(y_train_full)[:5]}\")\n    print(f\"- Jumlah kelas unik: {len(np.unique(y_train_full))}\")\n    \n    print(f\"\\n📊 Data Summary:\")\n    print(f\"- Shape label: {y_train_full.shape}\")\n    print(f\"- Shape data: {X_train_full.shape}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-03T14:08:41.117613Z","iopub.execute_input":"2025-12-03T14:08:41.118031Z","iopub.status.idle":"2025-12-03T14:08:41.289806Z","shell.execute_reply.started":"2025-12-03T14:08:41.118016Z","shell.execute_reply":"2025-12-03T14:08:41.289217Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 4.2 Encode Label dan Split Data (untuk data lokal)","metadata":{}},{"cell_type":"code","source":"# Hanya untuk data lokal (bukan TFRecords)\nif not USE_TFRECORDS:\n    # Encode label menjadi numerik\n    label_encoder = LabelEncoder()\n    y_encoded = label_encoder.fit_transform(y_train_full)\n\n    print(f\"📊 Label Encoding:\")\n    print(f\"- Jumlah kelas: {len(label_encoder.classes_)}\")\n    print(f\"- Contoh mapping: {dict(list(zip(label_encoder.classes_[:5], range(5))))}\")\n\n    # Split data menjadi training dan validation\n    X_train, X_val, y_train, y_val = train_test_split(\n        X_train_full, y_encoded, \n        test_size=VALIDATION_SPLIT, \n        random_state=42,\n        stratify=y_encoded  # Pastikan distribusi kelas seimbang\n    )\n\n    print(f\"\\n📊 Pembagian Data:\")\n    print(f\"- Training set: {X_train.shape[0]} sampel\")\n    print(f\"- Validation set: {X_val.shape[0]} sampel\")\n    print(f\"- Jumlah fitur: {X_train.shape[1]}\")\nelse:\n    print(\"✅ Menggunakan TFRecords dataset (sudah ter-split)\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-03T14:08:41.290567Z","iopub.execute_input":"2025-12-03T14:08:41.290826Z","iopub.status.idle":"2025-12-03T14:08:41.296002Z","shell.execute_reply.started":"2025-12-03T14:08:41.290805Z","shell.execute_reply":"2025-12-03T14:08:41.295451Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 4.3 Standardisasi Data (Preprocessing Lanjutan)\n\nStandardisasi menggunakan StandardScaler untuk normalisasi lebih lanjut (mean=0, std=1).","metadata":{}},{"cell_type":"code","source":"# Standardisasi hanya untuk data lokal (TFRecords sudah normalized)\nif not USE_TFRECORDS:\n    # Standardisasi fitur untuk meningkatkan konvergensi MLP\n    scaler = StandardScaler()\n    X_train_scaled = scaler.fit_transform(X_train)\n    X_val_scaled = scaler.transform(X_val)\n\n    print(\"✅ Data berhasil di-standardisasi!\")\n    print(f\"📊 Statistik setelah scaling:\")\n    print(f\"- Mean: {X_train_scaled.mean():.6f} (target: ~0)\")\n    print(f\"- Std: {X_train_scaled.std():.6f} (target: ~1)\")\n    print(f\"- Min: {X_train_scaled.min():.6f}\")\n    print(f\"- Max: {X_train_scaled.max():.6f}\")\nelse:\n    print(\"✅ TFRecords sudah ternormalisasi (0-1)\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-03T14:08:41.296667Z","iopub.execute_input":"2025-12-03T14:08:41.296896Z","iopub.status.idle":"2025-12-03T14:08:41.314568Z","shell.execute_reply.started":"2025-12-03T14:08:41.296871Z","shell.execute_reply":"2025-12-03T14:08:41.313965Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 5. Pembangunan Arsitektur MLP Lengkap\n\nMembangun Multi-Layer Perceptron dengan:\n- **Input Layer**: Flattened image (110,592 features)\n- **Hidden Layers**: 4 layers dengan ReLU activation\n- **Batch Normalization**: Untuk stabilitas training\n- **Dropout**: Untuk regularisasi dan mencegah overfitting\n- **Output Layer**: Softmax untuk 104 kelas","metadata":{}},{"cell_type":"code","source":"def build_mlp_model():\n    \"\"\"\n    Membangun arsitektur MLP yang lengkap.\n    \n    Arsitektur:\n    - Input: 110,592 features (192x192x3 flattened)\n    - Hidden Layer 1: 2048 neurons + BatchNorm + Dropout(0.5)\n    - Hidden Layer 2: 1024 neurons + BatchNorm + Dropout(0.5)\n    - Hidden Layer 3: 512 neurons + BatchNorm + Dropout(0.5)\n    - Hidden Layer 4: 256 neurons + BatchNorm + Dropout(0.5)\n    - Output: 104 neurons (Softmax)\n    \n    Returns:\n    --------\n    model : keras.Model\n        Model MLP yang sudah dikompilasi\n    \"\"\"\n    model = models.Sequential([\n        # Input Layer\n        layers.Input(shape=(FLAT_DIM,), name='input_layer'),\n        \n        # Hidden Layer 1\n        layers.Dense(HIDDEN_LAYERS[0], name='hidden_1'),\n        layers.BatchNormalization(name='bn_1'),\n        layers.Activation(ACTIVATION, name='activation_1'),\n        layers.Dropout(DROPOUT_RATE, name='dropout_1'),\n        \n        # Hidden Layer 2\n        layers.Dense(HIDDEN_LAYERS[1], name='hidden_2'),\n        layers.BatchNormalization(name='bn_2'),\n        layers.Activation(ACTIVATION, name='activation_2'),\n        layers.Dropout(DROPOUT_RATE, name='dropout_2'),\n        \n        # Hidden Layer 3\n        layers.Dense(HIDDEN_LAYERS[2], name='hidden_3'),\n        layers.BatchNormalization(name='bn_3'),\n        layers.Activation(ACTIVATION, name='activation_3'),\n        layers.Dropout(DROPOUT_RATE, name='dropout_3'),\n        \n        # Hidden Layer 4\n        layers.Dense(HIDDEN_LAYERS[3], name='hidden_4'),\n        layers.BatchNormalization(name='bn_4'),\n        layers.Activation(ACTIVATION, name='activation_4'),\n        layers.Dropout(DROPOUT_RATE, name='dropout_4'),\n        \n        # Output Layer\n        layers.Dense(NUM_CLASSES, activation='softmax', name='output_layer')\n    ], name='MLP_FlowerClassification')\n    \n    return model\n\n# Bangun model di dalam TPU strategy scope\nprint(\"=\" * 60)\nprint(\"MEMBANGUN ARSITEKTUR MLP\")\nprint(\"=\" * 60)\n\nwith strategy.scope():\n    model = build_mlp_model()\n    \n    # Compile model dengan optimizer dan loss function\n    model.compile(\n        optimizer=keras.optimizers.Adam(learning_rate=LEARNING_RATE),\n        loss='categorical_crossentropy',\n        metrics=['categorical_accuracy']\n    )\n\n# Tampilkan summary model\nprint(\"\\n📋 Model Summary:\")\nmodel.summary()\n\n# Hitung total parameters\ntrainable_params = np.sum([np.prod(v.shape) for v in model.trainable_weights])\nnon_trainable_params = np.sum([np.prod(v.shape) for v in model.non_trainable_weights])\n\nprint(\"\\n\" + \"=\" * 60)\nprint(f\"✅ Total Parameters: {trainable_params + non_trainable_params:,}\")\nprint(f\"   - Trainable: {trainable_params:,}\")\nprint(f\"   - Non-trainable: {non_trainable_params:,}\")\nprint(\"=\" * 60)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-03T14:08:41.315283Z","iopub.execute_input":"2025-12-03T14:08:41.315521Z","iopub.status.idle":"2025-12-03T14:08:41.556604Z","shell.execute_reply.started":"2025-12-03T14:08:41.315501Z","shell.execute_reply":"2025-12-03T14:08:41.556005Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 6. Training Model dengan Callbacks (Hyperparameter Tuning)\n\nMelatih model dengan callbacks untuk tuning otomatis:\n- **EarlyStopping**: Berhenti jika val_loss tidak improve (mencegah overfitting)\n- **ReduceLROnPlateau**: Kurangi learning rate jika loss plateau\n- **ModelCheckpoint**: Simpan model terbaik","metadata":{}},{"cell_type":"code","source":"# Setup callbacks untuk hyperparameter tuning otomatis\ncallbacks_list = [\n    # Early Stopping: Berhenti training jika val_loss tidak improve selama 10 epoch\n    callbacks.EarlyStopping(\n        monitor='val_loss',\n        patience=10,\n        restore_best_weights=True,\n        verbose=1,\n        mode='min'\n    ),\n    \n    # Reduce Learning Rate: Kurangi LR jika val_loss plateau selama 5 epoch\n    callbacks.ReduceLROnPlateau(\n        monitor='val_loss',\n        factor=0.5,  # Kurangi LR menjadi 50%\n        patience=5,\n        min_lr=1e-6,\n        verbose=1,\n        mode='min'\n    ),\n    \n    # Model Checkpoint: Simpan model terbaik\n    callbacks.ModelCheckpoint(\n        'best_mlp_model.keras',\n        monitor='val_categorical_accuracy',\n        save_best_only=True,\n        verbose=1,\n        mode='max'\n    )\n]\n\nprint(\"=\" * 60)\nprint(\"MEMULAI TRAINING\")\nprint(\"=\" * 60)\n\n# Training model\nif USE_TFRECORDS:\n    # Training dengan TFRecords dataset\n    history = model.fit(\n        train_dataset,\n        epochs=EPOCHS,\n        steps_per_epoch=STEPS_PER_EPOCH,\n        validation_data=val_dataset,\n        validation_steps=VALIDATION_STEPS,\n        callbacks=callbacks_list,\n        verbose=1\n    )\nelse:\n    # Training dengan data lokal\n    history = model.fit(\n        X_train_scaled, \n        keras.utils.to_categorical(y_train, NUM_CLASSES),\n        batch_size=BATCH_SIZE,\n        epochs=EPOCHS,\n        validation_data=(X_val_scaled, keras.utils.to_categorical(y_val, NUM_CLASSES)),\n        callbacks=callbacks_list,\n        verbose=1\n    )\n\nprint(\"\\n\" + \"=\" * 60)\nprint(\"✅ TRAINING SELESAI!\")\nprint(\"=\" * 60)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-03T14:08:41.557385Z","iopub.execute_input":"2025-12-03T14:08:41.55766Z","iopub.status.idle":"2025-12-03T14:15:14.589774Z","shell.execute_reply.started":"2025-12-03T14:08:41.55763Z","shell.execute_reply":"2025-12-03T14:15:14.588954Z"},"scrolled":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 7. Analisis Konvergensi Model\n\n**Tujuan**: Visualisasi proses learning untuk memastikan model konvergen dengan baik\n\n### 7.1 Plot Learning Curves (Loss dan Accuracy)","metadata":{}},{"cell_type":"code","source":"# Plot learning curves untuk analisis konvergensi\nimport matplotlib.pyplot as plt\n\nfig, axes = plt.subplots(1, 2, figsize=(16, 5))\n\n# Plot 1: Training & Validation Loss\naxes[0].plot(history.history['loss'], label='Training Loss', linewidth=2, color='blue')\naxes[0].plot(history.history['val_loss'], label='Validation Loss', linewidth=2, color='red')\naxes[0].set_title('Model Loss Over Epochs', fontsize=14, fontweight='bold')\naxes[0].set_xlabel('Epoch', fontsize=12)\naxes[0].set_ylabel('Loss (Categorical Crossentropy)', fontsize=12)\naxes[0].legend(fontsize=11)\naxes[0].grid(True, alpha=0.3)\n\n# Plot 2: Training & Validation Accuracy\naxes[1].plot(history.history['categorical_accuracy'], label='Training Accuracy', linewidth=2, color='blue')\naxes[1].plot(history.history['val_categorical_accuracy'], label='Validation Accuracy', linewidth=2, color='red')\naxes[1].set_title('Model Accuracy Over Epochs', fontsize=14, fontweight='bold')\naxes[1].set_xlabel('Epoch', fontsize=12)\naxes[1].set_ylabel('Accuracy', fontsize=12)\naxes[1].legend(fontsize=11)\naxes[1].grid(True, alpha=0.3)\n\nplt.tight_layout()\nplt.show()\n\n# Analisis konvergensi\nprint(\"\\n\" + \"=\" * 80)\nprint(\"ANALISIS KONVERGENSI MODEL\")\nprint(\"=\" * 80)\n\nfinal_train_loss = history.history['loss'][-1]\nfinal_val_loss = history.history['val_loss'][-1]\nfinal_train_acc = history.history['categorical_accuracy'][-1]\nfinal_val_acc = history.history['val_categorical_accuracy'][-1]\n\nprint(f\"\\n📊 Hasil Akhir Training:\")\nprint(f\"   - Training Loss: {final_train_loss:.4f}\")\nprint(f\"   - Validation Loss: {final_val_loss:.4f}\")\nprint(f\"   - Training Accuracy: {final_train_acc:.4f} ({final_train_acc*100:.2f}%)\")\nprint(f\"   - Validation Accuracy: {final_val_acc:.4f} ({final_val_acc*100:.2f}%)\")\n\n# Deteksi overfitting\ngap_loss = final_train_loss - final_val_loss\ngap_acc = final_train_acc - final_val_acc\n\nprint(f\"\\n🔍 Analisis Overfitting:\")\nprint(f\"   - Loss Gap (Train - Val): {gap_loss:.4f}\")\nprint(f\"   - Accuracy Gap (Train - Val): {gap_acc:.4f}\")\n\nif gap_acc > 0.1:\n    print(\"   ⚠️  WARNING: Model kemungkinan overfitting (accuracy gap > 10%)\")\nelif gap_acc > 0.05:\n    print(\"   ⚡ CAUTION: Model sedikit overfitting (accuracy gap 5-10%)\")\nelse:\n    print(\"   ✅ GOOD: Model tidak overfitting (accuracy gap < 5%)\")\n\n# Cek konvergensi\ntotal_epochs = len(history.history['loss'])\nprint(f\"\\n🎯 Info Training:\")\nprint(f\"   - Total Epochs Trained: {total_epochs}\")\n\nif 'early_stopping' in locals():\n    print(f\"   - Early Stopping: Aktif\")\n    print(f\"   - Best Epoch: {total_epochs - 10}\")  # Patience = 10\n\nprint(\"=\" * 80)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-03T14:15:14.592339Z","iopub.execute_input":"2025-12-03T14:15:14.592632Z","iopub.status.idle":"2025-12-03T14:15:15.030208Z","shell.execute_reply.started":"2025-12-03T14:15:14.592612Z","shell.execute_reply":"2025-12-03T14:15:15.029604Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 8. Evaluasi Model dengan K-Fold Cross Validation\n\n**Tujuan**: Validasi robustness model menggunakan K-Fold Cross Validation\n\n### 8.1 Implementasi K-Fold Cross Validation","metadata":{}},{"cell_type":"code","source":"# K-Fold Cross Validation untuk validasi model\nfrom sklearn.model_selection import KFold\n\n# Hanya lakukan K-Fold jika menggunakan data lokal (karena TFRecords sudah ter-split)\nif not USE_TFRECORDS:\n    print(\"=\" * 80)\n    print(\"K-FOLD CROSS VALIDATION (K=5)\")\n    print(\"=\" * 80)\n    \n    K_FOLDS = 5\n    kfold = KFold(n_splits=K_FOLDS, shuffle=True, random_state=42)\n    \n    # Gabungkan kembali train dan val untuk K-Fold\n    X_full = np.vstack([X_train_scaled, X_val_scaled])\n    y_full = np.concatenate([y_train, y_val])\n    y_full_categorical = keras.utils.to_categorical(y_full, NUM_CLASSES)\n    \n    cv_scores = []\n    fold_num = 1\n    \n    for train_idx, val_idx in kfold.split(X_full):\n        print(f\"\\n{'='*60}\")\n        print(f\"FOLD {fold_num}/{K_FOLDS}\")\n        print(f\"{'='*60}\")\n        \n        # Split data untuk fold ini\n        X_train_fold = X_full[train_idx]\n        X_val_fold = X_full[val_idx]\n        y_train_fold = y_full_categorical[train_idx]\n        y_val_fold = y_full_categorical[val_idx]\n        \n        # Build model baru untuk setiap fold\n        fold_model = build_mlp_model()\n        \n        # Training dengan early stopping (patience lebih kecil untuk K-Fold)\n        fold_callbacks = [\n            callbacks.EarlyStopping(\n                monitor='val_loss',\n                patience=5,\n                restore_best_weights=True,\n                verbose=0\n            ),\n            callbacks.ReduceLROnPlateau(\n                monitor='val_loss',\n                factor=0.5,\n                patience=3,\n                min_lr=1e-6,\n                verbose=0\n            )\n        ]\n        \n        fold_history = fold_model.fit(\n            X_train_fold, y_train_fold,\n            batch_size=BATCH_SIZE,\n            epochs=30,  # Epochs lebih sedikit untuk K-Fold\n            validation_data=(X_val_fold, y_val_fold),\n            callbacks=fold_callbacks,\n            verbose=0  # Suppress output untuk K-Fold\n        )\n        \n        # Evaluasi fold\n        val_loss, val_acc = fold_model.evaluate(X_val_fold, y_val_fold, verbose=0)\n        cv_scores.append(val_acc)\n        \n        print(f\"Fold {fold_num} - Validation Accuracy: {val_acc:.4f} ({val_acc*100:.2f}%)\")\n        fold_num += 1\n    \n    # Hasil K-Fold\n    print(f\"\\n{'='*80}\")\n    print(\"HASIL K-FOLD CROSS VALIDATION\")\n    print(f\"{'='*80}\")\n    print(f\"\\n📊 Accuracy per Fold:\")\n    for i, score in enumerate(cv_scores, 1):\n        print(f\"   Fold {i}: {score:.4f} ({score*100:.2f}%)\")\n    \n    print(f\"\\n📈 Statistik K-Fold:\")\n    print(f\"   - Mean Accuracy: {np.mean(cv_scores):.4f} ({np.mean(cv_scores)*100:.2f}%)\")\n    print(f\"   - Std Deviation: {np.std(cv_scores):.4f}\")\n    print(f\"   - Min Accuracy: {np.min(cv_scores):.4f}\")\n    print(f\"   - Max Accuracy: {np.max(cv_scores):.4f}\")\n    print(f\"\\n✅ Model terbukti robust dengan variasi accuracy rendah antar fold\")\n    print(\"=\" * 80)\nelse:\n    print(\"=\" * 80)\n    print(\"⚠️  K-FOLD CROSS VALIDATION SKIPPED\")\n    print(\"=\" * 80)\n    print(\"Alasan: TFRecords dataset sudah ter-split dengan benar.\")\n    print(\"Holdout validation (train/val split) sudah dilakukan.\")\n    print(\"=\" * 80)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-03T14:15:15.031062Z","iopub.execute_input":"2025-12-03T14:15:15.031345Z","iopub.status.idle":"2025-12-03T14:15:16.143547Z","shell.execute_reply.started":"2025-12-03T14:15:15.031323Z","shell.execute_reply":"2025-12-03T14:15:16.142806Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 9. Metrik Evaluasi Lengkap\n\n**Tujuan**: Analisis mendalam performa model dengan berbagai metrik\n\n### 9.1 Confusion Matrix dan Classification Report","metadata":{}},{"cell_type":"code","source":"from sklearn.metrics import confusion_matrix, classification_report, ConfusionMatrixDisplay\nfrom sklearn.metrics import precision_score, recall_score, f1_score\n\nprint(\"=\" * 80)\nprint(\"EVALUASI MODEL - METRIK LENGKAP\")\nprint(\"=\" * 80)\n\n# Prediksi pada validation set\nif USE_TFRECORDS:\n    # Untuk TFRecords, gunakan val_dataset\n    print(\"\\n⚠️  Untuk TFRecords: Gunakan hasil evaluasi dari history training\")\n    print(f\"Validation Accuracy: {final_val_acc:.4f} ({final_val_acc*100:.2f}%)\")\n    print(f\"Validation Loss: {final_val_loss:.4f}\")\nelse:\n    # Untuk data lokal, lakukan prediksi detail\n    y_val_pred_probs = model.predict(X_val_scaled)\n    y_val_pred = np.argmax(y_val_pred_probs, axis=1)\n    \n    # Hitung berbagai metrik\n    precision = precision_score(y_val, y_val_pred, average='macro', zero_division=0)\n    recall = recall_score(y_val, y_val_pred, average='macro', zero_division=0)\n    f1 = f1_score(y_val, y_val_pred, average='macro', zero_division=0)\n    \n    print(\"\\n📊 METRIK EVALUASI (Validation Set):\")\n    print(f\"   - Accuracy: {final_val_acc:.4f} ({final_val_acc*100:.2f}%)\")\n    print(f\"   - Precision (Macro): {precision:.4f}\")\n    print(f\"   - Recall (Macro): {recall:.4f}\")\n    print(f\"   - F1-Score (Macro): {f1:.4f}\")\n    \n    # Confusion Matrix\n    print(\"\\n📈 Generating Confusion Matrix...\")\n    cm = confusion_matrix(y_val, y_val_pred)\n    \n    # Plot Confusion Matrix (hanya sebagian, karena 104 kelas terlalu besar)\n    fig, ax = plt.subplots(figsize=(12, 10))\n    disp = ConfusionMatrixDisplay(confusion_matrix=cm[:20, :20])  # Tampilkan 20x20 pertama\n    disp.plot(ax=ax, cmap='Blues', values_format='d')\n    ax.set_title('Confusion Matrix (20 Kelas Pertama)', fontsize=14, fontweight='bold')\n    plt.tight_layout()\n    plt.show()\n    \n    # Classification Report\n    print(\"\\n📋 CLASSIFICATION REPORT (Top 10 Classes):\")\n    print(\"=\" * 80)\n    \n    # Ambil class names dari label encoder\n    class_names = label_encoder.classes_\n    \n    # Generate classification report\n    report = classification_report(\n        y_val, \n        y_val_pred, \n        target_names=class_names,\n        zero_division=0,\n        output_dict=True\n    )\n    \n    # Tampilkan report untuk 10 kelas pertama\n    for i, class_name in enumerate(class_names[:10]):\n        if class_name in report:\n            metrics = report[class_name]\n            print(f\"\\nClass {i}: {class_name}\")\n            print(f\"  Precision: {metrics['precision']:.4f}\")\n            print(f\"  Recall: {metrics['recall']:.4f}\")\n            print(f\"  F1-Score: {metrics['f1-score']:.4f}\")\n            print(f\"  Support: {int(metrics['support'])}\")\n    \n    # Macro averages\n    print(\"\\n\" + \"=\" * 80)\n    print(\"MACRO AVERAGES (Semua 104 Kelas):\")\n    print(\"=\" * 80)\n    print(f\"Precision: {report['macro avg']['precision']:.4f}\")\n    print(f\"Recall: {report['macro avg']['recall']:.4f}\")\n    print(f\"F1-Score: {report['macro avg']['f1-score']:.4f}\")\n    print(\"=\" * 80)\n\nprint(\"\\n✅ Evaluasi metrik lengkap selesai!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-03T14:15:16.144969Z","iopub.execute_input":"2025-12-03T14:15:16.145179Z","iopub.status.idle":"2025-12-03T14:15:18.126238Z","shell.execute_reply.started":"2025-12-03T14:15:16.145162Z","shell.execute_reply":"2025-12-03T14:15:18.12549Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 10. Prediksi pada Test Set dan Submission\n\n**Tujuan**: Membuat prediksi untuk Kaggle submission\n\n### 10.1 Melakukan Prediksi pada Test Set","metadata":{}},{"cell_type":"code","source":"# Lakukan prediksi pada test set\nprint(\"=\" * 80)\nprint(\"PREDIKSI PADA TEST SET\")\nprint(\"=\" * 80)\n\nif USE_TFRECORDS:\n    print(\"\\n🔮 Melakukan prediksi pada test dataset...\")\n    \n    # Prediksi menggunakan test_dataset\n    test_predictions = model.predict(test_dataset)\n    predicted_classes = np.argmax(test_predictions, axis=1)\n    \n    # Extract test IDs dari test_dataset\n    print(\"📝 Mengekstrak ID dari test dataset...\")\n    test_ids = []\n    \n    # Iterasi test_dataset untuk mendapatkan IDs\n    for images, ids in test_dataset:\n        test_ids.extend([id_val.numpy().decode('utf-8') for id_val in ids])\n    \n    print(f\"✅ Prediksi selesai! Total prediksi: {len(predicted_classes)}\")\n    print(f\"✅ Total IDs: {len(test_ids)}\")\n    \n    # Tampilkan contoh prediksi\n    print(f\"\\n📊 Contoh prediksi (5 pertama):\")\n    for i in range(min(5, len(test_ids))):\n        print(f\"  - ID: {test_ids[i]} -> Class: {predicted_classes[i]}\")\n    \n    # Distribusi prediksi\n    unique, counts = np.unique(predicted_classes, return_counts=True)\n    print(f\"\\n📈 Distribusi prediksi kelas (Top 10):\")\n    top_10_idx = np.argsort(counts)[-10:][::-1]\n    for idx in top_10_idx:\n        class_id = unique[idx]\n        count = counts[idx]\n        print(f\"  - Class {class_id}: {count} gambar ({count/len(predicted_classes)*100:.1f}%)\")\n    \nelse:\n    print(\"\\nMelakukan prediksi pada test set...\")\n    y_test_pred_probs = model.predict(X_test)\n    y_test_pred = np.argmax(y_test_pred_probs, axis=1)\n    \n    # Decode label kembali ke string\n    y_test_pred_labels = label_encoder.inverse_transform(y_test_pred)\n    \n    print(f\"Prediksi selesai! Total prediksi: {len(y_test_pred_labels)}\")\n    print(f\"\\nContoh prediksi:\")\n    for i in range(min(5, len(test_ids))):\n        print(f\"  - ID: {test_ids[i]} -> Predicted: {y_test_pred_labels[i]}\")\n    \n    # Distribusi prediksi\n    unique, counts = np.unique(y_test_pred_labels, return_counts=True)\n    print(f\"\\nDistribusi prediksi (Top 10):\")\n    top_10_idx = np.argsort(counts)[-10:][::-1]\n    for idx in top_10_idx:\n        label = unique[idx]\n        count = counts[idx]\n        print(f\"  - {label}: {count} gambar ({count/len(y_test_pred_labels)*100:.1f}%)\")\n\nprint(\"=\" * 80)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-03T14:15:18.127066Z","iopub.execute_input":"2025-12-03T14:15:18.127366Z","iopub.status.idle":"2025-12-03T14:15:27.300993Z","shell.execute_reply.started":"2025-12-03T14:15:18.127347Z","shell.execute_reply":"2025-12-03T14:15:27.300256Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 10.2 Membuat File Submission\n\nBuat file CSV dengan format yang diperlukan untuk submission Kaggle.","metadata":{}},{"cell_type":"code","source":"print(\"=\" * 80)\nprint(\"MEMBUAT FILE SUBMISSION\")\nprint(\"=\" * 80)\n\nif USE_TFRECORDS:\n    # Buat DataFrame submission untuk TFRecords\n    submission = pd.DataFrame({\n        'id': test_ids,\n        'label': predicted_classes\n    })\n    \n    # Simpan ke CSV\n    submission_filename = 'submission.csv'\n    submission.to_csv(submission_filename, index=False)\n    \n    print(f\"\\n✅ FILE SUBMISSION BERHASIL DIBUAT!\")\n    print(f\"📁 Filename: {submission_filename}\")\n    print(f\"\\n📊 Preview submission file:\")\n    print(submission.head(10))\n    print(f\"\\n📈 Shape: {submission.shape}\")\n    print(f\"📋 Kolom: {list(submission.columns)}\")\n    print(f\"\\n🚀 File siap di-submit ke Kaggle!\")\n    print(\"=\" * 80)\n    \nelse:\n    # Buat DataFrame submission untuk data lokal\n    submission = pd.DataFrame({\n        'id': test_ids,\n        'label': y_test_pred_labels\n    })\n    \n    # Simpan ke CSV\n    submission_filename = 'submission_mlp.csv'\n    submission.to_csv(submission_filename, index=False)\n    \n    print(f\"\\n✅ FILE SUBMISSION BERHASIL DIBUAT!\")\n    print(f\"📁 Filename: {submission_filename}\")\n    print(f\"\\n📊 Preview submission file:\")\n    print(submission.head(10))\n    print(f\"\\n📈 Shape: {submission.shape}\")\n    print(f\"📋 Kolom: {list(submission.columns)}\")\n    print(f\"\\n🚀 File siap di-upload ke Kaggle!\")\n    print(\"=\" * 80)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-03T14:15:27.301728Z","iopub.execute_input":"2025-12-03T14:15:27.301936Z","iopub.status.idle":"2025-12-03T14:15:27.319159Z","shell.execute_reply.started":"2025-12-03T14:15:27.301921Z","shell.execute_reply":"2025-12-03T14:15:27.318493Z"}},"outputs":[],"execution_count":null}]}