refactor: restrukturisasi folder eksperimen ke tasks modular
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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/")
|
||||
Reference in New Issue
Block a user