{"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":31193,"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-03T10:29:55.079706Z","iopub.execute_input":"2025-12-03T10:29:55.079982Z","iopub.status.idle":"2025-12-03T10:29:55.562533Z","shell.execute_reply.started":"2025-12-03T10:29:55.079963Z","shell.execute_reply":"2025-12-03T10:29:55.561889Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## LANGKAH 1: Setup GPU & Data Loading\nMengaktifkan 2 GPU T4, mengatur jalur data lokal, dan menerapkan Data Augmentation (rotasi/zoom) agar model lebih pintar mengenali bunga dari berbagai sudut.","metadata":{}},{"cell_type":"code","source":"import math, re, os, glob\nimport numpy as np\nimport tensorflow as tf\nfrom matplotlib import pyplot as plt\nfrom sklearn.metrics import classification_report\n\nprint(\"TensorFlow Version:\", tf.__version__)\n\n# --- 1. KONFIGURASI GPU T4 x2 ---\ntry:\n    gpus = tf.config.list_physical_devices('GPU')\n    if gpus:\n        strategy = tf.distribute.MirroredStrategy()\n        print(f'✅ Running on {len(gpus)} GPU(s)!')\n    else:\n        strategy = tf.distribute.get_strategy()\n        print(\"⚠️ GPU tidak terdeteksi. Cek Accelerator.\")\nexcept:\n    strategy = tf.distribute.get_strategy()\n\n# Batch size standar untuk kestabilan\nBATCH_SIZE = 16 * strategy.num_replicas_in_sync\n\n# --- 2. SETUP DATASET ---\nDS_PATH = '/kaggle/input/tpu-getting-started'\n# WAJIB 224x224 untuk Akurasi Tinggi\nIMAGE_SIZE = [224, 224] \n\n# --- 3. DECODE & AUGMENTASI ---\ndef decode_image(image_data):\n    image = tf.image.decode_jpeg(image_data, channels=3)\n    image = tf.cast(image, tf.float32) / 255.0\n    # Pastikan resize ke 224x224\n    image = tf.reshape(image, [*IMAGE_SIZE, 3])\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\n# Augmentasi: Rotasi & Zoom membantu model lebih pintar\ndef data_augment(image, label):\n    image = tf.image.random_flip_left_right(image)\n    image = tf.image.random_flip_up_down(image)\n    image = tf.image.random_brightness(image, max_delta=0.2)\n    image = tf.image.random_saturation(image, 0.8, 1.2)\n    # Rotasi acak\n    image = tf.image.rot90(image, k=tf.random.uniform(shape=[], minval=0, maxval=4, dtype=tf.int32))\n    return image, label\n\ndef load_dataset(filenames, labeled=True, ordered=False, augment=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.experimental.AUTOTUNE)\n    dataset = dataset.with_options(ignore_order)\n    dataset = dataset.map(read_labeled_tfrecord, num_parallel_calls=tf.data.experimental.AUTOTUNE)\n    if augment:\n        dataset = dataset.map(data_augment, num_parallel_calls=tf.data.experimental.AUTOTUNE)\n    return dataset\n\n# Load File\nFILENAMES_TRAIN = tf.io.gfile.glob(DS_PATH + '/tfrecords-jpeg-224x224/train/*.tfrec')\nFILENAMES_VAL = tf.io.gfile.glob(DS_PATH + '/tfrecords-jpeg-224x224/val/*.tfrec')\n\ntraining_dataset = load_dataset(FILENAMES_TRAIN, labeled=True, augment=True).repeat().shuffle(2048).batch(BATCH_SIZE).prefetch(tf.data.experimental.AUTOTUNE)\nvalidation_dataset = load_dataset(FILENAMES_VAL, labeled=True, ordered=True).batch(BATCH_SIZE).prefetch(tf.data.experimental.AUTOTUNE)\n\nprint(\"✅ Langkah 1 Selesai. Data 224x224 Siap.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-03T10:29:55.563692Z","iopub.execute_input":"2025-12-03T10:29:55.564019Z","iopub.status.idle":"2025-12-03T10:30:21.853627Z","shell.execute_reply.started":"2025-12-03T10:29:55.564Z","shell.execute_reply":"2025-12-03T10:30:21.852968Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## LANGKAH 2: Arsitektur Model (DenseNet + MLP Head)\nMenggunakan DenseNet201 yang dibekukan (trainable=False) sebagai pengganti mata manusia untuk mengekstrak fitur, kemudian disambungkan ke jaringan MLP yang kuat untuk klasifikasi.","metadata":{}},{"cell_type":"code","source":"with strategy.scope():\n    # Gunakan DenseNet201 (Lebih kuat dari VGG16)\n    pretrained_model = tf.keras.applications.DenseNet201(\n        weights='imagenet', \n        include_top=False ,\n        input_shape=[*IMAGE_SIZE, 3]\n    )\n    pretrained_model.trainable = False # Bekukan agar tetap sesuai rubrik (Hybrid MLP)\n\n    model = tf.keras.Sequential([\n        # --- FEATURE EXTRACTOR ---\n        pretrained_model,\n        \n        # --- ARSITEKTUR MLP (CLASSIFIER) ---\n        # GlobalAveragePooling = Flatten Cerdas\n        tf.keras.layers.GlobalAveragePooling2D(),\n\n        # Hidden Layer 1 (Besar & Kuat)\n        tf.keras.layers.Dense(1024, activation='relu'),\n        tf.keras.layers.BatchNormalization(),\n        tf.keras.layers.Dropout(0.3),\n\n        # Hidden Layer 2\n        tf.keras.layers.Dense(512, activation='relu'),\n        tf.keras.layers.BatchNormalization(),\n        tf.keras.layers.Dropout(0.3),\n\n        # Output Layer\n        tf.keras.layers.Dense(104, activation='softmax')\n    ])\n\n    model.compile(\n        optimizer='adam',\n        loss='sparse_categorical_crossentropy',\n        metrics=['sparse_categorical_accuracy']\n    )\n\nmodel.summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-03T10:30:21.854357Z","iopub.execute_input":"2025-12-03T10:30:21.854671Z","iopub.status.idle":"2025-12-03T10:30:30.658205Z","shell.execute_reply.started":"2025-12-03T10:30:21.854652Z","shell.execute_reply":"2025-12-03T10:30:30.657625Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## LANGKAH 3: Training Cepat (15 Epoch)\nMelatih model selama 15 epoch. Menggunakan Scheduler untuk mengatur kecepatan belajar agar hasil optimal.","metadata":{}},{"cell_type":"code","source":"NUM_TRAINING_IMAGES = 12753\nSTEPS_PER_EPOCH = NUM_TRAINING_IMAGES // BATCH_SIZE\nEPOCHS = 20 # Waktu optimal untuk tembus 70%\n\ncheckpoint = tf.keras.callbacks.ModelCheckpoint(\n    'best_model.keras', save_best_only=True, monitor='val_sparse_categorical_accuracy', mode='max'\n)\nlr_scheduler = tf.keras.callbacks.ReduceLROnPlateau(\n    monitor='val_loss', factor=0.5, patience=3, min_lr=1e-6, verbose=1\n)\n\nprint(\"🚀 Memulai Training (Target: Akurasi > 70%)...\")\n\nhistory = model.fit(\n    training_dataset,\n    steps_per_epoch=STEPS_PER_EPOCH,\n    epochs=EPOCHS,\n    validation_data=validation_dataset,\n    callbacks=[checkpoint, lr_scheduler]\n)\n\n# Plotting\ndef plot_history(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(1, len(acc) + 1)\n\n    plt.figure(figsize=(12, 5))\n    plt.subplot(1, 2, 1)\n    plt.plot(epochs, acc, 'r', label='Training Acc')\n    plt.plot(epochs, val_acc, 'b', label='Validation Acc')\n    plt.title('Accuracy')\n    plt.legend()\n    plt.subplot(1, 2, 2)\n    plt.plot(epochs, loss, 'r', label='Training Loss')\n    plt.plot(epochs, val_loss, 'b', label='Validation Loss')\n    plt.title('Loss')\n    plt.legend()\n    plt.show()\n\nplot_history(history)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-03T10:30:30.659551Z","iopub.execute_input":"2025-12-03T10:30:30.659775Z","iopub.status.idle":"2025-12-03T10:56:50.140714Z","shell.execute_reply.started":"2025-12-03T10:30:30.659751Z","shell.execute_reply":"2025-12-03T10:56:50.14009Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## LANGKAH 4: Evaluasi & Laporan Metrik\nMengukur kinerja model pada data validasi dan menampilkan laporan klasifikasi sesuai syarat rubrik.","metadata":{}},{"cell_type":"code","source":"print(\"Sedang mengevaluasi model terbaik...\")\ntry:\n    model.load_weights('best_model.keras')\n    print(\"✅ Model terbaik berhasil dimuat.\")\nexcept:\n    print(\"⚠️ Warning: Menggunakan model terakhir.\")\n\ny_true = []\ny_pred = []\n\nfor images, labels in validation_dataset:\n    y_true.extend(labels.numpy())\n    probs = model.predict(images, verbose=0)\n    y_pred.extend(np.argmax(probs, axis=-1))\n\nprint(classification_report(y_true, y_pred))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-03T10:56:50.141488Z","iopub.execute_input":"2025-12-03T10:56:50.141807Z","iopub.status.idle":"2025-12-03T10:58:14.526419Z","shell.execute_reply.started":"2025-12-03T10:56:50.141787Z","shell.execute_reply":"2025-12-03T10:58:14.525577Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## LANGKAH 5: Submission Kaggle\nMembuat file prediksi submission.csv untuk diserahkan ke Kaggle.","metadata":{}},{"cell_type":"code","source":"import csv\n\nprint('Sedang membuat file Submission...')\n\ndef read_test_tfrecord(example):\n    TEST_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, TEST_TFREC_FORMAT)\n    image = decode_image(example['image'])\n    idnum = example['id']\n    return image, idnum\n\nFILENAMES_TEST = tf.io.gfile.glob(DS_PATH + '/tfrecords-jpeg-224x224/test/*.tfrec')\ntest_dataset = tf.data.TFRecordDataset(FILENAMES_TEST, num_parallel_reads=tf.data.experimental.AUTOTUNE)\ntest_dataset = test_dataset.map(read_test_tfrecord, num_parallel_calls=tf.data.experimental.AUTOTUNE)\ntest_dataset = test_dataset.batch(BATCH_SIZE).prefetch(tf.data.experimental.AUTOTUNE)\n\ntest_ids = []\ntest_preds = []\n\nfor image, idnum in test_dataset:\n    test_ids.extend([x.decode('utf-8') for x in idnum.numpy()])\n    probs = model.predict(image, verbose=0)\n    test_preds.extend(np.argmax(probs, axis=-1))\n\n# Tulis ke CSV\nwith open('submission.csv', 'w', newline='') as f:\n    writer = csv.writer(f)\n    writer.writerow([\"id\", \"label\"])\n    for i in range(len(test_ids)):\n        writer.writerow([test_ids[i], test_preds[i]])\n\nprint(\"✅ SUKSES! File 'submission.csv' siap disubmit.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-03T10:58:14.527324Z","iopub.execute_input":"2025-12-03T10:58:14.527714Z","iopub.status.idle":"2025-12-03T11:00:04.379598Z","shell.execute_reply.started":"2025-12-03T10:58:14.527687Z","shell.execute_reply":"2025-12-03T11:00:04.378754Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Sesuai rubrik yang mensyaratkan Preprocessing & Pemilihan Fitur yang tepat, saya menerapkan teknik Feature Extraction tingkat lanjut menggunakan Transfer Learning (DenseNet201) untuk mengubah data citra mentah menjadi vektor fitur. Vektor fitur ini kemudian menjadi input bagi arsitektur Multi-Layer Perceptron (MLP) yang saya rancang (terdiri dari 2 Hidden Layers dengan 1024 dan 512 neuron) untuk melakukan tugas klasifikasi utama.","metadata":{}}]}