Files
zeavis-edu/Machine_Learning/notebook.ipynb
T

1.5 MiB

  1. Persiapan Lingkungan
In [1]:
!pip install -q split-folders
In [2]:
import os
import shutil
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import splitfolders
import tarfile
import zipfile
from PIL import Image

from pathlib import Path
from PIL import Image
from google.colab import drive


import tensorflow as tf
from tensorflow.keras import layers, models, callbacks
from tensorflow.keras.applications import EfficientNetV2B0
from sklearn.metrics import classification_report, confusion_matrix

print(f"TensorFlow Version: {tf.__version__}")
TensorFlow Version: 2.20.0
  1. Download dan Ekstraksi Dataset
In [4]:
drive.mount('/content/drive')

archive_path = '/content/drive/MyDrive/jagung/dataset_jagung.zip'
destination_path = '/content/dataset_jagung.zip'
extract_path = '/content/dataset'

if os.path.exists(archive_path):
    print("Moving ZIP file to /content...")
    shutil.copy(archive_path, destination_path)
    print("File successfully moved.")
else:
    print(f"Error: File not found at {archive_path}")

if os.path.exists(destination_path):
    if not os.path.exists(extract_path) or len(os.listdir(extract_path)) == 0:
        os.makedirs(extract_path, exist_ok=True)
        print("Extracting dataset...")
        try:
            with zipfile.ZipFile(destination_path, 'r') as zip_ref:
                zip_ref.extractall(path=extract_path)
            print("Extraction completed!")
        except Exception as e:
            print(f"Extraction failed: {e}")
    else:
        print("Dataset is already extracted and ready for use.")
Mounted at /content/drive
Moving ZIP file to /content...
File successfully moved.
Extracting dataset...
Extraction completed!
  1. Data Cleaning dan Validasi Gambat (RGB)
In [ ]:
dataset_path = "/content/dataset/dataset_jagung_v1"

def clean_image_data(directory):
    removed_count = 0
    for root, dirs, files in os.walk(directory):
        for file in files:
            file_path = os.path.join(root, file)
            try:
                img = Image.open(file_path)
                img.verify()

                img = Image.open(file_path)
                if img.mode != 'RGB':
                    img = img.convert('RGB')
                    img.save(file_path)
            except Exception as e:
                print(f"Removing corrupt/invalid file: {file_path}")
                os.remove(file_path)
                removed_count += 1
    return removed_count

print("Starting data cleanup process...")
removed = clean_image_data(dataset_path)
print(f"Cleanup completed. Total of {removed} problematic files removed.")
Starting data cleanup process...
Cleanup completed. Total of 0 problematic files removed.
  1. Data Spliting (70/15/15/)
In [ ]:
output_dir = "/content/dataset_split"

if os.path.exists(output_dir):
    shutil.rmtree(output_dir)

print("Splitting dataset into Train, Validation, and Test...")
splitfolders.ratio(dataset_path, output=output_dir,
                   seed=42, ratio=(0.7, 0.15, 0.15),
                   group_prefix=None, move=False)
print("Dataset splitting completed.")

train_dir = os.path.join(output_dir, 'train')
val_dir = os.path.join(output_dir, 'val')
test_dir = os.path.join(output_dir, 'test')
Splitting dataset into Train, Validation, and Test...
Copying files: 7106 files [01:11, 99.29 files/s]  
Dataset splitting completed.
  1. Data Loader dan Augmentasi
In [ ]:
BATCH_SIZE = 32
IMG_SIZE = (224, 224)

train_ds = tf.keras.utils.image_dataset_from_directory(
    train_dir, shuffle=True, batch_size=BATCH_SIZE, image_size=IMG_SIZE)

val_ds = tf.keras.utils.image_dataset_from_directory(
    val_dir, shuffle=True, batch_size=BATCH_SIZE, image_size=IMG_SIZE)

test_ds = tf.keras.utils.image_dataset_from_directory(
    test_dir, shuffle=False, batch_size=BATCH_SIZE, image_size=IMG_SIZE)

class_names = train_ds.class_names
NUM_CLASSES = len(class_names)
print(f"Number of classes: {NUM_CLASSES}")
print(f"Class names: {class_names}")

