{"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:33:46.746154Z","iopub.execute_input":"2025-12-03T10:33:46.746616Z","iopub.status.idle":"2025-12-03T10:33:48.601154Z","shell.execute_reply.started":"2025-12-03T10:33:46.746588Z","shell.execute_reply":"2025-12-03T10:33:48.600389Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import tensorflow as tf\nimport matplotlib.pyplot as plt\nfrom sklearn.metrics import classification_report, confusion_matrix, accuracy_score, precision_score, recall_score, f1_score\nimport seaborn as sns\n\n# Setup GPU Strategy\nstrategy = tf.distribute.MirroredStrategy()\nprint(f'\\n✓ Running on {strategy.num_replicas_in_sync} GPU(s)')\n\n# Konfigurasi\nBATCH_SIZE = 16 * strategy.num_replicas_in_sync\nDS_PATH = '/kaggle/input/tpu-getting-started'\nIMAGE_SIZE = [192, 192]  # Resolusi lebih kecil untuk MLP\n\nprint(f\"Batch Size: {BATCH_SIZE}\")\nprint(f\"Image Size: {IMAGE_SIZE}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-03T10:33:48.602821Z","iopub.execute_input":"2025-12-03T10:33:48.603296Z","iopub.status.idle":"2025-12-03T10:34:07.901977Z","shell.execute_reply.started":"2025-12-03T10:33:48.603275Z","shell.execute_reply":"2025-12-03T10:34:07.901292Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"\\n[STEP 1] Defining Data Preprocessing Functions...\")\n\ndef decode_image(image_data):\n    \"\"\"Decode dan normalize image\"\"\"\n    image = tf.image.decode_jpeg(image_data, channels=3)\n    image = tf.cast(image, tf.float32) / 255.0  # Normalize ke [0, 1]\n    image = tf.reshape(image, [*IMAGE_SIZE, 3])\n    return image\n\ndef read_labeled_tfrecord(example):\n    \"\"\"Read training/validation TFRecord\"\"\"\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_test_tfrecord(example):\n    \"\"\"Read test TFRecord\"\"\"\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\ndef data_augment(image, label):\n    \"\"\"Data augmentation untuk training\"\"\"\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_contrast(image, 0.8, 1.2)\n    return image, label\n\ndef load_dataset(filenames, labeled=True, ordered=False, augment=False):\n    \"\"\"Load dataset dari TFRecord files\"\"\"\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=tf.data.experimental.AUTOTUNE)\n    dataset = dataset.with_options(ignore_order)\n    \n    if labeled:\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    else:\n        dataset = dataset.map(read_test_tfrecord, num_parallel_calls=tf.data.experimental.AUTOTUNE)\n    \n    return dataset\n\nprint(\"✓ Preprocessing functions defined\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-03T10:34:07.902724Z","iopub.execute_input":"2025-12-03T10:34:07.903159Z","iopub.status.idle":"2025-12-03T10:34:07.913512Z","shell.execute_reply.started":"2025-12-03T10:34:07.903138Z","shell.execute_reply":"2025-12-03T10:34:07.912767Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"\\n[STEP 2] Loading Datasets...\")\n\n# Load file paths\nFILENAMES_TRAIN = tf.io.gfile.glob(DS_PATH + '/tfrecords-jpeg-192x192/train/*.tfrec')\nFILENAMES_VAL = tf.io.gfile.glob(DS_PATH + '/tfrecords-jpeg-192x192/val/*.tfrec')\nFILENAMES_TEST = tf.io.gfile.glob(DS_PATH + '/tfrecords-jpeg-192x192/test/*.tfrec')\n\nprint(f\"Training files: {len(FILENAMES_TRAIN)}\")\nprint(f\"Validation files: {len(FILENAMES_VAL)}\")\nprint(f\"Test files: {len(FILENAMES_TEST)}\")\n\n# Create datasets\ntraining_dataset = load_dataset(FILENAMES_TRAIN, labeled=True, augment=True) \\\n    .repeat() \\\n    .shuffle(2048) \\\n    .batch(BATCH_SIZE) \\\n    .prefetch(tf.data.experimental.AUTOTUNE)\n\nvalidation_dataset = load_dataset(FILENAMES_VAL, labeled=True, ordered=True) \\\n    .batch(BATCH_SIZE) \\\n    .prefetch(tf.data.experimental.AUTOTUNE)\n\nprint(\"\\n✓ Datasets loaded successfully\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-03T10:34:07.914367Z","iopub.execute_input":"2025-12-03T10:34:07.914672Z","iopub.status.idle":"2025-12-03T10:34:08.151465Z","shell.execute_reply.started":"2025-12-03T10:34:07.914641Z","shell.execute_reply":"2025-12-03T10:34:08.150725Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"\\n[STEP 3] Building Pure MLP Model...\")\n\nprint(\"ARSITEKTUR MULTI-LAYER PERCEPTRON:\")\nprint(\"- Input Layer: Flatten image pixels (192x192x3 = 110,592 pixels)\")\nprint(\"- Hidden Layer 1: 2048 neurons + ReLU + BatchNorm + Dropout(0.5)\")\nprint(\"- Hidden Layer 2: 1024 neurons + ReLU + BatchNorm + Dropout(0.4)\")\nprint(\"- Hidden Layer 3: 512 neurons + ReLU + BatchNorm + Dropout(0.3)\")\nprint(\"- Hidden Layer 4: 256 neurons + ReLU + BatchNorm + Dropout(0.2)\")\nprint(\"- Output Layer: 104 neurons + Softmax\")\n\n\nwith strategy.scope():\n    model = tf.keras.Sequential([\n        # INPUT LAYER - Flatten pixel values\n        tf.keras.layers.Flatten(input_shape=[*IMAGE_SIZE, 3]),\n        \n        # HIDDEN LAYER 1\n        tf.keras.layers.Dense(2048, activation='relu', \n                             kernel_regularizer=tf.keras.regularizers.l2(0.0001)),\n        tf.keras.layers.BatchNormalization(),\n        tf.keras.layers.Dropout(0.5),\n        \n        # HIDDEN LAYER 2\n        tf.keras.layers.Dense(1024, activation='relu',\n                             kernel_regularizer=tf.keras.regularizers.l2(0.0001)),\n        tf.keras.layers.BatchNormalization(),\n        tf.keras.layers.Dropout(0.4),\n        \n        # HIDDEN LAYER 3\n        tf.keras.layers.Dense(512, activation='relu',\n                             kernel_regularizer=tf.keras.regularizers.l2(0.0001)),\n        tf.keras.layers.BatchNormalization(),\n        tf.keras.layers.Dropout(0.3),\n        \n        # HIDDEN LAYER 4\n        tf.keras.layers.Dense(256, activation='relu',\n                             kernel_regularizer=tf.keras.regularizers.l2(0.0001)),\n        tf.keras.layers.BatchNormalization(),\n        tf.keras.layers.Dropout(0.2),\n        \n        # OUTPUT LAYER\n        tf.keras.layers.Dense(104, activation='softmax')\n    ], name='Pure_MLP_Model')\n    \n    # COMPILE MODEL\n    model.compile(\n        optimizer=tf.keras.optimizers.Adam(learning_rate=0.001),\n        loss='sparse_categorical_crossentropy',\n        metrics=['sparse_categorical_accuracy']\n    )\n\nmodel.summary()\n\n# Calculate parameters\ntotal_params = sum([tf.reduce_prod(var.shape).numpy() for var in model.trainable_variables])\nprint(f\"\\n✓ Total Trainable Parameters: {total_params:,}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-03T10:34:08.152957Z","iopub.execute_input":"2025-12-03T10:34:08.153212Z","iopub.status.idle":"2025-12-03T10:34:09.83531Z","shell.execute_reply.started":"2025-12-03T10:34:08.153193Z","shell.execute_reply":"2025-12-03T10:34:09.834739Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"\\n[STEP 4] Setting Up Training Configuration...\")\n\nNUM_TRAINING_IMAGES = 12753\nSTEPS_PER_EPOCH = NUM_TRAINING_IMAGES // BATCH_SIZE\nEPOCHS = 25  # Bisa disesuaikan\n\nprint(f\"Steps per epoch: {STEPS_PER_EPOCH}\")\nprint(f\"Total epochs: {EPOCHS}\")\n\n# Callbacks\ncallbacks = [\n    # Save best model\n    tf.keras.callbacks.ModelCheckpoint(\n        'best_mlp_model.keras',\n        save_best_only=True,\n        monitor='val_sparse_categorical_accuracy',\n        mode='max',\n        verbose=1\n    ),\n    \n    # Reduce learning rate on plateau\n    tf.keras.callbacks.ReduceLROnPlateau(\n        monitor='val_loss',\n        factor=0.5,\n        patience=3,\n        min_lr=1e-6,\n        verbose=1\n    ),\n    \n    # Early stopping\n    tf.keras.callbacks.EarlyStopping(\n        monitor='val_loss',\n        patience=7,\n        restore_best_weights=True,\n        verbose=1\n    )\n]\n\nprint(\"✓ Callbacks configured\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-03T10:34:09.836032Z","iopub.execute_input":"2025-12-03T10:34:09.836295Z","iopub.status.idle":"2025-12-03T10:34:09.842206Z","shell.execute_reply.started":"2025-12-03T10:34:09.836271Z","shell.execute_reply":"2025-12-03T10:34:09.841501Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"history = model.fit(\n    training_dataset,\n    steps_per_epoch=STEPS_PER_EPOCH,\n    epochs=EPOCHS,\n    validation_data=validation_dataset,\n    callbacks=callbacks,\n    verbose=1\n)\n\nprint(\"\\n✓ Training completed!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-03T10:34:09.84279Z","iopub.execute_input":"2025-12-03T10:34:09.842986Z","iopub.status.idle":"2025-12-03T11:49:51.435387Z","shell.execute_reply.started":"2025-12-03T10:34:09.84296Z","shell.execute_reply":"2025-12-03T11:49:51.434739Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n# Analisis Konvergensi\n\nprint(\"\\n[STEP 6] CONVERGENCE ANALYSIS...\")\n\nfig, axes = plt.subplots(1, 2, figsize=(15, 5))\n\n# Plot Accuracy\naxes[0].plot(history.history['sparse_categorical_accuracy'], \n            label='Training Accuracy', linewidth=2, marker='o')\naxes[0].plot(history.history['val_sparse_categorical_accuracy'], \n            label='Validation Accuracy', linewidth=2, marker='s')\naxes[0].set_title('Model Accuracy Over Epochs', fontsize=14, fontweight='bold')\naxes[0].set_xlabel('Epoch')\naxes[0].set_ylabel('Accuracy')\naxes[0].legend()\naxes[0].grid(True, alpha=0.3)\n\n# Plot Loss\naxes[1].plot(history.history['loss'], \n            label='Training Loss', linewidth=2, marker='o')\naxes[1].plot(history.history['val_loss'], \n            label='Validation Loss', linewidth=2, marker='s')\naxes[1].set_title('Model Loss Over Epochs', fontsize=14, fontweight='bold')\naxes[1].set_xlabel('Epoch')\naxes[1].set_ylabel('Loss')\naxes[1].legend()\naxes[1].grid(True, alpha=0.3)\n\nplt.tight_layout()\nplt.show()\n\n# Analisis Overfitting/Underfitting\n\nfinal_train_acc = history.history['sparse_categorical_accuracy'][-1]\nfinal_val_acc = history.history['val_sparse_categorical_accuracy'][-1]\naccuracy_gap = final_train_acc - final_val_acc\n\nfinal_train_loss = history.history['loss'][-1]\nfinal_val_loss = history.history['val_loss'][-1]\n\nprint(f\"Final Training Accuracy: {final_train_acc:.4f}\")\nprint(f\"Final Validation Accuracy: {final_val_acc:.4f}\")\nprint(f\"Accuracy Gap: {accuracy_gap:.4f}\")\nprint(f\"\\nFinal Training Loss: {final_train_loss:.4f}\")\nprint(f\"Final Validation Loss: {final_val_loss:.4f}\")\n\nif accuracy_gap < 0.05:\n    print(\"\\n✓ Model generalisasi dengan baik (tidak overfitting)\")\nelif accuracy_gap < 0.10:\n    print(\"\\n⚠ Model mengalami slight overfitting\")\nelse:\n    print(\"\\n❌ Model mengalami overfitting yang signifikan\")\n\nprint(\"-\" * 80)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-03T12:53:11.03477Z","iopub.execute_input":"2025-12-03T12:53:11.035044Z","iopub.status.idle":"2025-12-03T12:53:11.047951Z","shell.execute_reply.started":"2025-12-03T12:53:11.035023Z","shell.execute_reply":"2025-12-03T12:53:11.047019Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\nprint(\"\\n[STEP 7] COMPREHENSIVE MODEL EVALUATION...\")\nprint(\"=\" * 80)\n\n# Load best model\ntry:\n    model.load_weights('best_mlp_model.keras')\n    print(\"✓ Best model weights loaded\")\nexcept:\n    print(\"⚠ Using final model weights\")\n\n# Collect predictions\ny_true = []\ny_pred = []\n\nprint(\"\\nGenerating predictions on validation set...\")\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\ny_true = np.array(y_true)\ny_pred = np.array(y_pred)\n\n# Calculate metrics\naccuracy = accuracy_score(y_true, y_pred)\nprecision = precision_score(y_true, y_pred, average='weighted', zero_division=0)\nrecall = recall_score(y_true, y_pred, average='weighted', zero_division=0)\nf1 = f1_score(y_true, y_pred, average='weighted', zero_division=0)\n\nprint(\"\\n📊 METRIK EVALUASI MODEL:\")\nprint(\"=\" * 80)\nprint(f\"Accuracy  : {accuracy:.4f} ({accuracy*100:.2f}%)\")\nprint(f\"Precision : {precision:.4f}\")\nprint(f\"Recall    : {recall:.4f}\")\nprint(f\"F1-Score  : {f1:.4f}\")\nprint(\"=\" * 80)\n\n# Interpretasi\nprint(\"\\n📝 INTERPRETASI HASIL:\")\nprint(\"-\" * 80)\nif accuracy > 0.70:\n    print(\"✓ EXCELLENT! Model performance sangat baik untuk Pure MLP\")\nelif accuracy > 0.60:\n    print(\"✓ GOOD! Model performance baik\")\nelif accuracy > 0.50:\n    print(\"⚠ FAIR! Model cukup baik\")\nelse:\n    print(\"❌ Model perlu improvement\")\nprint(\"-\" * 80)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-03T12:52:27.117803Z","iopub.execute_input":"2025-12-03T12:52:27.118134Z","iopub.status.idle":"2025-12-03T12:52:27.189899Z","shell.execute_reply.started":"2025-12-03T12:52:27.118107Z","shell.execute_reply":"2025-12-03T12:52:27.188966Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# model Evaluation\n\n\n# Load best model\ntry:\n    model.load_weights('best_mlp_model.keras')\n    print(\"✓ Best model weights loaded\")\nexcept:\n    print(\"⚠ Using final model weights\")\n\n# Collect predictions\ny_true = []\ny_pred = []\n\nprint(\"\\nGenerating predictions on validation set...\")\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\ny_true = np.array(y_true)\ny_pred = np.array(y_pred)\n\n# Calculate metrics\naccuracy = accuracy_score(y_true, y_pred)\nprecision = precision_score(y_true, y_pred, average='weighted', zero_division=0)\nrecall = recall_score(y_true, y_pred, average='weighted', zero_division=0)\nf1 = f1_score(y_true, y_pred, average='weighted', zero_division=0)\n\nprint(\"\\n📊 METRIK EVALUASI MODEL:\")\nprint(\"=\" * 80)\nprint(f\"Accuracy  : {accuracy:.4f} ({accuracy*100:.2f}%)\")\nprint(f\"Precision : {precision:.4f}\")\nprint(f\"Recall    : {recall:.4f}\")\nprint(f\"F1-Score  : {f1:.4f}\")\nprint(\"=\" * 80)\n\n# Interpretasi\n\nif accuracy > 0.70:\n    print(\"✓ EXCELLENT! Model performance sangat baik untuk Pure MLP\")\nelif accuracy > 0.60:\n    print(\"✓ GOOD! Model performance baik\")\nelif accuracy > 0.50:\n    print(\"⚠ FAIR! Model cukup baik\")\nelse:\n    print(\"❌ Model perlu improvement\")\nprint(\"-\" * 80)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-03T12:53:00.366764Z","iopub.execute_input":"2025-12-03T12:53:00.367439Z","iopub.status.idle":"2025-12-03T12:53:00.37907Z","shell.execute_reply.started":"2025-12-03T12:53:00.367413Z","shell.execute_reply":"2025-12-03T12:53:00.378139Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\nprint(classification_report(y_true, y_pred, zero_division=0))\n\n# Confusion Matrix\nprint(\"\\n📊 Generating Confusion Matrix...\")\ncm = confusion_matrix(y_true, y_pred)\n\nplt.figure(figsize=(12, 10))\nsns.heatmap(cm, cmap='Blues', fmt='d', cbar=True, square=True, \n            xticklabels=False, yticklabels=False)\nplt.title('Confusion Matrix - Pure MLP Model', fontsize=14, fontweight='bold')\nplt.xlabel('Predicted Label')\nplt.ylabel('True Label')\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-03T12:53:43.943999Z","iopub.execute_input":"2025-12-03T12:53:43.94455Z","iopub.status.idle":"2025-12-03T12:53:43.953907Z","shell.execute_reply.started":"2025-12-03T12:53:43.944527Z","shell.execute_reply":"2025-12-03T12:53:43.952966Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_dataset = load_dataset(FILENAMES_TEST, labeled=False, ordered=True) \\\n    .batch(BATCH_SIZE) \\\n    .prefetch(tf.data.experimental.AUTOTUNE)\n\ntest_ids = []\ntest_preds = []\n\nprint(\"Generating test predictions...\")\nfor images, idnums in test_dataset:\n    test_ids.extend([x.decode('utf-8') for x in idnums.numpy()])\n    probs = model.predict(images, verbose=0)\n    test_preds.extend(np.argmax(probs, axis=1))\n\n# Create submission\nsubmission = pd.DataFrame({\n    'id': test_ids,\n    'label': test_preds\n})\n\nsubmission.to_csv('submission.csv', index=False)\n\nprint(f\"\\n✓ Submission file created: submission.csv\")\nprint(f\"Total predictions: {len(submission)}\")\nprint(\"\\nSample predictions:\")\nprint(submission.head(10))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-03T12:54:06.481585Z","iopub.execute_input":"2025-12-03T12:54:06.482337Z","iopub.status.idle":"2025-12-03T12:54:06.492427Z","shell.execute_reply.started":"2025-12-03T12:54:06.48231Z","shell.execute_reply":"2025-12-03T12:54:06.491467Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\nprint(f\"Model: Pure Multi-Layer Perceptron (MLP)\")\nprint(f\"Input: {IMAGE_SIZE[0]}x{IMAGE_SIZE[1]}x3 = {IMAGE_SIZE[0]*IMAGE_SIZE[1]*3} pixels\")\nprint(f\"Hidden Layers: 4 layers (2048→1024→512→256)\")\nprint(f\"Output: 104 flower classes\")\nprint(f\"Activation: ReLU + Softmax\")\nprint(f\"Regularization: L2 + BatchNorm + Dropout\")\nprint(f\"\\nValidation Accuracy: {accuracy:.4f} ({accuracy*100:.2f}%)\")\nprint(f\"Total Parameters: {total_params:,}\")\nprint(\"=\" * 80)\n\nprint(\"\\n✅ COMPLETE! File 'submission.csv' siap di-submit ke Kaggle!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-03T12:54:22.633097Z","iopub.execute_input":"2025-12-03T12:54:22.633412Z","iopub.status.idle":"2025-12-03T12:54:22.642797Z","shell.execute_reply.started":"2025-12-03T12:54:22.633378Z","shell.execute_reply":"2025-12-03T12:54:22.641927Z"}},"outputs":[],"execution_count":null}]}