{"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":"tpuV5e8","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":"markdown","source":"## 12S23050 - Yolanda Septania Saragih\n## Pendahuluan\n\nKlasterisasi merupakan salah satu teknik unsupervised learning yang digunakan untuk mengelompokkan data berdasarkan kemiripan karakteristik tanpa menggunakan label yang telah diketahui sebelumnya. Pada praktikum ini, dilakukan proses klasterisasi pada rangkaian citra bunga dari kompetisi *Kaggle – TPU Getting Started*, yang terdiri dari 104 kelas bunga dengan variasi warna, bentuk, dan tekstur yang cukup kompleks.\n\nDataset disediakan dalam bentuk **TFRecord**, sehingga pemrosesan data dilakukan melalui pipeline TensorFlow, mulai dari decoding gambar, normalisasi piksel, hingga pembentukan batch data untuk pelatihan. Model yang digunakan adalah jaringan saraf tiruan (Multi-Layer Perceptron/MLP) yang dilatih pada TPU agar proses training lebih cepat dan efisien.\n\nUntuk menilai performa model, sejumlah metrik evaluasi digunakan, antara lain akurasi, presisi, recall, F1-score, serta AUC makro. Selain itu, model yang telah dilatih digunakan untuk melakukan prediksi pada data uji, dan hasil akhirnya disusun dalam format *submission.csv* untuk diunggah ke Kaggle.\n\nSecara keseluruhan, tujuan utama praktikum ini adalah memahami alur lengkap pembangunan model klasifikasi citra menggunakan TensorFlow di lingkungan TPU, mulai dari persiapan data, arsitektur model, strategi pelatihan, hingga evaluasi dan pembuatan berkas submission.\n","metadata":{}},{"cell_type":"markdown","source":"### A. Import Library","metadata":{}},{"cell_type":"code","source":"# =====================================================\n# Import modul yang digunakan dalam proyek klasifikasi\n# =====================================================\n\nfrom pathlib import Path\nimport os\n\n# --- Library untuk manipulasi data ---\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\n\n# --- TensorFlow dan Keras ---\nimport tensorflow as tf\nfrom tensorflow.keras import models, layers\n\n# --- Evaluation metrics dari Scikit-Learn ---\nfrom sklearn.preprocessing import label_binarize\nfrom sklearn.metrics import (\n    precision_score,\n    recall_score,\n    f1_score,\n    roc_auc_score,\n    confusion_matrix,\n    classification_report\n)\n\n# Informasi versi TensorFlow yang sedang digunakan\nprint(\"Versi TensorFlow saat ini:\", tf.__version__)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-04T02:31:38.043841Z","iopub.execute_input":"2025-12-04T02:31:38.044582Z","iopub.status.idle":"2025-12-04T02:31:38.050322Z","shell.execute_reply.started":"2025-12-04T02:31:38.044549Z","shell.execute_reply":"2025-12-04T02:31:38.049248Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### B. Konfigurasi direktori dataset dan parameter pemrosesan awal","metadata":{}},{"cell_type":"code","source":"# ============================================================\n# Konfigurasi direktori dataset dan parameter pemrosesan awal\n# ============================================================\n\nROOT_PATH = Path(\"/kaggle/input/tpu-getting-started\")\n\n# Resolusi gambar dan pengaturan batch\nUKURAN_GAMBAR = (192, 192)\nJUMLAH_BATCH = 16\nAUTOTUNE_OPS = tf.data.AUTOTUNE\n\n# Lokasi file TFRecord sesuai resolusi gambar\nDIREKTORI_TFREC = ROOT_PATH / f\"tfrecords-jpeg-{UKURAN_GAMBAR[0]}x{UKURAN_GAMBAR[1]}\"\n\n# Menampilkan daftar folder berisi TFRecord\nprint(\"Isi direktori TFRecord:\", os.listdir(DIREKTORI_TFREC))\n\n# Mengambil file TFRecord untuk train, validation, dan test\nFILE_TRAIN = tf.io.gfile.glob(str(DIREKTORI_TFREC / \"train/*.tfrec\"))\nFILE_VALID = tf.io.gfile.glob(str(DIREKTORI_TFREC / \"val/*.tfrec\"))\nFILE_TEST  = tf.io.gfile.glob(str(DIREKTORI_TFREC / \"test/*.tfrec\"))\n\n# Informasi jumlah file\nprint(\"Jumlah file train     :\", len(FILE_TRAIN))\nprint(\"Jumlah file validasi  :\", len(FILE_VALID))\nprint(\"Jumlah file test      :\", len(FILE_TEST))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-04T02:31:33.630954Z","iopub.execute_input":"2025-12-04T02:31:33.631555Z","iopub.status.idle":"2025-12-04T02:31:33.671482Z","shell.execute_reply.started":"2025-12-04T02:31:33.631528Z","shell.execute_reply":"2025-12-04T02:31:33.669949Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### C. TFRecord","metadata":{}},{"cell_type":"code","source":"# ============================================================\n# Konfigurasi jumlah kelas (total kategori bunga)\n# ============================================================\nJUMLAH_KELAS = 104\n\n# ============================================================\n# Fungsi untuk mendekode gambar dari TFRecord\n# ============================================================\ndef proses_gambar(raw_image):\n    \"\"\"Decode gambar JPG → resize → normalisasi piksel.\"\"\"\n    img = tf.image.decode_jpeg(raw_image, channels=3)\n    img = tf.image.resize(img, UKURAN_GAMBAR)\n    img = tf.cast(img, tf.float32) / 255.0\n    return img\n\n# ============================================================\n# Pemrosesan TFRecord dengan label\n# ============================================================\ndef parse_tfrecord_berlabel(record):\n    format_berlabel = {\n        \"image\": tf.io.FixedLenFeature([], tf.string),\n        \"class\": tf.io.FixedLenFeature([], tf.int64),\n    }\n    data = tf.io.parse_single_example(record, format_berlabel)\n    gambar = proses_gambar(data[\"image\"])\n    label = tf.cast(data[\"class\"], tf.int32)\n    return gambar, label\n\n# ============================================================\n# Pemrosesan TFRecord tanpa label (untuk test set)\n# ============================================================\ndef parse_tfrecord_tanpa_label(record):\n    format_tanpa = {\n        \"image\": tf.io.FixedLenFeature([], tf.string),\n        \"id\": tf.io.FixedLenFeature([], tf.string),\n    }\n    data = tf.io.parse_single_example(record, format_tanpa)\n    gambar = proses_gambar(data[\"image\"])\n    return gambar, data[\"id\"]\n\n# ============================================================\n# Load TFRecord menjadi dataset TensorFlow\n# ============================================================\ndef buka_dataset(daftar_file, berlabel=True, urut=False):\n    ops = tf.data.Options()\n    ops.experimental_deterministic = urut\n\n    ds = tf.data.TFRecordDataset(daftar_file, num_parallel_reads=AUTOTUNE_OPS)\n    ds = ds.with_options(ops)\n\n    fungsi_parser = parse_tfrecord_berlabel if berlabel else parse_tfrecord_tanpa_label\n    ds = ds.map(fungsi_parser, num_parallel_calls=AUTOTUNE_OPS)\n    \n    return ds\n\n# ============================================================\n# Dataset Wrapper: batching, shuffle, prefetch\n# ============================================================\ndef siapkan_dataset(file_list, berlabel=True, urut=False, acak=False):\n    dataset = buka_dataset(file_list, berlabel=berlabel, urut=urut)\n    \n    if acak:\n        dataset = dataset.shuffle(2048)\n    \n    dataset = dataset.batch(JUMLAH_BATCH)\n    dataset = dataset.prefetch(AUTOTUNE_OPS)\n    \n    return dataset\n\n# Dataset final: train, val, test\ntrain_ds = siapkan_dataset(FILE_TRAIN, berlabel=True, acak=True)\nvalid_ds = siapkan_dataset(FILE_VALID, berlabel=True)\ntest_ds  = siapkan_dataset(FILE_TEST, berlabel=False)\n\nprint(\"Dataset train siap digunakan:\", train_ds)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-03T15:51:16.860219Z","iopub.execute_input":"2025-12-03T15:51:16.860750Z","iopub.status.idle":"2025-12-03T15:51:17.151469Z","shell.execute_reply.started":"2025-12-03T15:51:16.860716Z","shell.execute_reply":"2025-12-03T15:51:17.150304Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Model MLP","metadata":{}},{"cell_type":"code","source":"# ============================================================\n# Reset session TensorFlow untuk menghindari konflik model\n# ============================================================\ntf.keras.backend.clear_session()\n\n# ============================================================\n# Konfigurasi layer hidden dan dropout\n# ============================================================\nUK_H1 = 1024\nUK_H2 = 512\nUK_H3 = 256\n\nDRP_1 = 0.3\nDRP_2 = 0.3\nDRP_3 = 0.2\n\nLRATE = 5e-4\nJUMLAH_EPOCH = 5\n\n# ============================================================\n# Bangun arsitektur model MLP berbasis gambar\n# ============================================================\ninput_gambar = layers.Input(shape=(UKURAN_GAMBAR[0], UKURAN_GAMBAR[1], 3))\n\nz = layers.Flatten()(input_gambar)\n\nz = layers.Dense(UK_H1, activation=\"relu\")(z)\nz = layers.BatchNormalization()(z)\nz = layers.Dropout(DRP_1)(z)\n\nz = layers.Dense(UK_H2, activation=\"relu\")(z)\nz = layers.BatchNormalization()(z)\nz = layers.Dropout(DRP_2)(z)\n\nz = layers.Dense(UK_H3, activation=\"relu\")(z)\nz = layers.BatchNormalization()(z)\nz = layers.Dropout(DRP_3)(z)\n\n# Output klasifikasi\nkeluaran = layers.Dense(JUMLAH_KELAS, activation=\"softmax\")(z)\n\n# Model final\nmodel_mlp = models.Model(inputs=input_gambar, outputs=keluaran)\n\n# ============================================================\n# Kompilasi model dengan Adam dan Sparse Categorical Loss\n# ============================================================\nmodel_mlp.compile(\n    optimizer=tf.keras.optimizers.Adam(learning_rate=LRATE),\n    loss=\"sparse_categorical_crossentropy\",\n    metrics=[\"accuracy\"]\n)\n\n# Ringkasan arsitektur\nmodel_mlp.summary()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-03T15:56:10.852140Z","iopub.execute_input":"2025-12-03T15:56:10.852494Z","iopub.status.idle":"2025-12-03T15:56:11.980045Z","shell.execute_reply.started":"2025-12-03T15:56:10.852471Z","shell.execute_reply":"2025-12-03T15:56:11.979157Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### E. Proses Training","metadata":{}},{"cell_type":"code","source":"# ============================================================\n# Konfigurasi callback untuk proses pelatihan\n# ============================================================\n\ncallback_list = [\n    tf.keras.callbacks.EarlyStopping(\n        monitor=\"val_loss\",\n        patience=3,\n        restore_best_weights=True,\n        verbose=1\n    ),\n    \n    tf.keras.callbacks.ReduceLROnPlateau(\n        monitor=\"val_loss\",\n        factor=0.5,\n        patience=2,\n        verbose=1\n    ),\n]\n\n# ============================================================\n# Proses training model MLP dengan dataset train & validasi\n# ============================================================\n\nriwayat_training = model_mlp.fit(\n    train_ds,\n    validation_data=valid_ds,\n    epochs=JUMLAH_EPOCH,\n    callbacks=callback_list,\n    verbose=1\n)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-03T15:56:20.010477Z","iopub.execute_input":"2025-12-03T15:56:20.010929Z","iopub.status.idle":"2025-12-03T16:41:51.795809Z","shell.execute_reply.started":"2025-12-03T15:56:20.010896Z","shell.execute_reply":"2025-12-03T16:41:51.794449Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### F. Plot Loss dan Accuracy","metadata":{}},{"cell_type":"code","source":"# ============================================================\n# Ekstraksi riwayat training dari objek history\n# ============================================================\nlog_data = riwayat_training.history\nepoch_range = range(1, len(log_data[\"loss\"]) + 1)\n\n# ============================================================\n# Plot grafik perkembangan Loss dan Akurasi\n# ============================================================\nplt.figure(figsize=(15, 5))\n\n# ---------------- LOSS ----------------\nplt.subplot(1, 2, 1)\nplt.plot(epoch_range, log_data[\"loss\"], label=\"Loss - Train\")\nplt.plot(epoch_range, log_data[\"val_loss\"], label=\"Loss - Validasi\")\nplt.title(\"Perkembangan Nilai Loss\")\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"Loss\")\nplt.grid(True)\nplt.legend()\n\n# ---------------- ACCURACY ----------------\nplt.subplot(1, 2, 2)\nplt.plot(epoch_range, log_data[\"accuracy\"], label=\"Akurasi - Train\")\nplt.plot(epoch_range, log_data[\"val_accuracy\"], label=\"Akurasi - Validasi\")\nplt.title(\"Perkembangan Akurasi\")\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"Akurasi\")\nplt.grid(True)\nplt.legend()\n\nplt.tight_layout()\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-03T16:45:52.540567Z","iopub.execute_input":"2025-12-03T16:45:52.542305Z","iopub.status.idle":"2025-12-03T16:45:53.136616Z","shell.execute_reply.started":"2025-12-03T16:45:52.542255Z","shell.execute_reply":"2025-12-03T16:45:53.135172Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### G. Evaluasi model","metadata":{}},{"cell_type":"code","source":"# ============================================================\n# Ekstraksi prediksi dan label asli dari validation dataset\n# ============================================================\n\nlabel_asli = []\npred_prob_list = []\n\nfor batch_x, batch_y in valid_ds:\n    prob = model_mlp.predict(batch_x, verbose=0)\n    pred_prob_list.append(prob)\n    label_asli.append(batch_y.numpy())\n\n# Gabungkan seluruh batch\nlabel_asli = np.concatenate(label_asli)\npred_prob_list = np.concatenate(pred_prob_list)\n\n# Prediksi kelas berdasarkan probabilitas tertinggi\npred_label = np.argmax(pred_prob_list, axis=1)\n\n# ============================================================\n# Perhitungan evaluasi akurasi, presisi, recall, F1-score\n# ============================================================\n\nakurasi_model = np.mean(pred_label == label_asli)\npresisi_model = precision_score(label_asli, pred_label, average=\"macro\", zero_division=0)\nrecall_model   = recall_score(label_asli, pred_label, average=\"macro\", zero_division=0)\nf1_model       = f1_score(label_asli, pred_label, average=\"macro\", zero_division=0)\n\nprint(\"Akurasi   :\", akurasi_model)\nprint(\"Presisi   :\", presisi_model)\nprint(\"Recall    :\", recall_model)\nprint(\"F1-Score  :\", f1_model)\n\n# ============================================================\n# Hitung nilai AUC dengan teknik one-vs-rest\n# ============================================================\n\nlabel_asli_bin = label_binarize(label_asli, classes=list(range(JUMLAH_KELAS)))\nauc_macro = roc_auc_score(label_asli_bin, pred_prob_list, multi_class=\"ovr\")\n\nprint(\"AUC-Macro :\", auc_macro)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-03T16:45:56.918653Z","iopub.execute_input":"2025-12-03T16:45:56.919000Z","iopub.status.idle":"2025-12-03T16:46:19.968021Z","shell.execute_reply.started":"2025-12-03T16:45:56.918970Z","shell.execute_reply":"2025-12-03T16:46:19.966868Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### E. Submission","metadata":{}},{"cell_type":"code","source":"# ============================================================\n# Prediksi kelas untuk dataset test dan pengumpulan hasil\n# ============================================================\n\ndaftar_id = []\ndaftar_label = []\n\nfor batch_img, batch_id in test_ds:\n    prob_pred = model_mlp.predict(batch_img, verbose=0)\n    klas_pred = np.argmax(prob_pred, axis=1)\n\n    # Konversi byte string → string biasa\n    batch_id = [x.decode(\"utf-8\") for x in batch_id.numpy()]\n\n    daftar_id.extend(batch_id)\n    daftar_label.extend(klas_pred)\n\n# ============================================================\n# Membentuk DataFrame hasil prediksi\n# ============================================================\n\nhasil_submit = pd.DataFrame({\n    \"id\": daftar_id,\n    \"label\": daftar_label\n})\n\n# Menyimpan file CSV untuk diunggah ke Kaggle\nhasil_submit.to_csv(\"submission.csv\", index=False)\n\nhasil_submit.head()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-03T16:46:38.532574Z","iopub.execute_input":"2025-12-03T16:46:38.532938Z","iopub.status.idle":"2025-12-03T16:47:23.763865Z","shell.execute_reply.started":"2025-12-03T16:46:38.532909Z","shell.execute_reply":"2025-12-03T16:47:23.762749Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### F. Ringkasan hasil evaluasi dan konfirmasi pembuatan submission","metadata":{}},{"cell_type":"code","source":"# ============================================================\n# Ringkasan hasil evaluasi dan konfirmasi pembuatan submission\n# ============================================================\n\nprint(\"=== RINGKASAN HASIL EVALUASI ===\")\nprint(f\"Akurasi   : {akurasi_model}\")\nprint(f\"F1-Score  : {f1_model}\")\nprint(f\"AUC-Macro : {auc_macro}\")\n\nprint(\"\\nFile 'submission.csv' berhasil dibuat!\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-03T16:47:57.811455Z","iopub.execute_input":"2025-12-03T16:47:57.811802Z","iopub.status.idle":"2025-12-03T16:47:57.817840Z","shell.execute_reply.started":"2025-12-03T16:47:57.811781Z","shell.execute_reply":"2025-12-03T16:47:57.816864Z"}},"outputs":[],"execution_count":null}]}