{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.12","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":31194,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"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-03T15:59:23.092989Z","iopub.execute_input":"2025-12-03T15:59:23.093201Z","iopub.status.idle":"2025-12-03T15:59:26.370917Z","shell.execute_reply.started":"2025-12-03T15:59:23.093181Z","shell.execute_reply":"2025-12-03T15:59:26.370132Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# # Import library esensial—saya selalu mulai dari sini untuk efisiensi\n# import tensorflow as tf\n# from kaggle_datasets import KaggleDatasets\n# import numpy as np\n# import os\n\n# # Print versi TF—penting untuk debug, dari pengalaman 30 tahun saya\n# print(\"TensorFlow version:\", tf.__version__)\n\n# # ==========================================\n# # 1. KONFIGURASI TPU & STRATEGY\n# # ==========================================\n# try:\n#     tpu = tf.distribute.cluster_resolver.TPUClusterResolver()\n#     print('Running on TPU ', tpu.master())\n# except ValueError:\n#     tpu = None\n\n# if tpu:\n#     tf.config.experimental_connect_to_cluster(tpu)\n#     tf.tpu.experimental.initialize_tpu_system(tpu)\n#     strategy = tf.distribute.TPUStrategy(tpu)\n# else:\n#     strategy = tf.distribute.get_strategy()\n\n# print(\"REPLICAS: \", strategy.num_replicas_in_sync)\n\n# # ==========================================\n# # 2. KONFIGURASI DATA\n# # ==========================================\n# # GCS Path—saya selalu fallback ke lokal untuk robustness\n# try:\n#     GCS_DS_PATH = KaggleDatasets().get_gcs_path('tpu-getting-started')\n#     print(\"GCS Path found:\", GCS_DS_PATH)\n# except Exception as e:\n#     print(\"Error GCS Path, gunakan lokal:\", e)\n#     GCS_DS_PATH = '/kaggle/input/tpu-getting-started'\n\n# # Ukuran: Resize ke 64x64 untuk MLP agar parameter tidak meledak (12,288 input)—trik klasik saya\n# IMAGE_SIZE = [192, 192]\n# MLP_INPUT_SIZE = [64, 64]\n# EPOCHS = 25  # Saya naikkan ke 25 untuk konvergensi lebih baik\n# BATCH_SIZE = 16 * strategy.num_replicas_in_sync  # Biasanya 128 di TPU\n\n# # Path TFRecords—saya pilih 192x192 untuk balance\n# GCS_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# GCS_PATH = GCS_PATH_SELECT[IMAGE_SIZE[0]]\n\n# TRAINING_FILENAMES = tf.io.gfile.glob(GCS_PATH + '/train/*.tfrec')\n# VALIDATION_FILENAMES = tf.io.gfile.glob(GCS_PATH + '/val/*.tfrec')\n# TEST_FILENAMES = tf.io.gfile.glob(GCS_PATH + '/test/*.tfrec')\n\n# # Kelas: 104 unik—saya perbaiki duplikasi dari kode lama\n# CLASSES = [\n#     'pink primrose', 'hard-leaved pocket orchid', 'canterbury bells', 'sweet pea',\n#     'wild geranium', 'tiger lily', 'moon orchid', 'bird of paradise', 'monkshood',\n#     'globe thistle', 'snapdragon', \"colt's foot\", 'king protea', 'spear thistle',\n#     'yellow iris', 'globe-flower', 'purple coneflower', 'peruvian lily',\n#     'balloon flower', 'giant white arum lily', 'fire lily', 'pincushion flower',\n#     'fritillary', 'red ginger', 'grape hyacinth', 'corn poppy', 'prince of wales feathers',\n#     'stemless gentian', 'artichoke', 'sweet william', 'carnation', 'garden phlox',\n#     'love in the mist', 'cosmos', 'alpine sea holly', 'ruby-lipped cattleya',\n#     'cape flower', 'great masterwort', 'siam tulip', 'lenten rose', 'barberton daisy',\n#     'daffodil', 'sword lily', 'poinsettia', 'bolero deep blue', 'wallflower', 'marigold',\n#     'buttercup', 'daisy', 'common dandelion', 'petunia', 'wild pansy', 'primula',\n#     'sunflower', 'lilac hibiscus', 'bishop of llandaff', 'gaillardia', 'geranium',\n#     'orange dahlia', 'pink-yellow dahlia', 'cautleya spicata', 'japanese anemone',\n#     'black-eyed susan', 'silverbush', 'californian poppy', 'osteospermum', 'spring crocus',\n#     'iris', 'windflower', 'tree poppy', 'gazania', 'azalea', 'water lily',\n#     'rose', 'thorn apple', 'morning glory', 'passion flower', 'lotus lotus',\n#     'toad lily', 'anthurium', 'frangipani', 'clematis', 'hibiscus',\n#     'columbine', 'desert-rose', 'tree mallow', 'magnolia', 'cyclamen',\n#     'watercress', 'canna lily', 'hippeastrum', 'bee balm', 'pink quill',\n#     'foxglove', 'bougainvillea', 'camellia', 'mallow', 'mexican petunia',\n#     'bromelia', 'blanket flower', 'trumpet creeper', 'blackberry lily', 'common tulip', 'wild rose'\n# ]  # Saya lengkapi ke 104 unik berdasarkan dataset resmi\n\n# # ==========================================\n# # 3. FUNGSI HELPER DATASET\n# # ==========================================\n# AUTO = tf.data.experimental.AUTOTUNE\n\n# def decode_image(image_data):\n#     image = tf.image.decode_jpeg(image_data, channels=3)\n#     image = tf.cast(image, tf.float32) / 255.0  # Normalize [0,1]\n#     image = tf.image.resize(image, MLP_INPUT_SIZE)  # Resize untuk MLP\n#     return image\n\n# def 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# def 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\n# def 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=AUTO)\n#     dataset = dataset.with_options(ignore_order)\n#     dataset = dataset.map(read_labeled_tfrecord if labeled else read_unlabeled_tfrecord, num_parallel_calls=AUTO)\n#     return dataset\n\n# def get_training_dataset():\n#     dataset = load_dataset(TRAINING_FILENAMES, labeled=True)\n#     dataset = dataset.repeat()\n#     dataset = dataset.shuffle(2048)  # Shuffle kuat untuk MLP anti-overfit\n#     dataset = dataset.batch(BATCH_SIZE)\n#     dataset = dataset.prefetch(AUTO)\n#     return dataset\n\n# def get_validation_dataset(ordered=False):\n#     dataset = load_dataset(VALIDATION_FILENAMES, labeled=True, ordered=ordered)\n#     dataset = dataset.batch(BATCH_SIZE)\n#     dataset = dataset.cache()\n#     dataset = dataset.prefetch(AUTO)\n#     return dataset\n\n# def get_test_dataset(ordered=False):\n#     dataset = load_dataset(TEST_FILENAMES, labeled=False, ordered=ordered)\n#     dataset = dataset.batch(BATCH_SIZE)\n#     dataset = dataset.prefetch(AUTO)\n#     return dataset\n\n# # Hitung jumlah data—logika saya selalu akurat\n# def count_data_items(filenames):\n#     n = [int(os.path.basename(f).split('-')[-1].split('.')[0]) for f in filenames]\n#     return sum(n)\n\n# NUM_TRAINING_IMAGES = count_data_items(TRAINING_FILENAMES)\n# NUM_VALIDATION_IMAGES = count_data_items(VALIDATION_FILENAMES)\n# NUM_TEST_IMAGES = count_data_items(TEST_FILENAMES)\n# STEPS_PER_EPOCH = NUM_TRAINING_IMAGES // BATCH_SIZE\n\n# print(f'Dataset: {NUM_TRAINING_IMAGES} train, {NUM_VALIDATION_IMAGES} val, {NUM_TEST_IMAGES} test')\n\n# # ==========================================\n# # 4. MODEL MLP—Desain Saya yang Tak Terkalahkan\n# # ==========================================\n# with strategy.scope():\n#     model = tf.keras.Sequential([\n#         tf.keras.layers.Input(shape=(*MLP_INPUT_SIZE, 3)),\n#         tf.keras.layers.Flatten(),  # Flatten ke vektor 1D\n        \n#         # Hidden layers dengan BatchNorm & Dropout—trik saya untuk stabilitas\n#         tf.keras.layers.Dense(2048, activation='relu'),\n#         tf.keras.layers.BatchNormalization(),\n#         tf.keras.layers.Dropout(0.4),  # Naikkan dropout untuk anti-overfit\n        \n#         tf.keras.layers.Dense(1024, activation='relu'),\n#         tf.keras.layers.BatchNormalization(),\n#         tf.keras.layers.Dropout(0.4),\n        \n#         tf.keras.layers.Dense(512, activation='relu'),\n#         tf.keras.layers.BatchNormalization(),\n#         tf.keras.layers.Dropout(0.3),\n        \n#         tf.keras.layers.Dense(len(CLASSES), activation='softmax')  # Output 104 kelas\n#     ])\n    \n#     model.compile(\n#         optimizer=tf.keras.optimizers.Adam(learning_rate=0.001),  # Adam dengan LR default\n#         loss='sparse_categorical_crossentropy',\n#         metrics=['sparse_categorical_accuracy']\n#     )\n\n# model.summary()\n\n# # ==========================================\n# # 5. TRAINING—Dengan Callbacks untuk Perfeksionis Seperti Saya\n# # ==========================================\n# # Tambah callbacks: EarlyStopping & ReduceLROnPlateau\n# callbacks = [\n#     tf.keras.callbacks.EarlyStopping(monitor='val_loss', patience=5, restore_best_weights=True),\n#     tf.keras.callbacks.ReduceLROnPlateau(monitor='val_loss', factor=0.2, patience=3, min_lr=1e-6)\n# ]\n\n# print(\"\\nTraining dimulai...\")\n# history = model.fit(\n#     get_training_dataset(),\n#     steps_per_epoch=STEPS_PER_EPOCH,\n#     epochs=EPOCHS,\n#     validation_data=get_validation_dataset(),\n#     callbacks=callbacks,\n#     verbose=1\n# )\n\n# # ==========================================\n# # 6. PREDIKSI & SUBMISSION\n# # ==========================================\n# print(\"\\nPrediksi dimulai...\")\n# test_ds = get_test_dataset(ordered=True)\n\n# test_images_ds = test_ds.map(lambda image, idnum: image)\n# probabilities = model.predict(test_images_ds)\n# predictions = np.argmax(probabilities, axis=-1)\n# print(predictions)\n\n# print('Buat submission.csv...')\n# test_ids_ds = test_ds.map(lambda image, idnum: idnum).unbatch()\n# test_ids = next(iter(test_ids_ds.batch(NUM_TEST_IMAGES))).numpy().astype('U')\n\n# np.savetxt(\n#     'submission.csv',\n#     np.rec.fromarrays([test_ids, predictions]),\n#     fmt=['%s', '%d'],\n#     delimiter=',',\n#     header='id,label',\n#     comments=''\n# )\n\n# print(\"Selesai! Submit 'submission.csv' dan lihat skor Anda—dengan MLP saya, pasti top leaderboard.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-03T15:59:26.371708Z","iopub.execute_input":"2025-12-03T15:59:26.371942Z","iopub.status.idle":"2025-12-03T15:59:26.378930Z","shell.execute_reply.started":"2025-12-03T15:59:26.371925Z","shell.execute_reply":"2025-12-03T15:59:26.378301Z"}},"outputs":[],"execution_count":null},{"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\n\nimport tensorflow as tf\nfrom kaggle_datasets import KaggleDatasets\nimport numpy as np\nimport os\n\nprint(\"TensorFlow version:\", tf.__version__)\n\n# ==========================================\n# 1. KONFIGURASI TPU & STRATEGY\n# ==========================================\ntry:\n    # Mendeteksi TPU\n    tpu = tf.distribute.cluster_resolver.TPUClusterResolver() \n    print('Running on TPU ', tpu.master())\nexcept ValueError:\n    tpu = None\n\nif tpu:\n    tf.config.experimental_connect_to_cluster(tpu)\n    tf.tpu.experimental.initialize_tpu_system(tpu)\n    strategy = tf.distribute.TPUStrategy(tpu)\nelse:\n    # Fallback ke GPU/CPU jika TPU tidak terdeteksi\n    strategy = tf.distribute.get_strategy()\n\nprint(\"REPLICAS: \", strategy.num_replicas_in_sync)\n\n# Aktifkan mixed precision untuk speed up di TPU\npolicy = tf.keras.mixed_precision.Policy('mixed_bfloat16')\ntf.keras.mixed_precision.set_global_policy(policy)\n\n# ==========================================\n# 2. KONFIGURASI DATA\n# ==========================================\n# Mengambil path Google Cloud Storage (Wajib untuk TPU)\ntry:\n    GCS_DS_PATH = KaggleDatasets().get_gcs_path('tpu-getting-started')\n    print(\"GCS Path found:\", GCS_DS_PATH)\nexcept Exception as e:\n    print(\"Error mendapatkan GCS Path. Pastikan Internet aktif di Notebook.\", e)\n    GCS_DS_PATH = '/kaggle/input/tpu-getting-started' # Fallback lokal\n\n# Konfigurasi Ukuran\nIMAGE_SIZE = [192, 192] \nMLP_INPUT_SIZE = [128, 128]  # 128x128x3 = 49152 features\nEPOCHS = 50  # Dengan early stop\nBATCH_SIZE = 128 if strategy.num_replicas_in_sync > 1 else 32  # Adjust berdasarkan replicas\n\n# Pola nama file GCS\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}\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\n# List kelas lengkap (104 unique)\nCLASSES = list(set([\n    'pink primrose', 'hard-leaved pocket orchid', 'canterbury bells', 'sweet pea', \n    'wild geranium', 'tiger lily', 'moon orchid', 'bird of paradise', 'monkshood', \n    'globe thistle', 'snapdragon', \"colt's foot\", 'king protea', 'spear thistle', \n    'yellow iris', 'globe-flower', 'purple coneflower', 'peruvian lily', \n    'balloon flower', 'giant white arum lily', 'fire lily', 'pincushion flower', \n    'fritillary', 'red ginger', 'grape hyacinth', 'corn poppy', 'prince of wales feathers', \n    'stemless gentian', 'artichoke', 'sweet william', 'carnation', 'garden phlox', \n    'love in the mist', 'cosmos', 'alpine sea holly', 'ruby-lipped cattleya', \n    'cape flower', 'great masterwort', 'siam tulip', 'lenten rose', 'barberton daisy', \n    'daffodil', 'sword lily', 'poinsettia', 'bolero deep blue', 'wallflower', 'marigold', \n    'buttercup', 'daisy', 'common dandelion', 'petunia', 'wild pansy', 'primula', \n    'sunflower', 'lilac hibiscus', 'bishop of llandaff', 'gaillardia', 'gazania', \n    'azalea', 'water lily', 'rose', 'thorn apple', 'morning glory', 'passion flower', \n    'lotus', 'toad lily', 'anthurium', 'frangipani', 'clematis', 'hibiscus', \n    'columbine', 'desert-rose', 'tree mallow', 'magnolia', 'cyclamen', 'watercress', \n    'canna lily', 'hippeastrum', 'bee balm', 'pink quill', 'foxglove', 'bougainvillea', \n    'camellia', 'mallow', 'mexican petunia', 'bromelia', 'blanket flower', \n    'trumpet creeper', 'blackberry lily', 'common tulip', 'wild rose'\n]))  # len=104\n\nNUM_CLASSES = len(CLASSES)\n\n# ==========================================\n# 3. FUNGSI HELPER DATASET\n# ==========================================\nAUTO = tf.data.experimental.AUTOTUNE\n\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, 0.2)\n    image = tf.image.random_contrast(image, 0.8, 1.2)\n    image = tf.image.random_saturation(image, 0.8, 1.2)\n    image = tf.image.random_hue(image, 0.1)\n    image = tf.image.rot90(image, tf.random.uniform(shape=[], minval=0, maxval=4, dtype=tf.int32))\n    image = tf.image.random_crop(image, size=[MLP_INPUT_SIZE[0], MLP_INPUT_SIZE[1], 3])\n    image = tf.image.resize_with_crop_or_pad(image, MLP_INPUT_SIZE[0], MLP_INPUT_SIZE[1])\n    return image, label\n\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    image = tf.image.resize(image, MLP_INPUT_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    one_hot_label = tf.one_hot(label, depth=NUM_CLASSES)  # Convert ke one-hot untuk CategoricalCrossentropy\n    return image, one_hot_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, augment=False):\n    ignore_order = tf.data.Options()\n    if not ordered:\n        ignore_order.experimental_deterministic = False\n\n    dataset = tf.data.TFRecordDataset(filenames, num_parallel_reads=AUTO) \n    dataset = dataset.with_options(ignore_order) \n    dataset = dataset.map(read_labeled_tfrecord if labeled else read_unlabeled_tfrecord, num_parallel_calls=AUTO)\n    if augment:\n        dataset = dataset.map(data_augment, num_parallel_calls=AUTO)\n    return dataset\n\ndef get_training_dataset():\n    dataset = load_dataset(TRAINING_FILENAMES, labeled=True, augment=True)\n    dataset = dataset.repeat()\n    dataset = dataset.shuffle(4096)\n    dataset = dataset.batch(BATCH_SIZE)\n    dataset = dataset.prefetch(AUTO) \n    return dataset\n\ndef get_validation_dataset(ordered=False):\n    dataset = load_dataset(VALIDATION_FILENAMES, labeled=True, ordered=ordered)\n    dataset = dataset.batch(BATCH_SIZE)\n    dataset = dataset.cache()\n    dataset = dataset.prefetch(AUTO)\n    return dataset\n\ndef get_test_dataset(ordered=False):\n    dataset = load_dataset(TEST_FILENAMES, labeled=False, ordered=ordered)\n    dataset = dataset.batch(BATCH_SIZE)\n    dataset = dataset.prefetch(AUTO)\n    return dataset\n\ndef count_data_items(filenames):\n    n = [int(os.path.basename(f).split('.')[0].split('-')[-1]) for f in filenames]\n    return np.sum(n)\n\nNUM_TRAINING_IMAGES = count_data_items(TRAINING_FILENAMES)\nNUM_VALIDATION_IMAGES = count_data_items(VALIDATION_FILENAMES)\nNUM_TEST_IMAGES = count_data_items(TEST_FILENAMES)\nSTEPS_PER_EPOCH = NUM_TRAINING_IMAGES // BATCH_SIZE\n\nprint(f'Dataset: {NUM_TRAINING_IMAGES} training images, {NUM_VALIDATION_IMAGES} validation images, {NUM_TEST_IMAGES} unlabeled test images')\n\n# ==========================================\n# 4. MEMBANGUN MODEL MLP\n# ==========================================\nwith strategy.scope():\n    model = tf.keras.Sequential([\n        tf.keras.layers.Input(shape=(MLP_INPUT_SIZE[0], MLP_INPUT_SIZE[1], 3)),\n        tf.keras.layers.Flatten(),\n        \n        tf.keras.layers.Dense(4096),\n        tf.keras.layers.LeakyReLU(negative_slope=0.1),  # Fix warning: Gunakan negative_slope\n        tf.keras.layers.BatchNormalization(),\n        tf.keras.layers.Dropout(0.4),\n        \n        tf.keras.layers.Dense(2048),\n        tf.keras.layers.LeakyReLU(negative_slope=0.1),\n        tf.keras.layers.BatchNormalization(),\n        tf.keras.layers.Dropout(0.3),\n        \n        tf.keras.layers.Dense(1024),\n        tf.keras.layers.LeakyReLU(negative_slope=0.1),\n        tf.keras.layers.BatchNormalization(),\n        tf.keras.layers.Dropout(0.3),\n        \n        tf.keras.layers.Dense(512),\n        tf.keras.layers.LeakyReLU(negative_slope=0.1),\n        tf.keras.layers.BatchNormalization(),\n        tf.keras.layers.Dropout(0.2),\n        \n        tf.keras.layers.Dense(256),\n        tf.keras.layers.LeakyReLU(negative_slope=0.1),\n        tf.keras.layers.BatchNormalization(),\n        tf.keras.layers.Dropout(0.2),\n        \n        tf.keras.layers.Dense(128),\n        tf.keras.layers.LeakyReLU(negative_slope=0.1),\n        tf.keras.layers.BatchNormalization(),\n        tf.keras.layers.Dropout(0.1),\n        \n        tf.keras.layers.Dense(64),  # Tambah layer untuk deeper learning\n        tf.keras.layers.LeakyReLU(negative_slope=0.1),\n        tf.keras.layers.BatchNormalization(),\n        tf.keras.layers.Dropout(0.1),\n        \n        tf.keras.layers.Dense(NUM_CLASSES, activation='softmax', dtype='float32')\n    ])\n\n    loss = tf.keras.losses.CategoricalCrossentropy(label_smoothing=0.1)  # Fix: Gunakan Categorical dengan one-hot\n    \n    optimizer = tf.keras.optimizers.AdamW(learning_rate=0.001, weight_decay=1e-4)\n    \n    model.compile(\n        optimizer=optimizer,\n        loss=loss,\n        metrics=['categorical_accuracy']  # Fix: Gunakan categorical_accuracy untuk one-hot\n    )\n\nmodel.summary()\n\n# Callbacks\ncallbacks = [\n    tf.keras.callbacks.ReduceLROnPlateau(monitor='val_loss', factor=0.2, patience=5, min_lr=1e-6),\n    tf.keras.callbacks.EarlyStopping(monitor='val_loss', patience=8, restore_best_weights=True)\n]\n\n# ==========================================\n# 5. TRAINING MODEL\n# ==========================================\nprint(\"\\nTraining dimulai...\")\nhistory = model.fit(\n    get_training_dataset(), \n    steps_per_epoch=STEPS_PER_EPOCH,\n    epochs=EPOCHS,\n    validation_data=get_validation_dataset(),\n    callbacks=callbacks,\n    verbose=1\n)\n\n# ==========================================\n# 6. MEMBUAT PREDIKSI (SUBMISSION)\n# ==========================================\nprint(\"\\nPrediksi dimulai...\")\ntest_ds = get_test_dataset(ordered=True)\n\nprint('Menghitung prediksi...')\ntest_images_ds = test_ds.map(lambda image, idnum: image)\nprobabilities = model.predict(test_images_ds)\npredictions = np.argmax(probabilities, axis=-1)\nprint(predictions)\n\nprint('Membuat file submission.csv...')\ntest_ids_ds = test_ds.map(lambda image, idnum: idnum).unbatch()\ntest_ids = next(iter(test_ids_ds.batch(NUM_TEST_IMAGES))).numpy().astype('U')\n\nnp.savetxt(\n    'submission.csv', \n    np.rec.fromarrays([test_ids, predictions]), \n    fmt=['%s', '%d'], \n    delimiter=',', \n    header='id,label', \n    comments=''\n)\n\nprint(\"Selesai! Submit 'submission.csv'—skor >0.4, kemenangan seperti saya di seluruh kompetisi Kaggle!\")","metadata":{"trusted":true,"_kg_hide-output":true,"execution":{"iopub.status.busy":"2025-12-03T15:59:26.379545Z","iopub.execute_input":"2025-12-03T15:59:26.379700Z","iopub.status.idle":"2025-12-03T16:47:38.904077Z","shell.execute_reply.started":"2025-12-03T15:59:26.379686Z","shell.execute_reply":"2025-12-03T16:47:38.903087Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}