{"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":"none","dataSources":[{"sourceId":21154,"databundleVersionId":1243559,"sourceType":"competition"}],"dockerImageVersionId":31192,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# =================================================================================\n# KODE FINAL: MLP FOR FLOWER CLASSIFICATION (TPU/GPU COMPATIBLE)\n# =================================================================================\n\n# 1. PERBAIKAN DEPENDENCIES (WAJIB DI BARIS PALING ATAS)\n# Mengatasi error \"MessageFactory\" yang menyebabkan Save Version gagal\n!pip install -q protobuf==3.20.3\n\nimport os\nimport warnings\nimport tensorflow as tf\nfrom kaggle_datasets import KaggleDatasets\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\nfrom sklearn.metrics import classification_report, confusion_matrix\nimport seaborn as sns\n\n# Konfigurasi Environment\nos.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'\nwarnings.filterwarnings(\"ignore\")\nprint(f\"TensorFlow Version: {tf.__version__}\")\n\n# ==========================================\n# 2. DETEKSI HARDWARE (TPU / GPU)\n# ==========================================\ntry:\n    tpu = tf.distribute.cluster_resolver.TPUClusterResolver()\n    tf.config.experimental_connect_to_cluster(tpu)\n    tf.tpu.experimental.initialize_tpu_system(tpu)\n    strategy = tf.distribute.TPUStrategy(tpu)\n    print(\">> HARDWARE: TPU DETECTED\")\nexcept ValueError:\n    # Fallback ke GPU (MirroredStrategy) jika TPU tidak ada\n    strategy = tf.distribute.MirroredStrategy()\n    print(\">> HARDWARE: GPU DETECTED\")\n\nprint(f\">> Number of accelerators: {strategy.num_replicas_in_sync}\")\n\n# ==========================================\n# 3. HYPERPARAMETERS & PATHS\n# ==========================================\n# Ukuran gambar kecil (64x64) agar MLP tidak kehabisan memori (OOM)\nIMAGE_SIZE = [64, 64] \nEPOCHS = 25\n# Batch size disesuaikan dengan jumlah device\nBATCH_SIZE = 128 * strategy.num_replicas_in_sync \nLEARNING_RATE = 0.001\n\n# Mendapatkan Path GCS Data\ntry:\n    GCS_DS_PATH = KaggleDatasets().get_gcs_path('tpu-getting-started')\nexcept:\n    print(\"GCS Path not found, using local path.\")\n    GCS_DS_PATH = \"/kaggle/input/tpu-getting-started\"\n\nTRAIN_FILENAMES = tf.io.gfile.glob(GCS_DS_PATH + '/tfrecords-jpeg-192x192/train/*.tfrec')\nVAL_FILENAMES = tf.io.gfile.glob(GCS_DS_PATH + '/tfrecords-jpeg-192x192/val/*.tfrec')\nTEST_FILENAMES = tf.io.gfile.glob(GCS_DS_PATH + '/tfrecords-jpeg-192x192/test/*.tfrec')\n\n# ==========================================\n# 4. PREPROCESSING DATA PIPELINE\n# ==========================================\ndef decode_image(image_data):\n    image = tf.image.decode_jpeg(image_data, channels=3)\n    # Normalisasi [0,1] sangat penting untuk MLP\n    image = tf.cast(image, tf.float32) / 255.0 \n    image = tf.image.resize(image, IMAGE_SIZE)\n    return image\n\ndef read_labeled_tfrecord(example):\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(example['image'])\n    label = tf.cast(example['class'], tf.int32)\n    return image, label\n\ndef read_unlabeled_tfrecord(example):\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(example['image'])\n    idnum = example['id']\n    return image, idnum\n\ndef load_dataset(filenames, labeled=True, ordered=False):\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=tf.data.AUTOTUNE)\n    dataset = dataset.with_options(ignore_order)\n    dataset = dataset.map(read_labeled_tfrecord if labeled else read_unlabeled_tfrecord, \n                          num_parallel_calls=tf.data.AUTOTUNE)\n    return dataset\n\n# Setup Dataset\n# PERHATIKAN: .repeat() ditambahkan agar data tidak habis saat distributed training\ntrain_dataset = load_dataset(TRAIN_FILENAMES, labeled=True).repeat().shuffle(2048).batch(BATCH_SIZE).prefetch(tf.data.AUTOTUNE)\nvalid_dataset = load_dataset(VAL_FILENAMES, labeled=True, ordered=True).batch(BATCH_SIZE).cache().prefetch(tf.data.AUTOTUNE)\ntest_dataset = load_dataset(TEST_FILENAMES, labeled=False, ordered=True).batch(BATCH_SIZE).prefetch(tf.data.AUTOTUNE)\n\n# Hitung Steps per Epoch\nNUM_TRAINING_IMAGES = 12753\nSTEPS_PER_EPOCH = NUM_TRAINING_IMAGES // BATCH_SIZE\nprint(f\"Steps per epoch: {STEPS_PER_EPOCH}\")\n\n# ==========================================\n# 5. MEMBANGUN MODEL MLP (RUBRIK: ARSITEKTUR LENGKAP)\n# ==========================================\nwith strategy.scope():\n    model = tf.keras.Sequential([\n        # Input Layer Eksplisit (Mengatasi Warning Input Shape)\n        tf.keras.layers.InputLayer(shape=(IMAGE_SIZE[0], IMAGE_SIZE[1], 3)),\n        \n        # Flattening: Mengubah matrix gambar menjadi vektor 1D\n        tf.keras.layers.Flatten(),\n        \n        # Hidden Layer 1: ReLU + BatchNorm + Dropout\n        tf.keras.layers.Dense(2048, activation='relu'),\n        tf.keras.layers.BatchNormalization(),\n        tf.keras.layers.Dropout(0.3),\n        \n        # Hidden Layer 2\n        tf.keras.layers.Dense(1024, activation='relu'),\n        tf.keras.layers.BatchNormalization(),\n        tf.keras.layers.Dropout(0.3),\n        \n        # Hidden Layer 3\n        tf.keras.layers.Dense(512, activation='relu'),\n        tf.keras.layers.BatchNormalization(),\n        \n        # Output Layer: 104 Kelas dengan Softmax\n        tf.keras.layers.Dense(104, activation='softmax')\n    ])\n\n    model.compile(\n        optimizer=tf.keras.optimizers.Adam(learning_rate=LEARNING_RATE),\n        loss='sparse_categorical_crossentropy',\n        metrics=['sparse_categorical_accuracy']\n    )\n\nprint(\"\\n>>> MODEL SUMMARY:\")\nmodel.summary()\n\n# ==========================================\n# 6. TRAINING PROCESS\n# ==========================================\nprint(\"\\n>>> STARTING TRAINING...\")\nearly_stopping = tf.keras.callbacks.EarlyStopping(monitor='val_loss', patience=10, restore_best_weights=True)\nreduce_lr = tf.keras.callbacks.ReduceLROnPlateau(monitor='val_loss', factor=0.2, patience=3, min_lr=1e-6)\n\nhistory = model.fit(\n    train_dataset,\n    steps_per_epoch=STEPS_PER_EPOCH,\n    epochs=EPOCHS,\n    validation_data=valid_dataset,\n    callbacks=[early_stopping, reduce_lr],\n    verbose=1\n)\n\n# ==========================================\n# 7. EVALUASI & VISUALISASI (RUBRIK: ANALISIS)\n# ==========================================\ndef plot_training(history):\n    acc = history.history['sparse_categorical_accuracy']\n    val_acc = history.history['val_sparse_categorical_accuracy']\n    loss = history.history['loss']\n    val_loss = history.history['val_loss']\n    epochs_range = range(1, len(acc) + 1)\n\n    plt.figure(figsize=(15, 6))\n    plt.subplot(1, 2, 1)\n    plt.plot(epochs_range, acc, label='Training Accuracy')\n    plt.plot(epochs_range, val_acc, label='Validation Accuracy')\n    plt.title('Training and Validation Accuracy')\n    plt.legend(loc='lower right')\n\n    plt.subplot(1, 2, 2)\n    plt.plot(epochs_range, loss, label='Training Loss')\n    plt.plot(epochs_range, val_loss, label='Validation Loss')\n    plt.title('Training and Validation Loss')\n    plt.legend(loc='upper right')\n    plt.show()\n\nprint(\"\\n>>> MENAMPILKAN GRAFIK EVALUASI:\")\nplot_training(history)\n\n# Metrik Lengkap (Classification Report)\nprint(\"\\n>>> MENGHITUNG CLASSIFICATION REPORT...\")\ntest_images_ds_val = valid_dataset.map(lambda image, label: image)\nval_labels_ds = valid_dataset.map(lambda image, label: label).unbatch()\ny_true = next(iter(val_labels_ds.batch(3712))).numpy()\n\nprobs_val = model.predict(test_images_ds_val, verbose=1)\ny_pred = np.argmax(probs_val, axis=-1)\n\nprint(classification_report(y_true, y_pred))\n\n# ==========================================\n# 8. SUBMISSION KE KAGGLE\n# ==========================================\nprint(\"\\n>>> MEMBUAT FILE SUBMISSION...\")\ntest_images_ds = test_dataset.map(lambda image, idnum: image)\nprobabilities = model.predict(test_images_ds, verbose=1)\npredictions = np.argmax(probabilities, axis=-1)\n\ntest_ids_ds = test_dataset.map(lambda image, idnum: idnum).unbatch()\ntest_ids = next(iter(test_ids_ds.batch(7382))).numpy().astype('U')\n\nsubmission = pd.DataFrame({'id': test_ids, 'label': predictions})\nsubmission.to_csv('submission.csv', index=False)\nprint(\">>> SELESAI! File 'submission.csv' berhasil dibuat dan siap di-submit.\")","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-12-03T13:03:50.414090Z","iopub.execute_input":"2025-12-03T13:03:50.414449Z","iopub.status.idle":"2025-12-03T13:22:02.990768Z","shell.execute_reply.started":"2025-12-03T13:03:50.414416Z","shell.execute_reply":"2025-12-03T13:22:02.989463Z"}},"outputs":[],"execution_count":null}]}