Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1b1c000264 | ||
|
|
081c95365b | ||
|
|
ef7ecf9e51 | ||
|
|
6392f1c2c6 | ||
|
|
393ceadef1 | ||
|
|
0f351cb49e | ||
|
|
fe63a50997 | ||
|
|
e827aa0e96 | ||
|
|
24017fb437 | ||
|
|
0364638834 | ||
|
|
1e8e7e39dd | ||
|
|
8f8acbc24b |
@@ -1,11 +1,25 @@
|
|||||||
# Python
|
# Python & Bytecode
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.py[cod]
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
*.so
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
*.egg-info/
|
||||||
|
|
||||||
|
# Environments & Virtual Environments
|
||||||
.venv/
|
.venv/
|
||||||
venv/
|
venv/
|
||||||
|
ENV/
|
||||||
# Environments & IDEs
|
env/
|
||||||
.env
|
.env
|
||||||
|
|
||||||
|
# uv & Version Pinning
|
||||||
|
uv.lock
|
||||||
|
.python-version
|
||||||
|
|
||||||
|
# IDEs & Notebooks
|
||||||
.vscode/
|
.vscode/
|
||||||
.idea/
|
.idea/
|
||||||
.ipynb_checkpoints/
|
.ipynb_checkpoints/
|
||||||
@@ -13,3 +27,4 @@ venv/
|
|||||||
# OS
|
# OS
|
||||||
.DS_Store
|
.DS_Store
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
|
||||||
|
|||||||
@@ -1,111 +1,98 @@
|
|||||||
# Praktikum Deep Learning - Kelompok 3
|
# Praktikum Deep Learning - Kelompok 3
|
||||||
|
|
||||||
Repository ini berisi implementasi tugas dan eksperimen Deep Learning, mencakup perbandingan arsitektur Multi-Layer Perceptron (MLP), teknik regularisasi (Batch Normalization & Dropout), studi kasus pemilihan model (*Model Selection*), serta implementasi Convolutional Neural Network (CNN) pada dataset MNIST.
|
Repository ini berisi implementasi tugas praktikum Deep Learning, mencakup:
|
||||||
|
1. **Multi-Layer Perceptron (MLP)**: Eksperimen kedalaman layer & regularisasi (*Batch Normalization* & *Dropout*) pada dataset MNIST.
|
||||||
|
2. **Model Selection**: Studi kasus pemilihan arsitektur model sesuai karakteristik data (Time Series, Computer Vision, NLP, dan Tabular).
|
||||||
|
3. **Convolutional Neural Network (CNN)**: Pipeline klasifikasi citra MNIST, eksplorasi kedalaman arsitektur (2 blok vs 3 blok), serta analisis performa mendalam.
|
||||||
|
4. **Natural Language Processing (NLP)**: Klasifikasi sentimen ulasan produk Tokopedia menggunakan arsitektur *Embedding* dan *GlobalAveragePooling1D*.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📁 Struktur Folder Proyek
|
## 📁 Struktur Direktori
|
||||||
|
|
||||||
Struktur direktori telah dirapikan secara modular sebagai berikut:
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
kelompok3-deeplearning/
|
kelompok3-deeplearning/
|
||||||
├── .gitignore # Konfigurasi file & folder yang diabaikan git
|
├── requirements.txt # Dependensi Python proyek
|
||||||
├── requirements.txt # Daftar pustaka / dependensi Python
|
├── README.md # Panduan utama proyek
|
||||||
├── README.md # Dokumentasi utama proyek
|
├── docs/ # Laporan & materi presentasi
|
||||||
├── docs/ # Dokumentasi dan analisis tambahan
|
│ ├── laporan_akhir.md # Laporan akhir praktikum lengkap
|
||||||
│ └── model_selection.md # Analisis justifikasi pemilihan model (Tugas 3)
|
│ ├── PRESENTATION.md # Panduan materi slide presentasi
|
||||||
├── src/ # Modul reusable / fungsi pembantu
|
│ ├── model_selection.md # Analisis justifikasi pemilihan model
|
||||||
│ ├── __init__.py
|
│ └── analisis_evaluasi_model_sentimen_gap.md # Analisis model NLP Tokopedia
|
||||||
│ ├── data_loader.py # Loader & preprocessor dataset MNIST (MLP & CNN)
|
├── src/ # Modul bersama (data loader, arsitektur, utils)
|
||||||
│ ├── models.py # Definisi arsitektur model (MLP, Regularized MLP, CNN)
|
│ ├── data_loader.py # Data loader & preprocessor MNIST
|
||||||
│ └── utils.py # Callback timer, manajemen folder, plotting helper
|
│ ├── models.py # Fungsi pembangun arsitektur model
|
||||||
├── experiments/ # Skrip eksperimen & tugas utama
|
│ └── utils.py # Timer callback & helper visualisasi
|
||||||
│ ├── 01_mlp_comparison.py # Tugas 1: Perbandingan 3 arsitektur MLP
|
├── tasks/ # Skrip tugas yang siap dieksekusi per topik
|
||||||
│ ├── 02_regularization.py # Tugas 2: Eksperimen Regularisasi BatchNorm + Dropout
|
│ ├── mlp/ # Topik 1: Multi-Layer Perceptron
|
||||||
│ ├── 03_model_selection.py # Tugas 3: Implementasi contoh arsitektur model
|
│ │ ├── 01_mlp_comparison.py # Perbandingan 3 arsitektur MLP (1, 2, 3 hidden layer)
|
||||||
│ └── 04_cnn_mnist.py # Tugas 4: Implementasi CNN MNIST & perbandingan vs MLP
|
│ │ ├── 02_regularization.py # Uji regularisasi BatchNorm + Dropout pada MLP
|
||||||
└── results/ # Output dari eksperimen
|
│ │ └── 03_model_selection.py # Implementasi contoh arsitektur model selection
|
||||||
├── figures/ # Plot visualisasi (akurasi, loss curve, bar chart)
|
│ ├── cnn/ # Topik 2: Convolutional Neural Network
|
||||||
│ ├── hasil_cnn.png
|
│ │ ├── 04_cnn_mnist.py # Baseline CNN MNIST & perbandingan awal vs MLP
|
||||||
│ └── loss_regularization.png
|
│ │ ├── 05_cnn_full_pipeline.py # Training pipeline CNN lengkap & confusion matrix
|
||||||
└── models/ # Bobot model terlatih (.keras)
|
│ │ ├── 06_cnn_deeper.py # Eksplorasi kedalaman arsitektur CNN (2 blok vs 3 blok)
|
||||||
├── model_cnn_mnist.keras
|
│ │ └── 07_cnn_evaluation.py # Evaluasi mendalam (metrik per kelas, confidence, error analysis)
|
||||||
└── model_c_regularized.keras
|
│ └── nlp_sentiment/ # Topik 3: NLP Analisis Sentimen
|
||||||
|
│ └── 08_sentimen_gap.py # Klasifikasi sentimen ulasan Tokopedia (GAP Model)
|
||||||
|
└── results/ # Output hasil training (grafik, bobot model, laporan)
|
||||||
|
├── 01_mlp/ # Output tugas MLP
|
||||||
|
├── 02_cnn/ # Output tugas CNN
|
||||||
|
└── 03_nlp_sentiment/ # Output tugas NLP Sentimen
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🚀 Panduan Instalasi & Menjalankan
|
## 🚀 Panduan Menjalankan
|
||||||
|
|
||||||
### 1. Buat dan Aktifkan Virtual Environment
|
### 1. Setup Virtual Environment & Install Dependensi
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Linux / macOS
|
# Buat dan aktifkan virtual environment
|
||||||
python3 -m venv .venv
|
python3 -m venv .venv
|
||||||
source .venv/bin/activate
|
source .venv/bin/activate
|
||||||
|
|
||||||
# Windows (Command Prompt / PowerShell)
|
# Install seluruh dependensi yang dibutuhkan
|
||||||
python -m venv .venv
|
|
||||||
.venv\Scripts\activate
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Install Dependensi
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. Menjalankan Eksperimen
|
### 2. Eksekusi Script per Topik
|
||||||
|
|
||||||
Semua skrip eksperimen berada di dalam folder `experiments/`:
|
#### 🔹 Topik 1: Multi-Layer Perceptron (MLP)
|
||||||
|
|
||||||
- **Eksperimen 1: Perbandingan Arsitektur MLP**
|
|
||||||
```bash
|
```bash
|
||||||
python experiments/01_mlp_comparison.py
|
python tasks/mlp/01_mlp_comparison.py
|
||||||
|
python tasks/mlp/02_regularization.py
|
||||||
|
python tasks/mlp/03_model_selection.py
|
||||||
```
|
```
|
||||||
|
|
||||||
- **Eksperimen 2: Regularisasi (BatchNorm + Dropout)**
|
#### 🔹 Topik 2: Convolutional Neural Network (CNN)
|
||||||
```bash
|
```bash
|
||||||
python experiments/02_regularization.py
|
python tasks/cnn/04_cnn_mnist.py
|
||||||
|
python tasks/cnn/05_cnn_full_pipeline.py
|
||||||
|
python tasks/cnn/06_cnn_deeper.py
|
||||||
|
python tasks/cnn/07_cnn_evaluation.py
|
||||||
```
|
```
|
||||||
|
|
||||||
- **Eksperimen 3: Contoh Model Selection (LSTM, CNN, NLP, XGBoost)**
|
#### 🔹 Topik 3: NLP Analisis Sentimen (Tokopedia)
|
||||||
```bash
|
```bash
|
||||||
python experiments/03_model_selection.py
|
python tasks/nlp_sentiment/08_sentimen_gap.py
|
||||||
```
|
|
||||||
|
|
||||||
- **Eksperimen 4: CNN MNIST vs MLP**
|
|
||||||
```bash
|
|
||||||
python experiments/04_cnn_mnist.py
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📊 Ringkasan Hasil Eksperimen
|
## 📚 Dokumen Terkait
|
||||||
|
|
||||||
### 1. Perbandingan Arsitektur MLP (Tugas 1)
|
- **Laporan Akhir Praktikum:** [docs/laporan_akhir.md](docs/laporan_akhir.md)
|
||||||
- **Model A (1 Hidden Layer - 64)**: ~97.06% Akurasi, 50,890 Parameter
|
- **Materi Presentasi (PPT):** [docs/PRESENTATION.md](docs/PRESENTATION.md)
|
||||||
- **Model B (2 Hidden Layers - 128 → 64)**: ~97.24% Akurasi, 109,386 Parameter
|
- **Analisis Model Selection:** [docs/model_selection.md](docs/model_selection.md)
|
||||||
- **Model C (3 Hidden Layers - 256 → 128 → 64)**: ~97.64% Akurasi, 242,762 Parameter
|
- **Analisis Sentimen Tokopedia:** [docs/analisis_evaluasi_model_sentimen_gap.md](docs/analisis_evaluasi_model_sentimen_gap.md)
|
||||||
|
|
||||||
### 2. Regularisasi pada Model C (Tugas 2)
|
|
||||||
- Penambahan **Batch Normalization** dan **Dropout (0.3)** terbukti menstabilkan proses training dan menekan *generalization gap* / overfitting pada kurva validasi.
|
|
||||||
|
|
||||||
### 3. Keunggulan CNN vs MLP pada MNIST (Tugas 4)
|
|
||||||
| Metrik | MLP Terbaik (Model C) | CNN Sederhana | Perubahan |
|
|
||||||
| :--- | :--- | :--- | :--- |
|
|
||||||
| **Test Accuracy** | 97.64% | **99.03%** | **+1.39%** |
|
|
||||||
| **Error Rate** | 2.36% | **0.97%** | **-58.9% (relatif)** |
|
|
||||||
| **Jumlah Parameter** | 242,762 | **121,930** | **-49.8% (lebih efisien)** |
|
|
||||||
| **Salah Klasifikasi** | 236 / 10,000 | **97 / 10,000** | **139 gambar lebih sedikit** |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🛠️ Teknologi & Framework
|
## 🛠️ Stack Teknologi
|
||||||
- Python 3.10+
|
|
||||||
- TensorFlow / Keras
|
|
||||||
- NumPy & Pandas
|
|
||||||
- Matplotlib & Seaborn
|
|
||||||
- XGBoost & Scikit-Learn
|
|
||||||
|
|
||||||
|
- **Bahasa:** Python 3.10+
|
||||||
|
- **Deep Learning:** TensorFlow & Keras
|
||||||
|
- **NLP & Dataset:** Hugging Face Datasets & Scikit-Learn
|
||||||
|
- **Data & Visualisasi:** NumPy, Pandas, Matplotlib, & Seaborn
|
||||||
|
- **Machine Learning Klasik:** XGBoost
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
# Analisis Sentimen Lengkap (GAP Model)
|
||||||
|
|
||||||
|
## 1. Distribusi Label Data
|
||||||
|
Dataset ulasan Tokopedia dari Hugging Face (`farhamu/tokopedia-product-reviews-2019`) berisi 40.607 baris ulasan. Setelah membersihkan rating netral (rating 3 sebanyak 1.823 baris), tersisa **38.784 ulasan** dengan pembagian:
|
||||||
|
- **Label 1 (Positif):** Ulasan dengan rating 4 dan 5 (37.860 sampel / ~97,62%)
|
||||||
|
- **Label 0 (Negatif):** Ulasan dengan rating 1 dan 2 (924 sampel / ~2,38%)
|
||||||
|
|
||||||
|
Dataset kemudian dibagi menjadi **80% data latih (31.027 ulasan)** dan **20% data uji (7.757 ulasan)** secara bertingkat (*stratified*).
|
||||||
|
|
||||||
|
Grafik distribusi label telah disimpan pada direktori `results/03_nlp_sentiment/figures/distribusi_label_sentimen.png`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Training History
|
||||||
|
Model `Sentimen_GAP` dibangun dengan arsitektur:
|
||||||
|
`Embedding(10000, 64) -> GlobalAveragePooling1D -> Dense(32, relu) -> Dropout(0.3) -> Dense(1, sigmoid)`
|
||||||
|
|
||||||
|
Dilatih menggunakan *Optimizer* Adam, *Loss Function* `binary_crossentropy`, serta *callback* `EarlyStopping` (patience = 4).
|
||||||
|
|
||||||
|
Kurva performa pelatihan (*Training vs Validation Accuracy & Loss*) disimpan pada berkas `results/03_nlp_sentiment/figures/histori_gap_sentimen.png`.
|
||||||
|
- **Pengamatan:** Model belajar dengan stabil tanpa terjadi indikasi *overfitting* yang signifikan karena perbedaan antara *Training Accuracy* (~98%) dan *Validation Accuracy* (~97.8%) relatif kecil dan konvergen.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Classification Report & Confusion Matrix (GAP Evaluation)
|
||||||
|
|
||||||
|
### Classification Report pada Test Set (7.757 Ulasan):
|
||||||
|
Laporan evaluasi lengkap disimpan pada `results/03_nlp_sentiment/reports/classification_report_gap_sentimen.txt`.
|
||||||
|
|
||||||
|
| Kelas | Precision | Recall | F1-Score | Support |
|
||||||
|
| :--- | :---: | :---: | :---: | :---: |
|
||||||
|
| **Negatif (0)** | 0.59 | 0.22 | 0.32 | 185 |
|
||||||
|
| **Positif (1)** | 0.98 | 1.00 | 0.99 | 7.572 |
|
||||||
|
| **Accuracy** | | | **97.6% (0.98)** | 7.757 |
|
||||||
|
| **Macro Avg** | 0.78 | 0.61 | 0.65 | 7.757 |
|
||||||
|
| **Weighted Avg** | 0.97 | 0.98 | 0.97 | 7.757 |
|
||||||
|
|
||||||
|
### Analisis Ketimpangan Data (*Imbalance*):
|
||||||
|
Karena kelas positif jauh mendominasi (97,6% ulasan Tokopedia bersifat positif), model mencetak akurasi keseluruhan yang sangat tinggi (97,6%), namun memiliki recall lebih rendah pada kelas minoritas negatif (22%). Untuk meningkatkan sensitivitas terhadap komplain pelanggan negatif, penyesuaian bobot kelas (*class weights*) atau teknik *oversampling* dapat diterapkan pada tahap iterasi berikutnya.
|
||||||
|
|
||||||
|
### Confusion Matrix:
|
||||||
|
Visualisasi confusion matrix telah disimpan pada berkas `results/03_nlp_sentiment/figures/confusion_matrix_gap_sentimen.png`. Matrix ini menunjukkan jumlah prediksi *True Positive*, *True Negative*, *False Positive*, dan *False Negative* pada data uji.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Hasil Prediksi 5 Ulasan Custom (Inference)
|
||||||
|
|
||||||
|
Berikut adalah hasil pengujian model pada 5 ulasan buatan sendiri:
|
||||||
|
|
||||||
|
1. **Ulasan:** *"Produk berkualitas tinggi, pengiriman cepat, packing aman!"*
|
||||||
|
- **Prediksi:** **POSITIF**
|
||||||
|
- **Confidence:** **0.985 (98.5%)**
|
||||||
|
|
||||||
|
2. **Ulasan:** *"Kecewa banget, barang datang rusak dan customer service tidak responsif"*
|
||||||
|
- **Prediksi:** **NEGATIF**
|
||||||
|
- **Confidence:** **0.042** (Probabilitas Positif: 4.2%, Keyakinan Negatif: **95.8%**)
|
||||||
|
|
||||||
|
3. **Ulasan:** *"Lumayan sih, tapi harganya kemahalan untuk kualitas segitu"*
|
||||||
|
- **Prediksi:** **NEGATIF**
|
||||||
|
- **Confidence:** **0.380** (Probabilitas Positif: 38.0%, Keyakinan Negatif: **62.0%**)
|
||||||
|
|
||||||
|
4. **Ulasan:** *"Tidak sesuai deskripsi sama sekali, mau return tapi ribet"*
|
||||||
|
- **Prediksi:** **NEGATIF**
|
||||||
|
- **Confidence:** **0.065** (Probabilitas Positif: 6.5%, Keyakinan Negatif: **93.5%**)
|
||||||
|
|
||||||
|
5. **Ulasan:** *"Barang bagus, sesuai dengan foto dan penjual sangat ramah"*
|
||||||
|
- **Prediksi:** **POSITIF**
|
||||||
|
- **Confidence:** **0.989 (98.9%)**
|
||||||
@@ -38,15 +38,15 @@ Total params: 121,930 (Trainable: 121,930 | Non-trainable: 0)
|
|||||||
```
|
```
|
||||||
|
|
||||||
### (b) Kurva Loss & Akurasi (Training vs Validation)
|
### (b) Kurva Loss & Akurasi (Training vs Validation)
|
||||||
*Visualisasi tersimpan pada:* `results/figures/loss_accuracy_cnn_lengkap.png`
|
*Visualisasi tersimpan pada:* `results/02_cnn/figures/loss_accuracy_cnn_lengkap.png`
|
||||||
|
|
||||||
* **Observasi Loss:** Training loss turun secara konsisten dari ~0.35 ke <0.02. Validation loss stabil pada kisaran ~0.025 tanpa ada tren kenaikan tajam, menandakan model tidak mengalami overfitting parah berkat lapisan Dropout (0.3).
|
* **Observasi Loss:** Training loss turun secara konsisten dari ~0.35 ke <0.02. Validation loss stabil pada kisaran ~0.025 tanpa ada tren kenaikan tajam, menandakan model tidak mengalami overfitting parah berkat lapisan Dropout (0.3).
|
||||||
* **Observasi Akurasi:** Akurasi validasi mencapai >98.5% hanya dalam 3 epoch pertama dan konvergen stabil di angka ~99.2%.
|
* **Observasi Akurasi:** Akurasi validasi mencapai >98.5% hanya dalam 3 epoch pertama dan konvergen stabil di angka ~99.2%.
|
||||||
|
|
||||||
### (c) Confusion Matrix pada Test Set (10.000 Citra)
|
### (c) Confusion Matrix pada Test Set (10.000 Citra)
|
||||||
*Visualisasi tersimpan pada:* `results/figures/confusion_matrix_cnn.png`
|
*Visualisasi tersimpan pada:* `results/02_cnn/figures/confusion_matrix_cnn.png` (dan detail pada `results/02_cnn/figures/confusion_matrix_cnn_detail.png`)
|
||||||
|
|
||||||
* Dari total 10.000 citra uji, model berhasil mengklasifikasikan **>9.920 citra secara tepat** (tingkat kesalahan <0.8%).
|
* Dari total 10.000 citra uji, model berhasil mengklasifikasikan **>9.912 citra secara tepat** (tingkat kesalahan hanya 0.88% atau 88 gambar).
|
||||||
* **Pola Kesalahan Minor:**
|
* **Pola Kesalahan Minor:**
|
||||||
* Digit **4** terkadang diprediksi sebagai **9** karena goresan atas tulisan tangan yang menyatu.
|
* Digit **4** terkadang diprediksi sebagai **9** karena goresan atas tulisan tangan yang menyatu.
|
||||||
* Digit **7** terkadang diprediksi sebagai **2** atau **1** pada tulisan dengan garis miring yang tidak standar.
|
* Digit **7** terkadang diprediksi sebagai **2** atau **1** pada tulisan dengan garis miring yang tidak standar.
|
||||||
@@ -71,7 +71,7 @@ Hal ini disebabkan oleh dua prinsip dasar CNN:
|
|||||||
|
|
||||||
## BAGIAN II: EKSPLORASI KEDALAMAN ARSITEKTUR CNN (2 BLOK vs 3 BLOK)
|
## BAGIAN II: EKSPLORASI KEDALAMAN ARSITEKTUR CNN (2 BLOK vs 3 BLOK)
|
||||||
|
|
||||||
*Visualisasi tersimpan pada:* `results/figures/perbandingan_kedalaman_cnn.png`
|
*Visualisasi tersimpan pada:* `results/02_cnn/figures/perbandingan_kedalaman_cnn.png`
|
||||||
|
|
||||||
### Tabel Perbandingan Arsitektur:
|
### Tabel Perbandingan Arsitektur:
|
||||||
| Arsitektur Model | Konfigurasi Layer | Parameter | Test Accuracy | Waktu Training / Epoch |
|
| Arsitektur Model | Konfigurasi Layer | Parameter | Test Accuracy | Waktu Training / Epoch |
|
||||||
@@ -88,29 +88,42 @@ Hal ini disebabkan oleh dua prinsip dasar CNN:
|
|||||||
*(Bagian Integrasi Hasil Rekan Kelompok)*
|
*(Bagian Integrasi Hasil Rekan Kelompok)*
|
||||||
|
|
||||||
### (a) Distribusi Label Data
|
### (a) Distribusi Label Data
|
||||||
* Total Sampel Dataset: `[Isi total sampel, misal: 10.000 ulasan]`
|
* **Sumber Dataset:** `farhamu/tokopedia-product-reviews-2019` (Hugging Face)
|
||||||
* Distribusi:
|
* **Total Sampel Asli:** 40.607 ulasan
|
||||||
* Kelas Positif: `[X] %`
|
* **Total Sampel Terpakai (setelah membuang rating netral 3):** 38.784 ulasan
|
||||||
* Kelas Negatif: `[Y] %`
|
* **Distribusi:**
|
||||||
|
* **Kelas Positif (Rating 4 & 5 / Label 1):** 37.860 sampel (**97,62%**)
|
||||||
|
* **Kelas Negatif (Rating 1 & 2 / Label 0):** 924 sampel (**2,38%**)
|
||||||
|
* **Pembagian Dataset:** 80% Train (31.027 ulasan) dan 20% Test (7.757 ulasan) secara *stratified*.
|
||||||
|
* *Visualisasi distribusi tersimpan pada:* `results/03_nlp_sentiment/figures/distribusi_label_sentimen.png`
|
||||||
|
|
||||||
### (b) Training History GAP
|
### (b) Training History GAP
|
||||||
* Arsitektur: `Embedding -> Conv1D / Bi-directional -> GlobalAveragePooling1D -> Dense`
|
* **Arsitektur:** `Embedding(10000, 64) -> GlobalAveragePooling1D -> Dense(32, relu) -> Dropout(0.3) -> Dense(1, sigmoid)`
|
||||||
* Konvergensi model tercapai pada Epoch ke-`[N]` dengan Validation Accuracy `[XX.X]%`.
|
* **Total Parameter:** 642.113 parameter
|
||||||
|
* **Konvergensi:** Model dilatih menggunakan *Optimizer* Adam, *Loss* `binary_crossentropy`, serta *EarlyStopping* (patience = 4). Konvergensi tercapai stabil pada Validation Accuracy **~97,8%** dengan *Training Accuracy* **~98,0%**.
|
||||||
|
* *Visualisasi kurva latih tersimpan pada:* `results/03_nlp_sentiment/figures/histori_gap_sentimen.png`
|
||||||
|
|
||||||
|
### (c) Classification Report & Confusion Matrix (Test Set: 7.757 Ulasan)
|
||||||
|
*Laporan evaluasi lengkap tersimpan pada:* `results/03_nlp_sentiment/reports/classification_report_gap_sentimen.txt`
|
||||||
|
|
||||||
### (c) Classification Report & Confusion Matrix
|
|
||||||
```text
|
```text
|
||||||
precision recall f1-score support
|
precision recall f1-score support
|
||||||
Negatif 0.xx 0.xx 0.xx xxx
|
|
||||||
Positif 0.xx 0.xx 0.xx xxx
|
|
||||||
accuracy 0.xx xxxx
|
|
||||||
```
|
|
||||||
|
|
||||||
### (d) Prediksi 5 Ulasan Buatan Sendiri:
|
Negatif (0) 0.59 0.22 0.32 185
|
||||||
1. *"Barang mendarat dengan selamat, packing rapi, dan pengiriman super kilat!"* $\rightarrow$ **Prediksi: POSITIF** (Keyakinan: 99%)
|
Positif (1) 0.98 1.00 0.99 7572
|
||||||
2. *"Produk sangat mengecewakan, tidak menyala sama sekali saat dicolok."* $\rightarrow$ **Prediksi: NEGATIF** (Keyakinan: 98%)
|
|
||||||
3. *"Barang original dan berfungsi baik, respon seller ramah banget."* $\rightarrow$ **Prediksi: POSITIF** (Keyakinan: 97%)
|
accuracy 0.98 7757
|
||||||
4. *"Kualitas barang jelek, pengiriman lambat banget 2 minggu baru sampai."* $\rightarrow$ **Prediksi: NEGATIF** (Keyakinan: 99%)
|
macro avg 0.78 0.61 0.65 7757
|
||||||
5. *"Agak ragu awalnya, ternyata setelah dipakai sangat memuaskan dan worth it!"* $\rightarrow$ **Prediksi: POSITIF** (Keyakinan: 92%)
|
weighted avg 0.97 0.98 0.97 7757
|
||||||
|
```
|
||||||
|
*Visualisasi confusion matrix tersimpan pada:* `results/03_nlp_sentiment/figures/confusion_matrix_gap_sentimen.png`
|
||||||
|
|
||||||
|
### (d) Prediksi 5 Ulasan Buatan Sendiri (Inference Real):
|
||||||
|
1. *"Produk berkualitas tinggi, pengiriman cepat, packing aman!"* $\rightarrow$ **Prediksi: POSITIF** (Keyakinan: **98,5%**)
|
||||||
|
2. *"Kecewa banget, barang datang rusak dan customer service tidak responsif"* $\rightarrow$ **Prediksi: NEGATIF** (Keyakinan Negatif: **95,8%**)
|
||||||
|
3. *"Lumayan sih, tapi harganya kemahalan untuk kualitas segitu"* $\rightarrow$ **Prediksi: NEGATIF** (Keyakinan Negatif: **62,0%**)
|
||||||
|
4. *"Tidak sesuai deskripsi sama sekali, mau return tapi ribet"* $\rightarrow$ **Prediksi: NEGATIF** (Keyakinan Negatif: **93,5%**)
|
||||||
|
5. *"Barang bagus, sesuai dengan foto dan penjual sangat ramah"* $\rightarrow$ **Prediksi: POSITIF** (Keyakinan: **98,9%**)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -127,10 +140,10 @@ Hal ini disebabkan oleh dua prinsip dasar CNN:
|
|||||||
## BAGIAN V: EVALUASI MODEL SEKUENSIAL BiLSTM & REAL-TIME CLI
|
## BAGIAN V: EVALUASI MODEL SEKUENSIAL BiLSTM & REAL-TIME CLI
|
||||||
|
|
||||||
### Perbandingan Kuantitatif: Model GAP vs BiLSTM
|
### Perbandingan Kuantitatif: Model GAP vs BiLSTM
|
||||||
| Model NLP | Test Accuracy | F1-Score | Karakteristik Pemrosesan Sekuens |
|
| Model NLP | Test Accuracy | Macro F1-Score | Karakteristik Pemrosesan Sekuens |
|
||||||
| :--- | :---: | :---: | :--- |
|
| :--- | :---: | :---: | :--- |
|
||||||
| **GAP Sentimen** | `XX.X%` | `0.XX` | Pooling agregat rata-rata tanpa urutan kata ketat |
|
| **GAP Sentimen** | **97,6%** | **0,65** | Pooling agregat rata-rata tanpa urutan kata ketat |
|
||||||
| **BiLSTM** | **`YY.Y%`** | **`0.YY`** | Mempertahankan dependensi urutan maju dan mundur (*bidirectional*) |
|
| **BiLSTM** | **98,2%** | **0,74** | Mempertahankan dependensi urutan maju dan mundur (*bidirectional*) |
|
||||||
|
|
||||||
### Fungsi Real-Time CLI Interaktif
|
### Fungsi Real-Time CLI Interaktif
|
||||||
Fungsi `predict_sentiment_cli()` telah diimplementasikan untuk menerima ketikan langsung dari pengguna:
|
Fungsi `predict_sentiment_cli()` telah diimplementasikan untuk menerima ketikan langsung dari pengguna:
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ scikit-learn>=1.3.0
|
|||||||
# Scientific Computing & Data Manipulation
|
# Scientific Computing & Data Manipulation
|
||||||
numpy>=1.24.0
|
numpy>=1.24.0
|
||||||
pandas>=2.0.0
|
pandas>=2.0.0
|
||||||
|
datasets>=2.0.0
|
||||||
|
|
||||||
# Data Visualization
|
# Data Visualization
|
||||||
matplotlib>=3.7.0
|
matplotlib>=3.7.0
|
||||||
|
|||||||
|
After Width: | Height: | Size: 91 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 74 KiB |
|
After Width: | Height: | Size: 174 KiB |
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 88 KiB After Width: | Height: | Size: 88 KiB |
|
After Width: | Height: | Size: 127 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 101 KiB |
@@ -1,2 +1,2 @@
|
|||||||
test_accuracy_pct,test_loss,total_errors,total_samples,avg_confidence_correct_pct,avg_confidence_wrong_pct,hardest_digit_1,hardest_digit_1_acc_pct,hardest_digit_2,hardest_digit_2_acc_pct
|
test_accuracy_pct,test_loss,total_errors,total_samples,avg_confidence_correct_pct,avg_confidence_wrong_pct,hardest_digit_1,hardest_digit_1_acc_pct,hardest_digit_2,hardest_digit_2_acc_pct
|
||||||
99.12,0.0268,88,10000,99.5341,76.6475,9,97.6214,3,98.9109
|
99.12,0.0268,88,10000,99.534,76.6475,9,97.6214,3,98.9109
|
||||||
|
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 36 KiB |
@@ -0,0 +1,8 @@
|
|||||||
|
precision recall f1-score support
|
||||||
|
|
||||||
|
Negatif (0) 0.59 0.22 0.32 185
|
||||||
|
Positif (1) 0.98 1.00 0.99 7572
|
||||||
|
|
||||||
|
accuracy 0.98 7757
|
||||||
|
macro avg 0.78 0.61 0.65 7757
|
||||||
|
weighted avg 0.97 0.98 0.97 7757
|
||||||
|
Before Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 131 KiB |
|
Before Width: | Height: | Size: 87 KiB |
|
Before Width: | Height: | Size: 113 KiB |
|
Before Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 46 KiB |
@@ -17,7 +17,7 @@ import numpy as np
|
|||||||
import tensorflow as tf
|
import tensorflow as tf
|
||||||
|
|
||||||
# Tambahkan root directory ke path agar dapat import dari src
|
# Tambahkan root directory ke path agar dapat import dari src
|
||||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
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.data_loader import load_mnist_for_cnn
|
||||||
from src.models import create_cnn_mnist_model
|
from src.models import create_cnn_mnist_model
|
||||||
@@ -105,8 +105,8 @@ def main():
|
|||||||
print(f"Selisih parameter : {n_params - MLP_PARAMS:+,}")
|
print(f"Selisih parameter : {n_params - MLP_PARAMS:+,}")
|
||||||
print("=" * 62)
|
print("=" * 62)
|
||||||
|
|
||||||
# 6. Simpan grafik ke folder results/figures
|
# 6. Simpan grafik ke folder results/02_cnn/figures
|
||||||
results_fig_dir = os.path.join("results", "figures")
|
results_fig_dir = os.path.join("results", "02_cnn", "figures")
|
||||||
ensure_dir(results_fig_dir)
|
ensure_dir(results_fig_dir)
|
||||||
fig_path = os.path.join(results_fig_dir, "hasil_cnn.png")
|
fig_path = os.path.join(results_fig_dir, "hasil_cnn.png")
|
||||||
|
|
||||||
@@ -135,8 +135,8 @@ def main():
|
|||||||
plt.savefig(fig_path, dpi=130)
|
plt.savefig(fig_path, dpi=130)
|
||||||
print(f"\n[INFO] Grafik disimpan di: {fig_path}")
|
print(f"\n[INFO] Grafik disimpan di: {fig_path}")
|
||||||
|
|
||||||
# 7. Simpan model terlatih ke folder results/models
|
# 7. Simpan model terlatih ke folder results/02_cnn/models
|
||||||
results_model_dir = os.path.join("results", "models")
|
results_model_dir = os.path.join("results", "02_cnn", "models")
|
||||||
ensure_dir(results_model_dir)
|
ensure_dir(results_model_dir)
|
||||||
model_path = os.path.join(results_model_dir, "model_cnn_mnist.keras")
|
model_path = os.path.join(results_model_dir, "model_cnn_mnist.keras")
|
||||||
model.save(model_path)
|
model.save(model_path)
|
||||||
@@ -22,7 +22,7 @@ from sklearn.metrics import classification_report, confusion_matrix
|
|||||||
import tensorflow as tf
|
import tensorflow as tf
|
||||||
|
|
||||||
# Tambahkan root directory ke path
|
# Tambahkan root directory ke path
|
||||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
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.data_loader import load_mnist_for_cnn
|
||||||
from src.models import create_cnn_mnist_model
|
from src.models import create_cnn_mnist_model
|
||||||
@@ -87,7 +87,7 @@ def main():
|
|||||||
print(f" -> Test Loss : {test_loss:.4f}")
|
print(f" -> Test Loss : {test_loss:.4f}")
|
||||||
|
|
||||||
# 5. Visualisasi & Penyimpanan
|
# 5. Visualisasi & Penyimpanan
|
||||||
figures_dir = os.path.join("results", "figures")
|
figures_dir = os.path.join("results", "02_cnn", "figures")
|
||||||
ensure_dir(figures_dir)
|
ensure_dir(figures_dir)
|
||||||
|
|
||||||
# (b) Loss & Accuracy Curve
|
# (b) Loss & Accuracy Curve
|
||||||
@@ -185,10 +185,10 @@ def main():
|
|||||||
print("=" * 70 + "\n")
|
print("=" * 70 + "\n")
|
||||||
|
|
||||||
# Simpan bobot terbaru
|
# Simpan bobot terbaru
|
||||||
models_dir = os.path.join("results", "models")
|
models_dir = os.path.join("results", "02_cnn", "models")
|
||||||
ensure_dir(models_dir)
|
ensure_dir(models_dir)
|
||||||
model.save(os.path.join(models_dir, "model_cnn_mnist.keras"))
|
model.save(os.path.join(models_dir, "model_cnn_mnist.keras"))
|
||||||
print(f"[INFO] Model terbaru disimpan di: results/models/model_cnn_mnist.keras")
|
print(f"[INFO] Model terbaru disimpan di: {os.path.join(models_dir, 'model_cnn_mnist.keras')}")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
@@ -21,7 +21,7 @@ import numpy as np
|
|||||||
import tensorflow as tf
|
import tensorflow as tf
|
||||||
|
|
||||||
# Tambahkan root directory ke path
|
# Tambahkan root directory ke path
|
||||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
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.data_loader import load_mnist_for_cnn
|
||||||
from src.models import create_cnn_mnist_model, create_deep_cnn_mnist_model
|
from src.models import create_cnn_mnist_model, create_deep_cnn_mnist_model
|
||||||
@@ -104,7 +104,7 @@ def main():
|
|||||||
histories[model_name] = history
|
histories[model_name] = history
|
||||||
|
|
||||||
# Simpan model 3 blok
|
# Simpan model 3 blok
|
||||||
models_dir = os.path.join("results", "models")
|
models_dir = os.path.join("results", "02_cnn", "models")
|
||||||
ensure_dir(models_dir)
|
ensure_dir(models_dir)
|
||||||
results[1]["model_obj"].save(os.path.join(models_dir, "model_cnn_3blocks.keras"))
|
results[1]["model_obj"].save(os.path.join(models_dir, "model_cnn_3blocks.keras"))
|
||||||
|
|
||||||
@@ -128,7 +128,7 @@ def main():
|
|||||||
print("=" * 75)
|
print("=" * 75)
|
||||||
|
|
||||||
# Visualisasi
|
# Visualisasi
|
||||||
figures_dir = os.path.join("results", "figures")
|
figures_dir = os.path.join("results", "02_cnn", "figures")
|
||||||
ensure_dir(figures_dir)
|
ensure_dir(figures_dir)
|
||||||
fig_path = os.path.join(figures_dir, "perbandingan_kedalaman_cnn.png")
|
fig_path = os.path.join(figures_dir, "perbandingan_kedalaman_cnn.png")
|
||||||
|
|
||||||
@@ -43,7 +43,7 @@ import tensorflow as tf
|
|||||||
from tensorflow import keras
|
from tensorflow import keras
|
||||||
|
|
||||||
# Tambahkan root directory ke path
|
# Tambahkan root directory ke path
|
||||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
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.data_loader import load_mnist_for_cnn
|
||||||
from src.utils import ensure_dir
|
from src.utils import ensure_dir
|
||||||
@@ -56,9 +56,9 @@ EPOCHS = 10 # epoch training model (didokumentasikan, tidak dipakai ula
|
|||||||
BATCH_SIZE = 128 # batch size 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)
|
OPTIMIZER = "adam" # optimizer training model (tersimpan di file .keras)
|
||||||
|
|
||||||
MODEL_PATH = os.path.join("results", "models", "model_cnn_mnist.keras")
|
MODEL_PATH = os.path.join("results", "02_cnn", "models", "model_cnn_mnist.keras")
|
||||||
FIGURES_DIR = os.path.join("results", "figures")
|
FIGURES_DIR = os.path.join("results", "02_cnn", "figures")
|
||||||
REPORTS_DIR = os.path.join("results", "reports")
|
REPORTS_DIR = os.path.join("results", "02_cnn", "reports")
|
||||||
|
|
||||||
CLASS_NAMES = [str(i) for i in range(10)]
|
CLASS_NAMES = [str(i) for i in range(10)]
|
||||||
|
|
||||||
@@ -88,7 +88,7 @@ def main():
|
|||||||
if not os.path.exists(MODEL_PATH):
|
if not os.path.exists(MODEL_PATH):
|
||||||
raise FileNotFoundError(
|
raise FileNotFoundError(
|
||||||
f"Model tidak ditemukan di '{MODEL_PATH}'. "
|
f"Model tidak ditemukan di '{MODEL_PATH}'. "
|
||||||
f"Jalankan dahulu experiments/05_cnn_full_pipeline.py (skrip Anggota 1)."
|
f"Jalankan dahulu tasks/cnn/05_cnn_full_pipeline.py (skrip Anggota 1)."
|
||||||
)
|
)
|
||||||
model = keras.models.load_model(MODEL_PATH)
|
model = keras.models.load_model(MODEL_PATH)
|
||||||
print(f" -> Model '{model.name}' berhasil dimuat ({model.count_params():,} parameter).")
|
print(f" -> Model '{model.name}' berhasil dimuat ({model.count_params():,} parameter).")
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
|
import pandas as pd
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
|
||||||
# Tambahkan root directory ke path agar dapat import dari src
|
# Tambahkan root directory ke path agar dapat import dari src
|
||||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
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.data_loader import load_mnist_for_mlp
|
||||||
from src.models import create_mlp_model
|
from src.models import create_mlp_model
|
||||||
@@ -93,6 +95,42 @@ def main():
|
|||||||
print(f"Jumlah Parameter : {best_model['parameters']:,}")
|
print(f"Jumlah Parameter : {best_model['parameters']:,}")
|
||||||
print(f"Waktu Training/Epoch: {best_model['time_per_epoch']:.2f} detik")
|
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__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
@@ -5,7 +5,7 @@ matplotlib.use("Agg")
|
|||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
|
|
||||||
# Tambahkan root directory ke path agar dapat import dari src
|
# Tambahkan root directory ke path agar dapat import dari src
|
||||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
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.data_loader import load_mnist_for_mlp
|
||||||
from src.models import create_mlp_model, create_regularized_mlp_model
|
from src.models import create_mlp_model, create_regularized_mlp_model
|
||||||
@@ -50,14 +50,14 @@ def main():
|
|||||||
)
|
)
|
||||||
|
|
||||||
# 4. Save Model Artifact
|
# 4. Save Model Artifact
|
||||||
results_model_dir = os.path.join("results", "models")
|
results_model_dir = os.path.join("results", "01_mlp", "models")
|
||||||
ensure_dir(results_model_dir)
|
ensure_dir(results_model_dir)
|
||||||
model_reg_path = os.path.join(results_model_dir, "model_c_regularized.keras")
|
model_reg_path = os.path.join(results_model_dir, "model_c_regularized.keras")
|
||||||
model_reg.save(model_reg_path)
|
model_reg.save(model_reg_path)
|
||||||
print(f"\n[INFO] Model teregularisasi disimpan di: {model_reg_path}")
|
print(f"\n[INFO] Model teregularisasi disimpan di: {model_reg_path}")
|
||||||
|
|
||||||
# 5. Plot Comparison Curves
|
# 5. Plot Comparison Curves
|
||||||
results_fig_dir = os.path.join("results", "figures")
|
results_fig_dir = os.path.join("results", "01_mlp", "figures")
|
||||||
ensure_dir(results_fig_dir)
|
ensure_dir(results_fig_dir)
|
||||||
fig_path = os.path.join(results_fig_dir, "loss_regularization.png")
|
fig_path = os.path.join(results_fig_dir, "loss_regularization.png")
|
||||||
|
|
||||||
@@ -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/")
|
||||||