185 lines
5.5 KiB
Python

"""
Tugas No. 4 - Implementasi CNN sederhana untuk MNIST
dan perbandingannya dengan MLP dari Tugas No. 1.
Cara menjalankan di VS Code:
1. Aktifkan virtual environment
2. pip install tensorflow numpy matplotlib
3. Klik tombol Run, atau jalankan: python task4_cnn.py
Dataset akan diunduh otomatis saat pertama kali dijalankan
dan disimpan di ~/.keras/datasets/
"""
import os
# Sembunyikan log informasi TensorFlow (harus sebelum import tensorflow)
os.environ["TF_CPP_MIN_LOG_LEVEL"] = "2"
import time
import matplotlib
matplotlib.use("Agg") # simpan plot sebagai file, tidak membuka jendela
import matplotlib.pyplot as plt
import numpy as np
import tensorflow as tf
import keras
from keras import layers
#Konfigurasi
SEED = 42
EPOCHS = 10
BATCH_SIZE = 128
VAL_SPLIT = 0.1
tf.random.set_seed(SEED)
np.random.seed(SEED)
# Hasil MLP terbaik dari Tugas No. 1 (Model C: 256-128-64).
# Ganti angka di bawah ini dengan hasil eksperimen Anda sendiri.
MLP_ACCURACY = 0.9764
MLP_PARAMS = 242_762
MLP_EPOCH_TIME = 2.50
#1. Load dan preprocessing data
print("Memuat dataset MNIST...")
(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()
print(f"Data training : {x_train.shape[0]:,} gambar")
print(f"Data testing : {x_test.shape[0]:,} gambar")
print(f"Ukuran gambar : {x_train.shape[1]} x {x_train.shape[2]} piksel\n")
# ormalisasi nilai piksel dari 0-255 menjadi 0-1
x_train = x_train.astype("float32") / 255.0
x_test = x_test.astype("float32") / 255.0
#CNN membutuhkan dimensi channel: (N, 28, 28) -> (N, 28, 28, 1)
#Berbeda dengan MLP yang meratakan gambar menjadi (N, 784)
x_train = x_train.reshape(-1, 28, 28, 1)
x_test = x_test.reshape(-1, 28, 28, 1)
#2. Bangun model CNN
model = keras.Sequential(
name="cnn_mnist",
layers=[
layers.Input(shape=(28, 28, 1)),
# Blok konvolusi 1: deteksi fitur sederhana (tepi, garis)
layers.Conv2D(32, (3, 3), activation="relu"),
layers.MaxPooling2D((2, 2)),
# Blok konvolusi 2: gabungkan menjadi fitur lebih kompleks
layers.Conv2D(64, (3, 3), activation="relu"),
layers.MaxPooling2D((2, 2)),
# Klasifikasi
layers.Flatten(),
layers.Dense(64, activation="relu"),
layers.Dropout(0.3),
layers.Dense(10, activation="softmax"),
],
)
model.compile(
optimizer="adam",
loss="sparse_categorical_crossentropy",
metrics=["accuracy"],
)
model.summary()
# 3. Training dengan pencatatan waktu per epoch
epoch_times = []
class TimeCallback(keras.callbacks.Callback):
"""Mencatat durasi setiap epoch."""
def on_epoch_begin(self, epoch, logs=None):
self._start = time.time()
def on_epoch_end(self, epoch, logs=None):
epoch_times.append(time.time() - self._start)
print("\nMemulai training CNN...")
print("Perkiraan waktu: 15-20 detik per epoch di CPU (total sekitar 3 menit).\n")
history = model.fit(
x_train,
y_train,
validation_split=VAL_SPLIT,
epochs=EPOCHS,
batch_size=BATCH_SIZE,
verbose=2,
callbacks=[TimeCallback()],
)
# 4. Evaluasi
test_loss, test_acc = model.evaluate(x_test, y_test, verbose=0)
n_params = model.count_params()
avg_epoch_time = float(np.mean(epoch_times))
predictions = np.argmax(model.predict(x_test, verbose=0), axis=1)
n_wrong = int(np.sum(predictions != y_test))
# 5. Perbandingan CNN vs MLP
error_mlp = (1 - MLP_ACCURACY) * 100
error_cnn = (1 - test_acc) * 100
gain_point = (test_acc - MLP_ACCURACY) * 100
error_reduction = (1 - error_cnn / error_mlp) * 100
print("\n" + "=" * 62)
print("HASIL EKSPERIMEN CNN")
print("=" * 62)
print(f"Test accuracy : {test_acc * 100:.2f}%")
print(f"Test loss : {test_loss:.4f}")
print(f"Jumlah parameter : {n_params:,}")
print(f"Waktu training/epoch : {avg_epoch_time:.2f} detik")
print(f"Salah klasifikasi : {n_wrong} dari {len(y_test):,} gambar")
print("\n" + "=" * 62)
print("PERBANDINGAN CNN vs MLP")
print("=" * 62)
print(f"{'Metrik':<24}{'MLP':>14}{'CNN':>14}")
print("-" * 62)
print(f"{'Test accuracy':<24}{MLP_ACCURACY * 100:>13.2f}%{test_acc * 100:>13.2f}%")
print(f"{'Error rate':<24}{error_mlp:>13.2f}%{error_cnn:>13.2f}%")
print(f"{'Jumlah parameter':<24}{MLP_PARAMS:>14,}{n_params:>14,}")
print(f"{'Waktu/epoch (detik)':<24}{MLP_EPOCH_TIME:>14.2f}{avg_epoch_time:>14.2f}")
print("-" * 62)
print(f"Improvement akurasi : +{gain_point:.2f} poin persentase")
print(f"Pengurangan error rate : {error_reduction:.1f}% (relatif)")
print(f"Selisih parameter : {n_params - MLP_PARAMS:+,}")
print("=" * 62)
# 6. Simpan grafik
fig, axes = plt.subplots(1, 2, figsize=(12, 4.5))
epochs_range = range(1, EPOCHS + 1)
axes[0].plot(epochs_range, history.history["accuracy"], label="Train accuracy")
axes[0].plot(epochs_range, history.history["val_accuracy"], label="Validation accuracy")
axes[0].set_title("Learning Curve CNN")
axes[0].set_xlabel("Epoch")
axes[0].set_ylabel("Accuracy")
axes[0].legend()
axes[0].grid(alpha=0.3)
axes[1].bar(
["MLP terbaik", "CNN"],
[MLP_ACCURACY * 100, test_acc * 100],
color=["#55A868", "#C44E52"],
)
axes[1].set_ylim(96, 100)
axes[1].set_ylabel("Test Accuracy (%)")
axes[1].set_title("Perbandingan MLP vs CNN")
for i, value in enumerate([MLP_ACCURACY * 100, test_acc * 100]):
axes[1].text(i, value + 0.05, f"{value:.2f}%", ha="center")
plt.tight_layout()
plt.savefig("hasil_cnn.png", dpi=130)
print("\nGrafik disimpan sebagai: hasil_cnn.png")
# Simpan model terlatih agar tidak perlu training ulang
model.save("model_cnn_mnist.keras")
print("Model disimpan sebagai: model_cnn_mnist.keras")