{"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":"markdown","source":"# Flower Classification Using Multi-Layer Perceptron (MLP)","metadata":{}},{"cell_type":"markdown","source":"## 1. Import Library","metadata":{}},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport tensorflow as tf\nfrom tensorflow.keras.models import Sequential\nfrom tensorflow.keras.layers import Dense, Flatten, Dropout, BatchNormalization, Input\nfrom tensorflow.keras.optimizers import Adam\nfrom tensorflow.keras.callbacks import EarlyStopping\nimport matplotlib.pyplot as plt\nfrom sklearn.metrics import f1_score\nfrom kaggle_datasets import KaggleDatasets\n\nprint(\"TensorFlow:\", tf.__version__)\nprint(\"Devices:\", tf.config.list_physical_devices())\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-03T13:20:54.737938Z","iopub.execute_input":"2025-12-03T13:20:54.738145Z","iopub.status.idle":"2025-12-03T13:20:54.743566Z","shell.execute_reply.started":"2025-12-03T13:20:54.738128Z","shell.execute_reply":"2025-12-03T13:20:54.742937Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 2. Konfigurasi & Path Dataset\n","metadata":{}},{"cell_type":"code","source":"AUTO = tf.data.AUTOTUNE\nIMAGE_SIZE = [192, 192]\nNUM_CLASSES = 104\n\nGCS_DS_PATH = KaggleDatasets().get_gcs_path('tpu-getting-started')\n\nGCS_PATH_SELECT = {\n    192: GCS_DS_PATH + '/tfrecords-jpeg-192x192',\n    224: GCS_DS_PATH + '/tfrecords-jpeg-224x224',\n    331: GCS_DS_PATH + '/tfrecords-jpeg-331x331',\n    512: GCS_DS_PATH + '/tfrecords-jpeg-512x512'\n}\n\nGCS_PATH = GCS_PATH_SELECT[IMAGE_SIZE[0]]\n\nTRAINING_FILENAMES   = tf.io.gfile.glob(GCS_PATH + '/train/*.tfrec')\nVALIDATION_FILENAMES = tf.io.gfile.glob(GCS_PATH + '/val/*.tfrec')\nTEST_FILENAMES       = tf.io.gfile.glob(GCS_PATH + '/test/*.tfrec')\n\nlen(TRAINING_FILENAMES), len(VALIDATION_FILENAMES), len(TEST_FILENAMES)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-03T13:20:54.744571Z","iopub.execute_input":"2025-12-03T13:20:54.744802Z","iopub.status.idle":"2025-12-03T13:20:55.090466Z","shell.execute_reply.started":"2025-12-03T13:20:54.744787Z","shell.execute_reply":"2025-12-03T13:20:55.089886Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 3. TFRecord Loader","metadata":{}},{"cell_type":"code","source":"def decode_image(image_data):\n    image = tf.image.decode_jpeg(image_data, channels=3)\n    image = tf.image.resize(image, IMAGE_SIZE)\n    image = tf.cast(image, tf.float32) / 255.0\n    return image\n\ndef read_labeled_tfrecord(example):\n    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, 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    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, tfrec_format)\n    image = decode_image(example['image'])\n    image_id = example['id']\n    return image, image_id\n\ndef load_dataset(filenames, labeled=True, ordered=False):\n    options = tf.data.Options()\n    if not ordered:\n        options.experimental_deterministic = False\n\n    ds = tf.data.TFRecordDataset(filenames, num_parallel_reads=AUTO)\n    ds = ds.with_options(options)\n    ds = ds.map(\n        read_labeled_tfrecord if labeled else read_unlabeled_tfrecord,\n        num_parallel_calls=AUTO\n    )\n    return ds\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-03T13:20:55.091102Z","iopub.execute_input":"2025-12-03T13:20:55.091289Z","iopub.status.idle":"2025-12-03T13:20:55.09817Z","shell.execute_reply.started":"2025-12-03T13:20:55.091275Z","shell.execute_reply":"2025-12-03T13:20:55.097543Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 4. Membuat Dataset Train, Validation, dan Test\n\n- Train dataset:\n  - Shuffle, repeat, batch, prefetch.\n- Validation dataset:\n  - Batch, cache, prefetch.\n- Test dataset:\n  - Batch, prefetch.","metadata":{}},{"cell_type":"code","source":"BATCH_SIZE = 64\n\ndef get_training_dataset():\n    ds = load_dataset(TRAINING_FILENAMES, labeled=True)\n    ds = ds.shuffle(2048)\n    ds = ds.repeat()\n    ds = ds.batch(BATCH_SIZE)\n    return ds.prefetch(AUTO)\n\ndef get_validation_dataset():\n    ds = load_dataset(VALIDATION_FILENAMES, labeled=True, ordered=True)\n    ds = ds.batch(BATCH_SIZE).cache()\n    return ds.prefetch(AUTO)\n\ndef get_test_dataset():\n    ds = load_dataset(TEST_FILENAMES, labeled=False, ordered=True)\n    ds = ds.batch(BATCH_SIZE)\n    return ds.prefetch(AUTO)\n\ntrain_ds = get_training_dataset()\nval_ds   = get_validation_dataset()\ntest_ds  = get_test_dataset()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-03T13:20:55.098929Z","iopub.execute_input":"2025-12-03T13:20:55.099159Z","iopub.status.idle":"2025-12-03T13:20:55.216842Z","shell.execute_reply.started":"2025-12-03T13:20:55.099144Z","shell.execute_reply":"2025-12-03T13:20:55.216059Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 5. Visual Check","metadata":{}},{"cell_type":"code","source":"def show_samples(ds, n=9):\n    batch = next(iter(ds.unbatch().batch(n)))\n    images, labels = batch\n\n    plt.figure(figsize=(10,10))\n    for i in range(n):\n        plt.subplot(3,3,i+1)\n        plt.imshow(images[i].numpy())\n        plt.title(f\"Label: {labels[i].numpy()}\", fontsize=9)\n        plt.axis(\"off\")\n    plt.show()\n\nshow_samples(train_ds)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-03T13:20:55.217757Z","iopub.execute_input":"2025-12-03T13:20:55.218466Z","iopub.status.idle":"2025-12-03T13:20:57.133624Z","shell.execute_reply.started":"2025-12-03T13:20:55.218434Z","shell.execute_reply":"2025-12-03T13:20:57.13254Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 6. Arsitektur MLP","metadata":{}},{"cell_type":"code","source":"model = Sequential([\n    Input(shape=(192,192,3)),\n    Flatten(),\n\n    Dense(1024, activation='relu'),\n    BatchNormalization(),\n    Dropout(0.3),\n\n    Dense(512, activation='relu'),\n    BatchNormalization(),\n    Dropout(0.3),\n\n    Dense(256, activation='relu'),\n    Dropout(0.3),\n\n    Dense(NUM_CLASSES, activation='softmax')\n])\n\nmodel.summary()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-03T13:20:57.134984Z","iopub.execute_input":"2025-12-03T13:20:57.135326Z","iopub.status.idle":"2025-12-03T13:20:57.198489Z","shell.execute_reply.started":"2025-12-03T13:20:57.135298Z","shell.execute_reply":"2025-12-03T13:20:57.197914Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 7. Compile Model (Hyperparameter)","metadata":{}},{"cell_type":"code","source":"LEARNING_RATE = 5e-4\nEPOCHS = 25\n\nmodel.compile(\n    optimizer=Adam(learning_rate=LEARNING_RATE),\n    loss='sparse_categorical_crossentropy',\n    metrics=['accuracy']\n)\n\nearly_stop = EarlyStopping(\n    monitor='val_loss',\n    patience=5,\n    restore_best_weights=True\n)\n\nhistory = model.fit(\n    train_ds,\n    validation_data=val_ds,\n    epochs=EPOCHS,\n    steps_per_epoch=len(TRAINING_FILENAMES),\n    validation_steps=len(VALIDATION_FILENAMES),\n    callbacks=[early_stop],\n    verbose=1\n)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-03T13:20:57.199165Z","iopub.execute_input":"2025-12-03T13:20:57.199513Z","iopub.status.idle":"2025-12-03T13:21:45.656762Z","shell.execute_reply.started":"2025-12-03T13:20:57.199496Z","shell.execute_reply":"2025-12-03T13:21:45.656112Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 8. Prediksi Test & Submission","metadata":{}},{"cell_type":"code","source":"test_ids = []\ntest_pred = []\n\nfor images, ids in test_ds:\n    probs = model.predict(images, verbose=0)\n    labels = np.argmax(probs, axis=1)\n    test_pred.extend(labels)\n    test_ids.extend(ids.numpy().astype(str))\n\nsubmission = pd.DataFrame({\n    'id': test_ids,\n    'label': test_pred\n})\n\nsubmission.to_csv('submission.csv', index=False)\nsubmission.head()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-03T13:21:45.657689Z","iopub.execute_input":"2025-12-03T13:21:45.658028Z","iopub.status.idle":"2025-12-03T13:21:57.055354Z","shell.execute_reply.started":"2025-12-03T13:21:45.658007Z","shell.execute_reply":"2025-12-03T13:21:57.054732Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 9. Visualisasi Loss & Accuracy","metadata":{}},{"cell_type":"code","source":"plt.figure(figsize=(13,5))\n\n# Plot Loss\nplt.subplot(1,2,1)\nplt.plot(history.history[\"loss\"], label=\"Train Loss\")\nplt.plot(history.history[\"val_loss\"], label=\"Val Loss\")\nplt.title(\"Loss vs Epoch\")\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"Loss\")\nplt.legend()\n\n# Plot Accuracy\nplt.subplot(1,2,2)\nplt.plot(history.history[\"accuracy\"], label=\"Train Accuracy\")\nplt.plot(history.history[\"val_accuracy\"], label=\"Val Accuracy\")\nplt.title(\"Accuracy vs Epoch\")\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"Accuracy\")\nplt.legend()\n\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-03T13:21:57.056239Z","iopub.execute_input":"2025-12-03T13:21:57.05653Z","iopub.status.idle":"2025-12-03T13:21:57.382287Z","shell.execute_reply.started":"2025-12-03T13:21:57.05651Z","shell.execute_reply":"2025-12-03T13:21:57.381654Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 10. Evaluasi Macro F1 di Validation","metadata":{}},{"cell_type":"code","source":"from sklearn.metrics import f1_score\nimport numpy as np\n\n# Ambil seluruh data validation ke numpy\nval_images = []\nval_labels = []\n\nfor images, labels in val_ds:\n    val_images.append(images.numpy())\n    val_labels.append(labels.numpy())\n\nval_images = np.concatenate(val_images)\nval_labels = np.concatenate(val_labels)\n\n# Prediksi\nval_probs = model.predict(val_images, batch_size=32)\nval_preds = np.argmax(val_probs, axis=1)\n\nmacro_f1 = f1_score(val_labels, val_preds, average=\"macro\")\nprint(\"Validation Macro F1 Score:\", macro_f1)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-03T13:21:57.383035Z","iopub.execute_input":"2025-12-03T13:21:57.383243Z","iopub.status.idle":"2025-12-03T13:22:04.482267Z","shell.execute_reply.started":"2025-12-03T13:21:57.383225Z","shell.execute_reply":"2025-12-03T13:22:04.481455Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 11. Visualisasi Prediksi Model","metadata":{}},{"cell_type":"code","source":"def visualize_test_prediction(ds, model, n=9):\n    # Ambil n gambar benar dari dataset tanpa hilang bentuk\n    batch = next(iter(ds.take(1)))\n    images, ids = batch\n\n    # Ambil hanya n gambar pertama\n    images = images[:n]\n    ids = ids[:n]\n\n    # Prediksi\n    preds = model.predict(images)\n    labels = np.argmax(preds, axis=1)\n\n    plt.figure(figsize=(12,12))\n    for i in range(n):\n        plt.subplot(3,3,i+1)\n\n        # Convert float (0–1) ke uint8 agar bisa dilihat\n        img = (images[i].numpy() * 255).astype(\"uint8\")\n\n        plt.imshow(img)\n        plt.title(f\"Pred: {labels[i]}\")\n        plt.axis(\"off\")\n\n    plt.show()\n\n# Panggil\nvisualize_test_prediction(test_ds, model)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-03T13:27:51.997505Z","iopub.execute_input":"2025-12-03T13:27:51.998109Z","iopub.status.idle":"2025-12-03T13:27:53.382566Z","shell.execute_reply.started":"2025-12-03T13:27:51.998083Z","shell.execute_reply":"2025-12-03T13:27:53.381487Z"}},"outputs":[],"execution_count":null}]}