def to_one_hot(image, label):
    return image, tf.one_hot(label, NUM_CLASSES)

train_ds = train_ds.map(to_one_hot, num_parallel_calls=tf.data.AUTOTUNE)
val_ds = val_ds.map(to_one_hot, num_parallel_calls=tf.data.AUTOTUNE)
test_ds = test_ds.map(to_one_hot, num_parallel_calls=tf.data.AUTOTUNE)

AUTOTUNE = tf.data.AUTOTUNE
train_ds = train_ds.prefetch(buffer_size=AUTOTUNE)
val_ds = val_ds.prefetch(buffer_size=AUTOTUNE)
test_ds = test_ds.prefetch(buffer_size=AUTOTUNE)

data_augmentation = tf.keras.Sequential([
    layers.RandomFlip("horizontal_and_vertical"),
    layers.RandomRotation(0.2),
    layers.RandomZoom(0.2),
    layers.RandomTranslation(0.1, 0.1),
    layers.RandomContrast(0.2),
    layers.RandomBrightness(0.2)
], name="data_augmentation")
Found 4973 files belonging to 4 classes.
Found 1063 files belonging to 4 classes.
Found 1070 files belonging to 4 classes.
Number of classes: 4
Class names: ['Bercak Daun', 'Daun Sehat', 'Karat Daun', 'hawar daun']
  1. Visualisasi Sample dan Hasil Augmentasi
In [ ]:
plt.figure(figsize=(12, 6))
for images, labels in train_ds.take(1):
    for i in range(4):
        ax = plt.subplot(2, 4, i + 1)
        plt.imshow(images[i].numpy().astype("uint8"))
        orig_class_idx = np.argmax(labels[i].numpy())

        plt.title(f"Ori: {class_names[orig_class_idx]}")
        plt.axis("off")

    augmented_images = data_augmentation(images, training=True)
    for i in range(4):
        ax = plt.subplot(2, 4, i + 5)
        plt.imshow(augmented_images[i].numpy().astype("uint8"))
        orig_class_idx = np.argmax(labels[i].numpy())

        plt.title(f"Aug: {class_names[orig_class_idx]}")
        plt.axis("off")
plt.tight_layout()
plt.show()
  1. Arsitektur Model CNN (EfficientNetv2B0)
In [ ]:
def build_model(num_classes):
    base_model = EfficientNetV2B0(
        input_shape=IMG_SIZE + (3,),
        include_top=False,
        weights='imagenet',
    )
    base_model.trainable = False

    inputs = tf.keras.Input(shape=IMG_SIZE + (3,))

    x = data_augmentation(inputs)
    x = base_model(x, training=False)

    x = layers.Conv2D(512, (3, 3), padding='same', activation='swish')(x)
    x = layers.BatchNormalization()(x)
    x = layers.MaxPooling2D((2, 2))(x)
    x = layers.Dropout(0.2)(x)

    x = layers.Conv2D(256, (3, 3), padding='same', activation='swish')(x)
    x = layers.BatchNormalization()(x)

    x = layers.GlobalAveragePooling2D()(x)
    x = layers.Dropout(0.3)(x)

    x = layers.Dense(1024, activation='swish')(x)
    x = layers.BatchNormalization()(x)
    x = layers.Dropout(0.4)(x)

    outputs = layers.Dense(num_classes, activation='sigmoid', dtype='float32')(x)

    model = models.Model(inputs, outputs)
    return model, base_model

checkpoint_path = '/content/best_model/best_model.keras'

if os.path.exists(checkpoint_path):
    print(f"Loading weights from model checkpoint: {checkpoint_path}...")
    try:
        model = models.load_model(checkpoint_path)
    except Exception as e:
        print(f"Failed to load checkpoint model: {e}.")
        model, base_model = build_model(NUM_CLASSES)
else:
    print("No checkpoint found.")
    model, base_model = build_model(NUM_CLASSES)

