Download src/train.py from Minhtan210905/Toan-Roi-Rac-87M: direct link, hf CLI and curl.
- Browser
- Download file 6.32 kB
-
https://huggingface.co/Minhtan210905/Toan-Roi-Rac-87M/resolve/main/src/train.py
- Command line
-
hf download hf://Minhtan210905/Toan-Roi-Rac-87M/src/train.py
-
curl -L -o train.py https://huggingface.co/Minhtan210905/Toan-Roi-Rac-87M/resolve/main/src/train.py
6.32 kB
| import torch | |
| import torch.nn as nn | |
| from torch.utils.data import Dataset, DataLoader | |
| import time | |
| import sys | |
| import pickle | |
| import os | |
| from sklearn.model_selection import train_test_split | |
| from model import make_model | |
| sys.stdout.reconfigure(encoding='utf-8') | |
| DATA_PATH = 'data/processed/data.pkl' | |
| VOCAB_PATH = 'data/processed/vocab.pkl' | |
| SAVE_PATH = 'models/model.pt' | |
| BATCH_SIZE = 64 | |
| D_MODEL = 192 | |
| N_LAYER = 3 | |
| N_HEAD = 8 | |
| DROPOUT = 0.3 # Tăng từ 0.2 -> 0.3 để giảm overfitting | |
| EPOCHS = 200 | |
| LR = 5e-4 | |
| WARMUP_EPOCHS = 10 | |
| class EarlyStopping: | |
| def __init__(self, patience=50, delta=0.0): | |
| self.patience = patience | |
| self.delta = delta | |
| self.best_acc = 0.0 # track accuracy (higher = better) | |
| self.counter = 0 | |
| self.early_stop = False | |
| def __call__(self, val_acc): | |
| if val_acc > self.best_acc + self.delta: | |
| self.best_acc = val_acc | |
| self.counter = 0 | |
| else: | |
| self.counter += 1 | |
| if self.counter >= self.patience: | |
| self.early_stop = True | |
| class SeqDataset(Dataset): | |
| def __init__(self, sequences): | |
| self.sequences = sequences | |
| def __len__(self): | |
| return len(self.sequences) | |
| def __getitem__(self, idx): | |
| return torch.tensor(self.sequences[idx]) | |
| def collate_fn(batch, pad_idx): | |
| seqs = nn.utils.rnn.pad_sequence(batch, batch_first=True, padding_value=pad_idx) | |
| x = seqs[:, :-1] | |
| y = seqs[:, 1:].clone() | |
| y[y == pad_idx] = -1 | |
| return x, y | |
| def train(): | |
| device = torch.device("cpu") | |
| if not os.path.exists(DATA_PATH): return | |
| with open(VOCAB_PATH, 'rb') as f: vocab = pickle.load(f) | |
| with open(DATA_PATH, 'rb') as f: data = pickle.load(f) | |
| word2idx, pad_idx, vocab_size = vocab['word2idx'], vocab['word2idx']['<pad>'], len(vocab['word2idx']) | |
| train_seq = data['train']['sequences'] | |
| val_seq = data['val']['sequences'] | |
| train_dl = DataLoader(SeqDataset(train_seq), batch_size=BATCH_SIZE, shuffle=True, collate_fn=lambda b: collate_fn(b, pad_idx)) | |
| val_dl = DataLoader(SeqDataset(val_seq), batch_size=BATCH_SIZE, shuffle=False, collate_fn=lambda b: collate_fn(b, pad_idx)) | |
| model = make_model(vocab_size, n_layer=N_LAYER, d_model=D_MODEL, n_head=N_HEAD, dropout=DROPOUT) | |
| model.to(device) | |
| optimizer = torch.optim.AdamW(model.parameters(), lr=LR, weight_decay=0.05) | |
| # Warmup then ReduceLROnPlateau: LR tự giảm khi val_acc không tăng | |
| warmup_scheduler = torch.optim.lr_scheduler.LinearLR(optimizer, start_factor=0.1, end_factor=1.0, total_iters=WARMUP_EPOCHS) | |
| plateau_scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='max', factor=0.5, patience=15, min_lr=1e-6, verbose=True) | |
| early_stopping = EarlyStopping(patience=60, delta=0.1) # acc-based, stop if no +0.1% in 60 epochs | |
| best_val_loss = float('inf') | |
| best_val_acc = 0 | |
| best_epoch = 0 | |
| os.makedirs('models', exist_ok=True) | |
| os.makedirs('logs', exist_ok=True) | |
| with open('logs/progress.txt', 'w', encoding='utf-8') as f: | |
| f.write("=== Training Log ===\n") | |
| for epoch in range(EPOCHS): | |
| model.train() | |
| total_train_loss = total_train_acc = 0 | |
| start_time = time.time() | |
| for x, y in train_dl: | |
| x, y = x.to(device), y.to(device) | |
| optimizer.zero_grad() | |
| logits, loss = model(x, y) | |
| loss.backward() | |
| torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0) # Gradient Clipping | |
| optimizer.step() | |
| total_train_loss += loss.item() | |
| mask = y != -1 | |
| total_train_acc += (logits.argmax(dim=-1) == y)[mask].float().mean().item() | |
| if epoch < WARMUP_EPOCHS: | |
| warmup_scheduler.step() | |
| avg_train_loss, avg_train_acc = total_train_loss / len(train_dl), total_train_acc / len(train_dl) * 100 | |
| model.eval() | |
| total_val_loss = total_val_acc = 0 | |
| with torch.no_grad(): | |
| for x, y in val_dl: | |
| x, y = x.to(device), y.to(device) | |
| logits, loss = model(x, y) | |
| total_val_loss += loss.item() | |
| mask = y != -1 | |
| total_val_acc += (logits.argmax(dim=-1) == y)[mask].float().mean().item() | |
| avg_val_loss, avg_val_acc = total_val_loss / len(val_dl), total_val_acc / len(val_dl) * 100 | |
| is_overfitting = avg_val_loss > avg_train_loss + 2.5 | |
| warn = " ⚠️ OVERFIT" if is_overfitting else "" | |
| msg = f"Epoch {epoch+1:3d} | Train: {avg_train_loss:.4f} ({avg_train_acc:.1f}%) | Val: {avg_val_loss:.4f} ({avg_val_acc:.1f}%) | {time.time()-start_time:.1f}s{warn}" | |
| print(msg) | |
| with open('logs/progress.txt', 'a', encoding='utf-8') as f: | |
| f.write(msg + "\n") | |
| if avg_val_loss < best_val_loss: | |
| best_val_loss = avg_val_loss | |
| best_val_acc = avg_val_acc | |
| best_epoch = epoch + 1 | |
| torch.save({'model_state_dict': model.state_dict(), 'v_size': vocab_size, 'd_m': D_MODEL, 'n_l': N_LAYER, 'n_h': N_HEAD, 'dr': DROPOUT}, SAVE_PATH) | |
| if is_overfitting: | |
| stop_msg = f"Stopping training due to Overfitting (Gap > 2.5) at epoch {epoch+1}." | |
| print(stop_msg) | |
| with open('logs/progress.txt', 'a', encoding='utf-8') as f: | |
| f.write(stop_msg + "\n") | |
| break | |
| plateau_scheduler.step(avg_val_acc) # ReduceLROnPlateau theo val_acc | |
| early_stopping(avg_val_acc) # track accuracy | |
| if early_stopping.early_stop: | |
| stop_msg = f"Early stopping at epoch {epoch+1}. (no val acc improvement > 0.1% in {early_stopping.patience} epochs)" | |
| print(stop_msg) | |
| with open('logs/progress.txt', 'a', encoding='utf-8') as f: | |
| f.write(stop_msg + "\n") | |
| break | |
| final_msg = f"\n--- Training Complete ---\nBest Epoch: {best_epoch}\nBest Val Loss: {best_val_loss:.4f}\nBest Val Acc: {best_val_acc:.1f}%\nModel saved to: {SAVE_PATH}" | |
| print(final_msg) | |
| with open('logs/progress.txt', 'a', encoding='utf-8') as f: | |
| f.write(final_msg + "\n") | |
| if __name__ == "__main__": | |
| train() |