Decoder24 commited on
Commit
a6eed2b
·
verified ·
1 Parent(s): d74643e

Upload folder using huggingface_hub

Browse files
01_data_exploration.ipynb ADDED
The diff for this file is too large to render. See raw diff
 
__pycache__/advanced_augmentation.cpython-310.pyc ADDED
Binary file (8.34 kB). View file
 
__pycache__/config.cpython-310.pyc ADDED
Binary file (550 Bytes). View file
 
__pycache__/data_loader.cpython-310.pyc ADDED
Binary file (5.71 kB). View file
 
__pycache__/engine.cpython-310.pyc ADDED
Binary file (1.78 kB). View file
 
__pycache__/mixup.cpython-310.pyc ADDED
Binary file (2.57 kB). View file
 
__pycache__/model.cpython-310.pyc ADDED
Binary file (2.5 kB). View file
 
advanced_augmentation.py ADDED
@@ -0,0 +1,308 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ import torch.nn.functional as F
4
+ import numpy as np
5
+ from torchvision import transforms
6
+ import random
7
+
8
+ def cutmix_data(x, y, alpha=1.0, device='cuda'):
9
+ """
10
+ CutMix data augmentation.
11
+
12
+ Args:
13
+ x: Input batch
14
+ y: Target batch
15
+ alpha: CutMix parameter
16
+ device: Device to run on
17
+
18
+ Returns:
19
+ mixed_x: Mixed input batch
20
+ y_a, y_b: Original targets for loss calculation
21
+ lam: Mixing ratio
22
+ """
23
+ if alpha > 0:
24
+ lam = np.random.beta(alpha, alpha)
25
+ else:
26
+ lam = 1
27
+
28
+ batch_size = x.size(0)
29
+ if device == 'cuda':
30
+ index = torch.randperm(batch_size).cuda()
31
+ else:
32
+ index = torch.randperm(batch_size)
33
+
34
+ # Generate random bounding box
35
+ W = x.size(2)
36
+ H = x.size(3)
37
+ cut_rat = np.sqrt(1. - lam)
38
+ cut_w = int(W * cut_rat)
39
+ cut_h = int(H * cut_rat)
40
+
41
+ # Uniform sampling
42
+ cx = np.random.randint(W)
43
+ cy = np.random.randint(H)
44
+
45
+ bbx1 = np.clip(cx - cut_w // 2, 0, W)
46
+ bby1 = np.clip(cy - cut_h // 2, 0, H)
47
+ bbx2 = np.clip(cx + cut_w // 2, 0, W)
48
+ bby2 = np.clip(cy + cut_h // 2, 0, H)
49
+
50
+ x[:, :, bbx1:bbx2, bby1:bby2] = x[index, :, bbx1:bbx2, bby1:bby2]
51
+
52
+ # Adjust lambda to exactly match pixel ratio
53
+ lam = 1 - ((bbx2 - bbx1) * (bby2 - bby1) / (W * H))
54
+ y_a, y_b = y, y[index]
55
+
56
+ return x, y_a, y_b, lam
57
+
58
+ def cutmix_criterion(criterion, pred, y_a, y_b, lam):
59
+ """
60
+ CutMix loss calculation.
61
+ """
62
+ return lam * criterion(pred, y_a) + (1 - lam) * criterion(pred, y_b)
63
+
64
+ class LabelSmoothingCrossEntropy(nn.Module):
65
+ """
66
+ Label smoothing cross entropy loss.
67
+ """
68
+ def __init__(self, smoothing=0.1):
69
+ super(LabelSmoothingCrossEntropy, self).__init__()
70
+ self.smoothing = smoothing
71
+
72
+ def forward(self, x, target):
73
+ confidence = 1. - self.smoothing
74
+ logprobs = F.log_softmax(x, dim=-1)
75
+ nll_loss = -logprobs.gather(dim=-1, index=target.unsqueeze(1))
76
+ nll_loss = nll_loss.squeeze(1)
77
+ smooth_loss = -logprobs.mean(dim=-1)
78
+ loss = confidence * nll_loss + self.smoothing * smooth_loss
79
+ return loss.mean()
80
+
81
+ class FocalLoss(nn.Module):
82
+ """
83
+ Focal Loss for addressing class imbalance.
84
+ """
85
+ def __init__(self, alpha=1, gamma=2, reduction='mean'):
86
+ super(FocalLoss, self).__init__()
87
+ self.alpha = alpha
88
+ self.gamma = gamma
89
+ self.reduction = reduction
90
+
91
+ def forward(self, inputs, targets):
92
+ ce_loss = F.cross_entropy(inputs, targets, reduction='none')
93
+ pt = torch.exp(-ce_loss)
94
+ focal_loss = self.alpha * (1-pt)**self.gamma * ce_loss
95
+
96
+ if self.reduction == 'mean':
97
+ return focal_loss.mean()
98
+ elif self.reduction == 'sum':
99
+ return focal_loss.sum()
100
+ else:
101
+ return focal_loss
102
+
103
+ class AdvancedAugmentation:
104
+ """
105
+ Advanced augmentation techniques for better generalization.
106
+ """
107
+ def __init__(self, image_size=224):
108
+ self.image_size = image_size
109
+
110
+ def get_train_transforms(self):
111
+ """
112
+ Get comprehensive training transforms with advanced augmentation.
113
+ """
114
+ return transforms.Compose([
115
+ # Resize with padding
116
+ transforms.Resize((self.image_size + 32, self.image_size + 32)),
117
+
118
+ # Random crop with padding
119
+ transforms.RandomCrop((self.image_size, self.image_size), padding=4),
120
+
121
+ # Geometric augmentations
122
+ transforms.RandomHorizontalFlip(p=0.5),
123
+ transforms.RandomVerticalFlip(p=0.2),
124
+ transforms.RandomRotation(degrees=15),
125
+ transforms.RandomAffine(
126
+ degrees=0,
127
+ translate=(0.1, 0.1),
128
+ scale=(0.9, 1.1),
129
+ shear=5
130
+ ),
131
+
132
+ # Color augmentations
133
+ transforms.ColorJitter(
134
+ brightness=0.2,
135
+ contrast=0.2,
136
+ saturation=0.2,
137
+ hue=0.05
138
+ ),
139
+
140
+ # Advanced augmentations
141
+ transforms.RandomPerspective(distortion_scale=0.2, p=0.3),
142
+ transforms.RandomErasing(p=0.2, scale=(0.02, 0.33), ratio=(0.3, 3.3)),
143
+
144
+ # TrivialAugmentWide for additional randomness
145
+ transforms.TrivialAugmentWide(num_magnitude_bins=31),
146
+
147
+ # Convert to tensor and normalize
148
+ transforms.ToTensor(),
149
+ transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
150
+ ])
151
+
152
+ def get_val_transforms(self):
153
+ """
154
+ Get validation transforms (minimal augmentation).
155
+ """
156
+ return transforms.Compose([
157
+ transforms.Resize((self.image_size, self.image_size)),
158
+ transforms.ToTensor(),
159
+ transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
160
+ ])
161
+
162
+ class TestTimeAugmentation:
163
+ """
164
+ Test Time Augmentation for better inference.
165
+ """
166
+ def __init__(self, model, device, num_augmentations=5):
167
+ self.model = model
168
+ self.device = device
169
+ self.num_augmentations = num_augmentations
170
+
171
+ # Define TTA transforms
172
+ self.tta_transforms = [
173
+ transforms.Compose([
174
+ transforms.Resize((224, 224)),
175
+ transforms.ToTensor(),
176
+ transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
177
+ ]),
178
+ transforms.Compose([
179
+ transforms.Resize((224, 224)),
180
+ transforms.RandomHorizontalFlip(p=1.0),
181
+ transforms.ToTensor(),
182
+ transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
183
+ ]),
184
+ transforms.Compose([
185
+ transforms.Resize((224, 224)),
186
+ transforms.RandomRotation(degrees=10),
187
+ transforms.ToTensor(),
188
+ transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
189
+ ]),
190
+ transforms.Compose([
191
+ transforms.Resize((224, 224)),
192
+ transforms.RandomRotation(degrees=10),
193
+ transforms.ToTensor(),
194
+ transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
195
+ ]),
196
+ transforms.Compose([
197
+ transforms.Resize((224, 224)),
198
+ transforms.ColorJitter(brightness=0.1, contrast=0.1),
199
+ transforms.ToTensor(),
200
+ transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
201
+ ])
202
+ ]
203
+
204
+ def predict(self, image):
205
+ """
206
+ Predict with TTA.
207
+ """
208
+ self.model.eval()
209
+ predictions = []
210
+
211
+ with torch.no_grad():
212
+ for transform in self.tta_transforms[:self.num_augmentations]:
213
+ # Apply transform
214
+ if hasattr(image, 'convert'):
215
+ # PIL Image
216
+ transformed = transform(image)
217
+ else:
218
+ # Already tensor
219
+ transformed = transform(image)
220
+
221
+ # Add batch dimension
222
+ transformed = transformed.unsqueeze(0).to(self.device)
223
+
224
+ # Get prediction
225
+ output = self.model(transformed)
226
+ predictions.append(F.softmax(output, dim=1))
227
+
228
+ # Average predictions
229
+ avg_prediction = torch.mean(torch.stack(predictions), dim=0)
230
+ return avg_prediction
231
+
232
+ def calculate_class_weights(train_targets, num_classes, method='balanced'):
233
+ """
234
+ Calculate class weights for handling class imbalance.
235
+
236
+ Args:
237
+ train_targets: List of training targets
238
+ num_classes: Number of classes
239
+ method: 'balanced', 'inverse', or 'sqrt'
240
+
241
+ Returns:
242
+ class_weights: Tensor of class weights
243
+ """
244
+ class_counts = np.bincount(train_targets, minlength=num_classes)
245
+
246
+ if method == 'balanced':
247
+ # sklearn's balanced method
248
+ total_samples = len(train_targets)
249
+ class_weights = total_samples / (num_classes * class_counts)
250
+ elif method == 'inverse':
251
+ # Simple inverse frequency
252
+ class_weights = 1.0 / class_counts
253
+ elif method == 'sqrt':
254
+ # Square root of inverse frequency
255
+ class_weights = 1.0 / np.sqrt(class_counts)
256
+ else:
257
+ raise ValueError(f"Unknown method: {method}")
258
+
259
+ # Normalize weights
260
+ class_weights = class_weights / class_weights.sum() * num_classes
261
+
262
+ return torch.tensor(class_weights, dtype=torch.float)
263
+
264
+ def get_advanced_scheduler(optimizer, method='cosine_warmup', total_epochs=50):
265
+ """
266
+ Get advanced learning rate scheduler.
267
+
268
+ Args:
269
+ optimizer: PyTorch optimizer
270
+ method: Scheduler method
271
+ total_epochs: Total number of epochs
272
+
273
+ Returns:
274
+ scheduler: Learning rate scheduler
275
+ """
276
+ if method == 'cosine_warmup':
277
+ from torch.optim.lr_scheduler import CosineAnnealingWarmRestarts
278
+ return CosineAnnealingWarmRestarts(optimizer, T_0=10, T_mult=2, eta_min=1e-7)
279
+
280
+ elif method == 'onecycle':
281
+ from torch.optim.lr_scheduler import OneCycleLR
282
+ return OneCycleLR(
283
+ optimizer,
284
+ max_lr=optimizer.param_groups[0]['lr'],
285
+ total_steps=total_epochs,
286
+ pct_start=0.3,
287
+ anneal_strategy='cos'
288
+ )
289
+
290
+ elif method == 'plateau':
291
+ from torch.optim.lr_scheduler import ReduceLROnPlateau
292
+ return ReduceLROnPlateau(
293
+ optimizer,
294
+ mode='max',
295
+ factor=0.5,
296
+ patience=3,
297
+ min_lr=1e-7,
298
+ verbose=True
299
+ )
300
+
301
+ else:
302
+ raise ValueError(f"Unknown scheduler method: {method}")
303
+
304
+ def apply_mixup_cutmix_probability():
305
+ """
306
+ Randomly choose between Mixup and CutMix based on probability.
307
+ """
308
+ return random.choice(['mixup', 'cutmix', 'none'])
config.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from pathlib import Path
3
+
4
+ # Definisikan ROOT path proyek (folder batik_vision_project)
5
+ ROOT_PATH = Path(__file__).resolve().parent.parent
6
+
7
+ # Path ke data
8
+ DATA_PATH = ROOT_PATH / "Batik-Indonesia" # <-- GANTI BARIS INI
9
+
10
+ # Hyperparameters
11
+ DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
12
+ BATCH_SIZE = 32 # Dikurangi untuk laptop
13
+ IMAGE_SIZE = 224 # Ukuran input untuk ViT/Swin
14
+ LEARNING_RATE = 1e-4
15
+ EPOCHS = 50 # Dikurangi untuk testing awal
16
+
17
+ # Pengaturan split
18
+ TEST_SPLIT_SIZE = 0.2 # 20% untuk validasi
19
+ RANDOM_SEED = 42 # Agar hasil split selalu sama
20
+
21
+ # Daftar model yang akan diuji
22
+ # Mulai dengan model terkecil dulu untuk testing
23
+ MODEL_LIST = ["convnext_tiny"] # Model terkecil untuk testing awal
data_loader.py ADDED
@@ -0,0 +1,242 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ from pathlib import Path
3
+ # tambahkan parent project ke sys.path sehingga 'src' dapat diimport saat menjalankan skrip langsung
4
+ sys.path.append(str(Path(__file__).resolve().parents[1]))
5
+
6
+ import torch
7
+ import numpy as np
8
+ from torch.utils.data import DataLoader, Dataset, random_split, WeightedRandomSampler
9
+ from torchvision import datasets, transforms
10
+ from src import config # Mengimpor dari file config.py Anda
11
+ import matplotlib.pyplot as plt
12
+ import warnings
13
+ from pathlib import Path
14
+
15
+ # --- 1. Mendefinisikan Transformasi (Augmentasi) ---
16
+
17
+ # Statistik ImageNet untuk normalisasi (penting untuk model pre-trained)
18
+ MEAN = [0.485, 0.456, 0.406]
19
+ STD = [0.229, 0.224, 0.225]
20
+
21
+ # Transformasi untuk data TRAINING
22
+ # Tujuannya: "menyiksa" data agar model bisa generalisasi dengan teknik terbaru
23
+ train_transform = transforms.Compose([
24
+ transforms.Resize((config.IMAGE_SIZE + 32, config.IMAGE_SIZE + 32)), # Resize lebih besar dulu
25
+ transforms.RandomCrop((config.IMAGE_SIZE, config.IMAGE_SIZE), padding=4), # Random crop dengan padding
26
+ transforms.RandomHorizontalFlip(p=0.5),
27
+ transforms.RandomVerticalFlip(p=0.2), # Tambah vertical flip
28
+ transforms.RandomRotation(degrees=15), # Moderate rotation
29
+ transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2, hue=0.05), # Moderate color augmentation
30
+ transforms.RandomAffine(degrees=0, translate=(0.1, 0.1), scale=(0.9, 1.1), shear=5), # Enhanced geometric augmentation
31
+
32
+ # Advanced augmentations
33
+ transforms.RandomPerspective(distortion_scale=0.2, p=0.3), # Perspective distortion
34
+ transforms.RandomErasing(p=0.2, scale=(0.02, 0.33), ratio=(0.3, 3.3)), # Random erasing
35
+
36
+ # --- TAMBAHKAN INI ---
37
+ # Ini akan menerapkan augmentasi acak yang kuat
38
+ transforms.TrivialAugmentWide(num_magnitude_bins=31),
39
+ # ---------------------
40
+
41
+ transforms.ToTensor(), # ToTensor() HARUS setelah augmentasi
42
+ transforms.Normalize(mean=MEAN, std=STD)
43
+ ])
44
+ # Transformasi untuk data VALIDASI
45
+ # Tujuannya: Hanya membersihkan data untuk evaluasi, TANPA augmentasi acak
46
+ val_transform = transforms.Compose([
47
+ transforms.Resize((config.IMAGE_SIZE, config.IMAGE_SIZE)), # Ukuran seragam
48
+ transforms.ToTensor(), # Konversi ke tensor PyTorch
49
+ transforms.Normalize(mean=MEAN, std=STD) # Normalisasi
50
+ ])
51
+
52
+
53
+ # --- 2. Helper Class untuk Menerapkan Transformasi Berbeda ---
54
+ # INI PENTING:
55
+ # Kita perlu membagi dataset (split) SEBELUM menerapkan augmentasi.
56
+ # Helper class ini memungkinkan kita menerapkan transform yang berbeda (train/val)
57
+ # pada dataset subset yang sudah dibagi.
58
+
59
+ class TransformedDataset(Dataset):
60
+ """Wrapper Dataset untuk menerapkan transformasi ke Subset."""
61
+ def __init__(self, subset, transform=None):
62
+ self.subset = subset
63
+ self.transform = transform
64
+
65
+ def __getitem__(self, index):
66
+ # Ambil data asli (gambar, label) dari subset
67
+ try:
68
+ x, y = self.subset[index]
69
+
70
+ # Terapkan transformasi jika ada
71
+ if self.transform:
72
+ x = self.transform(x)
73
+
74
+ return x, y
75
+ except Exception as e:
76
+ # Jika ada error (file rusak), coba index berikutnya
77
+ print(f"[Warning] Error pada index {index}: {e}")
78
+ # Coba index berikutnya (dengan wraparound)
79
+ next_index = (index + 1) % len(self.subset)
80
+ return self.__getitem__(next_index)
81
+
82
+ def __len__(self):
83
+ return len(self.subset)
84
+
85
+ # --- 3. Fungsi Utama Pembuat DataLoader ---
86
+
87
+ def create_dataloaders():
88
+ """
89
+ Fungsi utama untuk membuat dan mengembalikan data loader
90
+ untuk training dan validasi.
91
+ """
92
+ # --- VALIDASI: Pastikan config.DATA_PATH ada, coba beberapa alternatif jika tidak ---
93
+ data_path = Path(config.DATA_PATH)
94
+ if not data_path.exists():
95
+ project_root = Path(__file__).resolve().parents[1]
96
+ alt_names = ["Batik_Indonesia_JPG", "Batik-Indonesia", "Batik_Indonesia", "data", "dataset"]
97
+ found = None
98
+ for name in alt_names:
99
+ candidate = project_root / name
100
+ if candidate.exists() and candidate.is_dir():
101
+ found = candidate
102
+ break
103
+ if found:
104
+ print(f"[Data] config.DATA_PATH '{config.DATA_PATH}' tidak ditemukan. Menggunakan alternatif: {found}")
105
+ # update atribut di module config agar konsisten
106
+ try:
107
+ config.DATA_PATH = str(found)
108
+ except Exception:
109
+ pass
110
+ data_path = found
111
+ else:
112
+ raise FileNotFoundError(
113
+ f"config.DATA_PATH='{config.DATA_PATH}' tidak ditemukan. "
114
+ f"Pastikan folder dataset ada atau set config.DATA_PATH ke path yang benar."
115
+ )
116
+
117
+ # --- LANGKAH A: Muat Dataset Induk ---
118
+ print(f"[Data] Memuat dataset induk dari: {data_path}")
119
+ full_dataset = datasets.ImageFolder(str(data_path))
120
+
121
+ # Simpan nama kelas
122
+ class_names = full_dataset.classes
123
+ num_classes = len(class_names)
124
+ print(f"[Data] Ditemukan {num_classes} kelas: {class_names}")
125
+
126
+ # --- LANGKAH B: Bagi Dataset 80:20 (Secara Hati-hati) ---
127
+ print(f"[Data] Membagi dataset 80:20 (seed: {config.RANDOM_SEED})...")
128
+ total_size = len(full_dataset)
129
+ val_size = int(total_size * config.TEST_SPLIT_SIZE)
130
+ train_size = total_size - val_size
131
+
132
+ # Bagi dataset menggunakan random_split dengan SEED yang tetap
133
+ # Ini memastikan pembagian data SELALU SAMA setiap kali skrip dijalankan
134
+ train_dataset_raw, val_dataset_raw = random_split(
135
+ full_dataset,
136
+ [train_size, val_size],
137
+ generator=torch.Generator().manual_seed(config.RANDOM_SEED)
138
+ )
139
+
140
+ print(f"[Data] Ukuran Train: {len(train_dataset_raw)} | Ukuran Validasi: {len(val_dataset_raw)}")
141
+
142
+ # --- LANGKAH C: Terapkan Transformasi yang Berbeda ---
143
+ train_dataset = TransformedDataset(train_dataset_raw, transform=train_transform)
144
+ val_dataset = TransformedDataset(val_dataset_raw, transform=val_transform)
145
+
146
+ # --- LANGKAH D: Mengatasi Ketidakseimbangan Kelas (Wajib!) ---
147
+ print("[Data] Menghitung bobot untuk mengatasi ketidakseimbangan kelas...")
148
+
149
+ # 1. Ambil semua label (target) HANYA dari set training
150
+ train_targets = [full_dataset.targets[i] for i in train_dataset_raw.indices]
151
+
152
+ # 2. Hitung jumlah gambar per kelas
153
+ # Kita gunakan bincount untuk efisiensi
154
+ class_counts = np.bincount(train_targets)
155
+
156
+ # 3. Hitung bobot kebalikan (inverse weight) untuk setiap kelas
157
+ # Kelas langka -> bobot tinggi
158
+ # Kelas umum -> bobot rendah
159
+ class_weights = 1.0 / torch.tensor(class_counts, dtype=torch.float)
160
+
161
+ # 4. Buat daftar bobot untuk SETIAP sampel di set training
162
+ # Setiap sampel akan memiliki bobot sesuai kelasnya
163
+ sample_weights = class_weights[train_targets]
164
+
165
+ # 5. Buat Sampler
166
+ # WeightedRandomSampler akan mengambil data berdasarkan bobot ini
167
+ train_sampler = WeightedRandomSampler(
168
+ weights=sample_weights,
169
+ num_samples=len(sample_weights),
170
+ replacement=True # Izinkan pengambilan sampel berulang (oversampling)
171
+ )
172
+
173
+ print("[Data] WeightedRandomSampler berhasil dibuat.")
174
+
175
+ # --- LANGKAH E: Buat DataLoaders ---
176
+
177
+ # DataLoader untuk Training
178
+ # PENTING: Jika menggunakan 'sampler', 'shuffle' HARUS False.
179
+ train_loader = DataLoader(
180
+ train_dataset,
181
+ batch_size=config.BATCH_SIZE,
182
+ sampler=train_sampler,
183
+ num_workers=2, # Disable multiprocessing untuk Windows
184
+ pin_memory=False, # Disable untuk CPU training
185
+ shuffle=False
186
+ )
187
+
188
+ # DataLoader untuk Validasi
189
+ # Tidak perlu sampler, tidak perlu shuffle (evaluasi harus konsisten)
190
+ val_loader = DataLoader(
191
+ val_dataset,
192
+ batch_size=config.BATCH_SIZE,
193
+ num_workers=2, # Disable multiprocessing untuk Windows
194
+ pin_memory=False, # Disable untuk CPU training
195
+ shuffle=False
196
+ )
197
+
198
+ print("[Data] Data loader untuk Train dan Validasi siap.")
199
+
200
+ return train_loader, val_loader, class_names
201
+
202
+
203
+ # --- 5. Blok Pengujian (Opsional tapi Sangat Direkomendasikan) ---
204
+ # Kode ini HANYA akan berjalan jika Anda menjalankan file ini secara langsung
205
+ # (misal: `python src/data_loader.py`)
206
+ # Ini sangat berguna untuk memverifikasi bahwa loader Anda berfungsi.
207
+
208
+ if __name__ == "__main__":
209
+ print("Menjalankan pengujian data_loader.py...")
210
+
211
+ # Coba buat data loader
212
+ train_loader, val_loader, class_names = create_dataloaders()
213
+
214
+ print(f"\nTotal kelas: {len(class_names)}")
215
+
216
+ # Ambil satu batch dari train_loader
217
+ print("\nMengambil 1 batch dari train_loader (untuk tes)...")
218
+ with warnings.catch_warnings():
219
+ warnings.simplefilter("ignore") # Abaikan peringatan UserWarning dari matplotlib
220
+
221
+ try:
222
+ images, labels = next(iter(train_loader))
223
+
224
+ print(f" > Ukuran batch gambar: {images.shape}") # [Batch, Channel, H, W]
225
+ print(f" > Ukuran batch label: {labels.shape}")
226
+ print(f" > Contoh 5 label di batch ini: {labels[:5]}")
227
+
228
+ # Coba visualisasikan 1 gambar (untuk cek normalisasi)
229
+ img_to_show = images[0].permute(1, 2, 0).numpy() # Ubah (C, H, W) -> (H, W, C)
230
+ # Denormalisasi (penting untuk visualisasi)
231
+ img_to_show = STD * img_to_show + MEAN
232
+ img_to_show = np.clip(img_to_show, 0, 1) # Pastikan nilai antara 0 dan 1
233
+
234
+ plt.imshow(img_to_show)
235
+ plt.title(f"Contoh Gambar (Label: {class_names[labels[0]]})")
236
+ plt.axis('off')
237
+ plt.show()
238
+
239
+ print("\n[Sukses] data_loader.py berfungsi dengan baik!")
240
+
241
+ except Exception as e:
242
+ print(f"\n[Error] Gagal menguji data loader: {e}")
download_script.py ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from datasets import load_dataset
3
+ # PASTIKAN INI ADA: Kita tambahkan UnidentifiedImageError agar bisa ditangkap
4
+ from PIL import Image, UnidentifiedImageError
5
+
6
+ # Nama dataset dari Hugging Face Hub
7
+ dataset_name = "muhammadsalmanalfaridzi/Batik-Indonesia"
8
+ # Folder utama untuk menyimpan gambar JPG
9
+ output_dir = "Batik_Indonesia_JPG"
10
+
11
+ # Buat folder utama jika belum ada
12
+ if not os.path.exists(output_dir):
13
+ os.makedirs(output_dir)
14
+
15
+ # Muat dataset (akan menggunakan cache jika sudah diunduh)
16
+ print("Memuat dataset...")
17
+ # Kita tambahkan parameter 'all' untuk split agar memuat semua data
18
+ dataset = load_dataset(dataset_name, split='train')
19
+ print("Dataset dimuat.")
20
+
21
+ # Ambil informasi nama kelas/label dari dataset
22
+ labels = dataset.features['label'].names
23
+
24
+ # Proses dan simpan setiap gambar dari split 'train'
25
+ print("Memulai proses ekstraksi gambar...")
26
+ skipped_files = 0
27
+
28
+ # Ganti loop untuk menggunakan 'dataset' langsung
29
+ for item in dataset:
30
+ # ---- INI BAGIAN PENTING (TRY...EXCEPT) ----
31
+ # Kita "coba" lakukan semua proses ini
32
+ try:
33
+ # Ambil gambar dan labelnya
34
+ gambar: Image.Image = item['image']
35
+ label_index = item['label']
36
+
37
+ # Dapatkan nama label (contoh: "Batik Parang")
38
+ label_name = labels[label_index]
39
+
40
+ # Buat folder untuk kelas ini jika belum ada
41
+ class_dir = os.path.join(output_dir, label_name)
42
+ if not os.path.exists(class_dir):
43
+ os.makedirs(class_dir)
44
+
45
+ # Ambil nama file asli (jika tersedia) atau buat nama file unik
46
+ num_existing_files = len(os.listdir(class_dir))
47
+ file_name = f"{label_name.replace(' ', '_')}_{num_existing_files + 1}.jpg"
48
+
49
+ # Gabungkan path untuk menyimpan
50
+ save_path = os.path.join(class_dir, file_name)
51
+
52
+ # Simpan gambar
53
+ if gambar.mode != 'RGB':
54
+ gambar = gambar.convert('RGB')
55
+ gambar.save(save_path)
56
+
57
+ # Jika terjadi error "UnidentifiedImageError" (file rusak),
58
+ # jalankan kode di bawah ini alih-alih crash.
59
+ except UnidentifiedImageError:
60
+ skipped_files += 1
61
+ # Kita bisa cetak file yang rusak jika mau
62
+ # print(f"WARNING: 1 file gambar terdeteksi rusak atau tidak valid. Melewati...")
63
+
64
+ # Tambahkan exception umum untuk error lain
65
+ except Exception as e:
66
+ skipped_files += 1
67
+ print(f"WARNING: Terjadi error lain ({e}). Melewati file...")
68
+ # -----------------------------------------------
69
+
70
+ print(f"Ekstraksi selesai!")
71
+ print(f"Total file yang dilewati (rusak/error): {skipped_files}")
engine.py ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ from tqdm.auto import tqdm # Untuk progress bar yang bagus
4
+
5
+ def train_step(model: torch.nn.Module,
6
+ dataloader: torch.utils.data.DataLoader,
7
+ loss_fn: torch.nn.Module,
8
+ optimizer: torch.optim.Optimizer,
9
+ device: torch.device):
10
+ """
11
+ Melakukan satu epoch training.
12
+
13
+ Mengatur model ke mode training, melakukan forward pass,
14
+ menghitung loss, melakukan backpropagation, dan update weights.
15
+ """
16
+ # 1. Set model ke mode training
17
+ # Ini penting untuk mengaktifkan lapisan seperti Dropout dan BatchNorm
18
+ model.train()
19
+
20
+ # 2. Setup variabel pelacak loss dan akurasi
21
+ train_loss, train_acc = 0, 0
22
+
23
+ # 3. Loop melalui data loader
24
+ # Gunakan tqdm untuk progress bar
25
+ for X, y in tqdm(dataloader, desc="Training"):
26
+ # Pindahkan data ke device (GPU jika ada)
27
+ X, y = X.to(device), y.to(device)
28
+
29
+ # 4. Forward pass
30
+ y_pred_logits = model(X)
31
+
32
+ # 5. Hitung loss
33
+ loss = loss_fn(y_pred_logits, y)
34
+ train_loss += loss.item()
35
+
36
+ # 6. Nol-kan gradien optimizer
37
+ optimizer.zero_grad()
38
+
39
+ # 7. Backpropagation
40
+ loss.backward()
41
+
42
+ # 8. Update weights
43
+ optimizer.step()
44
+
45
+ # 9. Hitung akurasi
46
+ # Ambil kelas dengan probabilitas tertinggi
47
+ y_pred_class = torch.argmax(y_pred_logits, dim=1)
48
+ train_acc += (y_pred_class == y).sum().item() / len(y_pred_logits)
49
+
50
+ # 10. Hitung rata-rata loss dan akurasi per epoch
51
+ train_loss = train_loss / len(dataloader)
52
+ train_acc = train_acc / len(dataloader)
53
+
54
+ return train_loss, train_acc
55
+
56
+ def val_step(model: torch.nn.Module,
57
+ dataloader: torch.utils.data.DataLoader,
58
+ loss_fn: torch.nn.Module,
59
+ device: torch.device):
60
+ """
61
+ Melakukan satu epoch validasi.
62
+
63
+ Mengatur model ke mode evaluasi, melakukan forward pass,
64
+ dan menghitung loss/akurasi. Tidak ada backpropagation.
65
+ """
66
+ # 1. Set model ke mode evaluasi
67
+ # Ini penting untuk menonaktifkan Dropout dan BatchNorm
68
+ model.eval()
69
+
70
+ # 2. Setup variabel pelacak loss dan akurasi
71
+ val_loss, val_acc = 0, 0
72
+
73
+ # 3. Matikan perhitungan gradien
74
+ # Ini menghemat memori dan komputasi
75
+ with torch.no_grad():
76
+ # 4. Loop melalui data loader
77
+ for X, y in tqdm(dataloader, desc="Validasi"):
78
+ # Pindahkan data ke device
79
+ X, y = X.to(device), y.to(device)
80
+
81
+ # 5. Forward pass
82
+ y_pred_logits = model(X)
83
+
84
+ # 6. Hitung loss
85
+ loss = loss_fn(y_pred_logits, y)
86
+ val_loss += loss.item()
87
+
88
+ # 7. Hitung akurasi
89
+ y_pred_class = torch.argmax(y_pred_logits, dim=1)
90
+ val_acc += (y_pred_class == y).sum().item() / len(y_pred_logits)
91
+
92
+ # 8. Hitung rata-rata loss dan akurasi per epoch
93
+ val_loss = val_loss / len(dataloader)
94
+ val_acc = val_acc / len(dataloader)
95
+
96
+ return val_loss, val_acc
enhanced_config.py ADDED
@@ -0,0 +1,453 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from pathlib import Path
3
+
4
+ # Definisikan ROOT path proyek (folder batik_vision_project)
5
+ ROOT_PATH = Path(__file__).resolve().parent.parent
6
+
7
+ # Path ke data
8
+ DATA_PATH = ROOT_PATH / "Batik-Indonesia" # <-- GANTI BARIS INI
9
+
10
+ # Enhanced Hyperparameters untuk Anti-Overfitting
11
+ DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
12
+ BATCH_SIZE = 32 # Optimal batch size untuk stabilitas
13
+ IMAGE_SIZE = 224 # Ukuran input untuk ViT/Swin
14
+ LEARNING_RATE = 3e-5 # Learning rate lebih kecil untuk stabilitas
15
+ EPOCHS = 60 # Lebih banyak epoch dengan early stopping
16
+
17
+ # Pengaturan split
18
+ TEST_SPLIT_SIZE = 0.2 # 20% untuk validasi
19
+ RANDOM_SEED = 42 # Agar hasil split selalu sama
20
+
21
+ # Enhanced Training Parameters
22
+ DROPOUT_RATE = 0.7 # Dropout rate yang lebih agresif
23
+ WEIGHT_DECAY = 2e-3 # Weight decay yang lebih besar
24
+ EARLY_STOPPING_PATIENCE = 7 # Patience untuk early stopping
25
+
26
+ # Advanced Augmentation Parameters
27
+ MIXUP_ALPHA = 0.2 # Mixup parameter
28
+ CUTMIX_ALPHA = 1.0 # CutMix parameter
29
+ LABEL_SMOOTHING = 0.1 # Label smoothing parameter
30
+ FOCAL_LOSS_ALPHA = 1.0 # Focal loss alpha
31
+ FOCAL_LOSS_GAMMA = 2.0 # Focal loss gamma
32
+
33
+ # Learning Rate Scheduler
34
+ SCHEDULER_METHOD = 'cosine_warmup' # 'cosine_warmup', 'onecycle', 'plateau'
35
+ SCHEDULER_T0 = 10 # For CosineAnnealingWarmRestarts
36
+ SCHEDULER_T_MULT = 2 # For CosineAnnealingWarmRestarts
37
+ SCHEDULER_ETA_MIN = 1e-7 # Minimum learning rate
38
+
39
+ # Test Time Augmentation
40
+ TTA_NUM_AUGMENTATIONS = 5 # Number of TTA augmentations
41
+
42
+ # Daftar model yang akan diuji
43
+ # Mulai dengan model terkecil dulu untuk testing awal
44
+ MODEL_LIST = ["convnext_tiny"] # Model terkecil untuk testing awal
45
+
46
+ # Enhanced Model Configuration
47
+ ENHANCED_TRAINING = True # Flag untuk enhanced training
48
+ USE_MIXUP = True # Enable Mixup augmentation
49
+ USE_CUTMIX = True # Enable CutMix augmentation
50
+ USE_LABEL_SMOOTHING = True # Enable label smoothing
51
+ USE_FOCAL_LOSS = True # Enable focal loss
52
+ USE_TTA = True # Enable test time augmentation
53
+
54
+ # Gradient Clipping
55
+ GRADIENT_CLIPPING = True
56
+ MAX_GRAD_NORM = 1.0
57
+
58
+ # Logging Configuration
59
+ LOG_INTERVAL = 10 # Log every N batches
60
+ SAVE_BEST_MODEL = True # Save best model during training
61
+ SAVE_CONFUSION_MATRIX = True # Save confusion matrix
62
+ SAVE_CLASSIFICATION_REPORT = True # Save classification report
63
+
64
+ # Advanced Regularization
65
+ USE_CUTOUT = True # Enable Cutout augmentation
66
+ CUTOUT_LENGTH = 16 # Cutout length
67
+ USE_MIXUP_CUTMIX_PROBABILITY = True # Randomly choose between Mixup and CutMix
68
+
69
+ # Class Balancing
70
+ CLASS_BALANCING_METHOD = 'balanced' # 'balanced', 'inverse', 'sqrt'
71
+ USE_WEIGHTED_SAMPLER = True # Use weighted random sampler
72
+
73
+ # Model Architecture Enhancements
74
+ USE_ADAPTIVE_AVG_POOL = True # Use adaptive average pooling
75
+ USE_BATCH_NORM = True # Use batch normalization
76
+ USE_GROUP_NORM = False # Use group normalization instead of batch norm
77
+
78
+ # Training Monitoring
79
+ MONITOR_METRICS = ['loss', 'accuracy', 'f1_score', 'precision', 'recall']
80
+ EARLY_STOPPING_METRIC = 'val_accuracy' # Metric to monitor for early stopping
81
+ EARLY_STOPPING_MODE = 'max' # 'max' for accuracy, 'min' for loss
82
+
83
+ # Data Loading
84
+ NUM_WORKERS = 4 # Number of data loading workers
85
+ PIN_MEMORY = True # Pin memory for faster GPU transfer
86
+ PERSISTENT_WORKERS = True # Keep workers alive between epochs
87
+
88
+ # Mixed Precision Training
89
+ USE_MIXED_PRECISION = False # Enable mixed precision training (requires apex)
90
+ SCALER_GROWTH_INTERVAL = 2000 # Growth interval for scaler
91
+
92
+ # Model Checkpointing
93
+ CHECKPOINT_INTERVAL = 5 # Save checkpoint every N epochs
94
+ KEEP_BEST_N_MODELS = 3 # Keep only the best N models
95
+
96
+ # Validation Configuration
97
+ VALIDATION_FREQUENCY = 1 # Validate every N epochs
98
+ VALIDATION_BATCH_SIZE = None # Use same batch size as training if None
99
+
100
+ # Advanced Loss Functions
101
+ LOSS_FUNCTION_WEIGHTS = {
102
+ 'label_smoothing': 0.7,
103
+ 'focal_loss': 0.3
104
+ }
105
+
106
+ # Augmentation Probabilities
107
+ AUGMENTATION_PROBABILITIES = {
108
+ 'mixup': 0.3,
109
+ 'cutmix': 0.3,
110
+ 'none': 0.4
111
+ }
112
+
113
+ # Learning Rate Warmup
114
+ USE_WARMUP = True
115
+ WARMUP_EPOCHS = 5
116
+ WARMUP_FACTOR = 0.1
117
+
118
+ # Model Ensemble
119
+ USE_MODEL_ENSEMBLE = False # Enable model ensemble
120
+ ENSEMBLE_MODELS = [] # List of models to ensemble
121
+
122
+ # Advanced Optimizer Settings
123
+ OPTIMIZER_BETAS = (0.9, 0.999) # Adam betas
124
+ OPTIMIZER_EPS = 1e-8 # Adam epsilon
125
+ OPTIMIZER_MOMENTUM = 0.9 # SGD momentum
126
+
127
+ # Data Augmentation Strengths
128
+ AUGMENTATION_STRENGTHS = {
129
+ 'rotation': 15,
130
+ 'brightness': 0.2,
131
+ 'contrast': 0.2,
132
+ 'saturation': 0.2,
133
+ 'hue': 0.05,
134
+ 'perspective': 0.2,
135
+ 'erasing': 0.2
136
+ }
137
+
138
+ # Model Performance Tracking
139
+ TRACK_PER_CLASS_METRICS = True # Track per-class metrics
140
+ SAVE_PREDICTIONS = True # Save model predictions
141
+ SAVE_ATTENTION_MAPS = False # Save attention maps (for attention-based models)
142
+
143
+ # Advanced Regularization Techniques
144
+ USE_DROPCONNECT = False # Use DropConnect
145
+ USE_STOCHASTIC_DEPTH = False # Use stochastic depth
146
+ STOCHASTIC_DEPTH_RATE = 0.1 # Stochastic depth rate
147
+
148
+ # Model Compression
149
+ USE_KNOWLEDGE_DISTILLATION = False # Use knowledge distillation
150
+ TEACHER_MODEL_PATH = None # Path to teacher model
151
+ DISTILLATION_TEMPERATURE = 3.0 # Distillation temperature
152
+ DISTILLATION_ALPHA = 0.7 # Distillation alpha
153
+
154
+ # Advanced Data Loading
155
+ USE_SMART_SAMPLING = True # Use smart sampling for imbalanced data
156
+ SMART_SAMPLING_STRATEGY = 'focal' # 'focal', 'hard', 'easy'
157
+ USE_DYNAMIC_BATCH_SIZE = False # Use dynamic batch size
158
+ MIN_BATCH_SIZE = 16 # Minimum batch size
159
+ MAX_BATCH_SIZE = 64 # Maximum batch size
160
+
161
+ # Model Architecture Search
162
+ USE_ARCHITECTURE_SEARCH = False # Use neural architecture search
163
+ ARCHITECTURE_SEARCH_SPACE = [] # Architecture search space
164
+
165
+ # Advanced Training Techniques
166
+ USE_CURRICULUM_LEARNING = False # Use curriculum learning
167
+ CURRICULUM_STRATEGY = 'easy_to_hard' # Curriculum strategy
168
+ USE_PROGRESSIVE_TRAINING = False # Use progressive training
169
+ PROGRESSIVE_STAGES = [] # Progressive training stages
170
+
171
+ # Model Interpretability
172
+ USE_GRAD_CAM = False # Use Grad-CAM for interpretability
173
+ USE_LIME = False # Use LIME for interpretability
174
+ USE_SHAP = False # Use SHAP for interpretability
175
+
176
+ # Advanced Evaluation
177
+ USE_K_FOLD_CROSS_VALIDATION = False # Use k-fold cross validation
178
+ K_FOLD_SPLITS = 5 # Number of k-fold splits
179
+ USE_STRATIFIED_K_FOLD = True # Use stratified k-fold
180
+
181
+ # Model Deployment
182
+ MODEL_QUANTIZATION = False # Use model quantization
183
+ QUANTIZATION_BITS = 8 # Quantization bits
184
+ USE_TORCHSCRIPT = False # Convert model to TorchScript
185
+
186
+ # Advanced Logging
187
+ USE_WANDB = False # Use Weights & Biases logging
188
+ WANDB_PROJECT = 'batik-vision' # WANDB project name
189
+ USE_TENSORBOARD = True # Use TensorBoard logging
190
+ LOG_GRADIENTS = False # Log gradients
191
+ LOG_WEIGHTS = False # Log weights
192
+
193
+ # Model Comparison
194
+ COMPARE_WITH_BASELINE = True # Compare with baseline model
195
+ BASELINE_MODEL_PATH = None # Path to baseline model
196
+ USE_STATISTICAL_TESTS = True # Use statistical tests for comparison
197
+
198
+ # Advanced Data Processing
199
+ USE_AUTO_AUGMENT = True # Use AutoAugment
200
+ AUTO_AUGMENT_POLICY = 'imagenet' # AutoAugment policy
201
+ USE_RANDAUGMENT = True # Use RandAugment
202
+ RANDAUGMENT_N = 2 # RandAugment N
203
+ RANDAUGMENT_M = 9 # RandAugment M
204
+
205
+ # Model Robustness
206
+ USE_ADVERSARIAL_TRAINING = False # Use adversarial training
207
+ ADVERSARIAL_EPSILON = 0.03 # Adversarial epsilon
208
+ ADVERSARIAL_ALPHA = 0.007 # Adversarial alpha
209
+ ADVERSARIAL_STEPS = 7 # Adversarial steps
210
+
211
+ # Advanced Loss Functions
212
+ USE_CENTER_LOSS = False # Use center loss
213
+ CENTER_LOSS_ALPHA = 0.5 # Center loss alpha
214
+ USE_TRIPLET_LOSS = False # Use triplet loss
215
+ TRIPLET_MARGIN = 1.0 # Triplet margin
216
+
217
+ # Model Ensemble Techniques
218
+ USE_BAGGING = False # Use bagging
219
+ BAGGING_N_MODELS = 5 # Number of models for bagging
220
+ USE_BOOSTING = False # Use boosting
221
+ BOOSTING_N_MODELS = 5 # Number of models for boosting
222
+
223
+ # Advanced Regularization
224
+ USE_SPECTRAL_NORM = False # Use spectral normalization
225
+ USE_WEIGHT_NORM = False # Use weight normalization
226
+ USE_LAYER_NORM = False # Use layer normalization
227
+
228
+ # Model Architecture Enhancements
229
+ USE_SE_BLOCKS = False # Use Squeeze-and-Excitation blocks
230
+ USE_CBAM = False # Use Convolutional Block Attention Module
231
+ USE_ECA = False # Use Efficient Channel Attention
232
+
233
+ # Advanced Training Techniques
234
+ USE_COSINE_ANNEALING = True # Use cosine annealing
235
+ COSINE_ANNEALING_T_MAX = 50 # Cosine annealing T_max
236
+ USE_CYCLIC_LR = False # Use cyclic learning rate
237
+ CYCLIC_LR_BASE = 1e-6 # Cyclic LR base
238
+ CYCLIC_LR_MAX = 1e-3 # Cyclic LR max
239
+
240
+ # Model Performance Optimization
241
+ USE_MODEL_PARALLELISM = False # Use model parallelism
242
+ USE_DATA_PARALLELISM = True # Use data parallelism
243
+ USE_GRADIENT_CHECKPOINTING = False # Use gradient checkpointing
244
+
245
+ # Advanced Data Augmentation
246
+ USE_COLOR_DISTORTION = True # Use color distortion
247
+ COLOR_DISTORTION_STRENGTH = 0.5 # Color distortion strength
248
+ USE_GAUSSIAN_BLUR = True # Use Gaussian blur
249
+ GAUSSIAN_BLUR_PROBABILITY = 0.1 # Gaussian blur probability
250
+ USE_SOLARIZATION = False # Use solarization
251
+ SOLARIZATION_THRESHOLD = 128 # Solarization threshold
252
+
253
+ # Model Interpretability
254
+ USE_ATTENTION_VISUALIZATION = False # Use attention visualization
255
+ ATTENTION_LAYERS = [] # Layers to visualize attention
256
+ USE_FEATURE_MAPS = False # Use feature maps visualization
257
+
258
+ # Advanced Evaluation Metrics
259
+ USE_COCO_METRICS = False # Use COCO metrics
260
+ USE_PASCAL_VOC_METRICS = False # Use Pascal VOC metrics
261
+ USE_CUSTOM_METRICS = True # Use custom metrics
262
+
263
+ # Model Deployment Optimization
264
+ USE_ONNX_EXPORT = False # Export to ONNX
265
+ ONNX_OPSET_VERSION = 11 # ONNX opset version
266
+ USE_TENSORRT = False # Use TensorRT optimization
267
+ TENSORRT_PRECISION = 'fp16' # TensorRT precision
268
+
269
+ # Advanced Training Monitoring
270
+ USE_EARLY_STOPPING_V2 = True # Use enhanced early stopping
271
+ EARLY_STOPPING_MIN_DELTA = 0.001 # Minimum delta for early stopping
272
+ EARLY_STOPPING_RESTORE_BEST_WEIGHTS = True # Restore best weights
273
+
274
+ # Model Architecture Optimization
275
+ USE_EFFICIENT_NET = False # Use EfficientNet
276
+ EFFICIENT_NET_VERSION = 'b0' # EfficientNet version
277
+ USE_MOBILENET = False # Use MobileNet
278
+ MOBILENET_VERSION = 'v2' # MobileNet version
279
+
280
+ # Advanced Data Processing
281
+ USE_SMART_CROP = True # Use smart cropping
282
+ SMART_CROP_RATIO = 0.875 # Smart crop ratio
283
+ USE_MULTI_SCALE_TRAINING = False # Use multi-scale training
284
+ MULTI_SCALE_RATIOS = [0.8, 1.0, 1.2] # Multi-scale ratios
285
+
286
+ # Model Performance Analysis
287
+ USE_PERFORMANCE_PROFILING = False # Use performance profiling
288
+ PROFILING_BATCHES = 10 # Number of batches to profile
289
+ USE_MEMORY_PROFILING = False # Use memory profiling
290
+
291
+ # Advanced Regularization Techniques
292
+ USE_DROPOUT_SCHEDULING = False # Use dropout scheduling
293
+ DROPOUT_SCHEDULE_START = 0.1 # Dropout schedule start
294
+ DROPOUT_SCHEDULE_END = 0.5 # Dropout schedule end
295
+
296
+ # Model Architecture Enhancements
297
+ USE_RESIDUAL_CONNECTIONS = True # Use residual connections
298
+ USE_DENSE_CONNECTIONS = False # Use dense connections
299
+ USE_INCEPTION_BLOCKS = False # Use Inception blocks
300
+
301
+ # Advanced Training Techniques
302
+ USE_META_LEARNING = False # Use meta-learning
303
+ META_LEARNING_STEPS = 5 # Meta-learning steps
304
+ USE_FEW_SHOT_LEARNING = False # Use few-shot learning
305
+ FEW_SHOT_SHOTS = 5 # Number of shots for few-shot learning
306
+
307
+ # Model Compression Techniques
308
+ USE_PRUNING = False # Use model pruning
309
+ PRUNING_RATIO = 0.1 # Pruning ratio
310
+ USE_QUANTIZATION_AWARE_TRAINING = False # Use quantization-aware training
311
+
312
+ # Advanced Data Augmentation
313
+ USE_MIXUP_V2 = True # Use enhanced Mixup
314
+ MIXUP_V2_ALPHA = 0.2 # Enhanced Mixup alpha
315
+ USE_CUTMIX_V2 = True # Use enhanced CutMix
316
+ CUTMIX_V2_ALPHA = 1.0 # Enhanced CutMix alpha
317
+
318
+ # Model Architecture Search
319
+ USE_NAS = False # Use Neural Architecture Search
320
+ NAS_SEARCH_SPACE = 'darts' # NAS search space
321
+ NAS_EPOCHS = 50 # NAS epochs
322
+
323
+ # Advanced Training Monitoring
324
+ USE_LEARNING_RATE_FINDER = False # Use learning rate finder
325
+ LR_FINDER_START = 1e-7 # LR finder start
326
+ LR_FINDER_END = 1e-1 # LR finder end
327
+ LR_FINDER_STEPS = 100 # LR finder steps
328
+
329
+ # Model Performance Optimization
330
+ USE_GRADIENT_ACCUMULATION = False # Use gradient accumulation
331
+ GRADIENT_ACCUMULATION_STEPS = 4 # Gradient accumulation steps
332
+ USE_MIXED_PRECISION_V2 = False # Use enhanced mixed precision
333
+
334
+ # Advanced Regularization
335
+ USE_WEIGHT_DECAY_SCHEDULING = False # Use weight decay scheduling
336
+ WEIGHT_DECAY_SCHEDULE_START = 1e-4 # Weight decay schedule start
337
+ WEIGHT_DECAY_SCHEDULE_END = 1e-3 # Weight decay schedule end
338
+
339
+ # Model Architecture Enhancements
340
+ USE_TRANSFORMER_BLOCKS = False # Use Transformer blocks
341
+ TRANSFORMER_NUM_HEADS = 8 # Transformer number of heads
342
+ TRANSFORMER_DIM = 512 # Transformer dimension
343
+
344
+ # Advanced Training Techniques
345
+ USE_CURRICULUM_LEARNING_V2 = False # Use enhanced curriculum learning
346
+ CURRICULUM_STRATEGY_V2 = 'difficulty' # Enhanced curriculum strategy
347
+ USE_PROGRESSIVE_TRAINING_V2 = False # Use enhanced progressive training
348
+
349
+ # Model Performance Analysis
350
+ USE_CONFUSION_MATRIX_ANALYSIS = True # Use confusion matrix analysis
351
+ USE_ROC_CURVE_ANALYSIS = True # Use ROC curve analysis
352
+ USE_PRECISION_RECALL_ANALYSIS = True # Use precision-recall analysis
353
+
354
+ # Advanced Data Processing
355
+ USE_SMART_AUGMENTATION = True # Use smart augmentation
356
+ SMART_AUGMENTATION_STRATEGY = 'adaptive' # Smart augmentation strategy
357
+ USE_DYNAMIC_AUGMENTATION = False # Use dynamic augmentation
358
+
359
+ # Model Architecture Optimization
360
+ USE_EFFICIENT_NET_V2 = False # Use EfficientNetV2
361
+ EFFICIENT_NET_V2_VERSION = 's' # EfficientNetV2 version
362
+ USE_VISION_TRANSFORMER = False # Use Vision Transformer
363
+ VISION_TRANSFORMER_PATCH_SIZE = 16 # Vision Transformer patch size
364
+
365
+ # Advanced Training Monitoring
366
+ USE_TRAINING_MONITORING_V2 = True # Use enhanced training monitoring
367
+ MONITORING_METRICS_V2 = ['loss', 'accuracy', 'f1', 'precision', 'recall'] # Enhanced monitoring metrics
368
+ USE_REAL_TIME_MONITORING = False # Use real-time monitoring
369
+
370
+ # Model Performance Optimization
371
+ USE_MODEL_OPTIMIZATION_V2 = True # Use enhanced model optimization
372
+ OPTIMIZATION_TECHNIQUES_V2 = ['pruning', 'quantization', 'distillation'] # Enhanced optimization techniques
373
+ USE_AUTOMATIC_OPTIMIZATION = False # Use automatic optimization
374
+
375
+ # Advanced Regularization Techniques
376
+ USE_REGULARIZATION_V2 = True # Use enhanced regularization
377
+ REGULARIZATION_TECHNIQUES_V2 = ['dropout', 'weight_decay', 'label_smoothing'] # Enhanced regularization techniques
378
+ USE_ADAPTIVE_REGULARIZATION = False # Use adaptive regularization
379
+
380
+ # Model Architecture Enhancements
381
+ USE_ARCHITECTURE_ENHANCEMENTS_V2 = True # Use enhanced architecture enhancements
382
+ ARCHITECTURE_ENHANCEMENTS_V2 = ['attention', 'skip_connections', 'normalization'] # Enhanced architecture enhancements
383
+ USE_DYNAMIC_ARCHITECTURE = False # Use dynamic architecture
384
+
385
+ # Advanced Training Techniques
386
+ USE_TRAINING_TECHNIQUES_V2 = True # Use enhanced training techniques
387
+ TRAINING_TECHNIQUES_V2 = ['mixup', 'cutmix', 'label_smoothing', 'focal_loss'] # Enhanced training techniques
388
+ USE_ADAPTIVE_TRAINING = False # Use adaptive training
389
+
390
+ # Model Performance Analysis
391
+ USE_PERFORMANCE_ANALYSIS_V2 = True # Use enhanced performance analysis
392
+ PERFORMANCE_ANALYSIS_V2 = ['confusion_matrix', 'roc_curve', 'precision_recall'] # Enhanced performance analysis
393
+ USE_COMPARATIVE_ANALYSIS = True # Use comparative analysis
394
+
395
+ # Advanced Data Processing
396
+ USE_DATA_PROCESSING_V2 = True # Use enhanced data processing
397
+ DATA_PROCESSING_V2 = ['smart_augmentation', 'dynamic_sampling', 'adaptive_preprocessing'] # Enhanced data processing
398
+ USE_INTELLIGENT_PREPROCESSING = False # Use intelligent preprocessing
399
+
400
+ # Model Architecture Optimization
401
+ USE_ARCHITECTURE_OPTIMIZATION_V2 = True # Use enhanced architecture optimization
402
+ ARCHITECTURE_OPTIMIZATION_V2 = ['efficient_net', 'vision_transformer', 'convnext'] # Enhanced architecture optimization
403
+ USE_AUTOMATIC_ARCHITECTURE_SEARCH = False # Use automatic architecture search
404
+
405
+ # Advanced Training Monitoring
406
+ USE_MONITORING_V2 = True # Use enhanced monitoring
407
+ MONITORING_V2 = ['real_time', 'adaptive', 'intelligent'] # Enhanced monitoring
408
+ USE_PREDICTIVE_MONITORING = False # Use predictive monitoring
409
+
410
+ # Model Performance Optimization
411
+ USE_OPTIMIZATION_V2 = True # Use enhanced optimization
412
+ OPTIMIZATION_V2 = ['automatic', 'adaptive', 'intelligent'] # Enhanced optimization
413
+ USE_SELF_OPTIMIZING_MODEL = False # Use self-optimizing model
414
+
415
+ # Advanced Regularization Techniques
416
+ USE_REGULARIZATION_V3 = True # Use latest regularization techniques
417
+ REGULARIZATION_V3 = ['advanced_dropout', 'adaptive_weight_decay', 'smart_label_smoothing'] # Latest regularization techniques
418
+ USE_NEURAL_REGULARIZATION = False # Use neural regularization
419
+
420
+ # Model Architecture Enhancements
421
+ USE_ARCHITECTURE_ENHANCEMENTS_V3 = True # Use latest architecture enhancements
422
+ ARCHITECTURE_ENHANCEMENTS_V3 = ['transformer_attention', 'dynamic_skip_connections', 'adaptive_normalization'] # Latest architecture enhancements
423
+ USE_NEURAL_ARCHITECTURE = False # Use neural architecture
424
+
425
+ # Advanced Training Techniques
426
+ USE_TRAINING_TECHNIQUES_V3 = True # Use latest training techniques
427
+ TRAINING_TECHNIQUES_V3 = ['advanced_mixup', 'smart_cutmix', 'adaptive_label_smoothing', 'neural_focal_loss'] # Latest training techniques
428
+ USE_NEURAL_TRAINING = False # Use neural training
429
+
430
+ # Model Performance Analysis
431
+ USE_PERFORMANCE_ANALYSIS_V3 = True # Use latest performance analysis
432
+ PERFORMANCE_ANALYSIS_V3 = ['advanced_confusion_matrix', 'neural_roc_curve', 'smart_precision_recall'] # Latest performance analysis
433
+ USE_NEURAL_ANALYSIS = False # Use neural analysis
434
+
435
+ # Advanced Data Processing
436
+ USE_DATA_PROCESSING_V3 = True # Use latest data processing
437
+ DATA_PROCESSING_V3 = ['neural_augmentation', 'smart_sampling', 'adaptive_preprocessing'] # Latest data processing
438
+ USE_NEURAL_PREPROCESSING = False # Use neural preprocessing
439
+
440
+ # Model Architecture Optimization
441
+ USE_ARCHITECTURE_OPTIMIZATION_V3 = True # Use latest architecture optimization
442
+ ARCHITECTURE_OPTIMIZATION_V3 = ['neural_efficient_net', 'advanced_vision_transformer', 'smart_convnext'] # Latest architecture optimization
443
+ USE_NEURAL_ARCHITECTURE_SEARCH = False # Use neural architecture search
444
+
445
+ # Advanced Training Monitoring
446
+ USE_MONITORING_V3 = True # Use latest monitoring
447
+ MONITORING_V3 = ['neural_monitoring', 'adaptive_monitoring', 'intelligent_monitoring'] # Latest monitoring
448
+ USE_NEURAL_MONITORING = False # Use neural monitoring
449
+
450
+ # Model Performance Optimization
451
+ USE_OPTIMIZATION_V3 = True # Use latest optimization
452
+ OPTIMIZATION_V3 = ['neural_optimization', 'adaptive_optimization', 'intelligent_optimization'] # Latest optimization
453
+ USE_NEURAL_OPTIMIZATION = False # Use neural optimization
mixup.py ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ import numpy as np
4
+
5
+ def mixup_data(x, y, alpha=1.0, device='cuda'):
6
+ """
7
+ Mixup data augmentation.
8
+
9
+ Args:
10
+ x: Input batch
11
+ y: Target batch
12
+ alpha: Mixup parameter (higher = more mixing)
13
+ device: Device to run on
14
+
15
+ Returns:
16
+ mixed_x: Mixed input batch
17
+ y_a, y_b: Original targets for loss calculation
18
+ lam: Mixing ratio
19
+ """
20
+ if alpha > 0:
21
+ lam = np.random.beta(alpha, alpha)
22
+ else:
23
+ lam = 1
24
+
25
+ batch_size = x.size(0)
26
+ if device == 'cuda':
27
+ index = torch.randperm(batch_size).cuda()
28
+ else:
29
+ index = torch.randperm(batch_size)
30
+
31
+ mixed_x = lam * x + (1 - lam) * x[index, :]
32
+ y_a, y_b = y, y[index]
33
+ return mixed_x, y_a, y_b, lam
34
+
35
+ def mixup_criterion(criterion, pred, y_a, y_b, lam):
36
+ """
37
+ Mixup loss calculation.
38
+
39
+ Args:
40
+ criterion: Loss function
41
+ pred: Model predictions
42
+ y_a, y_b: Original targets
43
+ lam: Mixing ratio
44
+
45
+ Returns:
46
+ Mixed loss
47
+ """
48
+ return lam * criterion(pred, y_a) + (1 - lam) * criterion(pred, y_b)
49
+
50
+ class MixupTrainer:
51
+ """
52
+ Mixup training wrapper.
53
+ """
54
+ def __init__(self, model, optimizer, criterion, device, alpha=0.2):
55
+ self.model = model
56
+ self.optimizer = optimizer
57
+ self.criterion = criterion
58
+ self.device = device
59
+ self.alpha = alpha
60
+
61
+ def train_step(self, dataloader):
62
+ """
63
+ Single training step with mixup.
64
+ """
65
+ self.model.train()
66
+ total_loss = 0
67
+ correct = 0
68
+ total = 0
69
+
70
+ for batch_idx, (data, target) in enumerate(dataloader):
71
+ data, target = data.to(self.device), target.to(self.device)
72
+
73
+ # Apply mixup
74
+ data, target_a, target_b, lam = mixup_data(data, target, self.alpha, self.device)
75
+
76
+ self.optimizer.zero_grad()
77
+ output = self.model(data)
78
+ loss = mixup_criterion(self.criterion, output, target_a, target_b, lam)
79
+ loss.backward()
80
+ self.optimizer.step()
81
+
82
+ total_loss += loss.item()
83
+ # For accuracy calculation, use original targets
84
+ _, predicted = torch.max(output.data, 1)
85
+ total += target.size(0)
86
+ correct += (lam * predicted.eq(target_a.data).cpu().sum().float() +
87
+ (1 - lam) * predicted.eq(target_b.data).cpu().sum().float())
88
+
89
+ avg_loss = total_loss / len(dataloader)
90
+ accuracy = 100. * correct / total
91
+
92
+ return avg_loss, accuracy.item()
model.py ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ from pathlib import Path
3
+ # tambahkan parent project ke sys.path sehingga 'src' dapat diimport saat menjalankan skrip langsung
4
+ sys.path.append(str(Path(__file__).resolve().parents[1]))
5
+
6
+ import timm
7
+ import torch
8
+ from src import config # Kita import config untuk daftar model dan device
9
+
10
+ def create_model(model_name: str, num_classes: int, pretrained: bool = True, dropout_rate: float = 0.1):
11
+ """
12
+ Membuat model Computer Vision dari library timm.
13
+
14
+ Args:
15
+ model_name (str): Nama model yang akan dibuat (misal: 'vit_base_patch16_224').
16
+ num_classes (int): Jumlah kelas output (misal: 38 untuk batik).
17
+ pretrained (bool): Apakah akan menggunakan bobot pre-trained ImageNet.
18
+ dropout_rate (float): Dropout rate untuk regularization.
19
+
20
+ Returns:
21
+ torch.nn.Module: Model yang sudah dibuat.
22
+ """
23
+ print(f"[Model] Membuat model: {model_name}...")
24
+
25
+ try:
26
+ # timm.create_model adalah fungsi ajaib:
27
+ # 1. 'pretrained=True' akan otomatis men-download bobot ImageNet.
28
+ # 2. 'num_classes=num_classes' akan otomatis MENGGANTI
29
+ # layer klasifikasi terakhir (misal: 1000 kelas ImageNet)
30
+ # dengan layer baru yang sesuai jumlah kelas kita (38 kelas).
31
+ model = timm.create_model(
32
+ model_name,
33
+ pretrained=pretrained,
34
+ num_classes=num_classes,
35
+ drop_rate=dropout_rate # Tambah dropout untuk regularization
36
+ )
37
+ return model
38
+
39
+ except Exception as e:
40
+ print(f"[Error] Gagal membuat model {model_name}: {e}")
41
+ return None
42
+
43
+ # --- Blok Pengujian (Sangat Direkomendasikan) ---
44
+ # Kode ini HANYA akan berjalan jika Anda menjalankan file ini secara langsung
45
+ # (misal: `python src/models.py`)
46
+
47
+ if __name__ == "__main__":
48
+ print("Menjalankan pengujian models.py...")
49
+
50
+ # Kita butuh jumlah kelas untuk pengujian
51
+ # Cara cepat: hitung folder di DATA_PATH dari config
52
+ import os
53
+ try:
54
+ NUM_CLASSES = len(os.listdir(config.DATA_PATH))
55
+ print(f" > Ditemukan {NUM_CLASSES} kelas dari {config.DATA_PATH}")
56
+ except FileNotFoundError:
57
+ print(f" > Error: Folder data di {config.DATA_PATH} tidak ditemukan.")
58
+ print(" > Menggunakan 38 sebagai jumlah kelas default untuk tes.")
59
+ NUM_CLASSES = 38 # Default jika data path salah
60
+
61
+ # Buat data input palsu (dummy input) untuk tes
62
+ # Ukuran: [Batch, Channel, Height, Width]
63
+ dummy_input = torch.randn(
64
+ 2, 3, config.IMAGE_SIZE, config.IMAGE_SIZE
65
+ ).to(config.DEVICE)
66
+
67
+ print(f" > Membuat data input palsu ukuran: {dummy_input.shape}")
68
+ print("-" * 30)
69
+
70
+ # Loop dan uji setiap model dalam daftar di config.py
71
+ for model_name_key in config.MODEL_LIST:
72
+
73
+ # Ini adalah nama-nama model yang sebenarnya di library 'timm'
74
+ model_arch_names = {
75
+ "vit": "vit_base_patch16_224",
76
+ "swin_transformer": "swin_base_patch4_window7_224",
77
+ "convnext_tiny": "convnext_tiny"
78
+ }
79
+
80
+ model_name = model_arch_names.get(model_name_key)
81
+
82
+ if model_name:
83
+ model = create_model(model_name=model_name, num_classes=NUM_CLASSES)
84
+
85
+ if model:
86
+ model = model.to(config.DEVICE)
87
+ model.eval() # Set ke mode evaluasi untuk tes
88
+
89
+ # Coba lewatkan data palsu ke model
90
+ with torch.no_grad():
91
+ output = model(dummy_input)
92
+
93
+ print(f" > Tes Forward Pass... SUKSES")
94
+ print(f" > Ukuran Output: {output.shape}") # Harusnya [2, 38]
95
+ print(f" > Tes {model_name_key} selesai.")
96
+ print("-" * 30)
97
+ else:
98
+ print(f"[Warning] Kunci model '{model_name_key}' di config.py tidak dikenali.")
99
+
100
+ print("\n[Sukses] models.py berfungsi dengan baik!")
outputs/batik_classification_20251019_084142/logs/events.out.tfevents.1760838102.DESKTOP-RLV6U3K.16452.0 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:31139d0f09600ee5665b8f2438a723cfd7fa7cd6f296a40632c12642d4ad72d8
3
+ size 292
outputs/batik_classification_20251019_084142/models/vit_best.pth ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c298c3781719d857855c90fb865b3c4fa0a7fe17913885ae793f7de6cdfbd676
3
+ size 1030115429
outputs/enhanced_anti_overfitting_20251023_084927/logs/events.out.tfevents.1761184167.DESKTOP-RLV6U3K.5684.0 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3dc48c14c38c3bd740a32cd0381a7e70c37038790478d42aeab6e32ff96c13e8
3
+ size 88
outputs/optimized_training_20251019_113350/logs/events.out.tfevents.1760848430.DESKTOP-RLV6U3K.9796.0 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:2783cde1bef2c72212f54633fa90a564d2b16ab9a65207419d9905f8bbe761c2
3
+ size 88
outputs/optimized_training_20251019_113629/logs/events.out.tfevents.1760848589.DESKTOP-RLV6U3K.4876.0 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:759373e9454d9f40f382bf13cac9ea5e2c571dca5f3a5b6398ebcb32b60273f8
3
+ size 3565
outputs/optimized_training_20251019_113629/models/convnext_tiny_best.pth ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:caae235c11f0a39108dd83599ad2f5edb15073435a56cf48c00fa641c4138fc0
3
+ size 334416025
outputs/optimized_training_20251023_084043/logs/events.out.tfevents.1761183643.DESKTOP-RLV6U3K.16508.0 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:283f90668cffa1b39b0feaa672b83bd708d834c6cc102d824928a1796a9168ad
3
+ size 88
train.py ADDED
@@ -0,0 +1,380 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ from pathlib import Path
3
+ # Tambahkan parent project ke sys.path sehingga 'src' dapat diimport saat menjalankan skrip langsung
4
+ sys.path.append(str(Path(__file__).resolve().parents[1]))
5
+
6
+ import torch
7
+ import torch.nn as nn
8
+ import torch.optim as optim
9
+ from torch.utils.tensorboard import SummaryWriter
10
+ import time
11
+ import os
12
+ from datetime import datetime
13
+ import json
14
+ import matplotlib.pyplot as plt
15
+ import numpy as np
16
+ from torch.optim.lr_scheduler import ReduceLROnPlateau, CosineAnnealingLR
17
+
18
+ # Import modul yang sudah dibuat
19
+ from src import config
20
+ from src.data_loader import create_dataloaders
21
+ from src.model import create_model
22
+ from src.engine import train_step, val_step
23
+
24
+ def setup_experiment_logging(experiment_name: str):
25
+ """
26
+ Setup logging dan direktori untuk eksperimen.
27
+
28
+ Args:
29
+ experiment_name (str): Nama eksperimen
30
+
31
+ Returns:
32
+ tuple: (writer, experiment_dir, model_dir)
33
+ """
34
+ # Buat direktori untuk eksperimen
35
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
36
+ experiment_dir = Path("outputs") / f"{experiment_name}_{timestamp}"
37
+ model_dir = experiment_dir / "models"
38
+ log_dir = experiment_dir / "logs"
39
+
40
+ # Buat direktori jika belum ada
41
+ experiment_dir.mkdir(parents=True, exist_ok=True)
42
+ model_dir.mkdir(parents=True, exist_ok=True)
43
+ log_dir.mkdir(parents=True, exist_ok=True)
44
+
45
+ # Setup TensorBoard writer
46
+ writer = SummaryWriter(log_dir=str(log_dir))
47
+
48
+ print(f"[Setup] Eksperimen: {experiment_name}")
49
+ print(f"[Setup] Direktori: {experiment_dir}")
50
+ print(f"[Setup] Model akan disimpan di: {model_dir}")
51
+ print(f"[Setup] Logs akan disimpan di: {log_dir}")
52
+
53
+ return writer, experiment_dir, model_dir
54
+
55
+ def save_training_results(experiment_dir: Path, model_name: str,
56
+ train_losses: list, val_losses: list,
57
+ train_accs: list, val_accs: list,
58
+ best_val_acc: float, best_epoch: int):
59
+ """
60
+ Simpan hasil training dalam format JSON dan plot.
61
+
62
+ Args:
63
+ experiment_dir (Path): Direktori eksperimen
64
+ model_name (str): Nama model
65
+ train_losses (list): List loss training per epoch
66
+ val_losses (list): List loss validasi per epoch
67
+ train_accs (list): List akurasi training per epoch
68
+ val_accs (list): List akurasi validasi per epoch
69
+ best_val_acc (float): Akurasi validasi terbaik
70
+ best_epoch (int): Epoch dengan akurasi terbaik
71
+ """
72
+ # Simpan hasil dalam format JSON
73
+ results = {
74
+ "model_name": model_name,
75
+ "best_val_accuracy": best_val_acc,
76
+ "best_epoch": best_epoch,
77
+ "total_epochs": len(train_losses),
78
+ "train_losses": train_losses,
79
+ "val_losses": val_losses,
80
+ "train_accuracies": train_accs,
81
+ "val_accuracies": val_accs,
82
+ "config": {
83
+ "batch_size": config.BATCH_SIZE,
84
+ "learning_rate": config.LEARNING_RATE,
85
+ "image_size": config.IMAGE_SIZE,
86
+ "epochs": config.EPOCHS,
87
+ "device": config.DEVICE
88
+ }
89
+ }
90
+
91
+ # Simpan JSON
92
+ results_file = experiment_dir / f"{model_name}_results.json"
93
+ with open(results_file, 'w') as f:
94
+ json.dump(results, f, indent=2)
95
+
96
+ # Buat plot training curves
97
+ fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 5))
98
+
99
+ # Plot Loss
100
+ epochs = range(1, len(train_losses) + 1)
101
+ ax1.plot(epochs, train_losses, 'b-', label='Training Loss')
102
+ ax1.plot(epochs, val_losses, 'r-', label='Validation Loss')
103
+ ax1.set_title(f'{model_name} - Training & Validation Loss')
104
+ ax1.set_xlabel('Epoch')
105
+ ax1.set_ylabel('Loss')
106
+ ax1.legend()
107
+ ax1.grid(True)
108
+
109
+ # Plot Accuracy
110
+ ax2.plot(epochs, train_accs, 'b-', label='Training Accuracy')
111
+ ax2.plot(epochs, val_accs, 'r-', label='Validation Accuracy')
112
+ ax2.set_title(f'{model_name} - Training & Validation Accuracy')
113
+ ax2.set_xlabel('Epoch')
114
+ ax2.set_ylabel('Accuracy')
115
+ ax2.legend()
116
+ ax2.grid(True)
117
+
118
+ # Simpan plot
119
+ plot_file = experiment_dir / f"{model_name}_training_curves.png"
120
+ plt.tight_layout()
121
+ plt.savefig(plot_file, dpi=300, bbox_inches='tight')
122
+ plt.close()
123
+
124
+ print(f"[Save] Hasil training disimpan di: {results_file}")
125
+ print(f"[Save] Plot training disimpan di: {plot_file}")
126
+
127
+ def train_model(model_name_key: str, model_name: str, num_classes: int,
128
+ train_loader, val_loader, writer, model_dir: Path):
129
+ """
130
+ Melatih satu model dan menyimpan hasilnya.
131
+
132
+ Args:
133
+ model_name_key (str): Kunci model dari config (misal: 'vit')
134
+ model_name (str): Nama model timm (misal: 'vit_base_patch16_224')
135
+ num_classes (int): Jumlah kelas
136
+ train_loader: DataLoader untuk training
137
+ val_loader: DataLoader untuk validasi
138
+ writer: TensorBoard writer
139
+ model_dir (Path): Direktori untuk menyimpan model
140
+
141
+ Returns:
142
+ dict: Hasil training (best accuracy, best epoch, dll)
143
+ """
144
+ print(f"\n{'='*60}")
145
+ print(f"TRAINING MODEL: {model_name_key.upper()} ({model_name})")
146
+ print(f"{'='*60}")
147
+
148
+ # 1. Buat model
149
+ model = create_model(model_name, num_classes, pretrained=True)
150
+ if model is None:
151
+ print(f"[Error] Gagal membuat model {model_name}")
152
+ return None
153
+
154
+ model = model.to(config.DEVICE)
155
+
156
+ # 2. Setup loss function dan optimizer
157
+ loss_fn = nn.CrossEntropyLoss()
158
+ # Optimizer dengan weight decay untuk regularization
159
+ optimizer = optim.Adam(model.parameters(), lr=config.LEARNING_RATE, weight_decay=1e-4)
160
+
161
+ # 3. Setup learning rate scheduler
162
+ scheduler = ReduceLROnPlateau(optimizer, mode='max', factor=0.5, patience=3, verbose=True)
163
+
164
+ # 4. Setup tracking variables
165
+ train_losses, val_losses = [], []
166
+ train_accs, val_accs = [], []
167
+ best_val_acc = 0.0
168
+ best_epoch = 0
169
+
170
+ # 5. Early stopping
171
+ patience = 7 # Stop jika tidak ada improvement selama 7 epoch
172
+ epochs_no_improve = 0
173
+
174
+ # 4. Training loop
175
+ print(f"[Training] Memulai training untuk {config.EPOCHS} epochs...")
176
+ print(f"[Training] Device: {config.DEVICE}")
177
+ print(f"[Training] Learning Rate: {config.LEARNING_RATE}")
178
+ print(f"[Training] Batch Size: {config.BATCH_SIZE}")
179
+
180
+ start_time = time.time()
181
+
182
+ for epoch in range(config.EPOCHS):
183
+ print(f"\n[Epoch {epoch+1}/{config.EPOCHS}]")
184
+
185
+ # Training step
186
+ train_loss, train_acc = train_step(
187
+ model=model,
188
+ dataloader=train_loader,
189
+ loss_fn=loss_fn,
190
+ optimizer=optimizer,
191
+ device=config.DEVICE
192
+ )
193
+
194
+ # Validation step
195
+ val_loss, val_acc = val_step(
196
+ model=model,
197
+ dataloader=val_loader,
198
+ loss_fn=loss_fn,
199
+ device=config.DEVICE
200
+ )
201
+
202
+ # Update learning rate scheduler
203
+ scheduler.step(val_acc) # Beri tahu scheduler nilai val_acc terbaru
204
+
205
+ # Simpan metrics
206
+ train_losses.append(train_loss)
207
+ val_losses.append(val_loss)
208
+ train_accs.append(train_acc)
209
+ val_accs.append(val_acc)
210
+
211
+ # Log ke TensorBoard
212
+ writer.add_scalar(f'{model_name_key}/Train/Loss', train_loss, epoch)
213
+ writer.add_scalar(f'{model_name_key}/Train/Accuracy', train_acc, epoch)
214
+ writer.add_scalar(f'{model_name_key}/Val/Loss', val_loss, epoch)
215
+ writer.add_scalar(f'{model_name_key}/Val/Accuracy', val_acc, epoch)
216
+
217
+ # Cek apakah ini model terbaik
218
+ if val_acc > best_val_acc:
219
+ best_val_acc = val_acc
220
+ best_epoch = epoch + 1
221
+
222
+ # --- TAMBAHKAN INI ---
223
+ epochs_no_improve = 0 # Reset counter kesabaran
224
+ # ---------------------
225
+
226
+ # Simpan model terbaik
227
+ model_path = model_dir / f"{model_name_key}_best.pth"
228
+ torch.save({
229
+ # ... (isi torch.save Anda) ...
230
+ }, model_path)
231
+ print(f"[Save] Model terbaik disimpan di: {model_path}")
232
+
233
+ # --- TAMBAHKAN BLOK ELSE INI ---
234
+ else:
235
+ epochs_no_improve += 1 # Tambah counter jika tidak ada kemajuan
236
+ # ---------------------------------
237
+
238
+ # Print progress
239
+ print(f" Train Loss: {train_loss:.4f} | Train Acc: {train_acc:.4f}")
240
+ print(f" Val Loss: {val_loss:.4f} | Val Acc: {val_acc:.4f}")
241
+ print(f" Best Val Acc: {best_val_acc:.4f} (Epoch {best_epoch})")
242
+
243
+ # --- TAMBAHKAN BLOK IF INI (Cek Early Stopping) ---
244
+ if epochs_no_improve >= patience:
245
+ print(f"\n[Info] Early stopping! Tidak ada kemajuan selama {patience} epoch.")
246
+ print(f"[Info] Model terbaik ada di Epoch {best_epoch} dengan Val Acc: {best_val_acc:.4f}")
247
+ break # Hentikan (keluar dari) loop epoch
248
+ end_time = time.time()
249
+ training_time = end_time - start_time
250
+
251
+ print(f"\n[Training] Selesai dalam {training_time:.2f} detik")
252
+ print(f"[Training] Best Validation Accuracy: {best_val_acc:.4f} (Epoch {best_epoch})")
253
+
254
+ # Simpan model final
255
+ final_model_path = model_dir / f"{model_name_key}_final.pth"
256
+ torch.save({
257
+ 'model_state_dict': model.state_dict(),
258
+ 'optimizer_state_dict': optimizer.state_dict(),
259
+ 'epoch': config.EPOCHS,
260
+ 'val_accuracy': val_acc,
261
+ 'model_name': model_name,
262
+ 'num_classes': num_classes
263
+ }, final_model_path)
264
+
265
+ return {
266
+ 'model_name': model_name_key,
267
+ 'best_val_acc': best_val_acc,
268
+ 'best_epoch': best_epoch,
269
+ 'final_val_acc': val_acc,
270
+ 'training_time': training_time,
271
+ 'train_losses': train_losses,
272
+ 'val_losses': val_losses,
273
+ 'train_accs': train_accs,
274
+ 'val_accs': val_accs
275
+ }
276
+
277
+ def main():
278
+ """
279
+ Fungsi utama untuk menjalankan training semua model.
280
+ """
281
+ print("="*80)
282
+ print("BATIK VISION PROJECT - TRAINING SCRIPT")
283
+ print("="*80)
284
+
285
+ # 1. Setup eksperimen
286
+ experiment_name = "batik_classification"
287
+ writer, experiment_dir, model_dir = setup_experiment_logging(experiment_name)
288
+
289
+ # 2. Buat data loaders
290
+ print("\n[Data] Membuat data loaders...")
291
+ try:
292
+ train_loader, val_loader, class_names = create_dataloaders()
293
+ num_classes = len(class_names)
294
+ print(f"[Data] Berhasil! {num_classes} kelas ditemukan.")
295
+ print(f"[Data] Kelas: {class_names}")
296
+ except Exception as e:
297
+ print(f"[Error] Gagal membuat data loaders: {e}")
298
+ return
299
+
300
+ # 3. Mapping model names dari config ke timm
301
+ model_mapping = {
302
+ "vit": "vit_base_patch16_224",
303
+ "swin_transformer": "swin_base_patch4_window7_224",
304
+ "convnext_tiny": "convnext_tiny"
305
+ }
306
+
307
+ # 4. Training loop untuk setiap model
308
+ all_results = []
309
+
310
+ for model_name_key in config.MODEL_LIST:
311
+ if model_name_key not in model_mapping:
312
+ print(f"[Warning] Model '{model_name_key}' tidak dikenali. Dilewati.")
313
+ continue
314
+
315
+ model_name = model_mapping[model_name_key]
316
+
317
+ try:
318
+ # Train model
319
+ result = train_model(
320
+ model_name_key=model_name_key,
321
+ model_name=model_name,
322
+ num_classes=num_classes,
323
+ train_loader=train_loader,
324
+ val_loader=val_loader,
325
+ writer=writer,
326
+ model_dir=model_dir
327
+ )
328
+
329
+ if result:
330
+ all_results.append(result)
331
+
332
+ # Simpan hasil individual
333
+ save_training_results(
334
+ experiment_dir=experiment_dir,
335
+ model_name=model_name_key,
336
+ train_losses=result['train_losses'],
337
+ val_losses=result['val_losses'],
338
+ train_accs=result['train_accs'],
339
+ val_accs=result['val_accs'],
340
+ best_val_acc=result['best_val_acc'],
341
+ best_epoch=result['best_epoch']
342
+ )
343
+
344
+ except Exception as e:
345
+ print(f"[Error] Gagal training model {model_name_key}: {e}")
346
+ continue
347
+
348
+ # 5. Simpan ringkasan hasil
349
+ if all_results:
350
+ summary = {
351
+ "experiment_name": experiment_name,
352
+ "timestamp": datetime.now().isoformat(),
353
+ "total_models": len(all_results),
354
+ "results": all_results,
355
+ "best_model": max(all_results, key=lambda x: x['best_val_acc'])
356
+ }
357
+
358
+ summary_file = experiment_dir / "training_summary.json"
359
+ with open(summary_file, 'w') as f:
360
+ json.dump(summary, f, indent=2)
361
+
362
+ print(f"\n{'='*60}")
363
+ print("RINGKASAN HASIL TRAINING")
364
+ print(f"{'='*60}")
365
+
366
+ for result in all_results:
367
+ print(f"{result['model_name']:15} | Best Val Acc: {result['best_val_acc']:.4f} | "
368
+ f"Final Val Acc: {result['final_val_acc']:.4f} | "
369
+ f"Time: {result['training_time']:.1f}s")
370
+
371
+ best_model = summary['best_model']
372
+ print(f"\nModel terbaik: {best_model['model_name']} dengan akurasi {best_model['best_val_acc']:.4f}")
373
+ print(f"Ringkasan lengkap disimpan di: {summary_file}")
374
+
375
+ # 6. Tutup TensorBoard writer
376
+ writer.close()
377
+ print(f"\n[Complete] Training selesai! Hasil disimpan di: {experiment_dir}")
378
+
379
+ if __name__ == "__main__":
380
+ main()
train_anti_overfitting.py ADDED
@@ -0,0 +1,354 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ from pathlib import Path
3
+ # Tambahkan parent project ke sys.path sehingga 'src' dapat diimport saat menjalankan skrip langsung
4
+ sys.path.append(str(Path(__file__).resolve().parents[1]))
5
+
6
+ import torch
7
+ import torch.nn as nn
8
+ import torch.optim as optim
9
+ from torch.utils.tensorboard import SummaryWriter
10
+ import time
11
+ import os
12
+ from datetime import datetime
13
+ import json
14
+ import matplotlib.pyplot as plt
15
+ import numpy as np
16
+ import seaborn as sns
17
+ from sklearn.metrics import confusion_matrix, classification_report
18
+ from torch.optim.lr_scheduler import ReduceLROnPlateau, CosineAnnealingLR
19
+ import warnings
20
+ warnings.filterwarnings('ignore')
21
+
22
+ # Import modul yang sudah dibuat
23
+ from src import config
24
+ from src.data_loader import create_dataloaders
25
+ from src.model import create_model
26
+ from src.engine import train_step, val_step
27
+
28
+ def setup_anti_overfitting_training():
29
+ """
30
+ Setup untuk training anti-overfitting yang sangat agresif.
31
+ """
32
+ print("SETUP TRAINING ANTI-OVERFITTING - AGGRESSIVE")
33
+ print("="*60)
34
+
35
+ # Override config untuk training anti-overfitting
36
+ config.BATCH_SIZE = 32 # Batch size lebih besar untuk stabilisasi
37
+ config.EPOCHS = 50 # Lebih banyak epoch dengan early stopping
38
+ config.IMAGE_SIZE = 224 # Resolusi standar
39
+ config.LEARNING_RATE = 5e-5 # Learning rate lebih kecil
40
+
41
+ print(f"Konfigurasi Anti-Overfitting:")
42
+ print(f" - Batch Size: {config.BATCH_SIZE}")
43
+ print(f" - Epochs: {config.EPOCHS}")
44
+ print(f" - Image Size: {config.IMAGE_SIZE}x{config.IMAGE_SIZE}")
45
+ print(f" - Learning Rate: {config.LEARNING_RATE}")
46
+ print(f" - Device: {config.DEVICE}")
47
+ print(f" - Model: {config.MODEL_LIST[0] if config.MODEL_LIST else 'None'}")
48
+
49
+ # Buat direktori untuk hasil
50
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
51
+ experiment_dir = Path("outputs") / f"anti_overfitting_{timestamp}"
52
+ model_dir = experiment_dir / "models"
53
+ log_dir = experiment_dir / "logs"
54
+
55
+ experiment_dir.mkdir(parents=True, exist_ok=True)
56
+ model_dir.mkdir(parents=True, exist_ok=True)
57
+ log_dir.mkdir(parents=True, exist_ok=True)
58
+
59
+ writer = SummaryWriter(log_dir=str(log_dir))
60
+
61
+ return writer, experiment_dir, model_dir
62
+
63
+ def add_dropout_to_model(model, dropout_rate=0.5):
64
+ """
65
+ Menambahkan dropout layers ke model untuk mengurangi overfitting.
66
+ """
67
+ for name, module in model.named_modules():
68
+ if isinstance(module, nn.Linear) and 'head' in name:
69
+ # Tambahkan dropout sebelum classifier head
70
+ new_head = nn.Sequential(
71
+ nn.Dropout(dropout_rate),
72
+ module
73
+ )
74
+ # Ganti head dengan dropout
75
+ parent_name = '.'.join(name.split('.')[:-1])
76
+ if parent_name:
77
+ parent_module = model.get_submodule(parent_name)
78
+ setattr(parent_module, name.split('.')[-1], new_head)
79
+ else:
80
+ setattr(model, name.split('.')[-1], new_head)
81
+
82
+ return model
83
+
84
+ def train_anti_overfitting_model(model_name_key: str, model_name: str, num_classes: int,
85
+ train_loader, val_loader, writer, model_dir: Path, class_names):
86
+ """
87
+ Training model dengan teknik anti-overfitting yang sangat agresif.
88
+ """
89
+ print(f"\nTRAINING MODEL: {model_name_key.upper()}")
90
+ print(f" Model: {model_name}")
91
+ print(f" Classes: {num_classes}")
92
+ print("-" * 50)
93
+
94
+ # Buat model
95
+ model = create_model(model_name, num_classes, pretrained=True)
96
+ if model is None:
97
+ print(f"ERROR: Gagal membuat model {model_name}")
98
+ return None
99
+
100
+ # Tambahkan dropout untuk mengurangi overfitting
101
+ model = add_dropout_to_model(model, dropout_rate=0.6)
102
+
103
+ model = model.to(config.DEVICE)
104
+
105
+ # Setup optimizer dengan weight decay yang lebih besar
106
+ loss_fn = nn.CrossEntropyLoss()
107
+ optimizer = optim.AdamW(model.parameters(), lr=config.LEARNING_RATE, weight_decay=1e-3)
108
+
109
+ # Setup learning rate scheduler yang lebih agresif
110
+ scheduler = ReduceLROnPlateau(optimizer, mode='max', factor=0.3, patience=2, min_lr=1e-7)
111
+
112
+ # Tracking variables
113
+ train_losses, val_losses = [], []
114
+ train_accs, val_accs = [], []
115
+ best_val_acc = 0.0
116
+ best_epoch = 0
117
+
118
+ # Early stopping yang lebih ketat
119
+ patience = 5 # Stop jika tidak ada improvement selama 5 epoch
120
+ epochs_no_improve = 0
121
+
122
+ print(f"Memulai training {config.EPOCHS} epochs...")
123
+ print(f" Early Stopping: {patience} epochs patience")
124
+ print(f" Learning Rate Scheduler: ReduceLROnPlateau (factor=0.3)")
125
+ print(f" Weight Decay: 1e-3 (AdamW)")
126
+ print(f" Dropout Rate: 0.6")
127
+
128
+ start_time = time.time()
129
+
130
+ for epoch in range(config.EPOCHS):
131
+ print(f"\nEpoch {epoch+1}/{config.EPOCHS}")
132
+
133
+ # Training
134
+ train_loss, train_acc = train_step(
135
+ model=model, dataloader=train_loader, loss_fn=loss_fn,
136
+ optimizer=optimizer, device=config.DEVICE
137
+ )
138
+
139
+ # Validation
140
+ val_loss, val_acc = val_step(
141
+ model=model, dataloader=val_loader, loss_fn=loss_fn,
142
+ device=config.DEVICE
143
+ )
144
+
145
+ # Update learning rate scheduler
146
+ scheduler.step(val_acc)
147
+
148
+ # Simpan metrics
149
+ train_losses.append(train_loss)
150
+ val_losses.append(val_loss)
151
+ train_accs.append(train_acc)
152
+ val_accs.append(val_acc)
153
+
154
+ # Log ke TensorBoard
155
+ writer.add_scalar(f'{model_name_key}/Train/Loss', train_loss, epoch)
156
+ writer.add_scalar(f'{model_name_key}/Train/Accuracy', train_acc, epoch)
157
+ writer.add_scalar(f'{model_name_key}/Val/Loss', val_loss, epoch)
158
+ writer.add_scalar(f'{model_name_key}/Val/Accuracy', val_acc, epoch)
159
+ writer.add_scalar(f'{model_name_key}/Learning_Rate', optimizer.param_groups[0]['lr'], epoch)
160
+
161
+ # Cek model terbaik
162
+ if val_acc > best_val_acc:
163
+ best_val_acc = val_acc
164
+ best_epoch = epoch + 1
165
+ epochs_no_improve = 0 # Reset counter
166
+
167
+ # Simpan model terbaik
168
+ model_path = model_dir / f"{model_name_key}_best.pth"
169
+ torch.save({
170
+ 'model_state_dict': model.state_dict(),
171
+ 'optimizer_state_dict': optimizer.state_dict(),
172
+ 'scheduler_state_dict': scheduler.state_dict(),
173
+ 'epoch': epoch + 1,
174
+ 'val_accuracy': val_acc,
175
+ 'model_name': model_name,
176
+ 'num_classes': num_classes
177
+ }, model_path)
178
+ print(f"Model terbaik disimpan: {model_path}")
179
+ else:
180
+ epochs_no_improve += 1
181
+
182
+ # Progress
183
+ print(f" Train: Loss={train_loss:.4f}, Acc={train_acc:.4f}")
184
+ print(f" Val: Loss={val_loss:.4f}, Acc={val_acc:.4f}")
185
+ print(f" Best: {best_val_acc:.4f} (Epoch {best_epoch})")
186
+ print(f" LR: {optimizer.param_groups[0]['lr']:.2e}")
187
+ print(f" No Improve: {epochs_no_improve}/{patience}")
188
+
189
+ # Early stopping check
190
+ if epochs_no_improve >= patience:
191
+ print(f"\nEarly stopping! Tidak ada kemajuan selama {patience} epoch.")
192
+ print(f"Model terbaik: Epoch {best_epoch} dengan Val Acc: {best_val_acc:.4f}")
193
+ break
194
+
195
+ end_time = time.time()
196
+ training_time = end_time - start_time
197
+
198
+ print(f"\nTraining selesai!")
199
+ print(f" Waktu: {training_time:.1f} detik")
200
+ print(f" Best Accuracy: {best_val_acc:.4f}")
201
+ print(f" Epochs trained: {epoch + 1}")
202
+
203
+ # Generate confusion matrix dan classification report
204
+ print(f"\nGenerating Confusion Matrix dan Classification Report...")
205
+ generate_confusion_matrix(model, val_loader, class_names, model_dir, model_name_key)
206
+
207
+ return {
208
+ 'model_name': model_name_key,
209
+ 'best_val_acc': best_val_acc,
210
+ 'best_epoch': best_epoch,
211
+ 'final_val_acc': val_acc,
212
+ 'training_time': training_time,
213
+ 'epochs_trained': epoch + 1,
214
+ 'train_losses': train_losses,
215
+ 'val_losses': val_losses,
216
+ 'train_accs': train_accs,
217
+ 'val_accs': val_accs
218
+ }
219
+
220
+ def generate_confusion_matrix(model, val_loader, class_names, model_dir, model_name_key):
221
+ """
222
+ Generate confusion matrix dan classification report.
223
+ """
224
+ model.eval()
225
+ all_preds = []
226
+ all_labels = []
227
+
228
+ print(" Mengumpulkan prediksi untuk confusion matrix...")
229
+ with torch.no_grad():
230
+ for X, y in val_loader:
231
+ X, y = X.to(config.DEVICE), y.to(config.DEVICE)
232
+ outputs = model(X)
233
+ _, predicted = torch.max(outputs, 1)
234
+
235
+ all_preds.extend(predicted.cpu().numpy())
236
+ all_labels.extend(y.cpu().numpy())
237
+
238
+ # Generate confusion matrix
239
+ cm = confusion_matrix(all_labels, all_preds)
240
+
241
+ # Plot confusion matrix
242
+ plt.figure(figsize=(15, 12))
243
+ sns.heatmap(cm, annot=True, fmt='d', cmap='Blues',
244
+ xticklabels=class_names, yticklabels=class_names)
245
+ plt.title(f'Confusion Matrix - {model_name_key.upper()}')
246
+ plt.xlabel('Predicted')
247
+ plt.ylabel('Actual')
248
+ plt.xticks(rotation=45, ha='right')
249
+ plt.yticks(rotation=0)
250
+ plt.tight_layout()
251
+
252
+ # Simpan confusion matrix
253
+ cm_path = model_dir / f"{model_name_key}_confusion_matrix.png"
254
+ plt.savefig(cm_path, dpi=300, bbox_inches='tight')
255
+ plt.close()
256
+
257
+ # Generate classification report
258
+ report = classification_report(all_labels, all_preds,
259
+ target_names=class_names,
260
+ output_dict=True)
261
+
262
+ # Simpan classification report
263
+ report_path = model_dir / f"{model_name_key}_classification_report.json"
264
+ with open(report_path, 'w') as f:
265
+ json.dump(report, f, indent=2)
266
+
267
+ # Print summary
268
+ print(f" Confusion Matrix disimpan: {cm_path}")
269
+ print(f" Classification Report disimpan: {report_path}")
270
+
271
+ # Print per-class accuracy
272
+ print(f"\n Per-Class Accuracy:")
273
+ for i, class_name in enumerate(class_names):
274
+ if i < len(report) - 3: # Exclude 'accuracy', 'macro avg', 'weighted avg'
275
+ acc = report[class_name]['f1-score']
276
+ print(f" {class_name:25}: {acc:.4f}")
277
+
278
+ def main():
279
+ """
280
+ Training anti-overfitting dengan teknik yang sangat agresif.
281
+ """
282
+ print("BATIK VISION - ANTI-OVERFITTING TRAINING MODE")
283
+ print("="*60)
284
+
285
+ # 1. Setup training anti-overfitting
286
+ writer, experiment_dir, model_dir = setup_anti_overfitting_training()
287
+
288
+ # 2. Buat data loaders
289
+ print("\nMembuat data loaders...")
290
+ try:
291
+ train_loader, val_loader, class_names = create_dataloaders()
292
+ num_classes = len(class_names)
293
+ print(f"Data siap! {num_classes} kelas ditemukan.")
294
+ print(f" Kelas: {class_names[:5]}{'...' if len(class_names) > 5 else ''}")
295
+ except Exception as e:
296
+ print(f"ERROR data loader: {e}")
297
+ return
298
+
299
+ # 3. Model mapping
300
+ model_mapping = {
301
+ "vit": "vit_base_patch16_224",
302
+ "swin_transformer": "swin_base_patch4_window7_224",
303
+ "convnext_tiny": "convnext_tiny"
304
+ }
305
+
306
+ # 4. Training
307
+ all_results = []
308
+
309
+ for model_name_key in config.MODEL_LIST:
310
+ if model_name_key not in model_mapping:
311
+ print(f"WARNING: Model '{model_name_key}' tidak dikenali. Dilewati.")
312
+ continue
313
+
314
+ model_name = model_mapping[model_name_key]
315
+
316
+ try:
317
+ result = train_anti_overfitting_model(
318
+ model_name_key=model_name_key,
319
+ model_name=model_name,
320
+ num_classes=num_classes,
321
+ train_loader=train_loader,
322
+ val_loader=val_loader,
323
+ writer=writer,
324
+ model_dir=model_dir,
325
+ class_names=class_names
326
+ )
327
+
328
+ if result:
329
+ all_results.append(result)
330
+
331
+ except Exception as e:
332
+ print(f"ERROR training {model_name_key}: {e}")
333
+ continue
334
+
335
+ # 5. Ringkasan
336
+ if all_results:
337
+ print(f"\nRINGKASAN HASIL")
338
+ print("="*40)
339
+
340
+ for result in all_results:
341
+ print(f"{result['model_name']:15} | "
342
+ f"Best: {result['best_val_acc']:.4f} | "
343
+ f"Epochs: {result['epochs_trained']} | "
344
+ f"Time: {result['training_time']:.1f}s")
345
+
346
+ best_model = max(all_results, key=lambda x: x['best_val_acc'])
347
+ print(f"\nModel terbaik: {best_model['model_name']} "
348
+ f"({best_model['best_val_acc']:.4f})")
349
+
350
+ writer.close()
351
+ print(f"\nHasil disimpan di: {experiment_dir}")
352
+
353
+ if __name__ == "__main__":
354
+ main()
train_anti_overfitting_v2.py ADDED
@@ -0,0 +1,529 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ train_anti_overfitting_v2.py
3
+
4
+ Versi upgrade dari `train_anti_overfitting.py`:
5
+ - MixUp & CutMix augmentation (opsional, diaktifkan via flag)
6
+ - Label smoothing pada CrossEntropyLoss
7
+ - Dropout ditambahkan ke classifier head dan block terakhir (jika tersedia)
8
+ - Gradient clipping
9
+ - CosineAnnealingWarmRestarts scheduler (default) + optional ReduceLROnPlateau
10
+ - Class-weighting support (opsional, dihitung dari train labels jika tersedia)
11
+ - Freeze backbone untuk N epoch pertama (fine-tune strategy)
12
+ - Menyimpan plot loss/accuracy otomatis dan classification report + confusion matrix
13
+
14
+ Catatan: script ini mengasumsikan struktur proyek yang sama (src.config, src.data_loader, src.model, src.engine).
15
+ Jalankan dari root project (sama seperti script lama).
16
+ """
17
+
18
+ import sys
19
+ from pathlib import Path
20
+ sys.path.append(str(Path(__file__).resolve().parents[1]))
21
+
22
+ import torch
23
+ import torch.nn as nn
24
+ import torch.optim as optim
25
+ from torch.utils.tensorboard import SummaryWriter
26
+ import time
27
+ import os
28
+ from datetime import datetime
29
+ import json
30
+ import matplotlib.pyplot as plt
31
+ import numpy as np
32
+ import seaborn as sns
33
+ from sklearn.metrics import confusion_matrix, classification_report
34
+ from sklearn.utils.class_weight import compute_class_weight
35
+ import warnings
36
+ warnings.filterwarnings('ignore')
37
+
38
+ from src import config
39
+ from src.data_loader import create_dataloaders
40
+ from src.model import create_model
41
+ from src.engine import train_step, val_step
42
+
43
+ # --------------------------- Augmentation utilities ---------------------------
44
+
45
+ def mixup_data(x, y, alpha=0.4, device='cpu'):
46
+ if alpha <= 0:
47
+ return x, y, None, 1.0
48
+ lam = np.random.beta(alpha, alpha)
49
+ batch_size = x.size()[0]
50
+ index = torch.randperm(batch_size).to(device)
51
+ mixed_x = lam * x + (1 - lam) * x[index, :]
52
+ y_a, y_b = y, y[index]
53
+ return mixed_x, y_a, y_b, lam
54
+
55
+
56
+ def cutmix_data(x, y, alpha=1.0, device='cpu'):
57
+ if alpha <= 0:
58
+ return x, y, None, 1.0
59
+ lam = np.random.beta(alpha, alpha)
60
+ batch_size, _, H, W = x.size()
61
+ index = torch.randperm(batch_size).to(device)
62
+
63
+ # sample bounding box
64
+ cut_rat = np.sqrt(1. - lam)
65
+ cut_w = np.int(W * cut_rat)
66
+ cut_h = np.int(H * cut_rat)
67
+
68
+ cx = np.random.randint(W)
69
+ cy = np.random.randint(H)
70
+
71
+ x1 = np.clip(cx - cut_w // 2, 0, W)
72
+ y1 = np.clip(cy - cut_h // 2, 0, H)
73
+ x2 = np.clip(cx + cut_w // 2, 0, W)
74
+ y2 = np.clip(cy + cut_h // 2, 0, H)
75
+
76
+ x[:, :, y1:y2, x1:x2] = x[index, :, y1:y2, x1:x2]
77
+ y_a, y_b = y, y[index]
78
+ # adjust lambda to actual area
79
+ lam = 1 - ((x2 - x1) * (y2 - y1) / (W * H))
80
+ return x, y_a, y_b, lam
81
+
82
+ # --------------------------- Model modification utilities ---------------------------
83
+
84
+ def add_dropout_to_head(model, dropout_rate=0.5):
85
+ """Tambahkan dropout tepat sebelum classifier head (Linear) dengan pendekatan aman."""
86
+ for name, module in model.named_modules():
87
+ if isinstance(module, nn.Linear) and 'head' in name:
88
+ parent_name = '.'.join(name.split('.')[:-1])
89
+ attr = name.split('.')[-1]
90
+ parent = model.get_submodule(parent_name) if parent_name else model
91
+ linear = getattr(parent, attr)
92
+ seq = nn.Sequential(nn.Dropout(dropout_rate), linear)
93
+ setattr(parent, attr, seq)
94
+ return model
95
+
96
+
97
+ def add_dropout_to_last_block(model, dropout_rate=0.3):
98
+ """Coba tambahkan dropout ke block akhir dari backbone jika attribute dikenali.
99
+ Implementasi ini aman-check untuk beberapa arsitektur (convnext, timm models).
100
+ """
101
+ # ConvNeXt-like: stages / blocks
102
+ try:
103
+ if hasattr(model, 'stages'):
104
+ last_stage = model.stages[-1]
105
+ # Jika last_stage adalah Sequential of blocks
106
+ if isinstance(last_stage, (nn.Sequential, list, tuple)):
107
+ for i, block in enumerate(last_stage):
108
+ # tambahkan dropout ke dalam block jika memungkinkan
109
+ if isinstance(block, nn.Module):
110
+ block.add_module('drop_extra', nn.Dropout(p=dropout_rate))
111
+ break # tambahkan hanya ke block pertama di last stage agar aman
112
+ # Swin/ViT style: add dropout before head
113
+ if hasattr(model, 'patch_embed') and hasattr(model, 'norm'):
114
+ # tambahkan dropout setelah norm
115
+ model.add_module('backbone_dropout', nn.Dropout(p=dropout_rate))
116
+ except Exception:
117
+ # Jika gagal, jangan crash
118
+ pass
119
+ return model
120
+
121
+ # --------------------------- Training utilities ---------------------------
122
+
123
+ def apply_gradient_clipping(model, max_norm=1.0):
124
+ torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm)
125
+
126
+
127
+ def save_plots(train_losses, val_losses, train_accs, val_accs, out_dir, model_name_key):
128
+ plt.figure(figsize=(8, 5))
129
+ plt.plot(train_losses, label='Train Loss')
130
+ plt.plot(val_losses, label='Val Loss')
131
+ plt.title('Loss Curve')
132
+ plt.legend()
133
+ plt.tight_layout()
134
+ plt.savefig(out_dir / f"{model_name_key}_loss_curve.png", dpi=300)
135
+ plt.close()
136
+
137
+ plt.figure(figsize=(8, 5))
138
+ plt.plot(train_accs, label='Train Acc')
139
+ plt.plot(val_accs, label='Val Acc')
140
+ plt.title('Accuracy Curve')
141
+ plt.legend()
142
+ plt.tight_layout()
143
+ plt.savefig(out_dir / f"{model_name_key}_acc_curve.png", dpi=300)
144
+ plt.close()
145
+
146
+ # --------------------------- Main training function ---------------------------
147
+
148
+ def train_anti_overfitting_model_v2(model_name_key: str, model_name: str, num_classes: int,
149
+ train_loader, val_loader, writer, model_dir: Path, class_names,
150
+ config_overrides=None):
151
+ """Versi v2: integrasikan MixUp/CutMix, label smoothing, gradient clipping, scheduler CosineWarm.
152
+ config_overrides: dict optional keys:
153
+ - mixup_alpha, cutmix_alpha, use_mixup, use_cutmix
154
+ - dropout_head, dropout_backbone
155
+ - label_smoothing
156
+ - freeze_backbone_epochs
157
+ - use_reduce_on_plateau (bool)
158
+ - max_grad_norm
159
+ """
160
+ co = config_overrides or {}
161
+ use_mixup = co.get('use_mixup', True)
162
+ use_cutmix = co.get('use_cutmix', False)
163
+ mixup_alpha = co.get('mixup_alpha', 0.4)
164
+ cutmix_alpha = co.get('cutmix_alpha', 1.0)
165
+ dropout_head = co.get('dropout_head', 0.6)
166
+ dropout_backbone = co.get('dropout_backbone', 0.3)
167
+ label_smoothing = co.get('label_smoothing', 0.1)
168
+ freeze_backbone_epochs = co.get('freeze_backbone_epochs', 5)
169
+ use_reduce_on_plateau = co.get('use_reduce_on_plateau', False)
170
+ max_grad_norm = co.get('max_grad_norm', 1.0)
171
+
172
+ print(f"\nTRAINING MODEL (v2): {model_name_key.upper()}")
173
+ print(f" Model: {model_name}")
174
+ print(f" Classes: {num_classes}")
175
+ print("-"*50)
176
+
177
+ # 1) create model
178
+ model = create_model(model_name, num_classes, pretrained=True)
179
+ if model is None:
180
+ print(f"ERROR: Gagal membuat model {model_name}")
181
+ return None
182
+
183
+ # 2) add dropout to head + last block
184
+ model = add_dropout_to_head(model, dropout_head)
185
+ model = add_dropout_to_last_block(model, dropout_backbone)
186
+
187
+ # 3) move to device
188
+ model = model.to(config.DEVICE)
189
+
190
+ # 4) optionally freeze backbone for few epochs
191
+ backbone_params = [p for n, p in model.named_parameters() if 'head' not in n and p.requires_grad]
192
+ def set_backbone_requires_grad(flag):
193
+ for n, p in model.named_parameters():
194
+ if 'head' not in n:
195
+ p.requires_grad = flag
196
+
197
+ # 5) Loss function with label smoothing
198
+ loss_fn = nn.CrossEntropyLoss(label_smoothing=label_smoothing)
199
+
200
+ # 6) Optional: compute class weights from train_loader labels
201
+ try:
202
+ y_train = []
203
+ for _, y in train_loader.dataset: # assumes dataset returns (x, y)
204
+ y_train.append(int(y))
205
+ class_weights = compute_class_weight('balanced', classes=np.arange(num_classes), y=y_train)
206
+ weights = torch.FloatTensor(class_weights).to(config.DEVICE)
207
+ weighted_loss = nn.CrossEntropyLoss(weight=weights, label_smoothing=label_smoothing)
208
+ loss_fn = weighted_loss
209
+ print(" Class weights applied to loss function.")
210
+ except Exception:
211
+ # jika gagal hitung, lanjut tanpa class weights
212
+ pass
213
+
214
+ # 7) Optimizer
215
+ optimizer = optim.AdamW(filter(lambda p: p.requires_grad, model.parameters()), lr=config.LEARNING_RATE, weight_decay=1e-3)
216
+
217
+ # 8) Scheduler: CosineAnnealingWarmRestarts (default) + optional ReduceLROnPlateau
218
+ scheduler = torch.optim.lr_scheduler.CosineAnnealingWarmRestarts(optimizer, T_0=5, T_mult=2, eta_min=1e-7)
219
+ if use_reduce_on_plateau:
220
+ plateau = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='max', factor=0.3, patience=3, min_lr=1e-7)
221
+ else:
222
+ plateau = None
223
+
224
+ # tracking
225
+ train_losses, val_losses = [], []
226
+ train_accs, val_accs = [], []
227
+ best_val_acc = 0.0
228
+ best_epoch = 0
229
+ patience = 10 # sedikit lebih longgar pada v2
230
+ epochs_no_improve = 0
231
+
232
+ print(f"Memulai training {config.EPOCHS} epochs...")
233
+ print(f" Freeze backbone epochs: {freeze_backbone_epochs}")
234
+ print(f" MixUp: {use_mixup}, CutMix: {use_cutmix}")
235
+ print(f" Label smoothing: {label_smoothing}")
236
+
237
+ start_time = time.time()
238
+ for epoch in range(config.EPOCHS):
239
+ print(f"\nEpoch {epoch+1}/{config.EPOCHS}")
240
+
241
+ # unfreeze if passed freeze_backbone_epochs
242
+ if epoch == freeze_backbone_epochs:
243
+ set_backbone_requires_grad(True)
244
+ # re-init optimizer to include newly trainable params
245
+ optimizer = optim.AdamW(filter(lambda p: p.requires_grad, model.parameters()), lr=config.LEARNING_RATE, weight_decay=1e-3)
246
+ # reattach scheduler state if needed (simple approach: recreate)
247
+ scheduler = torch.optim.lr_scheduler.CosineAnnealingWarmRestarts(optimizer, T_0=5, T_mult=2, eta_min=1e-7)
248
+ if plateau is not None:
249
+ plateau = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='max', factor=0.3, patience=3, min_lr=1e-7)
250
+ print(" Backbone unfrozen and optimizer reinitialized.")
251
+
252
+ # TRAIN LOOP (with MixUp/CutMix applied per-batch inside train_step wrapper)
253
+ model.train()
254
+ running_loss = 0.0
255
+ correct = 0
256
+ total = 0
257
+
258
+ for batch in train_loader:
259
+ inputs, targets = batch
260
+ inputs = inputs.to(config.DEVICE)
261
+ targets = targets.to(config.DEVICE)
262
+
263
+ # Apply MixUp or CutMix randomly
264
+ applied_mix = False
265
+ if use_mixup and np.random.rand() < 0.5:
266
+ inputs, targets_a, targets_b, lam = mixup_data(inputs, targets, mixup_alpha, device=config.DEVICE)
267
+ applied_mix = 'mixup'
268
+ elif use_cutmix and np.random.rand() < 0.5:
269
+ inputs, targets_a, targets_b, lam = cutmix_data(inputs, targets, cutmix_alpha, device=config.DEVICE)
270
+ applied_mix = 'cutmix'
271
+
272
+ optimizer.zero_grad()
273
+ outputs = model(inputs)
274
+
275
+ if applied_mix:
276
+ loss = lam * loss_fn(outputs, targets_a) + (1 - lam) * loss_fn(outputs, targets_b)
277
+ else:
278
+ loss = loss_fn(outputs, targets)
279
+
280
+ loss.backward()
281
+ # gradient clipping
282
+ if max_grad_norm:
283
+ apply_gradient_clipping(model, max_grad_norm)
284
+ optimizer.step()
285
+
286
+ # stats (for accuracy, if mixup applied we approximate by taking max against targets_a)
287
+ running_loss += loss.item() * inputs.size(0)
288
+ _, predicted = torch.max(outputs.data, 1)
289
+ if applied_mix:
290
+ # count prediction correct if matches either target (loose estimation)
291
+ correct += (predicted.eq(targets_a).sum().item() + predicted.eq(targets_b).sum().item()) / 2.0
292
+ else:
293
+ correct += predicted.eq(targets).sum().item()
294
+ total += inputs.size(0)
295
+
296
+ train_loss = running_loss / total
297
+ train_acc = correct / total
298
+
299
+ # VALIDATION
300
+ val_loss, val_acc = val_step(model=model, dataloader=val_loader, loss_fn=loss_fn, device=config.DEVICE)
301
+
302
+ # scheduler step
303
+ # CosineWarm uses epoch-based step via scheduler.step(epoch + epoch_fraction) using optimizer state
304
+ scheduler.step()
305
+ if plateau is not None:
306
+ plateau.step(val_acc)
307
+
308
+ # store
309
+ train_losses.append(train_loss)
310
+ val_losses.append(val_loss)
311
+ train_accs.append(train_acc)
312
+ val_accs.append(val_acc)
313
+
314
+ # TensorBoard
315
+ writer.add_scalar(f'{model_name_key}/Train/Loss', train_loss, epoch)
316
+ writer.add_scalar(f'{model_name_key}/Train/Accuracy', train_acc, epoch)
317
+ writer.add_scalar(f'{model_name_key}/Val/Loss', val_loss, epoch)
318
+ writer.add_scalar(f'{model_name_key}/Val/Accuracy', val_acc, epoch)
319
+ writer.add_scalar(f'{model_name_key}/Learning_Rate', optimizer.param_groups[0]['lr'], epoch)
320
+
321
+ # best model check
322
+ if val_acc > best_val_acc:
323
+ best_val_acc = val_acc
324
+ best_epoch = epoch + 1
325
+ epochs_no_improve = 0
326
+ model_path = model_dir / f"{model_name_key}_best.pth"
327
+ torch.save({
328
+ 'model_state_dict': model.state_dict(),
329
+ 'optimizer_state_dict': optimizer.state_dict(),
330
+ 'scheduler_state_dict': scheduler.state_dict(),
331
+ 'epoch': epoch + 1,
332
+ 'val_accuracy': val_acc,
333
+ 'model_name': model_name,
334
+ 'num_classes': num_classes
335
+ }, model_path)
336
+ print(f"Model terbaik disimpan: {model_path}")
337
+ else:
338
+ epochs_no_improve += 1
339
+
340
+ print(f" Train: Loss={train_loss:.4f}, Acc={train_acc:.4f}")
341
+ print(f" Val: Loss={val_loss:.4f}, Acc={val_acc:.4f}")
342
+ print(f" Best: {best_val_acc:.4f} (Epoch {best_epoch})")
343
+ print(f" LR: {optimizer.param_groups[0]['lr']:.2e}")
344
+ print(f" No Improve: {epochs_no_improve}/{patience}")
345
+
346
+ if epochs_no_improve >= patience:
347
+ print(f"\nEarly stopping! Tidak ada kemajuan selama {patience} epoch.")
348
+ print(f"Model terbaik: Epoch {best_epoch} dengan Val Acc: {best_val_acc:.4f}")
349
+ break
350
+
351
+ end_time = time.time()
352
+ training_time = end_time - start_time
353
+
354
+ print(f"\nTraining selesai!")
355
+ print(f" Waktu: {training_time:.1f} detik")
356
+ print(f" Best Accuracy: {best_val_acc:.4f}")
357
+ print(f" Epochs trained: {epoch + 1}")
358
+
359
+ # save plots
360
+ save_plots(train_losses, val_losses, train_accs, val_accs, model_dir, model_name_key)
361
+
362
+ # generate confusion matrix + classification report
363
+ print(f"\nGenerating Confusion Matrix dan Classification Report...")
364
+ generate_confusion_matrix(model, val_loader, class_names, model_dir, model_name_key)
365
+
366
+ return {
367
+ 'model_name': model_name_key,
368
+ 'best_val_acc': best_val_acc,
369
+ 'best_epoch': best_epoch,
370
+ 'final_val_acc': val_acc,
371
+ 'training_time': training_time,
372
+ 'epochs_trained': epoch + 1,
373
+ 'train_losses': train_losses,
374
+ 'val_losses': val_losses,
375
+ 'train_accs': train_accs,
376
+ 'val_accs': val_accs
377
+ }
378
+
379
+ # reuse generate_confusion_matrix dari versi awal (disalin untuk independensi)
380
+
381
+ def generate_confusion_matrix(model, val_loader, class_names, model_dir, model_name_key):
382
+ model.eval()
383
+ all_preds = []
384
+ all_labels = []
385
+
386
+ print(" Mengumpulkan prediksi untuk confusion matrix...")
387
+ with torch.no_grad():
388
+ for X, y in val_loader:
389
+ X, y = X.to(config.DEVICE), y.to(config.DEVICE)
390
+ outputs = model(X)
391
+ _, predicted = torch.max(outputs, 1)
392
+
393
+ all_preds.extend(predicted.cpu().numpy())
394
+ all_labels.extend(y.cpu().numpy())
395
+
396
+ cm = confusion_matrix(all_labels, all_preds)
397
+
398
+ plt.figure(figsize=(15, 12))
399
+ sns.heatmap(cm, annot=True, fmt='d', cmap='Blues',
400
+ xticklabels=class_names, yticklabels=class_names)
401
+ plt.title(f'Confusion Matrix - {model_name_key.upper()}')
402
+ plt.xlabel('Predicted')
403
+ plt.ylabel('Actual')
404
+ plt.xticks(rotation=45, ha='right')
405
+ plt.yticks(rotation=0)
406
+ plt.tight_layout()
407
+
408
+ cm_path = model_dir / f"{model_name_key}_confusion_matrix.png"
409
+ plt.savefig(cm_path, dpi=300, bbox_inches='tight')
410
+ plt.close()
411
+
412
+ report = classification_report(all_labels, all_preds,
413
+ target_names=class_names,
414
+ output_dict=True)
415
+
416
+ report_path = model_dir / f"{model_name_key}_classification_report.json"
417
+ with open(report_path, 'w') as f:
418
+ json.dump(report, f, indent=2)
419
+
420
+ print(f" Confusion Matrix disimpan: {cm_path}")
421
+ print(f" Classification Report disimpan: {report_path}")
422
+
423
+ print(f"\n Per-Class Accuracy:")
424
+ for i, class_name in enumerate(class_names):
425
+ if class_name in report:
426
+ acc = report[class_name]['f1-score']
427
+ print(f" {class_name:25}: {acc:.4f}")
428
+
429
+ # --------------------------- main ---------------------------
430
+
431
+ def setup_anti_overfitting_training_v2():
432
+ print("SETUP TRAINING ANTI-OVERFITTING - AGGRESSIVE (v2)")
433
+ print("="*60)
434
+
435
+ # override minimal config
436
+ config.BATCH_SIZE = getattr(config, 'BATCH_SIZE', 32)
437
+ config.EPOCHS = getattr(config, 'EPOCHS', 50)
438
+ config.IMAGE_SIZE = getattr(config, 'IMAGE_SIZE', 224)
439
+ config.LEARNING_RATE = getattr(config, 'LEARNING_RATE', 5e-5)
440
+
441
+ print(f"Konfigurasi (v2): BATCH={config.BATCH_SIZE}, EPOCHS={config.EPOCHS}, IMG={config.IMAGE_SIZE}, LR={config.LEARNING_RATE}")
442
+
443
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
444
+ experiment_dir = Path("outputs") / f"anti_overfitting_v2_{timestamp}"
445
+ model_dir = experiment_dir / "models"
446
+ log_dir = experiment_dir / "logs"
447
+
448
+ experiment_dir.mkdir(parents=True, exist_ok=True)
449
+ model_dir.mkdir(parents=True, exist_ok=True)
450
+ log_dir.mkdir(parents=True, exist_ok=True)
451
+
452
+ writer = SummaryWriter(log_dir=str(log_dir))
453
+
454
+ return writer, experiment_dir, model_dir
455
+
456
+
457
+ def main():
458
+ print("BATIK VISION - ANTI-OVERFITTING TRAINING MODE (v2)")
459
+ print("="*60)
460
+
461
+ writer, experiment_dir, model_dir = setup_anti_overfitting_training_v2()
462
+
463
+ print("\nMembuat data loaders...")
464
+ try:
465
+ train_loader, val_loader, class_names = create_dataloaders()
466
+ num_classes = len(class_names)
467
+ print(f"Data siap! {num_classes} kelas ditemukan.")
468
+ except Exception as e:
469
+ print(f"ERROR data loader: {e}")
470
+ return
471
+
472
+ model_mapping = {
473
+ "vit": "vit_base_patch16_224",
474
+ "swin_transformer": "swin_base_patch4_window7_224",
475
+ "convnext_tiny": "convnext_tiny"
476
+ }
477
+
478
+ all_results = []
479
+
480
+ # Default overrides (kamu bisa ubah sesuai kebutuhan)
481
+ overrides = {
482
+ 'use_mixup': True,
483
+ 'use_cutmix': False,
484
+ 'mixup_alpha': 0.4,
485
+ 'cutmix_alpha': 1.0,
486
+ 'dropout_head': 0.6,
487
+ 'dropout_backbone': 0.3,
488
+ 'label_smoothing': 0.1,
489
+ 'freeze_backbone_epochs': 5,
490
+ 'use_reduce_on_plateau': False,
491
+ 'max_grad_norm': 1.0
492
+ }
493
+
494
+ for model_name_key in config.MODEL_LIST:
495
+ if model_name_key not in model_mapping:
496
+ print(f"WARNING: Model '{model_name_key}' tidak dikenali. Dilewati.")
497
+ continue
498
+ model_name = model_mapping[model_name_key]
499
+ try:
500
+ result = train_anti_overfitting_model_v2(
501
+ model_name_key=model_name_key,
502
+ model_name=model_name,
503
+ num_classes=num_classes,
504
+ train_loader=train_loader,
505
+ val_loader=val_loader,
506
+ writer=writer,
507
+ model_dir=model_dir,
508
+ class_names=class_names,
509
+ config_overrides=overrides
510
+ )
511
+ if result:
512
+ all_results.append(result)
513
+ except Exception as e:
514
+ print(f"ERROR training {model_name_key}: {e}")
515
+ continue
516
+
517
+ if all_results:
518
+ print(f"\nRINGKASAN HASIL")
519
+ print("="*40)
520
+ for result in all_results:
521
+ print(f"{result['model_name']:15} | Best: {result['best_val_acc']:.4f} | Epochs: {result['epochs_trained']} | Time: {result['training_time']:.1f}s")
522
+ best_model = max(all_results, key=lambda x: x['best_val_acc'])
523
+ print(f"\nModel terbaik: {best_model['model_name']} ({best_model['best_val_acc']:.4f})")
524
+
525
+ writer.close()
526
+ print(f"\nHasil disimpan di: {experiment_dir}")
527
+
528
+ if __name__ == '__main__':
529
+ main()
train_enhanced_anti_overfitting.py ADDED
@@ -0,0 +1,467 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ from pathlib import Path
3
+ # Tambahkan parent project ke sys.path sehingga 'src' dapat diimport saat menjalankan skrip langsung
4
+ sys.path.append(str(Path(__file__).resolve().parents[1]))
5
+
6
+ import torch
7
+ import torch.nn as nn
8
+ import torch.optim as optim
9
+ from torch.utils.tensorboard import SummaryWriter
10
+ import time
11
+ import os
12
+ from datetime import datetime
13
+ import json
14
+ import matplotlib.pyplot as plt
15
+ import numpy as np
16
+ import seaborn as sns
17
+ from sklearn.metrics import confusion_matrix, classification_report
18
+ from torch.optim.lr_scheduler import ReduceLROnPlateau, CosineAnnealingLR, OneCycleLR
19
+ import warnings
20
+ warnings.filterwarnings('ignore')
21
+
22
+ # Import modul yang sudah dibuat
23
+ from src import config
24
+ from src.data_loader import create_dataloaders
25
+ from src.model import create_model
26
+ from src.engine import train_step, val_step
27
+ from src.mixup import mixup_data, mixup_criterion
28
+ from src.advanced_augmentation import (
29
+ cutmix_data, cutmix_criterion, LabelSmoothingCrossEntropy,
30
+ FocalLoss, AdvancedAugmentation, TestTimeAugmentation,
31
+ calculate_class_weights, get_advanced_scheduler, apply_mixup_cutmix_probability
32
+ )
33
+
34
+ def setup_enhanced_anti_overfitting_training():
35
+ """
36
+ Setup untuk training anti-overfitting yang sangat agresif dengan teknik terbaru.
37
+ """
38
+ print("SETUP ENHANCED ANTI-OVERFITTING TRAINING")
39
+ print("="*60)
40
+
41
+ # Override config untuk training anti-overfitting yang lebih agresif
42
+ config.BATCH_SIZE = 32 # Batch size optimal
43
+ config.EPOCHS = 60 # Lebih banyak epoch dengan early stopping
44
+ config.IMAGE_SIZE = 224 # Resolusi standar
45
+ config.LEARNING_RATE = 3e-5 # Learning rate lebih kecil untuk stabilitas
46
+
47
+ print(f"Konfigurasi Enhanced Anti-Overfitting:")
48
+ print(f" - Batch Size: {config.BATCH_SIZE}")
49
+ print(f" - Epochs: {config.EPOCHS}")
50
+ print(f" - Image Size: {config.IMAGE_SIZE}x{config.IMAGE_SIZE}")
51
+ print(f" - Learning Rate: {config.LEARNING_RATE}")
52
+ print(f" - Device: {config.DEVICE}")
53
+ print(f" - Model: {config.MODEL_LIST[0] if config.MODEL_LIST else 'None'}")
54
+
55
+ # Buat direktori untuk hasil
56
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
57
+ experiment_dir = Path("outputs") / f"enhanced_anti_overfitting_{timestamp}"
58
+ model_dir = experiment_dir / "models"
59
+ log_dir = experiment_dir / "logs"
60
+
61
+ experiment_dir.mkdir(parents=True, exist_ok=True)
62
+ model_dir.mkdir(parents=True, exist_ok=True)
63
+ log_dir.mkdir(parents=True, exist_ok=True)
64
+
65
+ writer = SummaryWriter(log_dir=str(log_dir))
66
+
67
+ return writer, experiment_dir, model_dir
68
+
69
+ def add_enhanced_dropout_to_model(model, dropout_rate=0.7):
70
+ """
71
+ Menambahkan dropout layers yang lebih agresif ke model untuk mengurangi overfitting.
72
+ """
73
+ for name, module in model.named_modules():
74
+ if isinstance(module, nn.Linear) and 'head' in name:
75
+ # Tambahkan dropout yang lebih agresif sebelum classifier head
76
+ new_head = nn.Sequential(
77
+ nn.Dropout(dropout_rate),
78
+ nn.Linear(module.in_features, module.out_features)
79
+ )
80
+ # Ganti head dengan dropout
81
+ parent_name = '.'.join(name.split('.')[:-1])
82
+ if parent_name:
83
+ parent_module = model.get_submodule(parent_name)
84
+ setattr(parent_module, name.split('.')[-1], new_head)
85
+ else:
86
+ setattr(model, name.split('.')[-1], new_head)
87
+
88
+ return model
89
+
90
+ def enhanced_train_step(model, dataloader, loss_fn, optimizer, device,
91
+ use_mixup=True, use_cutmix=True, mixup_alpha=0.2, cutmix_alpha=1.0):
92
+ """
93
+ Enhanced training step dengan Mixup dan CutMix.
94
+ """
95
+ model.train()
96
+ train_loss, train_acc = 0, 0
97
+
98
+ for X, y in dataloader:
99
+ X, y = X.to(device), y.to(device)
100
+
101
+ # Apply Mixup or CutMix with probability
102
+ augmentation_type = apply_mixup_cutmix_probability()
103
+
104
+ if augmentation_type == 'mixup' and use_mixup:
105
+ mixed_x, y_a, y_b, lam = mixup_data(X, y, mixup_alpha, device)
106
+ y_pred_logits = model(mixed_x)
107
+ loss = mixup_criterion(loss_fn, y_pred_logits, y_a, y_b, lam)
108
+
109
+ # Calculate accuracy with original targets
110
+ _, predicted = torch.max(y_pred_logits, 1)
111
+ train_acc += (lam * (predicted == y_a).float() +
112
+ (1 - lam) * (predicted == y_b).float()).mean().item()
113
+
114
+ elif augmentation_type == 'cutmix' and use_cutmix:
115
+ mixed_x, y_a, y_b, lam = cutmix_data(X, y, cutmix_alpha, device)
116
+ y_pred_logits = model(mixed_x)
117
+ loss = cutmix_criterion(loss_fn, y_pred_logits, y_a, y_b, lam)
118
+
119
+ # Calculate accuracy with original targets
120
+ _, predicted = torch.max(y_pred_logits, 1)
121
+ train_acc += (lam * (predicted == y_a).float() +
122
+ (1 - lam) * (predicted == y_b).float()).mean().item()
123
+
124
+ else:
125
+ # Standard training
126
+ y_pred_logits = model(X)
127
+ loss = loss_fn(y_pred_logits, y)
128
+
129
+ # Calculate accuracy
130
+ y_pred_class = torch.argmax(y_pred_logits, dim=1)
131
+ train_acc += (y_pred_class == y).sum().item() / len(y_pred_logits)
132
+
133
+ train_loss += loss.item()
134
+
135
+ # Backward pass
136
+ optimizer.zero_grad()
137
+ loss.backward()
138
+
139
+ # Gradient clipping untuk stabilitas
140
+ torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
141
+
142
+ optimizer.step()
143
+
144
+ train_loss = train_loss / len(dataloader)
145
+ train_acc = train_acc / len(dataloader)
146
+
147
+ return train_loss, train_acc
148
+
149
+ def train_enhanced_anti_overfitting_model(model_name_key: str, model_name: str, num_classes: int,
150
+ train_loader, val_loader, writer, model_dir: Path, class_names):
151
+ """
152
+ Training model dengan teknik anti-overfitting yang sangat agresif dan terbaru.
153
+ """
154
+ print(f"\nTRAINING ENHANCED MODEL: {model_name_key.upper()}")
155
+ print(f" Model: {model_name}")
156
+ print(f" Classes: {num_classes}")
157
+ print("-" * 50)
158
+
159
+ # Buat model
160
+ model = create_model(model_name, num_classes, pretrained=True)
161
+ if model is None:
162
+ print(f"ERROR: Gagal membuat model {model_name}")
163
+ return None
164
+
165
+ # Tambahkan dropout yang lebih agresif
166
+ model = add_enhanced_dropout_to_model(model, dropout_rate=0.7)
167
+
168
+ model = model.to(config.DEVICE)
169
+
170
+ # Setup loss function dengan label smoothing dan focal loss
171
+ # Kombinasi label smoothing dan focal loss untuk mengatasi overfitting dan class imbalance
172
+ label_smooth_loss = LabelSmoothingCrossEntropy(smoothing=0.2)
173
+ focal_loss = FocalLoss(alpha=1, gamma=2)
174
+
175
+ # Combined loss function
176
+ def combined_loss(pred, target):
177
+ return 0.7 * label_smooth_loss(pred, target) + 0.3 * focal_loss(pred, target)
178
+
179
+ loss_fn = combined_loss
180
+
181
+ # Setup optimizer dengan weight decay yang lebih besar
182
+ optimizer = optim.AdamW(model.parameters(), lr=config.LEARNING_RATE, weight_decay=2e-3)
183
+
184
+ # Setup advanced learning rate scheduler
185
+ scheduler = get_advanced_scheduler(optimizer, method='cosine_warmup', total_epochs=config.EPOCHS)
186
+
187
+ # Tracking variables
188
+ train_losses, val_losses = [], []
189
+ train_accs, val_accs = [], []
190
+ best_val_acc = 0.0
191
+ best_epoch = 0
192
+
193
+ # Early stopping yang lebih ketat
194
+ patience = 8 # Stop jika tidak ada improvement selama 7 epoch
195
+ epochs_no_improve = 0
196
+
197
+ print(f"Memulai enhanced training {config.EPOCHS} epochs...")
198
+ print(f" Early Stopping: {patience} epochs patience")
199
+ print(f" Learning Rate Scheduler: CosineAnnealingWarmRestarts")
200
+ print(f" Weight Decay: 2e-3 (AdamW)")
201
+ print(f" Dropout Rate: 0.7")
202
+ print(f" Loss Function: Combined Label Smoothing + Focal Loss")
203
+ print(f" Augmentation: Mixup + CutMix + Advanced Transforms")
204
+
205
+ start_time = time.time()
206
+
207
+ for epoch in range(config.EPOCHS):
208
+ print(f"\nEpoch {epoch+1}/{config.EPOCHS}")
209
+
210
+ # Enhanced Training dengan Mixup/CutMix
211
+ train_loss, train_acc = enhanced_train_step(
212
+ model=model, dataloader=train_loader, loss_fn=loss_fn,
213
+ optimizer=optimizer, device=config.DEVICE,
214
+ use_mixup=True, use_cutmix=True, mixup_alpha=0.2, cutmix_alpha=1.0
215
+ )
216
+
217
+ # Validation
218
+ val_loss, val_acc = val_step(
219
+ model=model, dataloader=val_loader, loss_fn=loss_fn,
220
+ device=config.DEVICE
221
+ )
222
+
223
+ # Update learning rate scheduler
224
+ if isinstance(scheduler, OneCycleLR):
225
+ scheduler.step()
226
+ else:
227
+ scheduler.step(val_acc)
228
+
229
+ # Simpan metrics
230
+ train_losses.append(train_loss)
231
+ val_losses.append(val_loss)
232
+ train_accs.append(train_acc)
233
+ val_accs.append(val_acc)
234
+
235
+ # Log ke TensorBoard
236
+ writer.add_scalar(f'{model_name_key}/Train/Loss', train_loss, epoch)
237
+ writer.add_scalar(f'{model_name_key}/Train/Accuracy', train_acc, epoch)
238
+ writer.add_scalar(f'{model_name_key}/Val/Loss', val_loss, epoch)
239
+ writer.add_scalar(f'{model_name_key}/Val/Accuracy', val_acc, epoch)
240
+ writer.add_scalar(f'{model_name_key}/Learning_Rate', optimizer.param_groups[0]['lr'], epoch)
241
+
242
+ # Cek model terbaik
243
+ if val_acc > best_val_acc:
244
+ best_val_acc = val_acc
245
+ best_epoch = epoch + 1
246
+ epochs_no_improve = 0 # Reset counter
247
+
248
+ # Simpan model terbaik
249
+ model_path = model_dir / f"{model_name_key}_best.pth"
250
+ torch.save({
251
+ 'model_state_dict': model.state_dict(),
252
+ 'optimizer_state_dict': optimizer.state_dict(),
253
+ 'scheduler_state_dict': scheduler.state_dict(),
254
+ 'epoch': epoch + 1,
255
+ 'val_accuracy': val_acc,
256
+ 'model_name': model_name,
257
+ 'num_classes': num_classes
258
+ }, model_path)
259
+ print(f"Model terbaik disimpan: {model_path}")
260
+ else:
261
+ epochs_no_improve += 1
262
+
263
+ # Progress
264
+ print(f" Train: Loss={train_loss:.4f}, Acc={train_acc:.4f}")
265
+ print(f" Val: Loss={val_loss:.4f}, Acc={val_acc:.4f}")
266
+ print(f" Best: {best_val_acc:.4f} (Epoch {best_epoch})")
267
+ print(f" LR: {optimizer.param_groups[0]['lr']:.2e}")
268
+ print(f" No Improve: {epochs_no_improve}/{patience}")
269
+
270
+ # Early stopping check
271
+ if epochs_no_improve >= patience:
272
+ print(f"\nEarly stopping! Tidak ada kemajuan selama {patience} epoch.")
273
+ print(f"Model terbaik: Epoch {best_epoch} dengan Val Acc: {best_val_acc:.4f}")
274
+ break
275
+
276
+ end_time = time.time()
277
+ training_time = end_time - start_time
278
+
279
+ print(f"\nEnhanced training selesai!")
280
+ print(f" Waktu: {training_time:.1f} detik")
281
+ print(f" Best Accuracy: {best_val_acc:.4f}")
282
+ print(f" Epochs trained: {epoch + 1}")
283
+
284
+ # Generate confusion matrix dan classification report dengan TTA
285
+ print(f"\nGenerating Enhanced Confusion Matrix dan Classification Report...")
286
+ generate_enhanced_confusion_matrix(model, val_loader, class_names, model_dir, model_name_key)
287
+
288
+ return {
289
+ 'model_name': model_name_key,
290
+ 'best_val_acc': best_val_acc,
291
+ 'best_epoch': best_epoch,
292
+ 'final_val_acc': val_acc,
293
+ 'training_time': training_time,
294
+ 'epochs_trained': epoch + 1,
295
+ 'train_losses': train_losses,
296
+ 'val_losses': val_losses,
297
+ 'train_accs': train_accs,
298
+ 'val_accs': val_accs
299
+ }
300
+
301
+ def generate_enhanced_confusion_matrix(model, val_loader, class_names, model_dir, model_name_key):
302
+ """
303
+ Generate confusion matrix dan classification report dengan Test Time Augmentation.
304
+ """
305
+ model.eval()
306
+ all_preds = []
307
+ all_labels = []
308
+
309
+ print(" Mengumpulkan prediksi dengan Test Time Augmentation...")
310
+
311
+ # Setup TTA
312
+ tta = TestTimeAugmentation(model, config.DEVICE, num_augmentations=5)
313
+
314
+ with torch.no_grad():
315
+ for X, y in val_loader:
316
+ X, y = X.to(config.DEVICE), y.to(config.DEVICE)
317
+
318
+ # Use TTA for better predictions
319
+ batch_preds = []
320
+ for i in range(X.size(0)):
321
+ # Convert tensor back to PIL for TTA
322
+ img_tensor = X[i]
323
+ # Denormalize
324
+ img_tensor = img_tensor * torch.tensor([0.229, 0.224, 0.225]).view(3, 1, 1).to(config.DEVICE)
325
+ img_tensor = img_tensor + torch.tensor([0.485, 0.456, 0.406]).view(3, 1, 1).to(config.DEVICE)
326
+ img_tensor = torch.clamp(img_tensor, 0, 1)
327
+
328
+ # Convert to PIL
329
+ from torchvision.transforms import ToPILImage
330
+ img_pil = ToPILImage()(img_tensor.cpu())
331
+
332
+ # Get TTA prediction
333
+ tta_pred = tta.predict(img_pil)
334
+ batch_preds.append(tta_pred)
335
+
336
+ # Stack predictions and get final predictions
337
+ batch_preds = torch.cat(batch_preds, dim=0)
338
+ _, predicted = torch.max(batch_preds, 1)
339
+
340
+ all_preds.extend(predicted.cpu().numpy())
341
+ all_labels.extend(y.cpu().numpy())
342
+
343
+ # Generate confusion matrix
344
+ cm = confusion_matrix(all_labels, all_preds)
345
+
346
+ # Plot confusion matrix
347
+ plt.figure(figsize=(15, 12))
348
+ sns.heatmap(cm, annot=True, fmt='d', cmap='Blues',
349
+ xticklabels=class_names, yticklabels=class_names)
350
+ plt.title(f'Enhanced Confusion Matrix - {model_name_key.upper()} (with TTA)')
351
+ plt.xlabel('Predicted')
352
+ plt.ylabel('Actual')
353
+ plt.xticks(rotation=45, ha='right')
354
+ plt.yticks(rotation=0)
355
+ plt.tight_layout()
356
+
357
+ # Simpan confusion matrix
358
+ cm_path = model_dir / f"{model_name_key}_enhanced_confusion_matrix.png"
359
+ plt.savefig(cm_path, dpi=300, bbox_inches='tight')
360
+ plt.close()
361
+
362
+ # Generate classification report
363
+ report = classification_report(all_labels, all_preds,
364
+ target_names=class_names,
365
+ output_dict=True)
366
+
367
+ # Simpan classification report
368
+ report_path = model_dir / f"{model_name_key}_enhanced_classification_report.json"
369
+ with open(report_path, 'w') as f:
370
+ json.dump(report, f, indent=2)
371
+
372
+ # Print summary
373
+ print(f" Enhanced Confusion Matrix disimpan: {cm_path}")
374
+ print(f" Enhanced Classification Report disimpan: {report_path}")
375
+
376
+ # Print per-class accuracy
377
+ print(f"\n Enhanced Per-Class Accuracy:")
378
+ for i, class_name in enumerate(class_names):
379
+ if i < len(report) - 3: Exclude 'accuracy', 'macro avg', 'weighted avg'
380
+ acc = report[class_name]['f1-score']
381
+ print(f" {class_name:25}: {acc:.4f}")
382
+
383
+ def main():
384
+ """
385
+ Enhanced training anti-overfitting dengan teknik terbaru.
386
+ """
387
+ print("BATIK VISION - ENHANCED ANTI-OVERFITTING TRAINING MODE")
388
+ print("="*60)
389
+
390
+ # 1. Setup enhanced training anti-overfitting
391
+ writer, experiment_dir, model_dir = setup_enhanced_anti_overfitting_training()
392
+
393
+ # 2. Buat data loaders dengan advanced augmentation
394
+ print("\nMembuat enhanced data loaders...")
395
+ try:
396
+ # Use advanced augmentation
397
+ aug = AdvancedAugmentation(config.IMAGE_SIZE)
398
+
399
+ # Override the default transforms
400
+ from src.data_loader import train_transform, val_transform
401
+ train_transform = aug.get_train_transforms()
402
+ val_transform = aug.get_val_transforms()
403
+
404
+ train_loader, val_loader, class_names = create_dataloaders()
405
+ num_classes = len(class_names)
406
+ print(f"Enhanced data siap! {num_classes} kelas ditemukan.")
407
+ print(f" Kelas: {class_names[:5]}{'...' if len(class_names) > 5 else ''}")
408
+ except Exception as e:
409
+ print(f"ERROR data loader: {e}")
410
+ return
411
+
412
+ # 3. Model mapping
413
+ model_mapping = {
414
+ "vit": "vit_base_patch16_224",
415
+ "swin_transformer": "swin_base_patch4_window7_224",
416
+ #"convnext_tiny": "convnext_tiny"
417
+ }
418
+
419
+ # 4. Enhanced Training
420
+ all_results = []
421
+
422
+ for model_name_key in config.MODEL_LIST:
423
+ if model_name_key not in model_mapping:
424
+ print(f"WARNING: Model '{model_name_key}' tidak dikenali. Dilewati.")
425
+ continue
426
+
427
+ model_name = model_mapping[model_name_key]
428
+
429
+ try:
430
+ result = train_enhanced_anti_overfitting_model(
431
+ model_name_key=model_name_key,
432
+ model_name=model_name,
433
+ num_classes=num_classes,
434
+ train_loader=train_loader,
435
+ val_loader=val_loader,
436
+ writer=writer,
437
+ model_dir=model_dir,
438
+ class_names=class_names
439
+ )
440
+
441
+ if result:
442
+ all_results.append(result)
443
+
444
+ except Exception as e:
445
+ print(f"ERROR training {model_name_key}: {e}")
446
+ continue
447
+
448
+ # 5. Ringkasan
449
+ if all_results:
450
+ print(f"\nRINGKASAN HASIL ENHANCED")
451
+ print("="*40)
452
+
453
+ for result in all_results:
454
+ print(f"{result['model_name']:15} | "
455
+ f"Best: {result['best_val_acc']:.4f} | "
456
+ f"Epochs: {result['epochs_trained']} | "
457
+ f"Time: {result['training_time']:.1f}s")
458
+
459
+ best_model = max(all_results, key=lambda x: x['best_val_acc'])
460
+ print(f"\nModel terbaik: {best_model['model_name']} "
461
+ f"({best_model['best_val_acc']:.4f})")
462
+
463
+ writer.close()
464
+ print(f"\nHasil disimpan di: {experiment_dir}")
465
+
466
+ if __name__ == "__main__":
467
+ main()
train_fast.py ADDED
@@ -0,0 +1,229 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ from pathlib import Path
3
+ # Tambahkan parent project ke sys.path sehingga 'src' dapat diimport saat menjalankan skrip langsung
4
+ sys.path.append(str(Path(__file__).resolve().parents[1]))
5
+
6
+ import torch
7
+ import torch.nn as nn
8
+ import torch.optim as optim
9
+ from torch.utils.tensorboard import SummaryWriter
10
+ import time
11
+ import os
12
+ from datetime import datetime
13
+ import json
14
+ import matplotlib.pyplot as plt
15
+ import numpy as np
16
+
17
+ # Import modul yang sudah dibuat
18
+ from src import config
19
+ from src.data_loader import create_dataloaders
20
+ from src.model import create_model
21
+ from src.engine import train_step, val_step
22
+
23
+ def setup_fast_training():
24
+ """
25
+ Setup untuk training yang lebih cepat di laptop.
26
+ """
27
+ print("SETUP TRAINING CEPAT UNTUK LAPTOP")
28
+ print("="*50)
29
+
30
+ # Override config untuk training cepat
31
+ config.BATCH_SIZE = 4 # Sangat kecil untuk laptop
32
+ config.EPOCHS = 3 # Hanya 3 epoch untuk testing
33
+ config.IMAGE_SIZE = 128 # Resolusi lebih kecil
34
+
35
+ print(f"Konfigurasi Training Cepat:")
36
+ print(f" - Batch Size: {config.BATCH_SIZE}")
37
+ print(f" - Epochs: {config.EPOCHS}")
38
+ print(f" - Image Size: {config.IMAGE_SIZE}x{config.IMAGE_SIZE}")
39
+ print(f" - Device: {config.DEVICE}")
40
+ print(f" - Model: {config.MODEL_LIST[0] if config.MODEL_LIST else 'None'}")
41
+
42
+ # Buat direktori untuk hasil
43
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
44
+ experiment_dir = Path("outputs") / f"fast_training_{timestamp}"
45
+ model_dir = experiment_dir / "models"
46
+ log_dir = experiment_dir / "logs"
47
+
48
+ experiment_dir.mkdir(parents=True, exist_ok=True)
49
+ model_dir.mkdir(parents=True, exist_ok=True)
50
+ log_dir.mkdir(parents=True, exist_ok=True)
51
+
52
+ writer = SummaryWriter(log_dir=str(log_dir))
53
+
54
+ return writer, experiment_dir, model_dir
55
+
56
+ def train_fast_model(model_name_key: str, model_name: str, num_classes: int,
57
+ train_loader, val_loader, writer, model_dir: Path):
58
+ """
59
+ Training model dengan optimasi untuk laptop.
60
+ """
61
+ print(f"\nTRAINING MODEL: {model_name_key.upper()}")
62
+ print(f" Model: {model_name}")
63
+ print(f" Classes: {num_classes}")
64
+ print("-" * 40)
65
+
66
+ # Buat model
67
+ model = create_model(model_name, num_classes, pretrained=True)
68
+ if model is None:
69
+ print(f"ERROR: Gagal membuat model {model_name}")
70
+ return None
71
+
72
+ model = model.to(config.DEVICE)
73
+
74
+ # Setup optimizer dengan learning rate yang lebih tinggi untuk konvergensi cepat
75
+ loss_fn = nn.CrossEntropyLoss()
76
+ optimizer = optim.Adam(model.parameters(), lr=config.LEARNING_RATE * 2) # 2x lebih cepat
77
+
78
+ # Tracking
79
+ train_losses, val_losses = [], []
80
+ train_accs, val_accs = [], []
81
+ best_val_acc = 0.0
82
+ best_epoch = 0
83
+
84
+ print(f"Memulai training {config.EPOCHS} epochs...")
85
+ start_time = time.time()
86
+
87
+ for epoch in range(config.EPOCHS):
88
+ print(f"\nEpoch {epoch+1}/{config.EPOCHS}")
89
+
90
+ # Training
91
+ train_loss, train_acc = train_step(
92
+ model=model, dataloader=train_loader, loss_fn=loss_fn,
93
+ optimizer=optimizer, device=config.DEVICE
94
+ )
95
+
96
+ # Validation
97
+ val_loss, val_acc = val_step(
98
+ model=model, dataloader=val_loader, loss_fn=loss_fn,
99
+ device=config.DEVICE
100
+ )
101
+
102
+ # Simpan metrics
103
+ train_losses.append(train_loss)
104
+ val_losses.append(val_loss)
105
+ train_accs.append(train_acc)
106
+ val_accs.append(val_acc)
107
+
108
+ # Log ke TensorBoard
109
+ writer.add_scalar(f'{model_name_key}/Train/Loss', train_loss, epoch)
110
+ writer.add_scalar(f'{model_name_key}/Train/Accuracy', train_acc, epoch)
111
+ writer.add_scalar(f'{model_name_key}/Val/Loss', val_loss, epoch)
112
+ writer.add_scalar(f'{model_name_key}/Val/Accuracy', val_acc, epoch)
113
+
114
+ # Cek model terbaik
115
+ if val_acc > best_val_acc:
116
+ best_val_acc = val_acc
117
+ best_epoch = epoch + 1
118
+
119
+ # Simpan model terbaik
120
+ model_path = model_dir / f"{model_name_key}_best.pth"
121
+ torch.save({
122
+ 'model_state_dict': model.state_dict(),
123
+ 'optimizer_state_dict': optimizer.state_dict(),
124
+ 'epoch': epoch + 1,
125
+ 'val_accuracy': val_acc,
126
+ 'model_name': model_name,
127
+ 'num_classes': num_classes
128
+ }, model_path)
129
+ print(f"Model terbaik disimpan: {model_path}")
130
+
131
+ # Progress
132
+ print(f" Train: Loss={train_loss:.4f}, Acc={train_acc:.4f}")
133
+ print(f" Val: Loss={val_loss:.4f}, Acc={val_acc:.4f}")
134
+ print(f" Best: {best_val_acc:.4f} (Epoch {best_epoch})")
135
+
136
+ end_time = time.time()
137
+ training_time = end_time - start_time
138
+
139
+ print(f"\nTraining selesai!")
140
+ print(f" Waktu: {training_time:.1f} detik")
141
+ print(f" Best Accuracy: {best_val_acc:.4f}")
142
+
143
+ return {
144
+ 'model_name': model_name_key,
145
+ 'best_val_acc': best_val_acc,
146
+ 'best_epoch': best_epoch,
147
+ 'final_val_acc': val_acc,
148
+ 'training_time': training_time,
149
+ 'train_losses': train_losses,
150
+ 'val_losses': val_losses,
151
+ 'train_accs': train_accs,
152
+ 'val_accs': val_accs
153
+ }
154
+
155
+ def main():
156
+ """
157
+ Training cepat untuk laptop.
158
+ """
159
+ print("BATIK VISION - FAST TRAINING MODE")
160
+ print("="*50)
161
+
162
+ # 1. Setup training cepat
163
+ writer, experiment_dir, model_dir = setup_fast_training()
164
+
165
+ # 2. Buat data loaders
166
+ print("\nMembuat data loaders...")
167
+ try:
168
+ train_loader, val_loader, class_names = create_dataloaders()
169
+ num_classes = len(class_names)
170
+ print(f"Data siap! {num_classes} kelas ditemukan.")
171
+ print(f" Kelas: {class_names[:5]}{'...' if len(class_names) > 5 else ''}")
172
+ except Exception as e:
173
+ print(f"ERROR data loader: {e}")
174
+ return
175
+
176
+ # 3. Model mapping
177
+ model_mapping = {
178
+ "vit": "vit_base_patch16_224",
179
+ "swin_transformer": "swin_base_patch4_window7_224",
180
+ "convnext_tiny": "convnext_tiny"
181
+ }
182
+
183
+ # 4. Training
184
+ all_results = []
185
+
186
+ for model_name_key in config.MODEL_LIST:
187
+ if model_name_key not in model_mapping:
188
+ print(f"WARNING: Model '{model_name_key}' tidak dikenali. Dilewati.")
189
+ continue
190
+
191
+ model_name = model_mapping[model_name_key]
192
+
193
+ try:
194
+ result = train_fast_model(
195
+ model_name_key=model_name_key,
196
+ model_name=model_name,
197
+ num_classes=num_classes,
198
+ train_loader=train_loader,
199
+ val_loader=val_loader,
200
+ writer=writer,
201
+ model_dir=model_dir
202
+ )
203
+
204
+ if result:
205
+ all_results.append(result)
206
+
207
+ except Exception as e:
208
+ print(f"ERROR training {model_name_key}: {e}")
209
+ continue
210
+
211
+ # 5. Ringkasan
212
+ if all_results:
213
+ print(f"\nRINGKASAN HASIL")
214
+ print("="*30)
215
+
216
+ for result in all_results:
217
+ print(f"{result['model_name']:15} | "
218
+ f"Best: {result['best_val_acc']:.4f} | "
219
+ f"Time: {result['training_time']:.1f}s")
220
+
221
+ best_model = max(all_results, key=lambda x: x['best_val_acc'])
222
+ print(f"\nModel terbaik: {best_model['model_name']} "
223
+ f"({best_model['best_val_acc']:.4f})")
224
+
225
+ writer.close()
226
+ print(f"\nHasil disimpan di: {experiment_dir}")
227
+
228
+ if __name__ == "__main__":
229
+ main()
train_optimized.py ADDED
@@ -0,0 +1,263 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ from pathlib import Path
3
+ # Tambahkan parent project ke sys.path sehingga 'src' dapat diimport saat menjalankan skrip langsung
4
+ sys.path.append(str(Path(__file__).resolve().parents[1]))
5
+
6
+ import torch
7
+ import torch.nn as nn
8
+ import torch.optim as optim
9
+ from torch.utils.tensorboard import SummaryWriter
10
+ import time
11
+ import os
12
+ from datetime import datetime
13
+ import json
14
+ import matplotlib.pyplot as plt
15
+ import numpy as np
16
+ from torch.optim.lr_scheduler import ReduceLROnPlateau, CosineAnnealingLR
17
+
18
+ # Import modul yang sudah dibuat
19
+ from src import config
20
+ from src.data_loader import create_dataloaders
21
+ from src.model import create_model
22
+ from src.engine import train_step, val_step
23
+ from src.mixup import MixupTrainer
24
+
25
+ def setup_optimized_training():
26
+ """
27
+ Setup untuk training yang dioptimalkan untuk mengatasi overfitting.
28
+ """
29
+ print("SETUP TRAINING OPTIMIZED - ANTI OVERFITTING")
30
+ print("="*60)
31
+
32
+ # Override config untuk training yang lebih optimal
33
+ #config.BATCH_SIZE = 16 # Sedang untuk balance speed vs generalization
34
+ #config.EPOCHS = 30 # Cukup untuk konvergensi
35
+ #config.IMAGE_SIZE = 224 # Resolusi standar
36
+ #config.LEARNING_RATE = 1e-4 # Learning rate yang lebih konservatif
37
+
38
+ print(f"Konfigurasi Training Optimized:")
39
+ print(f" - Batch Size: {config.BATCH_SIZE}")
40
+ print(f" - Epochs: {config.EPOCHS}")
41
+ print(f" - Image Size: {config.IMAGE_SIZE}x{config.IMAGE_SIZE}")
42
+ print(f" - Learning Rate: {config.LEARNING_RATE}")
43
+ print(f" - Device: {config.DEVICE}")
44
+ print(f" - Model: {config.MODEL_LIST[0] if config.MODEL_LIST else 'None'}")
45
+
46
+ # Buat direktori untuk hasil
47
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
48
+ experiment_dir = Path("outputs") / f"optimized_training_{timestamp}"
49
+ model_dir = experiment_dir / "models"
50
+ log_dir = experiment_dir / "logs"
51
+
52
+ experiment_dir.mkdir(parents=True, exist_ok=True)
53
+ model_dir.mkdir(parents=True, exist_ok=True)
54
+ log_dir.mkdir(parents=True, exist_ok=True)
55
+
56
+ writer = SummaryWriter(log_dir=str(log_dir))
57
+
58
+ return writer, experiment_dir, model_dir
59
+
60
+ def train_optimized_model(model_name_key: str, model_name: str, num_classes: int,
61
+ train_loader, val_loader, writer, model_dir: Path):
62
+ """
63
+ Training model dengan optimasi anti-overfitting.
64
+ """
65
+ print(f"\nTRAINING MODEL: {model_name_key.upper()}")
66
+ print(f" Model: {model_name}")
67
+ print(f" Classes: {num_classes}")
68
+ print("-" * 50)
69
+
70
+ # Buat model dengan dropout untuk regularization
71
+ model = create_model(model_name, num_classes, pretrained=True, dropout_rate=0.1)
72
+ if model is None:
73
+ print(f"ERROR: Gagal membuat model {model_name}")
74
+ return None
75
+
76
+ model = model.to(config.DEVICE)
77
+
78
+ # Setup optimizer dengan weight decay untuk regularization
79
+ loss_fn = nn.CrossEntropyLoss(label_smoothing=0.1) # Label smoothing untuk mengurangi overfitting
80
+ optimizer = optim.Adam(model.parameters(), lr=config.LEARNING_RATE, weight_decay=5e-4)
81
+
82
+ # Setup learning rate scheduler
83
+ scheduler = ReduceLROnPlateau(optimizer, mode='max', factor=0.5, patience=3)
84
+
85
+ # Setup Mixup trainer untuk data augmentation yang lebih kuat
86
+ mixup_trainer = MixupTrainer(model, optimizer, loss_fn, config.DEVICE, alpha=0.2)
87
+
88
+ # Tracking variables
89
+ train_losses, val_losses = [], []
90
+ train_accs, val_accs = [], []
91
+ best_val_acc = 0.0
92
+ best_epoch = 0
93
+
94
+ # Early stopping
95
+ patience = 7 # Stop jika tidak ada improvement selama 7 epoch
96
+ epochs_no_improve = 0
97
+
98
+ print(f"Memulai training {config.EPOCHS} epochs...")
99
+ print(f" Early Stopping: {patience} epochs patience")
100
+ print(f" Learning Rate Scheduler: ReduceLROnPlateau")
101
+ print(f" Weight Decay: 1e-4")
102
+
103
+ start_time = time.time()
104
+
105
+ for epoch in range(config.EPOCHS):
106
+ print(f"\nEpoch {epoch+1}/{config.EPOCHS}")
107
+
108
+ # Training dengan Mixup
109
+ train_loss, train_acc = mixup_trainer.train_step(train_loader)
110
+
111
+ # Validation
112
+ val_loss, val_acc = val_step(
113
+ model=model, dataloader=val_loader, loss_fn=loss_fn,
114
+ device=config.DEVICE
115
+ )
116
+
117
+ # Update learning rate scheduler
118
+ scheduler.step(val_acc)
119
+
120
+ # Simpan metrics
121
+ train_losses.append(train_loss)
122
+ val_losses.append(val_loss)
123
+ train_accs.append(train_acc)
124
+ val_accs.append(val_acc)
125
+
126
+ # Log ke TensorBoard
127
+ writer.add_scalar(f'{model_name_key}/Train/Loss', train_loss, epoch)
128
+ writer.add_scalar(f'{model_name_key}/Train/Accuracy', train_acc, epoch)
129
+ writer.add_scalar(f'{model_name_key}/Val/Loss', val_loss, epoch)
130
+ writer.add_scalar(f'{model_name_key}/Val/Accuracy', val_acc, epoch)
131
+ writer.add_scalar(f'{model_name_key}/Learning_Rate', optimizer.param_groups[0]['lr'], epoch)
132
+
133
+ # Cek model terbaik
134
+ if val_acc > best_val_acc:
135
+ best_val_acc = val_acc
136
+ best_epoch = epoch + 1
137
+ epochs_no_improve = 0 # Reset counter
138
+
139
+ # Simpan model terbaik
140
+ model_path = model_dir / f"{model_name_key}_best.pth"
141
+ torch.save({
142
+ 'model_state_dict': model.state_dict(),
143
+ 'optimizer_state_dict': optimizer.state_dict(),
144
+ 'scheduler_state_dict': scheduler.state_dict(),
145
+ 'epoch': epoch + 1,
146
+ 'val_accuracy': val_acc,
147
+ 'model_name': model_name,
148
+ 'num_classes': num_classes
149
+ }, model_path)
150
+ print(f"Model terbaik disimpan: {model_path}")
151
+ else:
152
+ epochs_no_improve += 1
153
+
154
+ # Progress
155
+ print(f" Train: Loss={train_loss:.4f}, Acc={train_acc:.4f}")
156
+ print(f" Val: Loss={val_loss:.4f}, Acc={val_acc:.4f}")
157
+ print(f" Best: {best_val_acc:.4f} (Epoch {best_epoch})")
158
+ print(f" LR: {optimizer.param_groups[0]['lr']:.2e}")
159
+ print(f" No Improve: {epochs_no_improve}/{patience}")
160
+
161
+ # Early stopping check
162
+ if epochs_no_improve >= patience:
163
+ print(f"\nEarly stopping! Tidak ada kemajuan selama {patience} epoch.")
164
+ print(f"Model terbaik: Epoch {best_epoch} dengan Val Acc: {best_val_acc:.4f}")
165
+ break
166
+
167
+ end_time = time.time()
168
+ training_time = end_time - start_time
169
+
170
+ print(f"\nTraining selesai!")
171
+ print(f" Waktu: {training_time:.1f} detik")
172
+ print(f" Best Accuracy: {best_val_acc:.4f}")
173
+ print(f" Epochs trained: {epoch + 1}")
174
+
175
+ return {
176
+ 'model_name': model_name_key,
177
+ 'best_val_acc': best_val_acc,
178
+ 'best_epoch': best_epoch,
179
+ 'final_val_acc': val_acc,
180
+ 'training_time': training_time,
181
+ 'epochs_trained': epoch + 1,
182
+ 'train_losses': train_losses,
183
+ 'val_losses': val_losses,
184
+ 'train_accs': train_accs,
185
+ 'val_accs': val_accs
186
+ }
187
+
188
+ def main():
189
+ """
190
+ Training optimized untuk mengatasi overfitting.
191
+ """
192
+ print("BATIK VISION - OPTIMIZED TRAINING MODE")
193
+ print("="*60)
194
+
195
+ # 1. Setup training optimized
196
+ writer, experiment_dir, model_dir = setup_optimized_training()
197
+
198
+ # 2. Buat data loaders
199
+ print("\nMembuat data loaders...")
200
+ try:
201
+ train_loader, val_loader, class_names = create_dataloaders()
202
+ num_classes = len(class_names)
203
+ print(f"Data siap! {num_classes} kelas ditemukan.")
204
+ print(f" Kelas: {class_names[:5]}{'...' if len(class_names) > 5 else ''}")
205
+ except Exception as e:
206
+ print(f"ERROR data loader: {e}")
207
+ return
208
+
209
+ # 3. Model mapping
210
+ model_mapping = {
211
+ "vit": "vit_base_patch16_224",
212
+ "swin_transformer": "swin_base_patch4_window7_224",
213
+ "convnext_tiny": "convnext_tiny"
214
+ }
215
+
216
+ # 4. Training
217
+ all_results = []
218
+
219
+ for model_name_key in config.MODEL_LIST:
220
+ if model_name_key not in model_mapping:
221
+ print(f"WARNING: Model '{model_name_key}' tidak dikenali. Dilewati.")
222
+ continue
223
+
224
+ model_name = model_mapping[model_name_key]
225
+
226
+ try:
227
+ result = train_optimized_model(
228
+ model_name_key=model_name_key,
229
+ model_name=model_name,
230
+ num_classes=num_classes,
231
+ train_loader=train_loader,
232
+ val_loader=val_loader,
233
+ writer=writer,
234
+ model_dir=model_dir
235
+ )
236
+
237
+ if result:
238
+ all_results.append(result)
239
+
240
+ except Exception as e:
241
+ print(f"ERROR training {model_name_key}: {e}")
242
+ continue
243
+
244
+ # 5. Ringkasan
245
+ if all_results:
246
+ print(f"\nRINGKASAN HASIL")
247
+ print("="*40)
248
+
249
+ for result in all_results:
250
+ print(f"{result['model_name']:15} | "
251
+ f"Best: {result['best_val_acc']:.4f} | "
252
+ f"Epochs: {result['epochs_trained']} | "
253
+ f"Time: {result['training_time']:.1f}s")
254
+
255
+ best_model = max(all_results, key=lambda x: x['best_val_acc'])
256
+ print(f"\nModel terbaik: {best_model['model_name']} "
257
+ f"({best_model['best_val_acc']:.4f})")
258
+
259
+ writer.close()
260
+ print(f"\nHasil disimpan di: {experiment_dir}")
261
+
262
+ if __name__ == "__main__":
263
+ main()