model.summary()
No checkpoint found.
Downloading data from https://storage.googleapis.com/tensorflow/keras-applications/efficientnet_v2/efficientnetv2-b0_notop.h5
24274472/24274472 ━━━━━━━━━━━━━━━━━━━━ 0s 0us/step
Model: "functional_1"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Layer (type)                     Output Shape                  Param # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ input_layer_2 (InputLayer)      │ (None, 224, 224, 3)    │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ data_augmentation (Sequential)  │ (None, 224, 224, 3)    │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ efficientnetv2-b0 (Functional)  │ (None, 7, 7, 1280)     │     5,919,312 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ conv2d (Conv2D)                 │ (None, 7, 7, 512)      │     5,898,752 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ batch_normalization             │ (None, 7, 7, 512)      │         2,048 │
│ (BatchNormalization)            │                        │               │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ max_pooling2d (MaxPooling2D)    │ (None, 3, 3, 512)      │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dropout (Dropout)               │ (None, 3, 3, 512)      │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ conv2d_1 (Conv2D)               │ (None, 3, 3, 256)      │     1,179,904 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ batch_normalization_1           │ (None, 3, 3, 256)      │         1,024 │
│ (BatchNormalization)            │                        │               │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ global_average_pooling2d        │ (None, 256)            │             0 │
│ (GlobalAveragePooling2D)        │                        │               │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dropout_1 (Dropout)             │ (None, 256)            │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense (Dense)                   │ (None, 1024)           │       263,168 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ batch_normalization_2           │ (None, 1024)           │         4,096 │
│ (BatchNormalization)            │                        │               │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dropout_2 (Dropout)             │ (None, 1024)           │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense_1 (Dense)                 │ (None, 4)              │         4,100 │
└─────────────────────────────────┴────────────────────────┴───────────────┘
 Total params: 13,272,404 (50.63 MB)
 Trainable params: 7,349,508 (28.04 MB)
 Non-trainable params: 5,922,896 (22.59 MB)
  1. Implementasi Callbacks
In [ ]:
checkpoint_cb = callbacks.ModelCheckpoint(
    "best_model/best_model.keras",
    save_best_only=True,
    monitor="val_loss",
    mode="min"
)

early_stopping_cb = callbacks.EarlyStopping(
    monitor="val_loss",
    patience=7,
    restore_best_weights=True,
    mode="min"
)

reduce_lr_cb = callbacks.ReduceLROnPlateau(
    monitor='val_loss',
    factor=0.2,
    patience=3,
    min_lr=1e-6,
    verbose=1,
    mode="min"
)

callbacks_list = [checkpoint_cb, early_stopping_cb, reduce_lr_cb]
  1. Training Model
In [ ]:
model.compile(
    optimizer=tf.keras.optimizers.Adam(learning_rate=1e-3),
    loss=tf.keras.losses.BinaryCrossentropy(),
    metrics=[tf.keras.metrics.BinaryAccuracy(name='accuracy')]
)

print("Training...")
EPOCHS_PHASE_1 = 15

