TIF_E4253362/backend/scripts/08_train_abjad_sibi.py

217 lines
7.6 KiB
Python

"""
Tahap 8: Training Abjad SIBI
Script ini melatih model TCN untuk mengenali abjad SIBI.
Sesuai README:
- Input: dataset/train/abjad_sibi/
- Output: models/abjad_sibi/model_abjad_sibi.pt
"""
import sys
import json
import numpy as np
from pathlib import Path
from typing import Tuple
import argparse
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader, Dataset
from tqdm import tqdm
import matplotlib.pyplot as plt
sys.path.insert(0, str(Path(__file__).parent.parent))
from utils.tcn import TCNClassifier
from utils.config import TRAIN_DIR, MODEL_DIR, FEATURE_SIZE, BATCH_SIZE, EPOCHS, LEARNING_RATE, ACCURACY_DIR, LOSS_DIR
class AbjadDataset(Dataset):
"""Dataset untuk abjad."""
def __init__(self, data_dir: Path):
self.data_dir = data_dir
self.samples = []
self.labels = []
self.label_to_idx = {}
self._load_data()
def _load_data(self):
if not self.data_dir.exists():
return
label_dirs = sorted([d for d in self.data_dir.iterdir() if d.is_dir()])
self.label_to_idx = {d.name: idx for idx, d in enumerate(label_dirs)}
for label_dir in label_dirs:
label_idx = self.label_to_idx[label_dir.name]
for npy_file in label_dir.glob("*.npy"):
try:
self.samples.append(np.load(npy_file))
self.labels.append(label_idx)
except Exception as e:
print(f"Error loading {npy_file}: {e}")
def __len__(self):
return len(self.samples)
def __getitem__(self, idx) -> Tuple:
return torch.FloatTensor(self.samples[idx]), torch.LongTensor([self.labels[idx]]).squeeze()
def train_epoch(model, train_loader, criterion, optimizer, device):
model.train()
total_loss, correct, total = 0, 0, 0
for sequences, labels in tqdm(train_loader, desc="Training"):
sequences, labels = sequences.to(device), labels.to(device)
optimizer.zero_grad()
outputs = model(sequences)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
total_loss += loss.item()
_, predicted = torch.max(outputs, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
return total_loss / len(train_loader), correct / total * 100
def evaluate(model, eval_loader, criterion, device):
model.eval()
total_loss, correct, total = 0, 0, 0
with torch.no_grad():
for sequences, labels in eval_loader:
sequences, labels = sequences.to(device), labels.to(device)
outputs = model(sequences)
loss = criterion(outputs, labels)
total_loss += loss.item()
_, predicted = torch.max(outputs, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
return total_loss / len(eval_loader), correct / total * 100
def train_abjad_sibi(data_dir: Path, model_dir: Path, epochs: int = EPOCHS,
batch_size: int = BATCH_SIZE, lr: float = LEARNING_RATE):
device = 'cuda' if torch.cuda.is_available() else 'cpu'
print(f"Using device: {device}")
print(f"\nLoading dataset from: {data_dir}")
train_dataset = AbjadDataset(data_dir / "train" / "abjad_sibi")
test_dataset = AbjadDataset(data_dir / "test" / "abjad_sibi")
print(f"Train samples: {len(train_dataset)}, Test samples: {len(test_dataset)}")
if len(train_dataset) == 0:
raise ValueError("No training samples found!")
num_classes = len(train_dataset.label_to_idx)
print(f"Number of classes: {num_classes}")
print(f"Classes: {list(train_dataset.label_to_idx.keys())}")
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True, num_workers=0)
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False, num_workers=0)
model = TCNClassifier(input_size=FEATURE_SIZE, num_classes=num_classes, num_channels=[64, 128, 256]).to(device)
print(f"\nTotal parameters: {sum(p.numel() for p in model.parameters()):,}")
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=lr)
scheduler = optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='min', factor=0.5, patience=5)
history = {"train_loss": [], "train_acc": [], "test_loss": [], "test_acc": []}
best_acc = 0
print("\n" + "="*60)
print("Training Abjad SIBI Model")
print("="*60)
for epoch in range(epochs):
print(f"\nEpoch {epoch+1}/{epochs}")
train_loss, train_acc = train_epoch(model, train_loader, criterion, optimizer, device)
test_loss, test_acc = evaluate(model, test_loader, criterion, device)
scheduler.step(test_loss)
history["train_loss"].append(train_loss)
history["train_acc"].append(train_acc)
history["test_loss"].append(test_loss)
history["test_acc"].append(test_acc)
print(f"Train Loss: {train_loss:.4f} | Train Acc: {train_acc:.2f}%")
print(f"Test Loss: {test_loss:.4f} | Test Acc: {test_acc:.2f}%")
if test_acc > best_acc:
best_acc = test_acc
best_state = model.state_dict().copy()
print(f"*** Best model! Acc: {best_acc:.2f}% ***")
if best_state:
model.load_state_dict(best_state)
# Save model
model_dir.mkdir(parents=True, exist_ok=True)
sibi_model_dir = model_dir / "abjad_sibi"
sibi_model_dir.mkdir(exist_ok=True)
model_path = sibi_model_dir / "model_abjad_sibi.pt"
torch.save(model, model_path)
print(f"\nModel saved to: {model_path}")
# Save label encoder
idx_to_label = {v: k for k, v in train_dataset.label_to_idx.items()}
with open(sibi_model_dir / "label_encoder.json", 'w') as f:
json.dump({"label_to_idx": train_dataset.label_to_idx, "idx_to_label": idx_to_label}, f, indent=2)
# Save history
with open(sibi_model_dir / "history.json", 'w') as f:
json.dump(history, f, indent=2)
# Plot Accuracy
plt.figure(figsize=(10, 6))
plt.plot(history['train_acc'], label='Train Accuracy')
plt.plot(history['test_acc'], label='Test (Validation) Accuracy')
plt.title('Abjad SIBI Model Accuracy')
plt.xlabel('Epoch')
plt.ylabel('Accuracy (%)')
plt.legend()
plt.grid(True)
plt.savefig(ACCURACY_DIR / "abjad_sibi_accuracy.png")
plt.close()
print(f"Accuracy plot saved to: {ACCURACY_DIR / 'abjad_sibi_accuracy.png'}")
# Plot Loss
plt.figure(figsize=(10, 6))
plt.plot(history['train_loss'], label='Train Loss')
plt.plot(history['test_loss'], label='Test (Validation) Loss')
plt.title('Abjad SIBI Model Loss')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.legend()
plt.grid(True)
plt.savefig(LOSS_DIR / "abjad_sibi_loss.png")
plt.close()
print(f"Loss plot saved to: {LOSS_DIR / 'abjad_sibi_loss.png'}")
print(f"\nBest Test Accuracy: {best_acc:.2f}%")
return history
def main():
parser = argparse.ArgumentParser(description="Training model abjad SIBI")
parser.add_argument("--data-dir", type=str, default=None)
parser.add_argument("--model-dir", type=str, default=None)
parser.add_argument("--epochs", type=int, default=EPOCHS)
parser.add_argument("--batch-size", type=int, default=BATCH_SIZE)
parser.add_argument("--lr", type=float, default=LEARNING_RATE)
args = parser.parse_args()
data_dir = Path(args.data_dir) if args.data_dir else TRAIN_DIR.parent
model_dir = Path(args.model_dir) if args.model_dir else MODEL_DIR
print("="*60)
print("TAHAP 8: TRAINING ABJAD SIBI")
print("="*60)
train_abjad_sibi(data_dir, model_dir, args.epochs, args.batch_size, args.lr)
if __name__ == "__main__":
main()