{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"},"kaggle":{"accelerator":"tpuV5e8","dataSources":[{"sourceId":21154,"databundleVersionId":1243559,"sourceType":"competition"}],"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},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Import\nimport numpy as np\nimport pandas as pd\nimport tensorflow as tf\nimport matplotlib.pyplot as plt\nfrom sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score\nfrom sklearn.metrics import confusion_matrix, classification_report, roc_auc_score\nfrom sklearn.model_selection import KFold\nimport re\nimport warnings\nwarnings.filterwarnings('ignore')\n\nprint(f\"TensorFlow: {tf.__version__}\")\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Config\n# Hyperparameters - TUNED FOR OPTIMAL PERFORMANCE\nIMAGE_SIZE = [224, 224]\nBATCH_SIZE = 32\nEPOCHS = 15\nNUM_CLASSES = 104\nK_FOLDS = 3  # For K-Fold Cross Validation\n\n# Optimized hyperparameters\nLEARNING_RATE = 0.001\nDROPOUT_RATES = [0.4, 0.3, 0.2]  # Progressive dropout\nL2_REG = 0.001  # L2 regularization\n\nprint(f\"Configuration:\")\nprint(f\"  Image: {IMAGE_SIZE}\")\nprint(f\"  Batch: {BATCH_SIZE}\")\nprint(f\"  Epochs: {EPOCHS}\")\nprint(f\"  K-Folds: {K_FOLDS}\")\nprint(f\"  Learning Rate: {LEARNING_RATE}\")\nprint(f\"  L2 Regularization: {L2_REG}\")\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Load Data\nfrom kaggle_datasets import KaggleDatasets\n\nGCS_PATH = KaggleDatasets().get_gcs_path('tpu-getting-started')\nGCS_PATH = GCS_PATH + '/tfrecords-jpeg-224x224'\n\nTRAIN_FILES = tf.io.gfile.glob(GCS_PATH + '/train/*.tfrec')\nVAL_FILES = tf.io.gfile.glob(GCS_PATH + '/val/*.tfrec')\nTEST_FILES = tf.io.gfile.glob(GCS_PATH + '/test/*.tfrec')\n\n# Combine for K-Fold\nALL_TRAIN_FILES = TRAIN_FILES + VAL_FILES\n\nprint(f\"Train: {len(TRAIN_FILES)} files\")\nprint(f\"Val: {len(VAL_FILES)} files\")\nprint(f\"Test: {len(TEST_FILES)} files\")\nprint(f\"Total for K-Fold: {len(ALL_TRAIN_FILES)} files\")\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Data Pipeline\nAUTO = tf.data.AUTOTUNE\n\ndef decode_image(img_data):\n    img = tf.image.decode_jpeg(img_data, channels=3)\n    img = tf.cast(img, tf.float32) / 255.0\n    # Additional preprocessing - feature engineering\n    img = tf.image.per_image_standardization(img)  # Standardization\n    img = tf.reshape(img, [*IMAGE_SIZE, 3])\n    return img\n\ndef parse_train(example):\n    features = {\n        \"image\": tf.io.FixedLenFeature([], tf.string),\n        \"class\": tf.io.FixedLenFeature([], tf.int64),\n    }\n    ex = tf.io.parse_single_example(example, features)\n    img = decode_image(ex['image'])\n    label = tf.cast(ex['class'], tf.int32)\n    return img, label\n\ndef parse_test(example):\n    features = {\n        \"image\": tf.io.FixedLenFeature([], tf.string),\n        \"id\": tf.io.FixedLenFeature([], tf.string),\n    }\n    ex = tf.io.parse_single_example(example, features)\n    img = decode_image(ex['image'])\n    return img, ex['id']\n\ndef augment(img, label):\n    # Data augmentation\n    img = tf.image.random_flip_left_right(img)\n    img = tf.image.random_brightness(img, 0.2)\n    img = tf.image.random_contrast(img, 0.8, 1.2)\n    return img, label\n\ndef load_data(files, labeled=True, augment_data=False):\n    ds = tf.data.TFRecordDataset(files, num_parallel_reads=AUTO)\n    ds = ds.map(parse_train if labeled else parse_test, num_parallel_calls=AUTO)\n    if augment_data:\n        ds = ds.map(augment, num_parallel_calls=AUTO)\n    return ds\n\nprint(\"✓ Data pipeline ready with preprocessing & augmentation\")\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Build Model FUnctiom\ndef build_mlp_model():\n    \"\"\"\n    Multi-Layer Perceptron with 3 Hidden Layers\n    Sesuai Rubrik: Arsitektur MLP Lengkap + Hyperparameter Tuning\n    \"\"\"\n    # Feature Extractor\n    base = tf.keras.applications.MobileNetV2(\n        input_shape=(*IMAGE_SIZE, 3),\n        include_top=False,\n        weights='imagenet'\n    )\n    base.trainable = False\n    \n    model = tf.keras.Sequential([\n        base,\n        tf.keras.layers.GlobalAveragePooling2D(),\n        \n        # Hidden Layer 1 (512 neurons) - SESUAI RUBRIK\n        tf.keras.layers.Dense(\n            512,\n            activation='relu',\n            kernel_initializer='he_normal',\n            kernel_regularizer=tf.keras.regularizers.l2(L2_REG)\n        ),\n        tf.keras.layers.BatchNormalization(),\n        tf.keras.layers.Dropout(DROPOUT_RATES[0]),\n        \n        # Hidden Layer 2 (256 neurons) - SESUAI RUBRIK\n        tf.keras.layers.Dense(\n            256,\n            activation='relu',\n            kernel_initializer='he_normal',\n            kernel_regularizer=tf.keras.regularizers.l2(L2_REG)\n        ),\n        tf.keras.layers.BatchNormalization(),\n        tf.keras.layers.Dropout(DROPOUT_RATES[1]),\n        \n        # Hidden Layer 3 (128 neurons) - SESUAI RUBRIK\n        tf.keras.layers.Dense(\n            128,\n            activation='relu',\n            kernel_initializer='he_normal',\n            kernel_regularizer=tf.keras.regularizers.l2(L2_REG)\n        ),\n        tf.keras.layers.BatchNormalization(),\n        tf.keras.layers.Dropout(DROPOUT_RATES[2]),\n        \n        # Output Layer\n        tf.keras.layers.Dense(NUM_CLASSES, activation='softmax')\n    ], name='MLP_3Layers')\n    \n    # Compile with tuned optimizer\n    model.compile(\n        optimizer=tf.keras.optimizers.Adam(\n            learning_rate=LEARNING_RATE,\n            beta_1=0.9,\n            beta_2=0.999\n        ),\n        loss='sparse_categorical_crossentropy',\n        metrics=['accuracy']\n    )\n    \n    return model\n\n# Test build\nmodel = build_mlp_model()\nmodel.summary()\nprint(f\"\\n✓ Model with 3 hidden layers (512→256→128→104)\")\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#K-Fold Cross Validation Training\ndef count_images(files):\n    return sum([int(re.search(r'-([0-9]+)\\.', f).group(1)) for f in files])\n\nNUM_TOTAL = count_images(ALL_TRAIN_FILES)\nSTEPS_PER_EPOCH = (NUM_TOTAL // K_FOLDS * (K_FOLDS-1)) // BATCH_SIZE\n\nprint(f\"K-Fold Cross Validation Setup:\")\nprint(f\"  Total images: {NUM_TOTAL}\")\nprint(f\"  Steps/epoch: {STEPS_PER_EPOCH}\\n\")\n\n# K-Fold Cross Validation - SESUAI RUBRIK\nkfold = KFold(n_splits=K_FOLDS, shuffle=True, random_state=42)\nfold_histories = []\nfold_models = []\n\nfor fold, (train_idx, val_idx) in enumerate(kfold.split(ALL_TRAIN_FILES)):\n    print(f\"\\n{'='*70}\")\n    print(f\"FOLD {fold + 1}/{K_FOLDS}\")\n    print(f\"{'='*70}\\n\")\n    \n    # Split files\n    train_files = [ALL_TRAIN_FILES[i] for i in train_idx]\n    val_files = [ALL_TRAIN_FILES[i] for i in val_idx]\n    \n    # Create datasets\n    train_ds = load_data(train_files, labeled=True, augment_data=True) \\\n        .shuffle(1024).repeat().batch(BATCH_SIZE).prefetch(AUTO)\n    val_ds = load_data(val_files, labeled=True) \\\n        .batch(BATCH_SIZE).prefetch(AUTO)\n    \n    # Build fresh model\n    model = build_mlp_model()\n    \n    # Callbacks - SESUAI RUBRIK\n    early_stop = tf.keras.callbacks.EarlyStopping(\n        monitor='val_accuracy',\n        patience=4,\n        restore_best_weights=True,\n        verbose=1\n    )\n    \n    reduce_lr = tf.keras.callbacks.ReduceLROnPlateau(\n        monitor='val_accuracy',\n        factor=0.5,\n        patience=2,\n        min_lr=1e-7,\n        verbose=1\n    )\n    \n    # Train\n    history = model.fit(\n        train_ds,\n        steps_per_epoch=STEPS_PER_EPOCH,\n        epochs=EPOCHS,\n        validation_data=val_ds,\n        callbacks=[early_stop, reduce_lr],\n        verbose=1\n    )\n    \n    fold_histories.append(history)\n    fold_models.append(model)\n    \n    best_acc = max(history.history['val_accuracy'])\n    print(f\"\\nFold {fold+1} Best Val Accuracy: {best_acc:.4f} ({best_acc*100:.2f}%)\")\n\nprint(f\"\\n✓ K-Fold Cross Validation completed!\")\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Plot training Analysis\n# Plot all folds - ANALISIS KONVERGENSI SESUAI RUBRIK\nfig, axes = plt.subplots(K_FOLDS, 2, figsize=(15, 5*K_FOLDS))\n\nfor i, history in enumerate(fold_histories):\n    # Loss\n    axes[i, 0].plot(history.history['loss'], 'b-', linewidth=2, label='Train')\n    axes[i, 0].plot(history.history['val_loss'], 'r-', linewidth=2, label='Val')\n    axes[i, 0].set_title(f'Fold {i+1} - Loss', fontweight='bold')\n    axes[i, 0].set_xlabel('Epoch')\n    axes[i, 0].legend()\n    axes[i, 0].grid(alpha=0.3)\n    \n    # Accuracy\n    axes[i, 1].plot(history.history['accuracy'], 'b-', linewidth=2, label='Train')\n    axes[i, 1].plot(history.history['val_accuracy'], 'r-', linewidth=2, label='Val')\n    axes[i, 1].set_title(f'Fold {i+1} - Accuracy', fontweight='bold')\n    axes[i, 1].set_xlabel('Epoch')\n    axes[i, 1].legend()\n    axes[i, 1].grid(alpha=0.3)\n\nplt.tight_layout()\nplt.savefig('training_kfold_analysis.png', dpi=150)\nplt.show()\n\n# Convergence Analysis - SESUAI RUBRIK\nprint(\"\\n\" + \"=\"*70)\nprint(\"CONVERGENCE ANALYSIS\")\nprint(\"=\"*70)\n\nfor i, history in enumerate(fold_histories):\n    train_loss = history.history['loss'][-1]\n    val_loss = history.history['val_loss'][-1]\n    train_acc = history.history['accuracy'][-1]\n    val_acc = history.history['val_accuracy'][-1]\n    \n    print(f\"\\nFold {i+1}:\")\n    print(f\"  Final Train Loss: {train_loss:.4f} | Val Loss: {val_loss:.4f}\")\n    print(f\"  Final Train Acc: {train_acc:.4f} | Val Acc: {val_acc:.4f}\")\n    \n    # Overfitting detection\n    if abs(train_loss - val_loss) > 0.5:\n        print(f\"  ⚠️ Overfitting detected (loss gap: {abs(train_loss - val_loss):.4f})\")\n    elif train_loss > 2.0 and val_loss > 2.0:\n        print(f\"  ⚠️ Underfitting detected\")\n    else:\n        print(f\"  ✓ Good convergence\")\n\nprint(\"=\"*70)\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Comprehensive Evaluation\nprint(\"\\nComprehensive Model Evaluation...\")\nprint(\"=\"*70)\n\nall_y_true = []\nall_y_pred = []\nall_y_prob = []\n\n# Evaluate each fold\nfor fold_idx, model in enumerate(fold_models):\n    print(f\"\\nEvaluating Fold {fold_idx + 1}...\")\n    \n    val_idx = list(kfold.split(ALL_TRAIN_FILES))[fold_idx][1]\n    val_files = [ALL_TRAIN_FILES[i] for i in val_idx]\n    val_ds = load_data(val_files, labeled=True).batch(BATCH_SIZE).prefetch(AUTO)\n    \n    y_true = []\n    y_pred = []\n    y_prob = []\n    \n    for imgs, labels in val_ds:\n        probs = model.predict(imgs, verbose=0)\n        y_prob.extend(probs)\n        y_pred.extend(np.argmax(probs, axis=1))\n        y_true.extend(labels.numpy())\n    \n    all_y_true.extend(y_true)\n    all_y_pred.extend(y_pred)\n    all_y_prob.extend(y_prob)\n\nall_y_true = np.array(all_y_true)\nall_y_pred = np.array(all_y_pred)\nall_y_prob = np.array(all_y_prob)\n\n# Calculate ALL metrics - SESUAI RUBRIK\nacc = accuracy_score(all_y_true, all_y_pred)\nprec_macro = precision_score(all_y_true, all_y_pred, average='macro', zero_division=0)\nrec_macro = recall_score(all_y_true, all_y_pred, average='macro', zero_division=0)\nf1_macro = f1_score(all_y_true, all_y_pred, average='macro', zero_division=0)\n\nprec_weighted = precision_score(all_y_true, all_y_pred, average='weighted', zero_division=0)\nrec_weighted = recall_score(all_y_true, all_y_pred, average='weighted', zero_division=0)\nf1_weighted = f1_score(all_y_true, all_y_pred, average='weighted', zero_division=0)\n\n# Sensitivity & Specificity per class\ncm = confusion_matrix(all_y_true, all_y_pred)\nsensitivity_per_class = np.diag(cm) / cm.sum(axis=1)\nspecificity_per_class = []\nfor i in range(NUM_CLASSES):\n    tn = cm.sum() - (cm[i,:].sum() + cm[:,i].sum() - cm[i,i])\n    fp = cm[:,i].sum() - cm[i,i]\n    specificity_per_class.append(tn / (tn + fp) if (tn + fp) > 0 else 0)\n\navg_sensitivity = np.mean(sensitivity_per_class)\navg_specificity = np.mean(specificity_per_class)\n\nprint(\"\\n\" + \"=\"*70)\nprint(\"COMPREHENSIVE EVALUATION METRICS\")\nprint(\"=\"*70)\nprint(f\"\\nAccuracy: {acc:.4f} ({acc*100:.2f}%)\")\nprint(f\"\\nMacro Average:\")\nprint(f\"  Precision:    {prec_macro:.4f}\")\nprint(f\"  Recall:       {rec_macro:.4f}\")\nprint(f\"  F1-Score:     {f1_macro:.4f}\")\nprint(f\"  Sensitivity:  {avg_sensitivity:.4f}\")\nprint(f\"  Specificity:  {avg_specificity:.4f}\")\nprint(f\"\\nWeighted Average:\")\nprint(f\"  Precision:    {prec_weighted:.4f}\")\nprint(f\"  Recall:       {rec_weighted:.4f}\")\nprint(f\"  F1-Score:     {f1_weighted:.4f}\")\nprint(\"=\"*70)\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Confusion Matrix\nplt.figure(figsize=(12, 10))\nplt.imshow(cm[:25, :25], cmap='Blues', aspect='auto', interpolation='nearest')\nplt.colorbar(label='Count')\nplt.title('Confusion Matrix (First 25 Classes)', fontsize=14, fontweight='bold')\nplt.xlabel('Predicted Label')\nplt.ylabel('True Label')\nplt.tight_layout()\nplt.savefig('confusion_matrix.png', dpi=150)\nplt.show()\n\nprint(f\"Confusion Matrix: {cm.shape}\")\nprint(f\"Correct: {np.trace(cm)} / {np.sum(cm)}\")\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Predict Test\nprint(\"Predicting test set with ensemble...\\n\")\n\ntest_ds = load_data(TEST_FILES, labeled=False).batch(BATCH_SIZE).prefetch(AUTO)\n\ntest_ids = []\nall_predictions = []\n\n# Ensemble prediction from all folds\nfor imgs, ids in test_ds:\n    batch_preds = []\n    for model in fold_models:\n        preds = model.predict(imgs, verbose=0)\n        batch_preds.append(preds)\n    \n    # Average predictions\n    ensemble_preds = np.mean(batch_preds, axis=0)\n    all_predictions.extend(np.argmax(ensemble_preds, axis=1))\n    test_ids.extend([i.numpy().decode('utf-8') for i in ids])\n\nsubmission = pd.DataFrame({'id': test_ids, 'label': all_predictions})\nsubmission.to_csv('submission.csv', index=False)\n\nprint(f\"✓ Ensemble submission: {len(submission)} predictions\")\nprint(submission.head(10))\n","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}