history_1 = model.fit(
    train_ds,
    validation_data=val_ds,
    epochs=EPOCHS_PHASE_1,
    callbacks=callbacks_list
)
Training...
Epoch 1/15
156/156 ━━━━━━━━━━━━━━━━━━━━ 207s 1s/step - accuracy: 0.9153 - loss: 0.2220 - val_accuracy: 0.9459 - val_loss: 0.1425 - learning_rate: 0.0010
Epoch 2/15
156/156 ━━━━━━━━━━━━━━━━━━━━ 186s 1s/step - accuracy: 0.9512 - loss: 0.1323 - val_accuracy: 0.9781 - val_loss: 0.0640 - learning_rate: 0.0010
Epoch 3/15
156/156 ━━━━━━━━━━━━━━━━━━━━ 191s 1s/step - accuracy: 0.9649 - loss: 0.0948 - val_accuracy: 0.9730 - val_loss: 0.0645 - learning_rate: 0.0010
Epoch 4/15
156/156 ━━━━━━━━━━━━━━━━━━━━ 180s 1s/step - accuracy: 0.9707 - loss: 0.0796 - val_accuracy: 0.9748 - val_loss: 0.0763 - learning_rate: 0.0010
Epoch 5/15
156/156 ━━━━━━━━━━━━━━━━━━━━ 0s 958ms/step - accuracy: 0.9725 - loss: 0.0770
Epoch 5: ReduceLROnPlateau reducing learning rate to 0.00020000000949949026.
156/156 ━━━━━━━━━━━━━━━━━━━━ 191s 1s/step - accuracy: 0.9733 - loss: 0.0757 - val_accuracy: 0.9661 - val_loss: 0.0838 - learning_rate: 0.0010
Epoch 6/15
156/156 ━━━━━━━━━━━━━━━━━━━━ 185s 1s/step - accuracy: 0.9778 - loss: 0.0564 - val_accuracy: 0.9824 - val_loss: 0.0445 - learning_rate: 2.0000e-04
Epoch 7/15
156/156 ━━━━━━━━━━━━━━━━━━━━ 179s 1s/step - accuracy: 0.9786 - loss: 0.0557 - val_accuracy: 0.9857 - val_loss: 0.0419 - learning_rate: 2.0000e-04
Epoch 8/15
156/156 ━━━━━━━━━━━━━━━━━━━━ 167s 1s/step - accuracy: 0.9804 - loss: 0.0534 - val_accuracy: 0.9880 - val_loss: 0.0354 - learning_rate: 2.0000e-04
Epoch 9/15
156/156 ━━━━━━━━━━━━━━━━━━━━ 171s 1s/step - accuracy: 0.9807 - loss: 0.0495 - val_accuracy: 0.9882 - val_loss: 0.0317 - learning_rate: 2.0000e-04
Epoch 10/15
156/156 ━━━━━━━━━━━━━━━━━━━━ 179s 1s/step - accuracy: 0.9842 - loss: 0.0427 - val_accuracy: 0.9894 - val_loss: 0.0319 - learning_rate: 2.0000e-04
Epoch 11/15
156/156 ━━━━━━━━━━━━━━━━━━━━ 173s 1s/step - accuracy: 0.9833 - loss: 0.0452 - val_accuracy: 0.9892 - val_loss: 0.0293 - learning_rate: 2.0000e-04
Epoch 12/15
156/156 ━━━━━━━━━━━━━━━━━━━━ 214s 1s/step - accuracy: 0.9839 - loss: 0.0413 - val_accuracy: 0.9889 - val_loss: 0.0320 - learning_rate: 2.0000e-04
Epoch 13/15
156/156 ━━━━━━━━━━━━━━━━━━━━ 181s 1s/step - accuracy: 0.9864 - loss: 0.0393 - val_accuracy: 0.9880 - val_loss: 0.0329 - learning_rate: 2.0000e-04
Epoch 14/15
156/156 ━━━━━━━━━━━━━━━━━━━━ 0s 1s/step - accuracy: 0.9878 - loss: 0.0355
Epoch 14: ReduceLROnPlateau reducing learning rate to 4.0000001899898055e-05.
156/156 ━━━━━━━━━━━━━━━━━━━━ 246s 1s/step - accuracy: 0.9877 - loss: 0.0358 - val_accuracy: 0.9885 - val_loss: 0.0340 - learning_rate: 2.0000e-04
Epoch 15/15
156/156 ━━━━━━━━━━━━━━━━━━━━ 176s 1s/step - accuracy: 0.9866 - loss: 0.0358 - val_accuracy: 0.9897 - val_loss: 0.0326 - learning_rate: 4.0000e-05
  1. Plot History
In [ ]:
def plot_history(history_1, initial_epochs):
    acc = history_1.history['accuracy']
    val_acc = history_1.history['val_accuracy']
    loss = history_1.history['loss']
    val_loss = history_1.history['val_loss']

    plt.figure(figsize=(16, 6))

    plt.subplot(1, 2, 1)
    plt.plot(acc, label='Training Accuracy')
    plt.plot(val_acc, label='Validation Accuracy')
    plt.legend(loc='lower right')
    plt.title('Training and Validation Accuracy')

    plt.subplot(1, 2, 2)
    plt.plot(loss, label='Training Loss')
    plt.plot(val_loss, label='Validation Loss')
    plt.legend(loc='upper right')
    plt.title('Training and Validation Loss')
    plt.show()

plot_history(history_1, EPOCHS_PHASE_1)
  1. Test Time Augmentation
In [ ]:
print("Evaluating model using TTA (Test-Time Augmentation) for Multi-Label...")

test_images = []
test_labels_true = []

for images, labels in test_ds.unbatch():
    test_images.append(images.numpy())
    test_labels_true.append(labels.numpy())

