Spaces:
Running
Running
| import os | |
| import torch | |
| import torch.nn as nn | |
| from torch.utils.data import DataLoader | |
| from torch.optim import AdamW | |
| from transformers import get_linear_schedule_with_warmup | |
| from preprocess import load_data_and_encoders | |
| from model_intent import JointPhoBERTModel | |
| from sklearn.metrics import accuracy_score | |
| def train(): | |
| current_dir = os.path.dirname(os.path.abspath(__file__)) | |
| project_root = os.path.dirname(os.path.dirname(current_dir)) | |
| data_dir = os.path.join(project_root, 'data') | |
| train_path = os.path.join(data_dir, 'train.json') | |
| dev_path = os.path.join(data_dir, 'dev.json') | |
| test_path = os.path.join(data_dir, 'test.json') | |
| model_name = "vinai/phobert-base-v2" | |
| batch_size = 16 | |
| epochs = 10 | |
| max_len = 128 | |
| learning_rate = 2e-5 | |
| print("1. Loading Data and Tokenizer...") | |
| train_dataset, dev_dataset, test_dataset, label_encoder, tokenizer = load_data_and_encoders( | |
| train_path, dev_path, test_path, model_name, max_len | |
| ) | |
| train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True) | |
| dev_loader = DataLoader(dev_dataset, batch_size=batch_size) | |
| test_loader = DataLoader(test_dataset, batch_size=batch_size) | |
| num_intents = label_encoder.get_num_intents() | |
| num_ner_tags = label_encoder.get_num_ner_tags() | |
| print(f" -> Num Intents: {num_intents}, Num NER Tags: {num_ner_tags}") | |
| device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') | |
| print(f"2. Using device: {device}") | |
| print("3. Initializing Model...") | |
| model = JointPhoBERTModel(model_name, num_intents, num_ner_tags) | |
| model.to(device) | |
| intent_criterion = nn.CrossEntropyLoss() | |
| # Bỏ qua index -100 (padding và các subwords phụ) khi tính loss cho NER | |
| ner_criterion = nn.CrossEntropyLoss(ignore_index=-100) | |
| optimizer = AdamW(model.parameters(), lr=learning_rate) | |
| total_steps = len(train_loader) * epochs | |
| scheduler = get_linear_schedule_with_warmup(optimizer, num_warmup_steps=0, num_training_steps=total_steps) | |
| best_val_loss = float('inf') | |
| print("4. Starting Training Loop...") | |
| for epoch in range(epochs): | |
| print(f"\\n========== Epoch {epoch+1}/{epochs} ==========") | |
| model.train() | |
| total_train_loss = 0 | |
| total_intent_loss = 0 | |
| total_ner_loss = 0 | |
| for step, batch in enumerate(train_loader): | |
| input_ids = batch['input_ids'].to(device) | |
| attention_mask = batch['attention_mask'].to(device) | |
| intent_labels = batch['intent_label'].to(device) | |
| ner_labels = batch['ner_labels'].to(device) | |
| optimizer.zero_grad() | |
| intent_logits, ner_logits = model(input_ids, attention_mask) | |
| loss_intent = intent_criterion(intent_logits, intent_labels) | |
| # Tính Loss NER (reshape lại logits và labels) | |
| active_logits = ner_logits.view(-1, num_ner_tags) | |
| active_labels = ner_labels.view(-1) | |
| loss_ner = ner_criterion(active_logits, active_labels) | |
| # Tổng hợp Loss | |
| loss = loss_intent + loss_ner | |
| total_train_loss += loss.item() | |
| total_intent_loss += loss_intent.item() | |
| total_ner_loss += loss_ner.item() | |
| loss.backward() | |
| torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0) | |
| optimizer.step() | |
| scheduler.step() | |
| if (step + 1) % 50 == 0: | |
| print(f" Step {step+1}/{len(train_loader)} | Loss: {loss.item():.4f} (Intent: {loss_intent.item():.4f}, NER: {loss_ner.item():.4f})") | |
| avg_train_loss = total_train_loss / len(train_loader) | |
| print(f"-> Average Training Loss: {avg_train_loss:.4f} (Intent: {total_intent_loss/len(train_loader):.4f}, NER: {total_ner_loss/len(train_loader):.4f})") | |
| # Validation | |
| model.eval() | |
| total_val_loss = 0 | |
| all_intent_preds = [] | |
| all_intent_labels = [] | |
| with torch.no_grad(): | |
| for batch in dev_loader: | |
| input_ids = batch['input_ids'].to(device) | |
| attention_mask = batch['attention_mask'].to(device) | |
| intent_labels = batch['intent_label'].to(device) | |
| ner_labels = batch['ner_labels'].to(device) | |
| intent_logits, ner_logits = model(input_ids, attention_mask) | |
| loss_intent = intent_criterion(intent_logits, intent_labels) | |
| active_logits = ner_logits.view(-1, num_ner_tags) | |
| active_labels = ner_labels.view(-1) | |
| loss_ner = ner_criterion(active_logits, active_labels) | |
| loss = loss_intent + loss_ner | |
| total_val_loss += loss.item() | |
| intent_preds = torch.argmax(intent_logits, dim=1).cpu().numpy() | |
| all_intent_preds.extend(intent_preds) | |
| all_intent_labels.extend(intent_labels.cpu().numpy()) | |
| avg_val_loss = total_val_loss / len(dev_loader) | |
| val_intent_acc = accuracy_score(all_intent_labels, all_intent_preds) | |
| print(f"-> Validation Loss: {avg_val_loss:.4f} | Intent Accuracy: {val_intent_acc:.4f}") | |
| if avg_val_loss < best_val_loss: | |
| best_val_loss = avg_val_loss | |
| ckpt_dir = os.path.join(project_root, 'src', 'checkpoints') | |
| os.makedirs(ckpt_dir, exist_ok=True) | |
| ckpt_path = os.path.join(ckpt_dir, "best_joint_model.pth") | |
| torch.save(model.state_dict(), ckpt_path) | |
| print(f"=> Saved new best model to '{ckpt_path}'") | |
| if __name__ == "__main__": | |
| train() | |