bebas
This commit is contained in:
@@ -0,0 +1,364 @@
|
||||
# Corn Leaf Disease Classification
|
||||
|
||||
Pipeline lengkap untuk klasifikasi penyakit daun jagung menggunakan **EfficientNetV2B0**, mulai dari persiapan dataset, pelatihan di Google Colab, hingga ekspor model ke format **TFLite** dan **TensorFlow.js** untuk kebutuhan produksi.
|
||||
|
||||
---
|
||||
|
||||
## Daftar Isi
|
||||
|
||||
1. [Kelas yang Diklasifikasikan](#1-kelas-yang-diklasifikasikan)
|
||||
2. [Struktur Proyek](#2-struktur-proyek)
|
||||
3. [Prasyarat & Instalasi](#3-prasyarat--instalasi)
|
||||
4. [Sumber Dataset](#4-sumber-dataset)
|
||||
5. [Tahap 1 — Preprocessing Lokal](#5-tahap-1--preprocessing-lokal)
|
||||
6. [Tahap 2 — Upload ke Google Drive & Training di Colab](#6-tahap-2--upload-ke-google-drive--training-di-colab)
|
||||
7. [Tahap 3 — Download Model dari Colab](#7-tahap-3--download-model-dari-colab)
|
||||
8. [Tahap 4 — Ekspor Model untuk Produksi](#8-tahap-4--ekspor-model-untuk-produksi)
|
||||
9. [Output Akhir](#9-output-akhir)
|
||||
10. [Troubleshooting](#10-troubleshooting)
|
||||
|
||||
---
|
||||
|
||||
## 1. Kelas yang Diklasifikasikan
|
||||
|
||||
Model dilatih untuk mengenali **4 kelas** kondisi daun jagung:
|
||||
|
||||
| Label (Bahasa Indonesia) | Deskripsi |
|
||||
|---|---|
|
||||
| **Bercak Daun** | *Gray Leaf Spot* — bercak abu-abu memanjang pada daun |
|
||||
| **Hawar Daun** | *Northern/Southern Leaf Blight* — hawar coklat berbentuk panjang |
|
||||
| **Karat Daun** | *Common Rust* — bintik coklat kemerahan berbentuk pustula |
|
||||
| **Daun Sehat** | Daun jagung tanpa gejala penyakit |
|
||||
|
||||
---
|
||||
|
||||
## 2. Struktur Proyek
|
||||
|
||||
```
|
||||
.
|
||||
├── dataset_1.zip # File ZIP dataset 1 (dari Kaggle)
|
||||
├── dataset_2.zip # File ZIP dataset 2 (dari Kaggle)
|
||||
├── dataset_3.zip # File ZIP dataset 3 (dari SciDB)
|
||||
│
|
||||
├── preprocessing.py # Skrip persiapan & penggabungan dataset
|
||||
├── notebook.ipynb # Notebook pelatihan (dijalankan di Google Colab)
|
||||
├── save_model.py # Skrip ekspor model ke TFLite & TensorFlow.js
|
||||
│
|
||||
├── best_model/
|
||||
│ └── best_model.keras # Model hasil training terbaik (diunduh dari Colab)
|
||||
│
|
||||
├── model/ # Output ekspor (dibuat otomatis)
|
||||
│ ├── saved_model/ # Format SavedModel TensorFlow
|
||||
│ ├── tfjs_model/ # Format TensorFlow.js
|
||||
│ └── model.tflite # Format TFLite untuk Mobile
|
||||
│
|
||||
├── requirements.txt # Daftar dependensi Python
|
||||
└── README.md
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Prasyarat & Instalasi
|
||||
|
||||
### Kebutuhan Sistem
|
||||
- Python **3.9 – 3.11**
|
||||
- Akun **Google** (untuk Google Drive & Google Colab)
|
||||
- Akun **Kaggle** (untuk mengunduh dataset)
|
||||
- Koneksi internet
|
||||
|
||||
### Instalasi Dependensi
|
||||
|
||||
Disarankan menggunakan virtual environment:
|
||||
|
||||
```bash
|
||||
# Buat environment baru (opsional tapi dianjurkan)
|
||||
python -m venv venv
|
||||
source venv/bin/activate # Linux / macOS
|
||||
# venv\Scripts\activate # Windows
|
||||
|
||||
# Instal semua dependensi
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Sumber Dataset
|
||||
|
||||
Proyek ini menggabungkan **3 dataset** dari sumber berbeda untuk menghasilkan data yang lebih beragam dan representatif.
|
||||
|
||||
### Dataset 1 — Kaggle (Corn Leaf Disease - Indonesia)
|
||||
> 🔗 https://www.kaggle.com/datasets/ndisan/corn-leaf-disease
|
||||
|
||||
Berisi gambar penyakit daun jagung dengan label dalam Bahasa Indonesia. Dataset ini memiliki **4 folder**, namun label **"Karat Daun" tidak digunakan** karena gambar di dalamnya tidak merepresentasikan penyakit karat yang sebenarnya.
|
||||
|
||||
| Folder di Dataset 1 | Tindakan |
|
||||
|---|---|
|
||||
| `Bercak Daun/` | Digunakan |
|
||||
| `Hawar Daun/` | Digunakan |
|
||||
| `Daun Sehat/` | Digunakan (sebagai tambahan) |
|
||||
| `Karat Daun/` | **Dibuang** — data tidak representatif |
|
||||
|
||||
### Dataset 2 — Kaggle (Corn or Maize Leaf Disease)
|
||||
> 🔗 https://www.kaggle.com/datasets/smaranjitghose/corn-or-maize-leaf-disease-dataset
|
||||
|
||||
Digunakan untuk **menggantikan** data Karat Daun dari Dataset 1 dan menambah variasi gambar Daun Sehat.
|
||||
|
||||
| Folder di Dataset 2 | Dipetakan ke Label |
|
||||
|---|---|
|
||||
| `Common_Rust/` | **Karat Daun** |
|
||||
| `Healthy/` | **Daun Sehat** (tambahan) |
|
||||
| `Blight/` | Tidak diproses |
|
||||
| `Gray_Leaf_Spot/` | Tidak diproses |
|
||||
|
||||
### Dataset 3 — SciDB (China Agricultural Dataset)
|
||||
> 🔗 https://www.scidb.cn/en/detail?dataSetId=19536c73f6d74946a212719a94f53ab3
|
||||
|
||||
Dataset dengan label berbahasa Mandarin. Digunakan untuk **menambah variasi data** pada tiga kelas utama. Pemetaan label dilakukan menggunakan file `desc.json` yang disertakan dalam dataset.
|
||||
|
||||
| Label Mandarin | Dipetakan ke Label |
|
||||
|---|---|
|
||||
| `大斑病` (Hawar Besar) | **Hawar Daun** |
|
||||
| `小斑病` (Hawar Kecil) | **Hawar Daun** |
|
||||
| `褐斑病` (Bercak Coklat) | **Bercak Daun** |
|
||||
| `弯孢霉叶斑病` (Bercak Curvularia) | **Bercak Daun** |
|
||||
| `圆斑病` (Bercak Bulat) | **Bercak Daun** |
|
||||
| `灰斑病` (Bercak Abu-abu) | **Bercak Daun** |
|
||||
| `南方锈病` (Karat Selatan) | **Karat Daun** |
|
||||
| `普通锈病` (Karat Biasa) | **Karat Daun** |
|
||||
|
||||
---
|
||||
|
||||
## 5. Tahap 1 — Preprocessing Lokal
|
||||
|
||||
Skrip `preprocessing.py` menjalankan **5 tahap** secara berurutan untuk menyiapkan dataset akhir yang siap diunggah ke Google Drive.
|
||||
|
||||
### Persiapan
|
||||
|
||||
Unduh ketiga dataset dari sumber di atas, lalu letakkan file ZIP-nya di direktori yang sama dengan `preprocessing.py`:
|
||||
|
||||
```
|
||||
.
|
||||
├── preprocessing.py
|
||||
├── dataset_1.zip
|
||||
├── dataset_2.zip
|
||||
└── dataset_3.zip
|
||||
```
|
||||
|
||||
### Menjalankan Preprocessing
|
||||
|
||||
```bash
|
||||
python preprocessing.py
|
||||
```
|
||||
|
||||
### Alur Kerja Otomatis
|
||||
|
||||
#### Tahap 1 — Ekstraksi ZIP
|
||||
Mengekstrak ketiga file `.zip` menjadi folder masing-masing (`dataset_1/`, `dataset_2/`, `dataset_3/`).
|
||||
|
||||
#### Tahap 2 — Menggabungkan Dataset 1 & 2
|
||||
- Menyalin folder `Bercak Daun`, `Hawar Daun`, dan `Daun Sehat` dari Dataset 1 ke folder `dataset/`.
|
||||
- **Folder `Karat Daun` dari Dataset 1 dilewati** karena gambarnya tidak representatif.
|
||||
- Mengambil gambar `Common_Rust` dari Dataset 2 → disimpan ke `dataset/Karat Daun/`.
|
||||
- Mengambil gambar `Healthy` dari Dataset 2 → digabung ke `dataset/Daun Sehat/`.
|
||||
|
||||
#### Tahap 3 — Menggabungkan Dataset 3
|
||||
- Membaca file `desc.json` dari Dataset 3 yang berisi pasangan nama gambar dan labelnya.
|
||||
- Memetakan label Mandarin ke label target (lihat tabel di atas).
|
||||
- Menyalin gambar yang relevan ke folder `dataset/` yang sesuai.
|
||||
|
||||
#### Tahap 4 — Pembersihan Data (Cleaning)
|
||||
Menghapus file gambar spesifik yang bermasalah (rusak, duplikat, atau tidak relevan):
|
||||
- `CBS28.jpg`
|
||||
- `Corn_Common_Rust (1275).jpg`
|
||||
- `Corn_Common_Rust (1289).jpg`
|
||||
- `Corn_Common_Rust (1295).jpg`
|
||||
- `Corn_Gray_Spot (1).jpg`
|
||||
|
||||
#### Tahap 5 — Kompresi ke ZIP
|
||||
Mengemas seluruh folder `dataset/` menjadi `dataset.zip` yang siap diunggah ke Google Drive.
|
||||
|
||||
### Hasil Akhir Preprocessing
|
||||
|
||||
```
|
||||
dataset/
|
||||
├── Bercak Daun/ ← DS1 + DS3
|
||||
├── Hawar Daun/ ← DS1 + DS3
|
||||
├── Karat Daun/ ← DS2 (Common Rust) + DS3
|
||||
└── Daun Sehat/ ← DS1 + DS2 (Healthy)
|
||||
|
||||
dataset.zip ← File siap upload ke Google Drive
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Tahap 2 — Upload ke Google Drive & Training di Colab
|
||||
|
||||
### Langkah 1: Upload `dataset.zip` ke Google Drive
|
||||
|
||||
1. Buka [Google Drive](https://drive.google.com) di browser.
|
||||
2. Upload file `dataset.zip` ke lokasi yang mudah diingat, misalnya:
|
||||
```
|
||||
My Drive/corn_disease/dataset.zip
|
||||
```
|
||||
|
||||
### Langkah 2: Buka Notebook di Google Colab
|
||||
|
||||
1. Upload `notebook.ipynb` ke Google Drive, atau buka langsung dari Colab:
|
||||
- Klik kanan file `notebook.ipynb` di Google Drive → **"Open with"** → **Google Colaboratory**.
|
||||
2. Aktifkan GPU: **Runtime** → **Change runtime type** → pilih **T4 GPU**.
|
||||
|
||||
### Langkah 3: Mount Google Drive di Colab
|
||||
|
||||
Di sel pertama notebook, jalankan:
|
||||
|
||||
```python
|
||||
from google.colab import drive
|
||||
drive.mount('/content/drive')
|
||||
```
|
||||
|
||||
Ikuti instruksi autentikasi yang muncul.
|
||||
|
||||
### Langkah 4: Ekstrak Dataset di Colab
|
||||
|
||||
```python
|
||||
import zipfile
|
||||
|
||||
zip_path = '/content/drive/MyDrive/corn_disease/dataset.zip'
|
||||
extract_path = '/content/dataset'
|
||||
|
||||
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
|
||||
zip_ref.extractall(extract_path)
|
||||
|
||||
print("Dataset berhasil diekstrak!")
|
||||
```
|
||||
|
||||
### Langkah 5: Jalankan Training
|
||||
|
||||
Jalankan seluruh sel di `notebook.ipynb` secara berurutan. Notebook akan melatih model `EfficientNetV2B0` dan menyimpan model terbaik secara otomatis ke:
|
||||
|
||||
```
|
||||
/content/drive/MyDrive/corn_disease/best_model.keras
|
||||
```
|
||||
|
||||
> **Tips:** Aktifkan **"Run all"** dan pantau grafik loss/accuracy. Proses training bisa memakan waktu 30–90 menit tergantung ukuran dataset dan kuota GPU Colab.
|
||||
|
||||
---
|
||||
|
||||
## 7. Tahap 3 — Download Model dari Colab
|
||||
|
||||
Setelah training selesai, unduh `best_model.keras` dari Google Drive ke komputer lokal.
|
||||
|
||||
### Opsi A: Unduh via Google Drive (Direkomendasikan)
|
||||
|
||||
1. Buka Google Drive → navigasi ke folder `corn_disease/`.
|
||||
2. Klik kanan `best_model.keras` → **Download**.
|
||||
3. Letakkan file yang diunduh ke folder `best_model/` di proyek lokal:
|
||||
```
|
||||
best_model/best_model.keras
|
||||
```
|
||||
|
||||
### Opsi B: Unduh Langsung dari Colab
|
||||
|
||||
Tambahkan sel berikut di akhir notebook, lalu jalankan:
|
||||
|
||||
```python
|
||||
from google.colab import files
|
||||
files.download('/content/drive/MyDrive/corn_disease/best_model.keras')
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Tahap 4 — Ekspor Model untuk Produksi
|
||||
|
||||
Setelah `best_model/best_model.keras` tersedia di lokal, jalankan skrip ekspor.
|
||||
|
||||
### Persiapan Struktur Folder
|
||||
|
||||
Pastikan struktur direktori sudah benar:
|
||||
|
||||
```
|
||||
.
|
||||
├── best_model/
|
||||
│ └── best_model.keras ← File hasil unduhan dari Colab
|
||||
└── save_model.py
|
||||
```
|
||||
|
||||
### Langkah 1: Ekspor ke SavedModel & TFLite
|
||||
|
||||
```bash
|
||||
python save_model.py
|
||||
```
|
||||
|
||||
Skrip ini akan:
|
||||
1. Memuat model dari `best_model/best_model.keras`.
|
||||
2. Membangun ulang arsitektur **tanpa layer augmentasi** (wajib untuk kompatibilitas TFJS).
|
||||
3. Menyalin semua bobot terlatih ke arsitektur baru.
|
||||
4. Mengekspor ke `model/saved_model/` (format SavedModel).
|
||||
5. Mengonversi ke `model/model.tflite` (format TFLite untuk Android/iOS).
|
||||
|
||||
### Langkah 2: Konversi ke TensorFlow.js (untuk Web/Browser)
|
||||
|
||||
Konversi TFJS **harus dilakukan via CLI**, bukan dari dalam Python, untuk menghindari konflik library Protobuf.
|
||||
|
||||
```bash
|
||||
# Langkah 2a: Atur variabel lingkungan
|
||||
export PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python
|
||||
|
||||
# Langkah 2b: Konversi
|
||||
tensorflowjs_converter \
|
||||
--input_format=tf_saved_model \
|
||||
--output_format=tfjs_graph_model \
|
||||
--signature_name=serving_default \
|
||||
--saved_model_tags=serve \
|
||||
model/saved_model \
|
||||
model/tfjs_model
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. Output Akhir
|
||||
|
||||
Setelah seluruh pipeline selesai dijalankan, berikut file output yang tersedia:
|
||||
|
||||
| File / Direktori | Format | Kegunaan |
|
||||
|---|---|---|
|
||||
| `dataset/` | Folder gambar terstruktur | Dataset akhir hasil preprocessing |
|
||||
| `dataset.zip` | ZIP | Dataset untuk diupload ke Google Drive / Colab |
|
||||
| `best_model/best_model.keras` | Keras | Model terlatih lengkap (dengan optimizer) |
|
||||
| `model/saved_model/` | SavedModel (PB) | Inferensi server-side & jembatan konversi TFJS |
|
||||
| `model/model.tflite` | TFLite | Inferensi di perangkat **Android / iOS** |
|
||||
| `model/tfjs_model/` | TensorFlow.js | Inferensi di **browser / Node.js** |
|
||||
|
||||
---
|
||||
|
||||
## 10. Troubleshooting
|
||||
|
||||
### `FileNotFoundError: dataset_1.zip tidak ditemukan`
|
||||
**Solusi:** Pastikan ketiga file ZIP sudah diunduh dan diletakkan di direktori yang sama dengan `preprocessing.py`.
|
||||
|
||||
---
|
||||
|
||||
### `FileNotFoundError: Model file not found at best_model/best_model.keras`
|
||||
**Solusi:** Unduh `best_model.keras` dari Google Drive/Colab dan letakkan di folder `best_model/`. Pastikan nama filenya tepat.
|
||||
|
||||
---
|
||||
|
||||
### `ValueError: incompatible with expected resource` (saat konversi TFJS)
|
||||
**Penyebab:** Layer augmentasi data Keras (`random_flip`, dll.) bersifat *stateful* dinamis dan tidak bisa dibekukan oleh konverter TFJS.
|
||||
|
||||
**Solusi:** Sudah ditangani otomatis oleh fungsi `build_clean_model()` dalam `save_model.py`, yang membangun ulang arsitektur tanpa layer augmentasi sebelum proses ekspor.
|
||||
|
||||
---
|
||||
|
||||
### `VersionError: Detected incompatible Protobuf Gencode/Runtime versions`
|
||||
**Penyebab:** Konflik antara library C++ Protobuf yang dibawa oleh `tensorflow_decision_forests` dengan *runtime* Python di lingkungan virtual lokal.
|
||||
|
||||
**Solusi:** Pastikan konversi TFJS dijalankan via **CLI langsung** (bukan `subprocess` dari Python) dengan variabel lingkungan berikut:
|
||||
```bash
|
||||
export PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Sesi Colab terputus saat training
|
||||
**Solusi:** Gunakan callback `ModelCheckpoint` di notebook untuk menyimpan checkpoint secara berkala ke Google Drive, sehingga training bisa dilanjutkan dari checkpoint terakhir tanpa mengulang dari awal.
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:4e8e3a4f86ae1594aeb221c40db3bc80e87c05b05f386bbb628ff1da16397424
|
||||
size 112903200
|
||||
Binary file not shown.
@@ -0,0 +1 @@
|
||||
©©ءّ¸ث‡ق¬§¢¥ؤج™إأà’–سèز÷ئخ ’ڑھ¸¥ؤي—·(،وظ—ئƒ„÷ٌ2:10851638082828504866
|
||||
Binary file not shown.
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:4e3831052cde64562f0a4e51d5f224c2c819dd625a1d89b35ef58d74b82f1858
|
||||
size 53214594
|
||||
Binary file not shown.
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:613179718d653f027cf5e70a02f223b423a98abaffe7120ef626793aec23177f
|
||||
size 4194304
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:20325958a36f2afdf149083a4a2079aee4725bb3d0c01f96694f617bb7c29a27
|
||||
size 4194304
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:853bb38c854ff011b06ed98d7037b25501e02a0b718e65159acbbc62e97874ae
|
||||
size 4194304
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:bf9a4b64fb22a0e83a2862f119a61842a72ec8ba15981ece3291b049dd4db11a
|
||||
size 2432372
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:84e7ddbfcf1fd5d55bf1198cc65fb024ff9110e544195779b42281245246b16d
|
||||
size 4194304
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:8d98aa415626d122e2a324d5ffd3a536bcd03f42957114bc23b3d4fc706c7eb8
|
||||
size 4194304
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:30cb3863522eb0e1f1688249f58629ed1e1f03e82d723fde7a13763ade2d60da
|
||||
size 4194304
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:2f8fffbfc14379d113100374729ce0236306b6f1aa8b32808e07020adfb3f0a0
|
||||
size 4194304
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:516a4af1cae907747b7dc91169579a29bd477a99ad4e638fbb205a9b0acaae9d
|
||||
size 4194304
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:76757c0eb38bc7c38c12d8a8ab8ab68b83d17f377cceca985905e5c48dece798
|
||||
size 4194304
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f5f1a2bd449370e6ffd13143d78e6d1b2803df4cbae968fee5ab4244349a64b5
|
||||
size 4194304
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:8c79a0792c83c7bcbf8cb2768d3e6d12a928f95506c64c6818698cea0e3f2fec
|
||||
size 4194304
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6c5a36d2a203ece90b2978f5295f42c7a0f9ff72a2edcdbcaa306e67c29b3d72
|
||||
size 4194304
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,197 @@
|
||||
import os
|
||||
import shutil
|
||||
import zipfile
|
||||
import json
|
||||
|
||||
# ==========================================
|
||||
# KONFIGURASI DAN MAPPING
|
||||
# ==========================================
|
||||
DAFTAR_ZIP = ['dataset_1.zip', 'dataset_2.zip', 'dataset_3.zip']
|
||||
TARGET_DIR = "dataset"
|
||||
|
||||
PEMETAAN_KATEGORI = {
|
||||
"大斑病": "Hawar Daun",
|
||||
"小斑病": "Hawar Daun",
|
||||
"褐斑病": "Bercak Daun",
|
||||
"弯孢霉叶斑病": "Bercak Daun",
|
||||
"圆斑病": "Bercak Daun",
|
||||
"灰斑病": "Bercak Daun",
|
||||
"南方锈病": "Karat Daun",
|
||||
"普通锈病": "Karat Daun",
|
||||
}
|
||||
|
||||
DAFTAR_FILE_HAPUS = [
|
||||
"CBS28.jpg",
|
||||
"Corn_Common_Rust (1275).jpg",
|
||||
"Corn_Common_Rust (1289).jpg",
|
||||
"Corn_Common_Rust (1295).jpg",
|
||||
"Corn_Gray_Spot (1).jpg"
|
||||
]
|
||||
|
||||
# ==========================================
|
||||
# TAHAP 1: EKSTRAKSI DATASET
|
||||
# ==========================================
|
||||
def ekstrak_semua_zip():
|
||||
print("--- TAHAP 1: Mengekstrak File ZIP ---")
|
||||
for zip_file in DAFTAR_ZIP:
|
||||
if os.path.exists(zip_file):
|
||||
folder_name = os.path.splitext(zip_file)[0]
|
||||
os.makedirs(folder_name, exist_ok=True)
|
||||
try:
|
||||
with zipfile.ZipFile(zip_file, 'r') as zip_ref:
|
||||
zip_ref.extractall(folder_name)
|
||||
print(f" [OK] {zip_file} -> {folder_name}/")
|
||||
except zipfile.BadZipFile:
|
||||
print(f" [ERROR] {zip_file} rusak.")
|
||||
else:
|
||||
print(f" [SKIP] File {zip_file} tidak ditemukan.")
|
||||
print("\n")
|
||||
|
||||
# ==========================================
|
||||
# TAHAP 2: GABUNGKAN DATASET 1 & 2
|
||||
# ==========================================
|
||||
def cari_folder_ds2(base_path, keywords):
|
||||
if not os.path.exists(base_path): return None
|
||||
for f in os.listdir(base_path):
|
||||
f_lower = f.lower()
|
||||
if any(k in f_lower for k in keywords):
|
||||
return os.path.join(base_path, f)
|
||||
return None
|
||||
|
||||
def gabungkan_dataset_1_dan_2():
|
||||
print("--- TAHAP 2: Menggabungkan Dataset 1 & 2 ---")
|
||||
os.makedirs(TARGET_DIR, exist_ok=True)
|
||||
|
||||
# 1. Salin dari dataset_1
|
||||
folder_dari_ds1 = ["Bercak Daun", "Daun Sehat", "Hawar Daun"]
|
||||
for folder in folder_dari_ds1:
|
||||
src = os.path.join("dataset_1", folder)
|
||||
dst = os.path.join(TARGET_DIR, folder)
|
||||
if os.path.exists(src):
|
||||
shutil.copytree(src, dst, dirs_exist_ok=True)
|
||||
print(f" [OK] Menyalin folder {src} ke {dst}")
|
||||
|
||||
# 2. Salin gambar dari dataset_2
|
||||
base_ds2 = os.path.join("dataset_2", "data")
|
||||
mapping_ds2 = {
|
||||
("common_rust", "commont_rust"): "Karat Daun",
|
||||
("healthy",): "Daun Sehat"
|
||||
}
|
||||
|
||||
if os.path.exists(base_ds2):
|
||||
for keywords, target_subfolder in mapping_ds2.items():
|
||||
src_folder = cari_folder_ds2(base_ds2, keywords)
|
||||
dst_folder = os.path.join(TARGET_DIR, target_subfolder)
|
||||
os.makedirs(dst_folder, exist_ok=True)
|
||||
|
||||
if src_folder and os.path.exists(src_folder):
|
||||
file_count = 0
|
||||
for file_name in os.listdir(src_folder):
|
||||
full_file_name = os.path.join(src_folder, file_name)
|
||||
if os.path.isfile(full_file_name):
|
||||
shutil.copy(full_file_name, dst_folder)
|
||||
file_count += 1
|
||||
print(f" [OK] Menyalin {file_count} gambar dari {src_folder} ke {dst_folder}")
|
||||
else:
|
||||
print(f" [SKIP] Folder untuk '{target_subfolder}' tidak ditemukan di {base_ds2}")
|
||||
print("\n")
|
||||
|
||||
# ==========================================
|
||||
# TAHAP 3: GABUNGKAN DATASET 3 (JSON MAPPING)
|
||||
# ==========================================
|
||||
def cari_gambar_fleksibel(folder_sumber, nama_file_target):
|
||||
nama_file_target = nama_file_target.strip()
|
||||
path_langsung = os.path.join(folder_sumber, nama_file_target)
|
||||
if os.path.exists(path_langsung):
|
||||
return path_langsung
|
||||
|
||||
target_lower = nama_file_target.lower()
|
||||
for f in os.listdir(folder_sumber):
|
||||
if f.lower() == target_lower or os.path.splitext(f)[0].lower() == os.path.splitext(target_lower)[0]:
|
||||
return os.path.join(folder_sumber, f)
|
||||
return None
|
||||
|
||||
def gabungkan_dataset_3():
|
||||
print("--- TAHAP 3: Menggabungkan Dataset 3 berdasarkan JSON ---")
|
||||
folder_data = os.path.join("dataset_3", "data")
|
||||
file_json = os.path.join("dataset_3", "desc.json")
|
||||
|
||||
if not os.path.exists(file_json):
|
||||
print(f" [SKIP] {file_json} tidak ditemukan.\n")
|
||||
return
|
||||
|
||||
with open(file_json, 'r', encoding='utf-8') as f:
|
||||
data_label = json.load(f)
|
||||
|
||||
berhasil = 0
|
||||
for item in data_label:
|
||||
image_name = item.get("image_name")
|
||||
label = item.get("label", "").strip()
|
||||
|
||||
if image_name and label in PEMETAAN_KATEGORI:
|
||||
nama_folder_target = PEMETAAN_KATEGORI[label]
|
||||
folder_tujuan = os.path.join(TARGET_DIR, nama_folder_target)
|
||||
os.makedirs(folder_tujuan, exist_ok=True)
|
||||
|
||||
path_sumber = cari_gambar_fleksibel(folder_data, image_name)
|
||||
if path_sumber:
|
||||
nama_asli = os.path.basename(path_sumber)
|
||||
shutil.copy(path_sumber, os.path.join(folder_tujuan, nama_asli))
|
||||
berhasil += 1
|
||||
|
||||
print(f" [OK] Berhasil merutekan {berhasil} gambar dari dataset_3 ke '{TARGET_DIR}'\n")
|
||||
|
||||
# ==========================================
|
||||
# TAHAP 4: PEMBERSIHAN DATA (CLEANING)
|
||||
# ==========================================
|
||||
def bersihkan_dataset():
|
||||
print("--- TAHAP 4: Menghapus File Spesifik ---")
|
||||
set_hapus = set(DAFTAR_FILE_HAPUS)
|
||||
terhapus = 0
|
||||
|
||||
if os.path.exists(TARGET_DIR):
|
||||
for root, _, files in os.walk(TARGET_DIR):
|
||||
for nama_file in files:
|
||||
if nama_file in set_hapus:
|
||||
path_lengkap = os.path.join(root, nama_file)
|
||||
try:
|
||||
os.remove(path_lengkap)
|
||||
print(f" [TERHAPUS] {path_lengkap}")
|
||||
set_hapus.remove(nama_file)
|
||||
terhapus += 1
|
||||
except Exception as e:
|
||||
print(f" [GAGAL] {path_lengkap} ({e})")
|
||||
|
||||
print(f" [OK] Total file dihapus: {terhapus}")
|
||||
if set_hapus:
|
||||
print(f" [INFO] {len(set_hapus)} file tidak ditemukan (mungkin sudah terhapus sebelumnya):")
|
||||
for sisa in set_hapus:
|
||||
print(f" - {sisa}")
|
||||
print("\n")
|
||||
|
||||
# ==========================================
|
||||
# TAHAP 5: BUNGKUS KE ZIP
|
||||
# ==========================================
|
||||
def zip_dataset():
|
||||
print("--- TAHAP 5: Mengompresi Folder Dataset ---")
|
||||
if os.path.exists(TARGET_DIR):
|
||||
print(f" Membuat file {TARGET_DIR}.zip, mohon tunggu sebentar...")
|
||||
# shutil.make_archive(nama_output_tanpa_ext, format, folder_yang_dizip)
|
||||
shutil.make_archive(TARGET_DIR, 'zip', TARGET_DIR)
|
||||
print(f" [OK] Berhasil! File '{TARGET_DIR}.zip' sudah siap.\n")
|
||||
else:
|
||||
print(f" [ERROR] Folder '{TARGET_DIR}' tidak ditemukan, proses zip dibatalkan.\n")
|
||||
|
||||
|
||||
# ==========================================
|
||||
# MAIN EXECUTION
|
||||
# ==========================================
|
||||
if __name__ == "__main__":
|
||||
print("=== MEMULAI PREPROCESSING DATASET ===\n")
|
||||
ekstrak_semua_zip()
|
||||
gabungkan_dataset_1_dan_2()
|
||||
gabungkan_dataset_3()
|
||||
bersihkan_dataset()
|
||||
zip_dataset()
|
||||
print("=== PREPROCESSING SELESAI ===")
|
||||
print(f"Dataset akhir Anda kini siap digunakan di dalam folder '{TARGET_DIR}' dan '{TARGET_DIR}.zip'.")
|
||||
@@ -0,0 +1,4 @@
|
||||
tensorflow>=2.13.0
|
||||
tensorflowjs>=4.10.0
|
||||
jupyter>=1.0.0
|
||||
ipykernel>=6.25.0
|
||||
@@ -0,0 +1,70 @@
|
||||
import os
|
||||
import logging
|
||||
import traceback
|
||||
import tensorflow as tf
|
||||
from tensorflow.keras import layers, models
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
|
||||
|
||||
def build_clean_model(num_classes, img_size=(224, 224)):
|
||||
base_model = tf.keras.applications.EfficientNetV2B0(
|
||||
input_shape=img_size + (3,),
|
||||
include_top=False,
|
||||
weights=None,
|
||||
)
|
||||
inputs = tf.keras.Input(shape=img_size + (3,))
|
||||
x = base_model(inputs, training=False)
|
||||
x = layers.Conv2D(512, (3, 3), padding='same', activation='swish')(x)
|
||||
x = layers.BatchNormalization()(x)
|
||||
x = layers.MaxPooling2D((2, 2))(x)
|
||||
x = layers.Dropout(0.2)(x)
|
||||
x = layers.Conv2D(256, (3, 3), padding='same', activation='swish')(x)
|
||||
x = layers.BatchNormalization()(x)
|
||||
x = layers.GlobalAveragePooling2D()(x)
|
||||
x = layers.Dropout(0.3)(x)
|
||||
x = layers.Dense(1024, activation='swish')(x)
|
||||
x = layers.BatchNormalization()(x)
|
||||
x = layers.Dropout(0.4)(x)
|
||||
outputs = layers.Dense(num_classes, activation='sigmoid', dtype='float32')(x)
|
||||
return models.Model(inputs, outputs)
|
||||
|
||||
logging.info("=== EXPORT STARTED ===")
|
||||
try:
|
||||
MODEL_KERAS_PATH = "best_model/best_model.keras"
|
||||
OUTPUT_DIR = "model"
|
||||
saved_model_dir = os.path.join(OUTPUT_DIR, "saved_model")
|
||||
tflite_path = os.path.join(OUTPUT_DIR, "model.tflite")
|
||||
|
||||
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
||||
|
||||
if not os.path.exists(MODEL_KERAS_PATH):
|
||||
raise FileNotFoundError(f"Model file not found at {MODEL_KERAS_PATH}")
|
||||
|
||||
logging.info(f"Loading trained weights from {MODEL_KERAS_PATH}...")
|
||||
original_model = tf.keras.models.load_model(MODEL_KERAS_PATH, compile=False)
|
||||
|
||||
logging.info("Building clean architecture...")
|
||||
clean_model = build_clean_model(num_classes=original_model.output_shape[-1])
|
||||
clean_model.set_weights(original_model.get_weights())
|
||||
logging.info("Weights cloned successfully.")
|
||||
|
||||
logging.info(f"Exporting to SavedModel format at: {saved_model_dir}...")
|
||||
tf.saved_model.save(clean_model, saved_model_dir)
|
||||
logging.info("SavedModel export completed successfully.")
|
||||
|
||||
logging.info(f"Converting to TFLite format at: {tflite_path}...")
|
||||
converter = tf.lite.TFLiteConverter.from_keras_model(clean_model)
|
||||
converter.target_spec.supported_ops = [
|
||||
tf.lite.OpsSet.TFLITE_BUILTINS,
|
||||
tf.lite.OpsSet.SELECT_TF_OPS,
|
||||
]
|
||||
converter.optimizations = [tf.lite.Optimize.DEFAULT]
|
||||
tflite_model = converter.convert()
|
||||
with open(tflite_path, "wb") as f:
|
||||
f.write(tflite_model)
|
||||
logging.info("TFLite conversion completed successfully.")
|
||||
logging.info("=== EXPORT COMPLETED ===")
|
||||
|
||||
except Exception:
|
||||
logging.error("EXPORT FAILED")
|
||||
logging.error(traceback.format_exc())
|
||||
Reference in New Issue
Block a user