test_images = np.array(test_images)
test_labels_true = np.array(test_labels_true)

TTA_STEPS = 5
tta_predictions = []

for i in range(TTA_STEPS):
    print(f"TTA Step {i+1}/{TTA_STEPS}")
    aug_images = data_augmentation(test_images, training=True)
    preds = model.predict(aug_images, batch_size=BATCH_SIZE, verbose=0)
    tta_predictions.append(preds)

mean_tta_preds = np.mean(tta_predictions, axis=0)

THRESHOLD = 0.5
test_preds_binary = (mean_tta_preds > THRESHOLD).astype(int)

correct_predictions = np.equal(test_preds_binary, test_labels_true.astype(int))
tta_accuracy = np.mean(correct_predictions)

print(f"\nSubset Accuracy (Exact Match) on Test Set (with TTA): {np.mean(np.all(correct_predictions, axis=1)) * 100:.2f}%")
print(f"Binary Accuracy (Per-Class Average) on Test Set (with TTA): {tta_accuracy * 100:.2f}%")
Evaluating model using TTA (Test-Time Augmentation) for Multi-Label...
TTA Step 1/5
TTA Step 2/5
TTA Step 3/5
TTA Step 4/5
TTA Step 5/5

Subset Accuracy (Exact Match) on Test Set (with TTA): 97.20%
Binary Accuracy (Per-Class Average) on Test Set (with TTA): 98.86%
  1. Metrik Evaluasi
In [ ]:
from sklearn.metrics import multilabel_confusion_matrix

print("\nClassification Report (Multi-Label):\n")
print(classification_report(test_labels_true, test_preds_binary, target_names=class_names))

mcm = multilabel_confusion_matrix(test_labels_true, test_preds_binary)

fig, axes = plt.subplots(2, 2, figsize=(12, 10))
axes = axes.ravel()

for i, class_name in enumerate(class_names):
    sns.heatmap(mcm[i], annot=True, fmt='d', cmap='Blues', ax=axes[i], cbar=False,
                xticklabels=['Negative', 'Positive'], yticklabels=['Negative', 'Positive'])
    axes[i].set_title(f'Confusion Matrix: {class_name}')
    axes[i].set_xlabel('Predicted')
    axes[i].set_ylabel('True')

plt.tight_layout()
plt.show()
Classification Report (Multi-Label):

              precision    recall  f1-score   support

 Bercak Daun       0.96      0.94      0.95       224
  Daun Sehat       0.99      1.00      1.00       345
  Karat Daun       0.97      0.98      0.98       259
  hawar daun       0.98      0.97      0.98       242

   micro avg       0.98      0.98      0.98      1070
   macro avg       0.98      0.97      0.97      1070
weighted avg       0.98      0.98      0.98      1070
 samples avg       0.97      0.98      0.98      1070

/usr/local/lib/python3.12/dist-packages/sklearn/metrics/_classification.py:1565: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 in samples with no predicted labels. Use `zero_division` parameter to control this behavior.
  _warn_prf(average, modifier, f"{metric.capitalize()} is", len(result))
In [ ]:
# ──────────────────────────────────────────────
# 14. Upload Model ke Hugging Face Hub
# ──────────────────────────────────────────────
print("Mengunggah model ke Hugging Face Hub...")
try:
    from huggingface_hub import HfApi, login
    import os

    hf_token = os.environ.get("HF_TOKEN")
    if hf_token:
        login(token=hf_token, add_to_git_credential=False)

    api = HfApi()
    model_path = "/content/best_model/best_model.keras"
    repo_id = "MythEclipse2737/zeavis-edu-model"

    if os.path.exists(model_path):
        api.upload_file(
            path_or_fileobj=model_path,
            path_in_repo="best_model/best_model.keras",
            repo_id=repo_id,
            repo_type="model",
        )
        print(f"✅ Model berhasil diunggah ke https://huggingface.co/{repo_id}")
    else:
        print(f"⚠️  {model_path} tidak ditemukan, upload dilewati.")
except ImportError:
    print("⚠️  huggingface_hub tidak terinstal. Jalankan: pip install huggingface_hub")
except Exception as e:
    print(f"⚠️  Upload gagal: {e}")