refactor: restrukturisasi folder eksperimen ke tasks modular

This commit is contained in:
matthewflrnt
2026-09-27 18:50:08 +07:00
parent e827aa0e96
commit fe63a50997
9 changed files with 112 additions and 60 deletions
+148
View File
@@ -0,0 +1,148 @@
"""
Tugas No. 4 - Implementasi CNN sederhana untuk MNIST
dan perbandingannya dengan MLP dari Tugas No. 1.
"""
import os
import sys
import time
# Sembunyikan log informasi TensorFlow
os.environ["TF_CPP_MIN_LOG_LEVEL"] = "2"
import matplotlib
matplotlib.use("Agg") # simpan plot sebagai file
import matplotlib.pyplot as plt
import numpy as np
import tensorflow as tf
# Tambahkan root directory ke path agar dapat import dari src
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
from src.utils import EpochTimerCallback, ensure_dir
def main():
# 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)
MLP_ACCURACY = 0.9764
MLP_PARAMS = 242_762
MLP_EPOCH_TIME = 2.50
# 1. Load dan preprocessing data
print("Memuat dataset MNIST untuk CNN...")
(x_train, y_train), (x_test, y_test) = load_mnist_for_cnn()
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 (channel={x_train.shape[3]})\n")
# 2. Bangun model CNN
model = create_cnn_mnist_model()
model.summary()
# 3. Training dengan pencatatan waktu per epoch
timer_cb = EpochTimerCallback()
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=[timer_cb],
)
# 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(timer_cb.epoch_times)) if timer_cb.epoch_times else 0.0
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 ke folder results/02_cnn/figures
results_fig_dir = os.path.join("results", "02_cnn", "figures")
ensure_dir(results_fig_dir)
fig_path = os.path.join(results_fig_dir, "hasil_cnn.png")
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(fig_path, dpi=130)
print(f"\n[INFO] Grafik disimpan di: {fig_path}")
# 7. Simpan model terlatih ke folder results/02_cnn/models
results_model_dir = os.path.join("results", "02_cnn", "models")
ensure_dir(results_model_dir)
model_path = os.path.join(results_model_dir, "model_cnn_mnist.keras")
model.save(model_path)
print(f"[INFO] Model disimpan di: {model_path}")
if __name__ == "__main__":
main()
+195
View File
@@ -0,0 +1,195 @@
"""
Tugas 1 (Week 2): Pipeline CNN MNIST Lengkap
Memenuhi seluruh poin instruksi:
(a) Model Summary
(b) Loss & Accuracy curve training vs validation
(c) Confusion Matrix pada test set
(d) Perbandingan accuracy MLP vs CNN dan analisis efisiensi parameter
"""
import os
import sys
# 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 seaborn as sns
from sklearn.metrics import classification_report, confusion_matrix
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
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)
# Baseline MLP Terbaik (Model C dari Tugas 1 & 2)
MLP_ACCURACY = 0.9824
MLP_PARAMS = 244_554
MLP_EPOCH_TIME = 2.50
print("=" * 70)
print("TUGAS 1: PIPELINE CNN MNIST LENGKAP")
print("=" * 70)
# 1. Load Data
print("\n[1/5] Memuat dataset MNIST untuk CNN...")
(x_train, y_train), (x_test, y_test) = load_mnist_for_cnn()
print(f" -> Data training : {x_train.shape[0]:,} gambar ({x_train.shape[1]}x{x_train.shape[2]}x{x_train.shape[3]})")
print(f" -> Data testing : {x_test.shape[0]:,} gambar")
# 2. Model Summary (Poin A)
print("\n[2/5] Membangun Arsitektur CNN MNIST...")
model = create_cnn_mnist_model()
print("\n" + "-" * 70)
print("(a) MODEL SUMMARY:")
print("-" * 70)
model.summary()
# 3. Training Model
print("\n[3/5] Memulai proses training (10 Epochs)...")
timer_cb = EpochTimerCallback()
history = model.fit(
x_train,
y_train,
validation_split=VAL_SPLIT,
epochs=EPOCHS,
batch_size=BATCH_SIZE,
verbose=1,
callbacks=[timer_cb],
)
avg_epoch_time = float(np.mean(timer_cb.epoch_times)) if timer_cb.epoch_times else 0.0
# 4. Evaluasi & Prediksi pada Test Set
print("\n[4/5] Evaluasi pada Test Set (10,000 data)...")
test_loss, test_acc = model.evaluate(x_test, y_test, verbose=0)
y_pred_probs = model.predict(x_test, verbose=0)
y_pred = np.argmax(y_pred_probs, axis=1)
print(f" -> Test Accuracy : {test_acc * 100:.2f}%")
print(f" -> Test Loss : {test_loss:.4f}")
# 5. Visualisasi & Penyimpanan
figures_dir = os.path.join("results", "02_cnn", "figures")
ensure_dir(figures_dir)
# (b) Loss & Accuracy Curve
print("\n[5/5] Membuat visualisasi...")
curve_path = os.path.join(figures_dir, "loss_accuracy_cnn_lengkap.png")
fig, axes = plt.subplots(1, 2, figsize=(13, 5))
epochs_range = range(1, EPOCHS + 1)
# Loss Curve
axes[0].plot(epochs_range, history.history["loss"], "b-o", label="Training Loss")
axes[0].plot(epochs_range, history.history["val_loss"], "r--s", label="Validation Loss")
axes[0].set_title("(b1) Loss Curve: Training vs Validation", fontsize=12, fontweight="bold")
axes[0].set_xlabel("Epoch", fontsize=10)
axes[0].set_ylabel("Cross-Entropy Loss", fontsize=10)
axes[0].legend()
axes[0].grid(True, alpha=0.3)
# Accuracy Curve
axes[1].plot(epochs_range, history.history["accuracy"], "b-o", label="Training Accuracy")
axes[1].plot(epochs_range, history.history["val_accuracy"], "r--s", label="Validation Accuracy")
axes[1].set_title("(b2) Accuracy Curve: Training vs Validation", fontsize=12, fontweight="bold")
axes[1].set_xlabel("Epoch", fontsize=10)
axes[1].set_ylabel("Accuracy", fontsize=10)
axes[1].legend()
axes[1].grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig(curve_path, dpi=150)
plt.close()
print(f" -> (b) Kurva Loss & Accuracy tersimpan: {curve_path}")
# (c) Confusion Matrix pada Test Set
cm_path = os.path.join(figures_dir, "confusion_matrix_cnn.png")
cm = confusion_matrix(y_test, y_pred)
plt.figure(figsize=(9, 7.5))
sns.heatmap(
cm,
annot=True,
fmt="d",
cmap="Blues",
cbar=True,
xticklabels=range(10),
yticklabels=range(10),
annot_kws={"size": 10}
)
plt.title("(c) Confusion Matrix CNN pada MNIST Test Set (10.000 Gambar)", fontsize=13, fontweight="bold", pad=12)
plt.xlabel("Predicted Digit", fontsize=11, labelpad=8)
plt.ylabel("True Digit", fontsize=11, labelpad=8)
plt.tight_layout()
plt.savefig(cm_path, dpi=150)
plt.close()
print(f" -> (c) Confusion Matrix tersimpan: {cm_path}")
# (d) Perbandingan Kuantitatif MLP vs CNN
n_params_cnn = model.count_params()
diff_acc = (test_acc - MLP_ACCURACY) * 100
param_saving = (1 - n_params_cnn / MLP_PARAMS) * 100
error_mlp = (1 - MLP_ACCURACY) * 100
error_cnn = (1 - test_acc) * 100
rel_error_red = (1 - error_cnn / error_mlp) * 100
print("\n" + "=" * 70)
print("(d) PERBANDINGAN ACCURACY & EFISIENSI: MLP vs CNN")
print("=" * 70)
print(f"{'Metrik Evaluasi':<28}{'MLP Terbaik':>18}{'CNN Sederhana':>18}")
print("-" * 70)
print(f"{'Test Accuracy':<28}{MLP_ACCURACY * 100:>17.2f}%{test_acc * 100:>17.2f}%")
print(f"{'Error Rate':<28}{error_mlp:>17.2f}%{error_cnn:>17.2f}%")
print(f"{'Jumlah Parameter':<28}{MLP_PARAMS:>18,}{n_params_cnn:>18,}")
print(f"{'Waktu Training / Epoch':<28}{MLP_EPOCH_TIME:>16.2f}s{avg_epoch_time:>16.2f}s")
print("-" * 70)
print(f"-> Improvement Akurasi : +{diff_acc:.2f}% poin persentase")
print(f"-> Pengurangan Error : {rel_error_red:.1f}% penurunan error relatif")
print(f"-> Efisiensi Parameter : CNN hemat {param_saving:.1f}% bobot dibanding MLP")
print("=" * 70)
# Analisis Singkat Tertulis
print("\n" + "=" * 70)
print("ANALISIS: EFISIENSI PARAMETER & PERFORMA (MLP vs CNN)")
print("=" * 70)
print(
f"1. Jumlah Parameter:\n"
f" Model CNN memiliki {n_params_cnn:,} parameter, sedangkan MLP terbaik memiliki {MLP_PARAMS:,} parameter.\n"
f" CNN menggunakan {param_saving:.1f}% parameter lebih sedikit (sekitar separuh dari MLP).\n\n"
f"2. Mana yang Lebih Efisien?\n"
f" CNN jauh lebih efisien. Meskipun memiliki parameter yang jauh lebih sedikit, CNN menghasilkan\n"
f" akurasi {test_acc * 100:.2f}% (lebih tinggi dari MLP {MLP_ACCURACY * 100:.2f}%).\n\n"
f"3. Mengapa demikian?\n"
f" - Parameter Sharing: Filter konvolusi berukuran kecil (3x3) digunakan secara berulang di seluruh citra,\n"
f" sehingga tidak membutuhkan koneksi bobot penuh antar setiap piksel seperti Dense layer pada MLP.\n"
f" - Preservasi Spasial: CNN memproses data dalam representasi 2D asli (lebar x tinggi x channel),\n"
f" menjaga hubungan antar-piksel tetangga, tepi, dan kontur angka tulisan tangan."
)
print("=" * 70 + "\n")
# Simpan bobot terbaru
models_dir = os.path.join("results", "02_cnn", "models")
ensure_dir(models_dir)
model.save(os.path.join(models_dir, "model_cnn_mnist.keras"))
print(f"[INFO] Model terbaru disimpan di: {os.path.join(models_dir, 'model_cnn_mnist.keras')}")
if __name__ == "__main__":
main()
+197
View File
@@ -0,0 +1,197 @@
"""
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", "02_cnn", "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", "02_cnn", "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()
+302
View File
@@ -0,0 +1,302 @@
"""
Tugas Anggota 2 (Week 2): CNN Evaluation + Visualization
=========================================================
PENTING - KONSISTENSI KONFIGURASI DENGAN ANGGOTA 1:
Skrip ini TIDAK melatih ulang model dari nol. Sebagai gantinya, skrip ini
memuat bobot model CNN yang sudah dilatih oleh Anggota 1
(results/models/model_cnn_mnist.keras) pada 05_cnn_full_pipeline.py.
Ini adalah cara paling aman untuk menjamin "tidak ada konfigurasi berbeda"
antara Anggota 1 dan Anggota 2, karena:
- Dataset yang sama -> load_mnist_for_cnn() (src/data_loader.py)
- Preprocessing yang sama -> normalisasi 0-1, reshape (28, 28, 1)
- Epoch yang sama -> 10 epoch (bobot hasil training Anggota 1)
- Batch size yang sama -> 128 (dipakai saat training oleh Anggota 1)
- Optimizer yang sama -> Adam (tersimpan di dalam file .keras)
Konstanta di bawah HANYA didokumentasikan ulang (bukan dipakai untuk
melatih ulang) agar terlihat eksplisit bahwa konfigurasi cocok dengan
skrip Anggota 1.
Cakupan tugas (sesuai skema kelompok):
(1) Evaluasi menyeluruh pada test set (classification report per kelas)
(2) Visualisasi confusion matrix (raw & ternormalisasi/persentase)
(3) Visualisasi akurasi per kelas (digit mana yang paling sulit)
(4) Visualisasi distribusi confidence prediksi (benar vs salah)
(5) Visualisasi contoh gambar yang salah diklasifikasikan
"""
import os
import sys
# 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 pandas as pd
import seaborn as sns
from sklearn.metrics import classification_report, confusion_matrix
import tensorflow as tf
from tensorflow import keras
# 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.utils import ensure_dir
# ---------------------------------------------------------------------------
# KONFIGURASI (harus identik dengan Anggota 1 / 05_cnn_full_pipeline.py)
# ---------------------------------------------------------------------------
SEED = 42
EPOCHS = 10 # epoch training model (didokumentasikan, tidak dipakai ulang)
BATCH_SIZE = 128 # batch size training model (didokumentasikan, tidak dipakai ulang)
OPTIMIZER = "adam" # optimizer training model (tersimpan di file .keras)
MODEL_PATH = os.path.join("results", "02_cnn", "models", "model_cnn_mnist.keras")
FIGURES_DIR = os.path.join("results", "02_cnn", "figures")
REPORTS_DIR = os.path.join("results", "02_cnn", "reports")
CLASS_NAMES = [str(i) for i in range(10)]
def main():
tf.random.set_seed(SEED)
np.random.seed(SEED)
ensure_dir(FIGURES_DIR)
ensure_dir(REPORTS_DIR)
print("=" * 75)
print("TUGAS ANGGOTA 2: CNN EVALUATION + VISUALIZATION")
print("=" * 75)
print(
f"[KONFIGURASI - selaras dengan Anggota 1] "
f"seed={SEED} | epochs={EPOCHS} | batch_size={BATCH_SIZE} | optimizer={OPTIMIZER}"
)
# 1. Load Data (dataset & preprocessing sama persis dengan Anggota 1)
print("\n[1/6] Memuat dataset MNIST (load_mnist_for_cnn - sama dengan Anggota 1)...")
(x_train, y_train), (x_test, y_test) = load_mnist_for_cnn()
print(f" -> Data testing : {x_test.shape[0]:,} gambar {x_test.shape[1:]}")
# 2. Load Model Terlatih dari Anggota 1
print(f"\n[2/6] Memuat model terlatih dari: {MODEL_PATH}")
if not os.path.exists(MODEL_PATH):
raise FileNotFoundError(
f"Model tidak ditemukan di '{MODEL_PATH}'. "
f"Jalankan dahulu tasks/cnn/05_cnn_full_pipeline.py (skrip Anggota 1)."
)
model = keras.models.load_model(MODEL_PATH)
print(f" -> Model '{model.name}' berhasil dimuat ({model.count_params():,} parameter).")
# 3. Evaluasi Umum pada Test Set
print("\n[3/6] Mengevaluasi model pada test set...")
test_loss, test_acc = model.evaluate(x_test, y_test, verbose=0)
y_pred_probs = model.predict(x_test, verbose=0)
y_pred = np.argmax(y_pred_probs, axis=1)
y_confidence = np.max(y_pred_probs, axis=1)
is_correct = (y_pred == y_test)
print(f" -> Test Accuracy : {test_acc * 100:.2f}%")
print(f" -> Test Loss : {test_loss:.4f}")
print(f" -> Total Salah : {np.sum(~is_correct):,} dari {len(y_test):,} gambar")
# -----------------------------------------------------------------
# (1) Classification Report per Kelas
# -----------------------------------------------------------------
print("\n[4/6] Membuat classification report per kelas...")
report_dict = classification_report(
y_test, y_pred, target_names=CLASS_NAMES, digits=4, output_dict=True
)
report_text = classification_report(y_test, y_pred, target_names=CLASS_NAMES, digits=4)
print("\n" + "-" * 75)
print("CLASSIFICATION REPORT (Precision / Recall / F1-Score per Digit):")
print("-" * 75)
print(report_text)
report_txt_path = os.path.join(REPORTS_DIR, "classification_report_cnn.txt")
with open(report_txt_path, "w") as f:
f.write("CLASSIFICATION REPORT - CNN MNIST (Anggota 2: CNN Evaluation)\n")
f.write(f"Test Accuracy: {test_acc * 100:.2f}% | Test Loss: {test_loss:.4f}\n")
f.write("=" * 65 + "\n")
f.write(report_text)
print(f" -> Laporan teks tersimpan: {report_txt_path}")
report_df = pd.DataFrame(report_dict).transpose()
report_csv_path = os.path.join(REPORTS_DIR, "classification_report_cnn.csv")
report_df.to_csv(report_csv_path)
print(f" -> Laporan CSV tersimpan : {report_csv_path}")
# Visualisasi precision/recall/f1 per kelas (bar chart berkelompok)
per_class_df = report_df.loc[CLASS_NAMES, ["precision", "recall", "f1-score"]]
fig, ax = plt.subplots(figsize=(11, 5))
per_class_df.plot(kind="bar", ax=ax, width=0.75,
color=["#3498db", "#2ecc71", "#e74c3c"])
ax.set_title("Precision, Recall, dan F1-Score per Digit (CNN)", fontsize=13, fontweight="bold")
ax.set_xlabel("Digit", fontsize=11)
ax.set_ylabel("Skor", fontsize=11)
ax.set_ylim(0.90, 1.005)
ax.set_xticklabels(CLASS_NAMES, rotation=0)
ax.legend(["Precision", "Recall", "F1-Score"], loc="lower right")
ax.grid(True, axis="y", alpha=0.3)
plt.tight_layout()
metrics_bar_path = os.path.join(FIGURES_DIR, "per_class_metrics_cnn.png")
plt.savefig(metrics_bar_path, dpi=150)
plt.close()
print(f" -> Grafik metrik per kelas tersimpan: {metrics_bar_path}")
# -----------------------------------------------------------------
# (2) Confusion Matrix: Raw Count & Ternormalisasi (%)
# -----------------------------------------------------------------
print("\n[5/6] Membuat visualisasi confusion matrix (raw & normalisasi)...")
cm = confusion_matrix(y_test, y_pred)
cm_norm = cm.astype("float") / cm.sum(axis=1, keepdims=True) * 100
fig, axes = plt.subplots(1, 2, figsize=(17, 7))
sns.heatmap(cm, annot=True, fmt="d", cmap="Blues", cbar=True,
xticklabels=CLASS_NAMES, yticklabels=CLASS_NAMES,
annot_kws={"size": 9}, ax=axes[0])
axes[0].set_title("Confusion Matrix (Jumlah Gambar)", fontsize=12, fontweight="bold")
axes[0].set_xlabel("Predicted Digit")
axes[0].set_ylabel("True Digit")
sns.heatmap(cm_norm, annot=True, fmt=".1f", cmap="Oranges", cbar=True,
xticklabels=CLASS_NAMES, yticklabels=CLASS_NAMES,
annot_kws={"size": 9}, ax=axes[1])
axes[1].set_title("Confusion Matrix (Persentase per Baris / Recall %)", fontsize=12, fontweight="bold")
axes[1].set_xlabel("Predicted Digit")
axes[1].set_ylabel("True Digit")
plt.suptitle("Evaluasi CNN pada MNIST Test Set (10.000 Gambar)", fontsize=14, fontweight="bold", y=1.02)
plt.tight_layout()
cm_dual_path = os.path.join(FIGURES_DIR, "confusion_matrix_cnn_detail.png")
plt.savefig(cm_dual_path, dpi=150, bbox_inches="tight")
plt.close()
print(f" -> Confusion matrix detail tersimpan: {cm_dual_path}")
# -----------------------------------------------------------------
# (3) Akurasi per Kelas (diagonal confusion matrix ternormalisasi)
# -----------------------------------------------------------------
per_class_acc = np.diag(cm_norm)
fig, ax = plt.subplots(figsize=(10, 5))
colors = ["#e74c3c" if a < per_class_acc.mean() else "#2ecc71" for a in per_class_acc]
bars = ax.bar(CLASS_NAMES, per_class_acc, color=colors)
ax.axhline(per_class_acc.mean(), color="gray", linestyle="--", linewidth=1.5,
label=f"Rata-rata: {per_class_acc.mean():.2f}%")
for bar, acc in zip(bars, per_class_acc):
ax.text(bar.get_x() + bar.get_width() / 2, bar.get_height() - 1.2,
f"{acc:.1f}%", ha="center", va="top", fontsize=9, color="white", fontweight="bold")
ax.set_title("Akurasi per Digit pada Test Set (CNN)", fontsize=13, fontweight="bold")
ax.set_xlabel("Digit")
ax.set_ylabel("Akurasi (%)")
ax.set_ylim(min(per_class_acc) - 5, 100.5)
ax.legend()
ax.grid(True, axis="y", alpha=0.3)
plt.tight_layout()
per_class_acc_path = os.path.join(FIGURES_DIR, "per_class_accuracy_cnn.png")
plt.savefig(per_class_acc_path, dpi=150)
plt.close()
print(f" -> Grafik akurasi per kelas tersimpan: {per_class_acc_path}")
hardest_idx = np.argsort(per_class_acc)[:3]
print(" -> 3 digit paling sulit dikenali:")
for idx in hardest_idx:
print(f" Digit {idx}: akurasi {per_class_acc[idx]:.2f}%")
# -----------------------------------------------------------------
# (4) Distribusi Confidence Prediksi (Benar vs Salah)
# -----------------------------------------------------------------
print("\n[6/6] Membuat visualisasi distribusi confidence & sampel kesalahan...")
fig, ax = plt.subplots(figsize=(9, 5.5))
ax.hist(y_confidence[is_correct], bins=30, alpha=0.65, color="#2ecc71",
label=f"Prediksi Benar (n={np.sum(is_correct):,})", density=True)
ax.hist(y_confidence[~is_correct], bins=30, alpha=0.75, color="#e74c3c",
label=f"Prediksi Salah (n={np.sum(~is_correct):,})", density=True)
ax.set_title("Distribusi Confidence Prediksi Model CNN", fontsize=13, fontweight="bold")
ax.set_xlabel("Confidence (Probabilitas Softmax Maksimum)")
ax.set_ylabel("Kepadatan (Density)")
ax.legend()
ax.grid(True, alpha=0.3)
plt.tight_layout()
confidence_path = os.path.join(FIGURES_DIR, "confidence_distribution_cnn.png")
plt.savefig(confidence_path, dpi=150)
plt.close()
print(f" -> Distribusi confidence tersimpan: {confidence_path}")
avg_conf_correct = y_confidence[is_correct].mean() * 100
avg_conf_wrong = y_confidence[~is_correct].mean() * 100
print(f" -> Rata-rata confidence saat BENAR : {avg_conf_correct:.2f}%")
print(f" -> Rata-rata confidence saat SALAH : {avg_conf_wrong:.2f}%")
# -----------------------------------------------------------------
# (5) Contoh Gambar yang Salah Diklasifikasikan
# -----------------------------------------------------------------
wrong_idx = np.where(~is_correct)[0]
rng = np.random.default_rng(SEED)
n_show = min(25, len(wrong_idx))
sample_idx = rng.choice(wrong_idx, size=n_show, replace=False)
fig, axes = plt.subplots(5, 5, figsize=(11, 12))
for ax, idx in zip(axes.flat, sample_idx):
ax.imshow(x_test[idx].reshape(28, 28), cmap="gray")
conf = y_confidence[idx] * 100
ax.set_title(f"True: {y_test[idx]} | Pred: {y_pred[idx]}\nConf: {conf:.1f}%",
fontsize=9, color="#c0392b")
ax.axis("off")
for ax in axes.flat[n_show:]:
ax.axis("off")
plt.suptitle(f"Contoh {n_show} Gambar Salah Klasifikasi dari {len(wrong_idx)} Total Kesalahan",
fontsize=13, fontweight="bold", y=1.0)
plt.tight_layout()
misclassified_path = os.path.join(FIGURES_DIR, "misclassified_samples_cnn.png")
plt.savefig(misclassified_path, dpi=150, bbox_inches="tight")
plt.close()
print(f" -> Contoh gambar salah klasifikasi tersimpan: {misclassified_path}")
# -----------------------------------------------------------------
# Ringkasan Akhir
# -----------------------------------------------------------------
summary_path = os.path.join(REPORTS_DIR, "evaluation_summary_cnn.csv")
summary_df = pd.DataFrame([{
"test_accuracy_pct": round(test_acc * 100, 4),
"test_loss": round(test_loss, 4),
"total_errors": int(np.sum(~is_correct)),
"total_samples": int(len(y_test)),
"avg_confidence_correct_pct": round(avg_conf_correct, 4),
"avg_confidence_wrong_pct": round(avg_conf_wrong, 4),
"hardest_digit_1": int(hardest_idx[0]),
"hardest_digit_1_acc_pct": round(per_class_acc[hardest_idx[0]], 4),
"hardest_digit_2": int(hardest_idx[1]),
"hardest_digit_2_acc_pct": round(per_class_acc[hardest_idx[1]], 4),
}])
summary_df.to_csv(summary_path, index=False)
print("\n" + "=" * 75)
print("RINGKASAN EVALUASI CNN (ANGGOTA 2)")
print("=" * 75)
print(f"Test Accuracy : {test_acc * 100:.2f}%")
print(f"Test Loss : {test_loss:.4f}")
print(f"Total Kesalahan : {np.sum(~is_correct):,} / {len(y_test):,}")
print(f"Confidence saat Benar : {avg_conf_correct:.2f}%")
print(f"Confidence saat Salah : {avg_conf_wrong:.2f}%")
print(f"Digit Paling Sulit : {hardest_idx[0]} ({per_class_acc[hardest_idx[0]]:.2f}%), "
f"{hardest_idx[1]} ({per_class_acc[hardest_idx[1]]:.2f}%)")
print("-" * 75)
print("File yang dihasilkan:")
print(f" 1. {metrics_bar_path}")
print(f" 2. {cm_dual_path}")
print(f" 3. {per_class_acc_path}")
print(f" 4. {confidence_path}")
print(f" 5. {misclassified_path}")
print(f" 6. {report_txt_path}")
print(f" 7. {report_csv_path}")
print(f" 8. {summary_path}")
print("=" * 75 + "\n")
if __name__ == "__main__":
main()
+137
View File
@@ -0,0 +1,137 @@
import sys
import os
import time
import pandas as pd
import matplotlib.pyplot as plt
# Tambahkan root directory ke path agar dapat import dari src
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")))
from src.data_loader import load_mnist_for_mlp
from src.models import create_mlp_model
def main():
print("=" * 60)
print("TUGAS 1: MEMUAT DATASET MNIST & EKSPERIMEN MLP")
print("=" * 60)
# 1. Load Data
(x_train, y_train), (x_test, y_test) = load_mnist_for_mlp()
print(f"Data training : {x_train.shape}")
print(f"Data testing : {x_test.shape}")
# 2. Konfigurasi 3 Model MLP
models_config = {
"Model A - 1 Hidden Layer": [64],
"Model B - 2 Hidden Layer": [128, 64],
"Model C - 3 Hidden Layer": [256, 128, 64]
}
results = []
EPOCHS = 10
BATCH_SIZE = 128
# 3. Training & Evaluasi
for model_name, architecture in models_config.items():
print("\n" + "=" * 60)
print(model_name)
print("=" * 60)
model = create_mlp_model(architecture)
model.summary()
print("\nMemulai training...")
start_time = time.time()
model.fit(
x_train,
y_train,
epochs=EPOCHS,
batch_size=BATCH_SIZE,
validation_split=0.1,
verbose=1
)
total_time = time.time() - start_time
average_time = total_time / EPOCHS
test_loss, test_accuracy = model.evaluate(x_test, y_test, verbose=0)
total_parameters = model.count_params()
results.append({
"model": model_name,
"parameters": total_parameters,
"accuracy": test_accuracy * 100,
"time_per_epoch": average_time
})
print("\nHasil:")
print(f"Test Accuracy : {test_accuracy * 100:.2f}%")
print(f"Jumlah Parameter : {total_parameters:,}")
print(f"Waktu/Epoch : {average_time:.2f} detik")
# 4. Ringkasan Perbandingan
print("\n\n" + "=" * 80)
print("HASIL PERBANDINGAN 3 MODEL MLP")
print("=" * 80)
print(f"{'Model':<30}{'Parameter':>15}{'Accuracy':>15}{'Time/Epoch':>15}")
print("-" * 80)
for res in results:
print(
f"{res['model']:<30}"
f"{res['parameters']:>15,}"
f"{res['accuracy']:>14.2f}%"
f"{res['time_per_epoch']:>14.2f}s"
)
best_model = max(results, key=lambda x: x["accuracy"])
print("\n" + "=" * 60)
print("MODEL DENGAN TEST ACCURACY TERTINGGI")
print("=" * 60)
print(f"Model : {best_model['model']}")
print(f"Test Accuracy : {best_model['accuracy']:.2f}%")
print(f"Jumlah Parameter : {best_model['parameters']:,}")
print(f"Waktu Training/Epoch: {best_model['time_per_epoch']:.2f} detik")
# 5. Simpan Hasil ke Folder results/01_mlp
REPORTS_DIR = os.path.join("results", "01_mlp", "reports")
FIGURES_DIR = os.path.join("results", "01_mlp", "figures")
os.makedirs(REPORTS_DIR, exist_ok=True)
os.makedirs(FIGURES_DIR, exist_ok=True)
df_results = pd.DataFrame(results)
csv_path = os.path.join(REPORTS_DIR, "perbandingan_mlp.csv")
df_results.to_csv(csv_path, index=False)
print(f"\n[OK] Ringkasan CSV disimpan ke : {csv_path}")
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
names = ["Model A\n(1 Layer)", "Model B\n(2 Layer)", "Model C\n(3 Layer)"]
accs = [r["accuracy"] for r in results]
bars1 = ax1.bar(names, accs, color=["#4C72B0", "#55A868", "#C44E52"])
ax1.set_title("Perbandingan Test Accuracy (%)", fontsize=12, fontweight="bold")
ax1.set_ylabel("Accuracy (%)")
ax1.set_ylim([max(0, min(accs) - 2), 100])
for bar in bars1:
yval = bar.get_height()
ax1.text(bar.get_x() + bar.get_width()/2, yval + 0.2, f"{yval:.2f}%", ha='center', va='bottom')
times = [r["time_per_epoch"] for r in results]
bars2 = ax2.bar(names, times, color=["#4C72B0", "#55A868", "#C44E52"])
ax2.set_title("Waktu Training per Epoch (detik)", fontsize=12, fontweight="bold")
ax2.set_ylabel("Detik / Epoch")
for bar in bars2:
yval = bar.get_height()
ax2.text(bar.get_x() + bar.get_width()/2, yval + 0.05, f"{yval:.2f}s", ha='center', va='bottom')
plt.tight_layout()
fig_path = os.path.join(FIGURES_DIR, "perbandingan_3_model_mlp.png")
plt.savefig(fig_path, dpi=130)
plt.close()
print(f"[OK] Grafik perbandingan disimpan ke: {fig_path}")
if __name__ == "__main__":
main()
+93
View File
@@ -0,0 +1,93 @@
import sys
import os
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
# Tambahkan root directory ke path agar dapat import dari src
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")))
from src.data_loader import load_mnist_for_mlp
from src.models import create_mlp_model, create_regularized_mlp_model
from src.utils import ensure_dir
def main():
print("=" * 60)
print("TUGAS 2: EKSPERIMEN REGULARISASI (BASELINE vs BATCHNORM + DROPOUT)")
print("=" * 60)
# 1. Load Data
(x_train, y_train), (x_test, y_test) = load_mnist_for_mlp()
EPOCHS = 20
BATCH_SIZE = 128
# 2. Baseline Model (Without Regularization)
print("\n" + "=" * 60)
print("TRAINING BASELINE MODEL (WITHOUT REGULARIZATION)")
print("=" * 60)
model_base = create_mlp_model([256, 128, 64])
history_base = model_base.fit(
x_train, y_train,
epochs=EPOCHS,
batch_size=BATCH_SIZE,
validation_split=0.1,
verbose=1
)
# 3. Regularized Model (BatchNorm + Dropout 0.3)
print("\n" + "=" * 60)
print("TRAINING REGULARIZED MODEL (WITH BATCHNORM + DROPOUT)")
print("=" * 60)
model_reg = create_regularized_mlp_model(hidden_layers=[256, 128, 64], dropout_rate=0.3)
history_reg = model_reg.fit(
x_train, y_train,
epochs=EPOCHS,
batch_size=BATCH_SIZE,
validation_split=0.1,
verbose=1
)
# 4. Save Model Artifact
results_model_dir = os.path.join("results", "01_mlp", "models")
ensure_dir(results_model_dir)
model_reg_path = os.path.join(results_model_dir, "model_c_regularized.keras")
model_reg.save(model_reg_path)
print(f"\n[INFO] Model teregularisasi disimpan di: {model_reg_path}")
# 5. Plot Comparison Curves
results_fig_dir = os.path.join("results", "01_mlp", "figures")
ensure_dir(results_fig_dir)
fig_path = os.path.join(results_fig_dir, "loss_regularization.png")
plt.figure(figsize=(14, 5))
# Subplot 1: Baseline
plt.subplot(1, 2, 1)
plt.plot(history_base.history['loss'], label='Training Loss', color='blue')
plt.plot(history_base.history['val_loss'], label='Validation Loss', color='orange', linestyle='--')
plt.title('Model C Without Regularization')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.legend()
plt.grid(True, alpha=0.3)
# Subplot 2: Regularized
plt.subplot(1, 2, 2)
plt.plot(history_reg.history['loss'], label='Training Loss', color='blue')
plt.plot(history_reg.history['val_loss'], label='Validation Loss', color='green', linestyle='--')
plt.title('Model C With BatchNorm + Dropout(0.3)')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig(fig_path, dpi=130)
print(f"[INFO] Grafik perbandingan loss disimpan di: {fig_path}")
if __name__ == "__main__":
main()
+67
View File
@@ -0,0 +1,67 @@
"""
Tugas 3: Contoh Implementasi Berbagai Arsitektur Model Sesuai Karakteristik Data
"""
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras.layers import Conv2D, Dense, Embedding, Flatten, LSTM, MaxPooling2D
from tensorflow.keras.models import Sequential
from xgboost import XGBClassifier
def build_models():
print("=" * 60)
print("TUGAS 3: CONTOH ARSITEKTUR MODEL SELECTION")
print("=" * 60)
# (a) Prediksi Stok Barang dari Deret Waktu (LSTM)
# Input shape: (samples, time_steps=12, features=1)
model_a = Sequential([
LSTM(32, input_shape=(12, 1)),
Dense(1)
], name="LSTM_Stock_Forecasting")
model_a.compile(optimizer='adam', loss='mse')
print("\n[Case A] Model LSTM untuk Prediksi Stok Barang (Time Series):")
model_a.summary()
# (b) Klasifikasi Foto Produk Rusak vs Normal (CNN)
# Input shape contoh citra RGB 128x128
model_b = Sequential([
Conv2D(32, (3, 3), activation='relu', input_shape=(128, 128, 3)),
MaxPooling2D((2, 2)),
Flatten(),
Dense(64, activation='relu'),
Dense(1, activation='sigmoid') # binary: rusak vs normal
], name="CNN_Defect_Classification")
model_b.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
print("\n[Case B] Model CNN untuk Klasifikasi Foto Produk (Computer Vision):")
model_b.summary()
# (c) Analisis Sentimen Ulasan Play Store (LSTM + Embedding)
vocab_size = 5000
max_length = 100
model_c = Sequential([
Embedding(input_dim=vocab_size, output_dim=64, input_length=max_length),
LSTM(64),
Dense(32, activation='relu'),
Dense(1, activation='sigmoid') # binary: positif vs negatif
], name="LSTM_Sentiment_Analysis")
model_c.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
print("\n[Case C] Model LSTM + Embedding untuk Analisis Sentimen (NLP):")
model_c.summary()
# (d) Prediksi Churn Pelanggan Tabular (ML Klasik - XGBoost)
# X_train memiliki dimensi (n_samples, 15)
model_d = XGBClassifier(
n_estimators=100,
max_depth=4,
learning_rate=0.1,
random_state=42
)
print("\n[Case D] Model XGBoost untuk Prediksi Churn (Tabular 15 fitur):")
print(model_d)
if __name__ == "__main__":
build_models()
+288
View File
@@ -0,0 +1,288 @@
import os
import re
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from datasets import load_dataset
from sklearn.model_selection import train_test_split
from sklearn.metrics import (
classification_report,
confusion_matrix,
ConfusionMatrixDisplay
)
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
from tensorflow.keras.callbacks import EarlyStopping
from tensorflow.keras.preprocessing.text import Tokenizer # type: ignore
from tensorflow.keras.preprocessing.sequence import pad_sequences
# ============================================================
# KONFIGURASI DIREKTORI PENYIMPANAN
# ============================================================
VOCAB_SIZE = 10000
MAX_LEN = 60
EMBED_DIM = 64
EPOCHS = 20
BATCH_SIZE = 64
RANDOM_STATE = 42
FIGURES_DIR = os.path.join("results", "03_nlp_sentiment", "figures")
REPORTS_DIR = os.path.join("results", "03_nlp_sentiment", "reports")
MODELS_DIR = os.path.join("results", "03_nlp_sentiment", "models")
os.makedirs(FIGURES_DIR, exist_ok=True)
os.makedirs(REPORTS_DIR, exist_ok=True)
os.makedirs(MODELS_DIR, exist_ok=True)
# ============================================================
# SEL 7 — LOAD DATASET TOKOPEDIA
# ============================================================
print("=" * 60)
print("SEL 7 — LOAD DATASET")
print("=" * 60)
dataset = load_dataset(
"farhamu/tokopedia-product-reviews-2019"
)
df = dataset["train"].to_pandas()
# Buang rating 3
df = df[df["rating"] != 3].copy()
# Rating 4-5 = positif (1), Rating 1-2 = negatif (0)
df["label"] = (df["rating"] >= 4).astype(int)
# Rename text menjadi teks
df = df.rename(
columns={"text": "teks"}
)[["teks", "label"]].reset_index(drop=True)
print(df.head(5))
print(f"\nShape : {df.shape}")
print(f"Kolom : {df.columns.tolist()}")
print(f"\nDistribusi label:\n{df['label'].value_counts().sort_index()}")
print(f"\nNull : {df.isnull().sum().sum()}")
# ============================================================
# DISTRIBUSI LABEL
# ============================================================
label_counts = df["label"].value_counts().sort_index()
plt.figure(figsize=(7, 5))
plt.bar(
["Negatif", "Positif"],
[label_counts.get(0, 0), label_counts.get(1, 0)]
)
plt.title("Distribusi Label Sentimen Tokopedia")
plt.xlabel("Sentimen")
plt.ylabel("Jumlah Review")
plt.tight_layout()
plt.savefig(f"{FIGURES_DIR}/distribusi_label_sentimen.png")
plt.close()
# ============================================================
# CEK PANJANG TEKS
# ============================================================
df["panjang"] = df["teks"].apply(lambda x: len(str(x).split()))
print(f"\nPanjang teks:\n{df['panjang'].describe()}")
# ============================================================
# SEL 8 — TEXT PREPROCESSING
# ============================================================
print("\n" + "=" * 60)
print("SEL 8 — TEXT PREPROCESSING")
print("=" * 60)
def clean_text(teks):
teks = str(teks).lower()
teks = re.sub(r"[^a-z\s]", "", teks)
teks = re.sub(r"\s+", " ", teks).strip()
return teks
df["teks_bersih"] = df["teks"].apply(clean_text)
# Train/Test Split
X = np.array(df["teks_bersih"].tolist())
y = np.array(df["label"].tolist(), dtype=int)
X_tr, X_te, y_tr, y_te = train_test_split(
X, y,
test_size=0.2,
random_state=RANDOM_STATE,
stratify=y
)
print(f"Training data : {len(X_tr)}")
print(f"Testing data : {len(X_te)}")
# Tokenizer
tokenizer = Tokenizer(num_words=VOCAB_SIZE, oov_token="<OOV>")
tokenizer.fit_on_texts(X_tr)
# Text -> Integer Sequence
X_tr_seq = tokenizer.texts_to_sequences(X_tr)
X_te_seq = tokenizer.texts_to_sequences(X_te)
# Padding
X_tr_pad = pad_sequences(X_tr_seq, maxlen=MAX_LEN, padding="post")
X_te_pad = pad_sequences(X_te_seq, maxlen=MAX_LEN, padding="post")
print(f"\nX_tr_pad shape : {X_tr_pad.shape}")
print(f"X_te_pad shape : {X_te_pad.shape}")
print(f"Vocab size : {len(tokenizer.word_index)}")
# ============================================================
# SEL 9 — MODEL GAP
# ============================================================
print("\n" + "=" * 60)
print("SEL 9 — MODEL GAP")
print("=" * 60)
sent_model = keras.Sequential([
layers.Embedding(input_dim=VOCAB_SIZE, output_dim=EMBED_DIM),
layers.GlobalAveragePooling1D(),
layers.Dense(64, activation="relu"),
layers.Dropout(0.4),
layers.Dense(1, activation="sigmoid")
], name="Sentimen_GAP")
sent_model.compile(
optimizer="adam",
loss="binary_crossentropy",
metrics=["accuracy"]
)
sent_model.summary()
# ============================================================
# TRAINING
# ============================================================
print("\n" + "=" * 60)
print("TRAINING MODEL GAP")
print("=" * 60)
early_stop = EarlyStopping(
monitor="val_loss",
patience=4,
restore_best_weights=True
)
history_sent = sent_model.fit(
X_tr_pad, y_tr,
epochs=EPOCHS,
batch_size=BATCH_SIZE,
validation_split=0.15,
callbacks=[early_stop],
verbose=1
)
# ============================================================
# TEST ACCURACY & HISTORY PLOTS
# ============================================================
loss_s, acc_s = sent_model.evaluate(X_te_pad, y_te, verbose=0)
print(f"\nSentimen GAP → Test accuracy: {acc_s:.4f}")
# Plot Accuracy & Loss
plt.figure(figsize=(8, 5))
plt.plot(history_sent.history["accuracy"], label="Training Accuracy")
plt.plot(history_sent.history["val_accuracy"], label="Validation Accuracy")
plt.title("Training vs Validation Accuracy — GAP")
plt.xlabel("Epoch")
plt.ylabel("Accuracy")
plt.legend()
plt.tight_layout()
plt.savefig(f"{FIGURES_DIR}/histori_gap_sentimen.png")
plt.close()
# ============================================================
# SEL 11 — EVALUATION
# ============================================================
print("\n" + "=" * 60)
print("EVALUATION")
print("=" * 60)
y_pred_prob = sent_model.predict(X_te_pad, verbose=0)
y_pred = (y_pred_prob > 0.5).astype("int").flatten()
report = classification_report(
y_te, y_pred,
target_names=["Negatif (0)", "Positif (1)"]
)
print("\nClassification Report:")
print(report)
# Simpan Classification Report Txt
with open(f"{REPORTS_DIR}/classification_report_gap_sentimen.txt", "w", encoding="utf-8") as f:
f.write(report)
# Confusion Matrix Plot
cm = confusion_matrix(y_te, y_pred)
disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=["Negatif", "Positif"])
fig, ax = plt.subplots(figsize=(6, 6))
disp.plot(ax=ax, cmap="Blues")
plt.title("Confusion Matrix — Model Sentimen GAP")
plt.tight_layout()
plt.savefig(f"{FIGURES_DIR}/confusion_matrix_gap_sentimen.png")
plt.close()
# ============================================================
# SEL 12 — INFERENCE (PREDIKSI 5 ULASAN)
# ============================================================
def prediksi_sentimen(teks, model, tokenizer, maxlen):
teks_bersih = clean_text(teks)
seq = tokenizer.texts_to_sequences([teks_bersih])
padded = pad_sequences(seq, maxlen=maxlen, padding="post")
prob = model.predict(padded, verbose=0)[0][0]
label = "Positif" if prob > 0.5 else "Negatif"
return label, prob
print("\n" + "=" * 60)
print("PREDIKSI 5 ULASAN")
print("=" * 60)
ulasan_uji = [
"Produk berkualitas tinggi, pengiriman cepat, packing aman!",
"Kecewa banget, barang datang rusak dan customer service tidak responsif",
"Lumayan sih, tapi harganya kemahalan untuk kualitas segitu",
"Tidak sesuai deskripsi sama sekali, mau return tapi ribet",
"Barang bagus, sesuai dengan foto dan penjual sangat ramah"
]
for i, ulasan in enumerate(ulasan_uji, start=1):
label, prob = prediksi_sentimen(ulasan, sent_model, tokenizer, MAX_LEN)
print(f"\n{i}. {ulasan}\n Prediksi : {label}\n Confidence: {prob:.3f}")
# ============================================================
# SIMPAN MODEL
# ============================================================
sent_model.save(f"{MODELS_DIR}/model_gap_sentimen.keras")
print("\nModel GAP berhasil disimpan di folder results/models/")