From 8f32804f5fb9abcdbb7c45a9e10dc8f2f67541ed Mon Sep 17 00:00:00 2001 From: Yustina Date: Sat, 19 Sep 2026 22:12:48 +0700 Subject: [PATCH] refactor: organize project into subfolders --- README_No4_MNIST_CNN .md | 333 ------------------------------------ hasil_cnn.png | Bin 57125 -> 0 bytes model_selection.md | 23 --- model_selection_examples.py | 47 ----- requirements.txt | 3 - task4_cnn.py | 184 -------------------- 6 files changed, 590 deletions(-) delete mode 100644 README_No4_MNIST_CNN .md delete mode 100644 hasil_cnn.png delete mode 100644 model_selection.md delete mode 100644 model_selection_examples.py delete mode 100644 requirements.txt delete mode 100644 task4_cnn.py diff --git a/README_No4_MNIST_CNN .md b/README_No4_MNIST_CNN .md deleted file mode 100644 index cf53f55..0000000 --- a/README_No4_MNIST_CNN .md +++ /dev/null @@ -1,333 +0,0 @@ -# Implementasi CNN pada MNIST dan Perbandingannya dengan MLP - -## 1. Deskripsi - -Program ini dibuat untuk menyelesaikan **Tugas No. 4: Implementasi CNN** menggunakan dataset **MNIST** dan framework **TensorFlow/Keras**. - -Eksperimen bertujuan untuk: - -- Membangun model **Convolutional Neural Network (CNN)** sederhana menggunakan `Conv2D` dan `MaxPooling2D` sebelum Dense layers. -- Membandingkan performanya dengan model **MLP terbaik** dari Tugas No. 1 (Model C — 3 hidden layer 256 → 128 → 64). -- Mengukur besar peningkatan (improvement) akurasi yang diperoleh. -- Menjelaskan mengapa CNN lebih efektif untuk data gambar dibandingkan MLP. - -## 2. Dataset - -Dataset yang digunakan sama dengan Tugas No. 1, yaitu **MNIST**, dataset gambar angka tulisan tangan 0 sampai 9. - -Dataset dimuat menggunakan fungsi bawaan Keras: - -``` python -keras.datasets.mnist.load_data() -``` - -Karakteristik dataset: - -- 60.000 data training -- 10.000 data testing -- Ukuran gambar: 28 × 28 piksel -- Jumlah kelas: 10 -- Kelas: angka 0 sampai 9 - -## 3. Preprocessing - -Perbedaan utama preprocessing CNN dibandingkan MLP terletak pada **bentuk input**. - -| Aspek | MLP (Tugas 1) | CNN (Tugas 4) | -|---|---|---| -| Bentuk input | `(N, 784)` — diratakan (flatten) | `(N, 28, 28, 1)` — tetap 2D + channel | -| Normalisasi | 0–255 → 0–1 | 0–255 → 0–1 | -| Struktur spasial | Hilang | Dipertahankan | - -MLP meratakan gambar menjadi vektor 784 angka sehingga informasi posisi antar piksel hilang. CNN tetap memproses gambar dalam bentuk 2D aslinya dan hanya menambahkan dimensi channel (grayscale = 1). - -``` python -x_train = x_train.astype("float32") / 255.0 -x_test = x_test.astype("float32") / 255.0 - -# CNN membutuhkan dimensi channel -x_train = x_train.reshape(-1, 28, 28, 1) -x_test = x_test.reshape(-1, 28, 28, 1) -``` - -## 4. Arsitektur Model CNN - -``` text -Input (28, 28, 1) - ↓ -Conv2D (32 filter, 3×3, ReLU) → (26, 26, 32) - ↓ -MaxPooling2D (2×2) → (13, 13, 32) - ↓ -Conv2D (64 filter, 3×3, ReLU) → (11, 11, 64) - ↓ -MaxPooling2D (2×2) → (5, 5, 64) - ↓ -Flatten → (1600) - ↓ -Dense (64, ReLU) - ↓ -Dropout (0.3) - ↓ -Output (10, Softmax) -``` - -Kode model: - -``` python -model = keras.Sequential([ - layers.Input(shape=(28, 28, 1)), - layers.Conv2D(32, (3, 3), activation="relu"), - layers.MaxPooling2D((2, 2)), - layers.Conv2D(64, (3, 3), activation="relu"), - layers.MaxPooling2D((2, 2)), - layers.Flatten(), - layers.Dense(64, activation="relu"), - layers.Dropout(0.3), - layers.Dense(10, activation="softmax"), -]) - -model.compile( - optimizer="adam", - loss="sparse_categorical_crossentropy", - metrics=["accuracy"], -) -``` - -### Rincian Jumlah Parameter - -| Layer | Output Shape | Parameter | -|---|---|---| -| Conv2D (32) | (26, 26, 32) | 320 | -| MaxPooling2D | (13, 13, 32) | 0 | -| Conv2D (64) | (11, 11, 64) | 18.496 | -| MaxPooling2D | (5, 5, 64) | 0 | -| Flatten | (1600) | 0 | -| Dense (64) | (64) | 102.464 | -| Dropout (0.3) | (64) | 0 | -| Dense (10) | (10) | 650 | -| **Total** | | **121.930** | - -Perhatikan bahwa dua layer konvolusi hanya menggunakan **18.816 parameter** untuk seluruh ekstraksi fitur, sedangkan satu Dense layer saja sudah menghabiskan 102.464 parameter. Ini menunjukkan efisiensi *parameter sharing* pada konvolusi. - -## 5. Konfigurasi Training - -Konfigurasi dibuat identik dengan Tugas No. 1 agar perbandingan adil. - -| Parameter | Nilai | -|---|---| -| Optimizer | Adam | -| Loss Function | Sparse Categorical Crossentropy | -| Epochs | 10 | -| Batch Size | 128 | -| Validation Split | 10% | -| Hidden Activation | ReLU | -| Output Activation | Softmax | -| Regularisasi | Dropout (0.3) sebelum output layer | - -## 6. Hasil Eksperimen - -### 6.1 Progres Training CNN per Epoch - -| Epoch | Train Accuracy | Validation Accuracy | -|---|---|---| -| 1 | 90,04% | 98,18% | -| 2 | 96,80% | 98,50% | -| 3 | 97,55% | 98,87% | -| 4 | 98,07% | 98,83% | -| 5 | 98,42% | 99,02% | -| 6 | 98,59% | 98,80% | -| 7 | 98,72% | 98,93% | -| 8 | 98,93% | 99,05% | -| 9 | 98,96% | 99,05% | -| 10 | 99,07% | 99,02% | - -Validation accuracy sudah melampaui 98% sejak **epoch pertama** — sesuatu yang tidak pernah dicapai MLP bahkan setelah 10 epoch. - -### 6.2 Perbandingan CNN vs MLP - -| Model | Arsitektur | Jumlah Parameter | Test Accuracy | Waktu Training/Epoch | -|---|---|---|---|---| -| Model A (MLP) | 64 | 50.890 | 97,06% | 1,22 detik | -| Model B (MLP) | 128 → 64 | 109.386 | 97,24% | 1,29 detik | -| **Model C (MLP terbaik)** | 256 → 128 → 64 | 242.762 | **97,64%** | 2,50 detik | -| **Model CNN** | Conv32 → Pool → Conv64 → Pool → Dense64 | **121.930** | **99,03%** | 16,56 detik | - -### 6.3 Besar Improvement - -| Metrik | MLP Terbaik | CNN | Perubahan | -|---|---|---|---| -| Test Accuracy | 97,64% | 99,03% | **+1,39 poin persentase** | -| Test Loss | 0,0933 | 0,0297 | **−68,2%** | -| Error Rate | 2,36% | 0,97% | **−58,9% (relatif)** | -| Salah klasifikasi | 236 dari 10.000 | **97 dari 10.000** | 139 gambar lebih sedikit | -| Jumlah Parameter | 242.762 | 121.930 | **−49,8% (separuh lebih sedikit)** | -| Waktu/Epoch | 2,50 detik | 16,56 detik | +562% (lebih lambat) | - -**Catatan penting mengenai interpretasi improvement:** - -Kenaikan 1,39 poin persentase terlihat kecil, tetapi cara yang lebih tepat membacanya adalah lewat **error rate**. CNN memangkas hampir **59% kesalahan** yang masih dilakukan MLP. Pada skala industri, misalnya sistem pembacaan kode pos otomatis yang memproses 1 juta surat, perbedaan ini berarti sekitar **13.900 surat lebih sedikit yang salah baca**. - -## 7. Analisis: Mengapa CNN Lebih Efektif untuk Data Gambar? - -### 7.1 Mempertahankan Struktur Spasial - -MLP meratakan gambar 28 × 28 menjadi vektor 784 angka. Setelah diratakan, piksel yang secara visual bersebelahan (misalnya piksel baris 1 kolom 28 dan baris 2 kolom 1) menjadi berjauhan, sementara model tidak punya cara mengetahui bahwa keduanya sebenarnya bertetangga. Informasi geometris gambar hilang sebelum training dimulai. CNN memproses gambar dalam bentuk 2D aslinya, sehingga pola lokal seperti tepi, sudut, dan lengkungan angka tetap utuh. - -### 7.2 Local Receptive Field dan Parameter Sharing - -Setiap filter konvolusi berukuran 3 × 3 hanya melihat area kecil gambar, lalu **digeser ke seluruh permukaan gambar dengan bobot yang sama**. Artinya, satu filter yang belajar mendeteksi garis diagonal dapat menemukan garis diagonal di mana pun posisinya. - -Pada MLP, setiap posisi piksel punya bobotnya sendiri-sendiri, sehingga model harus mempelajari pola yang sama berulang kali untuk setiap posisi. Inilah alasan CNN mencapai akurasi lebih tinggi meskipun jumlah parameternya **separuh lebih sedikit** dari MLP terbaik. - -### 7.3 Hierarki Fitur Otomatis - -CNN membangun pemahaman secara bertingkat, mirip cara kerja sistem visual manusia: - -``` text -Conv layer 1 → fitur sederhana: tepi, garis, gradien terang-gelap - ↓ -Conv layer 2 → kombinasi fitur: lengkungan, sudut, lingkaran kecil - ↓ -Dense layer → konsep utuh: "ini bentuk angka 8" -``` - -MLP tidak memiliki hierarki semacam ini karena semua piksel langsung dihubungkan ke semua neuron tanpa tahapan abstraksi spasial. - -### 7.4 Translation Invariance melalui MaxPooling - -`MaxPooling2D` mengambil nilai maksimum dari setiap area 2 × 2, sehingga pergeseran kecil posisi angka dalam gambar tidak banyak mengubah output. Model menjadi tahan terhadap variasi penulisan, misalnya angka yang ditulis sedikit lebih ke kiri atau lebih ke kanan. Pada MLP, pergeseran satu piksel saja mengubah seluruh vektor input dan dapat mengubah hasil prediksi. - -### 7.5 Trade-off yang Perlu Dicatat - -CNN unggul dalam akurasi dan efisiensi parameter, tetapi **waktu training per epoch jauh lebih lama** (16,56 detik vs 2,50 detik). Operasi konvolusi secara komputasi lebih mahal dibanding perkalian matriks pada Dense layer, karena filter harus digeser ke setiap posisi gambar. Perlu diingat bahwa pengukuran ini dilakukan pada CPU; pada GPU, selisih waktu ini mengecil drastis karena konvolusi sangat mudah diparalelkan. - -## 8. Kesimpulan - -1. Model CNN sederhana dengan dua blok `Conv2D` + `MaxPooling2D` berhasil mencapai **test accuracy 99,03%** pada MNIST. -2. Dibandingkan MLP terbaik dari Tugas No. 1 (97,64%), CNN memberikan improvement **+1,39 poin persentase**, atau setara dengan **pengurangan error rate sebesar 58,9%**. -3. CNN mencapai hasil tersebut dengan **jumlah parameter hampir separuh lebih sedikit**, membuktikan bahwa keunggulannya berasal dari *inductive bias* yang sesuai dengan sifat data gambar, bukan sekadar dari kapasitas model yang lebih besar. -4. Keunggulan CNN untuk data gambar berakar pada empat hal: preservasi struktur spasial, parameter sharing, hierarki fitur otomatis, dan translation invariance. -5. Konsekuensinya adalah waktu training per epoch yang lebih lama, sehingga pemilihan arsitektur tetap perlu mempertimbangkan ketersediaan sumber daya komputasi. - -## 9. Teknologi - -- Python 3.10 – 3.12 -- TensorFlow 2.x -- Keras -- NumPy -- Matplotlib (untuk plot learning curve) -- MNIST -- **Visual Studio Code** (editor & runtime lokal) - -## 10. Struktur Folder - -```text -tugas-mnist/ -├── .venv/ # virtual environment (tidak perlu di-commit) -├── task4_cnn.py # program utama CNN -├── README_No4_MNIST_CNN.md # dokumentasi ini -└── requirements.txt -``` - -Isi `requirements.txt`: - -```text -tensorflow>=2.16 -numpy -matplotlib -``` - -## 11. Cara Menjalankan di VS Code - -### 11.1 Persiapan Awal (sekali saja) - -**1. Install ekstensi yang dibutuhkan** - -Buka panel Extensions di VS Code (`Ctrl+Shift+X`), lalu install: - -- **Python** (Microsoft) -- **Pylance** (Microsoft) -- **Jupyter** (opsional, jika ingin menjalankan per-cell) - -**2. Buka folder proyek** - -`File → Open Folder…` lalu pilih folder `tugas-mnist`. - -**3. Buat virtual environment** - -Buka terminal terintegrasi dengan `` Ctrl+` `` lalu jalankan: - -```bash -# Windows -python -m venv .venv -.venv\Scripts\activate - -# macOS / Linux -python3 -m venv .venv -source .venv/bin/activate -``` - -**4. Pilih interpreter di VS Code** - -Tekan `Ctrl+Shift+P` → ketik **Python: Select Interpreter** → pilih interpreter yang berada di dalam folder `.venv`. Langkah ini penting agar VS Code menjalankan kode memakai environment yang benar, bukan Python sistem. - -**5. Install dependensi** - -```bash -pip install -r requirements.txt -``` - -Catatan: jika pemasangan TensorFlow terasa berat atau tidak memiliki GPU, gunakan versi CPU yang ukurannya lebih ringan: - -```bash -pip install tensorflow-cpu numpy matplotlib -``` - -### 11.2 Menjalankan Program - -Ada tiga cara, pilih salah satu: - -| Cara | Langkah | -|---|---| -| Tombol Run | Buka `task4_cnn.py`, klik tombol **▶ Run Python File** di kanan atas | -| Terminal | Jalankan `python task4_cnn.py` | -| Debug | Tekan `F5`, pilih konfigurasi **Python File** | - -### 11.3 Hal yang Perlu Diperhatikan - -**Dataset akan diunduh otomatis pada eksekusi pertama.** `keras.datasets.mnist.load_data()` mengunduh berkas `mnist.npz` (± 11 MB) dan menyimpannya di: - -```text -Windows : C:\Users\\.keras\datasets\ -macOS/Linux : ~/.keras/datasets/ -``` - -Eksekusi berikutnya akan memakai cache tersebut, jadi tidak perlu koneksi internet lagi. Pastikan koneksi aktif saat menjalankan pertama kali. - -**Waktu training di CPU.** Model CNN membutuhkan sekitar **15–20 detik per epoch** di CPU, sehingga total training 10 epoch memakan waktu sekitar **3 menit**. Ini wajar; jangan dikira program macet. Gunakan `verbose=2` agar progres tercetak rapi per epoch di terminal VS Code. - -**Peringatan oneDNN/CUDA di awal output.** TensorFlow biasanya mencetak pesan seperti `Could not find cuda drivers` atau `oneDNN custom operations are on`. Pesan ini **normal dan bukan error** — artinya TensorFlow berjalan memakai CPU. Program tetap berjalan dengan benar. Jika ingin menyembunyikannya, tambahkan di baris paling atas program, sebelum `import tensorflow`: - -```python -import os -os.environ["TF_CPP_MIN_LOG_LEVEL"] = "2" -``` - -**Plot Matplotlib.** Saat dijalankan sebagai file `.py`, jendela plot akan terbuka terpisah dan program berhenti sampai jendela ditutup. Agar plot langsung tersimpan sebagai gambar tanpa mengganggu alur program, gunakan `plt.savefig("nama.png")` alih-alih `plt.show()`. - -### 11.4 Langkah Pengerjaan Tugas - -1. Jalankan program hingga selesai 10 epoch. -2. Catat **test accuracy**, **jumlah parameter** (dari `model.summary()`), dan **waktu training per epoch**. -3. Bandingkan hasilnya dengan tabel hasil MLP dari Tugas No. 1. -4. Hitung improvement, baik dalam poin persentase maupun pengurangan error rate. -5. Isi tabel pada bagian **Hasil Eksperimen** dengan angka aktual yang Anda peroleh. - -Nilai akurasi yang Anda dapatkan mungkin berbeda tipis dari dokumen ini (biasanya di kisaran 98,9% – 99,2%) karena inisialisasi bobot bersifat acak. Untuk hasil yang dapat direproduksi, atur seed di awal program: - -```python -import numpy as np, tensorflow as tf -tf.random.set_seed(42) -np.random.seed(42) -``` diff --git a/hasil_cnn.png b/hasil_cnn.png deleted file mode 100644 index 252f9e5eaebbfc108c6e35a0643a80e4bdc1a5f7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 57125 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

l8$y7hEHOOwShA< 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

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