Spaces:
Running
Running
| import torch | |
| import torch.nn as nn | |
| def compute_pos_weight(loader, device): | |
| """ | |
| Walk the training data once to compute the empirical positive-class weight. | |
| pos_weight = n_negative / n_positive (per BCEWithLogitsLoss convention). | |
| Falls back to 10.0 if the data gives extreme values. | |
| """ | |
| n_pos = n_neg = 0 | |
| for batch in loader: | |
| labels = batch["hal_label"].view(-1) | |
| n_pos += (labels > 0.5).sum().item() | |
| n_neg += (labels < 0.5).sum().item() | |
| if n_pos == 0: | |
| return torch.tensor([10.0]).to(device) | |
| weight = n_neg / n_pos | |
| # Clamp to [2, 20] β avoids degenerate regimes | |
| weight = max(2.0, min(20.0, weight)) | |
| return torch.tensor([weight]).to(device) | |
| def train_epoch(model, loader, optimizer, scheduler, pos_weight, grad_accum_steps=4): | |
| """ | |
| Train for one epoch. | |
| Args | |
| ββββ | |
| model : AgentSightModel | |
| loader : DataLoader (batch_size=1, one trajectory per item) | |
| optimizer : AdamW | |
| scheduler : LR scheduler (stepped once per *gradient update*, not per batch) | |
| pos_weight : tensor([float]) for BCEWithLogitsLoss | |
| grad_accum_steps : accumulate gradients over N trajectories before stepping | |
| (effective batch size = grad_accum_steps) | |
| """ | |
| model.train() | |
| device = next(model.parameters()).device | |
| bce = nn.BCEWithLogitsLoss(pos_weight=pos_weight.to(device)) | |
| total_loss = 0.0 | |
| optimizer.zero_grad() | |
| for step_idx, batch in enumerate(loader): | |
| input_ids = batch["input_ids"].squeeze(0).to(device) | |
| attention_mask = batch["attention_mask"].squeeze(0).to(device) | |
| hal_labels = batch["hal_label"].squeeze(0).float().to(device) | |
| # Guard against out-of-vocabulary tokens (rare but seen in practice) | |
| vocab_size = model.encoder.config.vocab_size | |
| input_ids = torch.clamp(input_ids, 0, vocab_size - 1) | |
| hal_logits = model(input_ids, attention_mask) | |
| loss = bce(hal_logits, hal_labels) / grad_accum_steps | |
| loss.backward() | |
| total_loss += loss.item() * grad_accum_steps # un-scale for logging | |
| # Gradient update every grad_accum_steps trajectories | |
| if (step_idx + 1) % grad_accum_steps == 0: | |
| torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0) | |
| optimizer.step() | |
| if scheduler is not None: | |
| scheduler.step() | |
| optimizer.zero_grad() | |
| # Flush remaining gradients | |
| if len(loader) % grad_accum_steps != 0: | |
| torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0) | |
| optimizer.step() | |
| if scheduler is not None: | |
| scheduler.step() | |
| optimizer.zero_grad() | |
| return total_loss / len(loader) if loader else 0.0 | |