198 lines
7.1 KiB
Python
198 lines
7.1 KiB
Python
"""
|
|
Tugas 2 (Week 2): Eksplorasi Kedalaman Arsitektur CNN
|
|
Memodifikasi CNN dengan menambahkan satu blok Conv2D + MaxPooling ketiga.
|
|
Mengevaluasi:
|
|
1. Apakah model yang lebih dalam menghasilkan akurasi lebih tinggi?
|
|
2. Bagaimana perbandingan waktu training per epoch?
|
|
3. Catatan temuan dalam satu paragraf analitis.
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import time
|
|
|
|
# Sembunyikan log TensorFlow yang berlebihan
|
|
os.environ["TF_CPP_MIN_LOG_LEVEL"] = "2"
|
|
|
|
import matplotlib
|
|
matplotlib.use("Agg")
|
|
import matplotlib.pyplot as plt
|
|
import numpy as np
|
|
import tensorflow as tf
|
|
|
|
# Tambahkan root directory ke path
|
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
|
|
|
from src.data_loader import load_mnist_for_cnn
|
|
from src.models import create_cnn_mnist_model, create_deep_cnn_mnist_model
|
|
from src.utils import EpochTimerCallback, ensure_dir
|
|
|
|
|
|
def main():
|
|
SEED = 42
|
|
EPOCHS = 10
|
|
BATCH_SIZE = 128
|
|
VAL_SPLIT = 0.1
|
|
|
|
tf.random.set_seed(SEED)
|
|
np.random.seed(SEED)
|
|
|
|
print("=" * 75)
|
|
print("TUGAS 2: EKSPLORASI KEDALAMAN ARSITEKTUR CNN (2 BLOK vs 3 BLOK)")
|
|
print("=" * 75)
|
|
|
|
# 1. Load Data
|
|
print("\nMemuat dataset MNIST...")
|
|
(x_train, y_train), (x_test, y_test) = load_mnist_for_cnn()
|
|
|
|
architectures = [
|
|
{
|
|
"name": "CNN 2 Blok (Baseline)",
|
|
"builder": create_cnn_mnist_model,
|
|
"desc": "Conv(32) + MaxPool -> Conv(64) + MaxPool -> Dense(64)"
|
|
},
|
|
{
|
|
"name": "CNN 3 Blok (Lebih Dalam)",
|
|
"builder": create_deep_cnn_mnist_model,
|
|
"desc": "Conv(32) + MaxPool -> Conv(64) + MaxPool -> Conv(128) + MaxPool -> Dense(64)"
|
|
}
|
|
]
|
|
|
|
results = []
|
|
histories = {}
|
|
|
|
for arch in architectures:
|
|
model_name = arch["name"]
|
|
print("\n" + "=" * 75)
|
|
print(f"PELATIHAN: {model_name}")
|
|
print(f"Konfigurasi: {arch['desc']}")
|
|
print("=" * 75)
|
|
|
|
model = arch["builder"]()
|
|
model.summary()
|
|
|
|
timer_cb = EpochTimerCallback()
|
|
start_train_time = time.time()
|
|
|
|
history = model.fit(
|
|
x_train,
|
|
y_train,
|
|
validation_split=VAL_SPLIT,
|
|
epochs=EPOCHS,
|
|
batch_size=BATCH_SIZE,
|
|
verbose=1,
|
|
callbacks=[timer_cb]
|
|
)
|
|
|
|
total_train_time = time.time() - start_train_time
|
|
avg_epoch_time = float(np.mean(timer_cb.epoch_times)) if timer_cb.epoch_times else 0.0
|
|
|
|
# Evaluasi pada test set
|
|
test_loss, test_acc = model.evaluate(x_test, y_test, verbose=0)
|
|
n_params = model.count_params()
|
|
|
|
results.append({
|
|
"name": model_name,
|
|
"params": n_params,
|
|
"test_acc": test_acc * 100,
|
|
"test_loss": test_loss,
|
|
"epoch_time": avg_epoch_time,
|
|
"total_time": total_train_time,
|
|
"model_obj": model
|
|
})
|
|
|
|
histories[model_name] = history
|
|
|
|
# Simpan model 3 blok
|
|
models_dir = os.path.join("results", "models")
|
|
ensure_dir(models_dir)
|
|
results[1]["model_obj"].save(os.path.join(models_dir, "model_cnn_3blocks.keras"))
|
|
|
|
# Tabel Perbandingan Kuantitatif
|
|
print("\n\n" + "=" * 75)
|
|
print("HASIL PERBANDINGAN: KEDALAMAN CNN PADA MNIST")
|
|
print("=" * 75)
|
|
print(f"{'Arsitektur Model':<28}{'Parameter':>14}{'Test Acc':>14}{'Waktu/Epoch':>18}")
|
|
print("-" * 75)
|
|
for r in results:
|
|
print(f"{r['name']:<28}{r['params']:>14,}{r['test_acc']:>13.2f}%{r['epoch_time']:>16.2f}s")
|
|
print("-" * 75)
|
|
|
|
diff_acc = results[1]["test_acc"] - results[0]["test_acc"]
|
|
time_increase = ((results[1]["epoch_time"] - results[0]["epoch_time"]) / results[0]["epoch_time"]) * 100
|
|
param_diff = results[1]["params"] - results[0]["params"]
|
|
|
|
print(f"Selisih Akurasi (3 Blok vs 2 Blok) : {diff_acc:+.2f}% poin")
|
|
print(f"Perubahan Waktu Training / Epoch : {time_increase:+.1f}%")
|
|
print(f"Selisih Jumlah Parameter : {param_diff:+,} parameter")
|
|
print("=" * 75)
|
|
|
|
# Visualisasi
|
|
figures_dir = os.path.join("results", "figures")
|
|
ensure_dir(figures_dir)
|
|
fig_path = os.path.join(figures_dir, "perbandingan_kedalaman_cnn.png")
|
|
|
|
fig, axes = plt.subplots(1, 2, figsize=(13, 5))
|
|
epochs_range = range(1, EPOCHS + 1)
|
|
|
|
# Plot Accuracy Perbandingan
|
|
for name, hist in histories.items():
|
|
axes[0].plot(epochs_range, hist.history["val_accuracy"], label=f"{name} (Val Acc)", linewidth=2)
|
|
axes[0].set_title("Validation Accuracy per Epoch", fontsize=12, fontweight="bold")
|
|
axes[0].set_xlabel("Epoch", fontsize=10)
|
|
axes[0].set_ylabel("Accuracy", fontsize=10)
|
|
axes[0].legend()
|
|
axes[0].grid(True, alpha=0.3)
|
|
|
|
# Bar chart perbandingan Waktu vs Akurasi
|
|
labels = [r["name"] for r in results]
|
|
accs = [r["test_acc"] for r in results]
|
|
times = [r["epoch_time"] for r in results]
|
|
|
|
x = np.arange(len(labels))
|
|
width = 0.35
|
|
|
|
ax2 = axes[1].twinx()
|
|
p1 = axes[1].bar(x - width/2, accs, width, label="Test Accuracy (%)", color="#3498db")
|
|
p2 = ax2.bar(x + width/2, times, width, label="Waktu / Epoch (s)", color="#e67e22")
|
|
|
|
axes[1].set_ylabel("Accuracy (%)", color="#3498db", fontsize=10)
|
|
ax2.set_ylabel("Detik per Epoch", color="#e67e22", fontsize=10)
|
|
axes[1].set_xticks(x)
|
|
axes[1].set_xticklabels(labels, fontsize=10)
|
|
axes[1].set_ylim(97, 100)
|
|
axes[1].set_title("Trade-off: Test Accuracy vs Waktu Training", fontsize=12, fontweight="bold")
|
|
|
|
# Nilai di atas bar
|
|
for bar in p1:
|
|
axes[1].text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.05, f"{bar.get_height():.2f}%", ha='center', va='bottom', fontsize=9)
|
|
for bar in p2:
|
|
ax2.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.2, f"{bar.get_height():.2f}s", ha='center', va='bottom', fontsize=9)
|
|
|
|
plt.tight_layout()
|
|
plt.savefig(fig_path, dpi=150)
|
|
plt.close()
|
|
print(f"\n[INFO] Grafik perbandingan tersimpan di: {fig_path}")
|
|
|
|
# Catatan Analisis Satu Paragraf (Sesuai Instruksi Tugas 2)
|
|
print("\n" + "=" * 75)
|
|
print("CATATAN TEMUAN (SATU PARAGRAF ANALISIS):")
|
|
print("=" * 75)
|
|
paragraf = (
|
|
f"Penambahan blok ketiga (Conv2D 128 filter + MaxPooling2D) pada citra MNIST (28x28 piksel) "
|
|
f"menghasilkan akurasi test sebesar {results[1]['test_acc']:.2f}% dibandingkan dengan {results[0]['test_acc']:.2f}% "
|
|
f"pada baseline 2 blok (selisih {diff_acc:+.2f}% poin), namun meningkatkan waktu komputasi training "
|
|
f"sebesar {time_increase:+.1f}% per epoch ({results[1]['epoch_time']:.2f}s vs {results[0]['epoch_time']:.2f}s) "
|
|
f"serta menambah kompleksitas parameter. Pada dataset sederhana dengan resolusi spasial kecil seperti MNIST, "
|
|
f"tiga kali operasi pooling mereduksi resolusi spasial representasi secara drastis (hingga 3x3 piksel), "
|
|
f"sehingga arsitektur yang lebih dalam mengalami fenomena diminishing returns di mana penambahan layer "
|
|
f"tidak memberikan peningkatan performa yang sebanding dengan biaya komputasi yang dikeluarkan; "
|
|
f"oleh karena itu, arsitektur CNN 2 blok tetap menjadi pilihan paling optimal dan efisien untuk MNIST."
|
|
)
|
|
print(paragraf)
|
|
print("=" * 75 + "\n")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|